# Goal

Zbudować aplikację do podglądu kamery w czasie rzeczywistym (PySide6) z telemetrią, overlayami i systemem logowania do pliku — docelowo na Mac Mini z kamerą ELP.

---

# Constraints & Preferences

- Python 3.12.10, venv w `.venv-win`
- Dev: Windows 11
- Target: Mac Mini Intel i7, macOS Ventura, kamera ELP USB
- `PySide6 6.11.0`, `psutil`, `pytest`, `ruff`
- `QVideoWidget` porzucony — na Windows zasłania wszystkie child/sibling widgets przez natywny HWND D3D
- Logi:
  - `logs/` w katalogu projektu
  - nowy plik z timestamp per sesja
  - max 20 plików
- CSV telemetrii:
  - co 5 sekund
  - `flush` po każdym wierszu
- CPU:
  - pokazywać oba:
    - sys %
    - per-core % (jak Task Manager)
- Zmiana formatu kamery przez `stop+start`
  - nie `setCameraFormat` na żywej kamerze

---

# Progress

## Done

### Faza 0–7 MVP

- scaffolding
- `CameraService`
- `FrameDispatcher`
- `TelemetryCollector`
- `CameraView`
- `TelemetryOverlay`
- `IOverlayLayer`
- `AppMenuBar`
- `MainWindow`

### Rendering

- Usunięto `QVideoWidget`
- `CameraView(QWidget)` z `paintEvent`
  - render + overlay w jednym przejściu

### Telemetria

`TelemetrySnapshot` — pola:

- `fps`
- `target_fps`
- `frame_time_ms`
- `dropped_frames`
- `cpu_percent_sys`
- `cpu_percent_core`
- `memory_mb`

### Kamera

- `CameraService.format_changed(float)` sygnał
- `stop+start` przy:
  - `set_resolution`
  - `set_fps`

### Logowanie

- `_log_actual_format()`
  - loguje rzeczywisty format po starcie kamery
  - warning jeśli FPS się nie zgadza

### CameraFormat

`CameraFormat` dataclass z:

```python
pixel_format: str
```

Przykłady:

- `MJPG`
- `YUY2`
- `NV12`

w `camera_enumerator.py`

### Logging

`logging_setup.py`

- `FileHandler`
  - `DEBUG`
  - zawsze aktywny
- `StreamHandler`
  - `WARNING`
  - przełączalny
- nagłówek sesji:
  - wersja
  - platforma
  - Python
  - PySide6
  - CPU
  - RAM
- pruning starych logów

### CSV

`csv_logger.py`

- `CsvTelemetryLogger`
- throttling:
  - 5 s
- `flush` po każdym wierszu

### Menu

`menu_bar.py`

- dostosowany do `CameraFormat`
- `set_log_file_path()`
- `set_console_level()`

### Config

`config.py`

- `LOG_DIR`
- `MAX_LOG_FILES`
- `TELEMETRY_CSV_INTERVAL_S`

### Main

`main.py`

- wywołuje `setup_logging()`
- przekazuje `log_path` do `MainWindow`

### Main Window

`main_window.py`

- przyjmuje:

```python
log_path: Path | None
```

- tworzy:

```python
CsvTelemetryLogger(log_path.with_suffix(".csv"))
```

- podpina do:
  - `telemetry.metrics_updated`
- wywołuje:
  - `menu.set_log_file_path()`
- `csv_logger.close()` w `closeEvent`

### Repo

`.gitignore`

- dodano `logs/`

### Testy i jakość

- 20 testów jednostkowych
  - wszystkie zielone
- `ruff`
  - czysty
  - wszystkie błędy naprawione

### Artefakty sesji

Para plików per sesja:

```text
logs/duck-preview_<timestamp>.log
logs/duck-preview_<timestamp>.csv
```

---

## In Progress

- (none)

---

## Blocked

- (none)

---

# Key Decisions

## Rendering

### `QVideoWidget` → własny `CameraView(QWidget)`

Jedyny skuteczny sposób obejścia problemu z natywnym HWND na Windows.

## Overlay Architecture

### `IOverlayLayer` ABC

Pluggable overlaye bez modyfikacji `CameraView`.

Gotowe pod:

- YOLO
- OCR
- inne overlaye

## Pipeline

### `FrameDispatcher`

Pub/sub z `drop_if_busy`:

- render może gubić klatki
- telemetria nigdy

