feat(multi-chart): refactor _run_multi_tick + fix alert spam pe oscilație strip
Bug critic: _strips_match(tol=10) trip pe pulsații naturale de lățime ~18px între ticks (ex. 792↔810px). Fiecare trip → _commit_layout_change → reset FSM + alert Telegram + scheduler stop. Logul 2026-05-04.jsonl arăta 576 evenimente layout_change/zi, plus prime alerts repetate la dark_red/dark_green (FSM resetat înghite lockout-ul) și sincronizare cross-chart pe ambele FSM-uri simultan. Fix: - main.py:1511 — gate doar pe count change (len(new) != len(current)); count stabil → silent update sub_roi indiferent de jitter - main.py:1438 — silent=True pe alert layout_change (Telegram fără sunet) - 2 teste regresie noi: width oscillation 792↔810 + silent assertion - 2 teste async reparate: bootstrap _detect_strips_for_ctx pentru ScriptedDetector (regresie după ce _run_multi_tick a devenit unica cale de detecție) Plus refactor multi-chart pre-existent: layout.py modul nou, _detect_strips_for_ctx, ChartState per-chart FSM/Detector, ROI per-strip pe screenshots, scripts/diag_*. Verificat: 292 passed, 2 skipped în 10s. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -283,6 +283,62 @@ def test_real_screenshot_rightmost_dot(screenshot: str, expected: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_dot_roi_override_uses_sub_roi() -> None:
|
||||
"""dot_roi_override must be used instead of cfg.dot_roi for crop + offset.
|
||||
|
||||
Paint a yellow dot inside the override ROI but **outside** cfg.dot_roi.
|
||||
The default DOT_ROI is (10,10,280,80); we override with an ROI placed
|
||||
well to the right (x=200, w=80) so the painted dot only intersects the
|
||||
override. If the detector still cropped from cfg.dot_roi the yellow dot
|
||||
would land at the rightmost edge of the larger ROI as well — so we use
|
||||
a frame that has nothing in the cfg.dot_roi region except inside the
|
||||
override window, and assert dot_pos_abs falls inside the override.
|
||||
"""
|
||||
override = ROI(x=200, y=20, w=80, h=60)
|
||||
# Background-only frame, then paint yellow only inside the override
|
||||
frame = np.full((100, 300, 3), BG_VAL, dtype=np.uint8)
|
||||
fy0, fy1 = override.y, override.y + override.h
|
||||
fx0, fx1 = override.x + 50, override.x + override.w # right edge of override
|
||||
frame[fy0:fy1, fx0:fx1] = YELLOW_BGR
|
||||
|
||||
cfg = _make_cfg(debounce_depth=1)
|
||||
det = Detector(cfg, capture=lambda: frame, dot_roi_override=override)
|
||||
|
||||
r = det.step(0.0)
|
||||
|
||||
assert r.dot_found is True
|
||||
assert r.match is not None
|
||||
assert r.match.name == "yellow"
|
||||
assert r.dot_pos_abs is not None
|
||||
abs_x, abs_y = r.dot_pos_abs
|
||||
assert override.x <= abs_x < override.x + override.w
|
||||
assert override.y <= abs_y < override.y + override.h
|
||||
|
||||
|
||||
def test_dot_pos_abs_with_offset() -> None:
|
||||
"""dot_pos_abs must include the override ROI's (x, y) offset."""
|
||||
override = ROI(x=100, y=20, w=50, h=40)
|
||||
frame = np.full((100, 300, 3), BG_VAL, dtype=np.uint8)
|
||||
# Paint a single full-height yellow stripe at roi-local x in [40, 50)
|
||||
# so find_rightmost_dot lands somewhere inside that stripe.
|
||||
fy0, fy1 = override.y, override.y + override.h
|
||||
fx0, fx1 = override.x + 40, override.x + 50
|
||||
frame[fy0:fy1, fx0:fx1] = YELLOW_BGR
|
||||
|
||||
cfg = _make_cfg(debounce_depth=1)
|
||||
det = Detector(cfg, capture=lambda: frame, dot_roi_override=override)
|
||||
|
||||
r = det.step(0.0)
|
||||
|
||||
assert r.dot_found is True
|
||||
assert r.dot_pos_abs is not None
|
||||
abs_x, abs_y = r.dot_pos_abs
|
||||
# Painted stripe: roi-local x in [40,50), y in [0, h). Absolute coords
|
||||
# must be offset by override.(x, y).
|
||||
assert override.x + 40 <= abs_x < override.x + 50
|
||||
assert override.y <= abs_y < override.y + override.h
|
||||
|
||||
|
||||
def test_fused_blob_samples_rightmost_dot() -> None:
|
||||
"""Fused multi-colour stripe must classify the rightmost colour, not the
|
||||
centroid colour. Pre-fix the centroid fell on an interior gray segment
|
||||
|
||||
Reference in New Issue
Block a user