feat: auto-capture scrie direct în calibration/frames/ (elimină pasul manual)

Live loop-ul dumpa frame-uri pe schimbare de culoare în samples/, iar userul
copia manual cele utile în calibration/frames/ pentru labelling și regresie.
Pas inutil — acum scrie direct în corpus.

- samples_dir → calibration/frames/ (mkdir parents=True)
- stub capture (ATM_STUB_CAPTURE pentru smoke test Linux) citește din aceeași locație
- 8 PNG-uri orfane din samples/ (20260421_*) mutate în corpus
- CLAUDE.md clarifică: filename = culoarea detectată (poate fi greșită);
  calibration_labels.json rămâne singurul ground truth (manual)

Impact zero pe validate-calibration (iterează peste labels.json, ignoră fișiere
extra) și test_scenarios_regression.py (referă doar frame-uri curate din
scenarios.json).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-21 22:31:06 +03:00
parent ebc986abd3
commit 75a17f9640
10 changed files with 11 additions and 7 deletions

View File

@@ -19,12 +19,12 @@ pytest tests/test_scenarios_regression.py -v # FSM pe imagini reale
## Calibration corpus
`calibration/` — persistent, auto-suficient, safe to keep când `samples/` și `logs/fires/` se golesc. Conține:
- `frames/` — PNG-uri `{ts}_{color}.png` (ground truth în nume)
- `calibration_labels.json` — gate offline pentru `atm validate-calibration`
`calibration/` — persistent, auto-suficient. Conține:
- `frames/` — PNG-uri raw `{ts}_{color}.png` scrise **automat** de live loop la fiecare schimbare de culoare (filename = culoarea detectată, poate fi greșită)
- `calibration_labels.json`ground truth **manual** (gate offline pentru `atm validate-calibration`)
- `scenarios.json` — secvențe FSM pentru `tests/test_scenarios_regression.py`
Când adaugi un frame: copiezi din `logs/fires/` → redenumești `{ts}_{color}.png` → adaugi entry în JSON. Validare după orice recalibrare.
Workflow după sesiune: review frame-urile noi din `frames/`, adaugi entry-uri în `calibration_labels.json` cu culoarea pe care ai văzut-o TU pe chart (nu neapărat cea din filename), rulezi `atm validate-calibration`.
## Telegram commands (live)

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

View File

@@ -1382,8 +1382,12 @@ async def run_live_async(cfg, duration_s=None, capture_stub: bool = False) -> No
start = time.monotonic()
heartbeat_due = time.monotonic() + cfg.heartbeat_min * 60
samples_dir = Path("samples")
samples_dir.mkdir(exist_ok=True)
# Dump raw frames direct în corpusul de calibrare — fără pas manual
# de copy-paste din samples/. calibration_labels.json rămâne manual
# (filename-ul folosește culoarea detectată, care poate fi greșită;
# curarea labels.json e ce face validate-calibration truthful).
samples_dir = Path("calibration") / "frames"
samples_dir.mkdir(parents=True, exist_ok=True)
fires_dir = Path("logs") / "fires"
fires_dir.mkdir(parents=True, exist_ok=True)
try:
@@ -1496,7 +1500,7 @@ def _build_capture(cfg, capture_stub: bool = False):
if use_stub:
import itertools
samples_dir = Path("samples")
samples_dir = Path("calibration") / "frames"
pngs = sorted(samples_dir.glob("*.png")) if samples_dir.exists() else []
_cycle = itertools.cycle(pngs) if pngs else None