## CPU Metrics

- CPU per-core (`psutil`)
- dzielone przez `cpu_count`
- wynik zgodny z Task Manager

## Memory Metrics

- `memory_info().wset` (Windows)
- zamiast `rss`
- odpowiada `Private Working Set`

## Logging Strategy

Dwa osobne pliki per sesja:

- `.log` — diagnostyka
- `.csv` — metryki szeregów czasowych

## CameraFormat

`CameraFormat` dataclass zamiast krotki:

- niesie `pixel_format`
- potrzebny w logach

---

# Next Steps

1. Przetestować na Windows:
   - czy logi powstają w `logs/`
   - czy CSV zapisuje dane co ~5 s

2. Przenieść na Mac Mini i przetestować z kamerą ELP:
   - sprawdzić `pixel_format` w logu
   - sprawdzić wykrycie backendu AVFoundation

3. Zweryfikować overlay telemetrii na żywym obrazie

4. (Opcjonalnie) dodać kolejne `IOverlayLayer`
   - YOLO
   - OCR

---

# Critical Context

## Camera Enumerator

`CameraEnumerator.list_cameras()` zwraca:

```python
list[CameraInfo]
```

gdzie:

```python
formats: list[CameraFormat]
```

`menu_bar.py` i `camera_service.py` iterują po:

- `fmt.width`
- `fmt.height`
- `fmt.max_fps`

---

## CSV Logger

Ścieżka CSV:

```python
log_path.with_suffix(".csv")
```

Powstaje para:

- `.log`
- `.csv`

z tym samym timestamp.

---

## Console Logging

```python
logging_setup.set_console_level(debug: bool)
```

wywoływane z:

```python
menu_bar._on_log_toggled
```

---

## Pixel Format

```python
pixel_format_name()
```

eksportowane z:

```python
camera_enumerator.py
```

używane również w:

```python
camera_service.py
```

---

## Log Header

Nagłówek logu zawiera:

- wersję aplikacji
- platformę
- Python
- PySide6
- liczbę CPU
- RAM

---

## Uruchamianie

```bash
.venv-win\Scripts\python.exe -m app.main
```

---

## Ruff Fixes

Naprawiono:

- `E501`
  - długa linia w `camera_enumerator.py`
- `F401`
- `I001`
  - w `camera_service.py`
  - w `main.py`

---

# Relevant Files

## Entry Point

- `app/main.py`
  - wywołuje `setup_logging()`
  - przekazuje `log_path`

## Config

- `app/config.py`
  - `LOG_DIR`
  - `MAX_LOG_FILES`
  - `TELEMETRY_CSV_INTERVAL_S`

## Logging

- `app/logging_setup.py`
  - konfiguracja logowania
  - nagłówek sesji
  - pruning

## Telemetry

- `app/telemetry/csv_logger.py`
  - zapis CSV z throttlingiem

- `app/telemetry/telemetry_collector.py`
  - `TelemetrySnapshot`
  - `TelemetryCollector`

## Camera

- `app/camera/camera_enumerator.py`
  - `CameraFormat`
  - `CameraInfo`
  - `pixel_format_name()`

- `app/camera/camera_service.py`
  - `CameraService`
  - `format_changed`
  - `_log_actual_format()`

## UI

- `app/ui/main_window.py`
  - kompletny
  - podpięty `CsvTelemetryLogger`

- `app/ui/menu_bar.py`
  - `set_log_file_path()`
  - `set_console_level()`

- `app/ui/camera_view.py`
  - `CameraView`
  - rejestr `IOverlayLayer`

## Overlay

- `app/overlay/overlay_layer.py`
  - `IOverlayLayer` ABC

- `app/overlay/telemetry_overlay.py`
  - `TelemetryOverlay(IOverlayLayer)`

## Pipeline

- `app/pipeline/frame_dispatcher.py`
  - pub/sub
  - `drop_if_busy`

## Tests

- `tests/test_telemetry_collector.py`
  - 12 testów
  - mockuje `cpu_count`

- `tests/test_frame_dispatcher.py`
  - 8 testów

## Repo / Docs

- `.gitignore`
  - zawiera `logs/`

- `notes/01-mvp-preview.md`
  - PRD

- `notes/01-mvp-plan.md`
  - plan implementacji

- `notes/02-mvp-app.md`
  - stan aplikacji
  - historia prób i decyzji architektonicznych
