Files
planeMapper/_bmad-output/implementation-artifacts/3-2-automatic-recovery-on-fresh-decode.md
Matt Edholm ddde3358ef review(3-2): story 3-2 passes all ACs — mark done, epic-3 complete
All five review criteria pass: fresh fetch resets is_stale=False (AC1), no stale
outlines after recovery (AC2), two-cycle sequence tests exist and pass (AC3),
stale_needed is False when len(fresh) > 0 (AC4), no changes to main.py or
aircraft.py (AC5). No new deferred items. Epic-3 (Stale Data Resilience) complete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 23:53:45 -04:00

2.5 KiB

Story 3.2: Automatic Recovery on Fresh Decode

Status: done

Story

As a user whose RTL-SDR has recovered, I want the display to automatically return to normal filled aircraft rendering on the next successful fetch, So that recovery requires no intervention.

Acceptance Criteria

AC1: Given the display is in stale state When HttpFetcher.fetch() returns a non-empty aircraft list Then all fetched aircraft have is_stale=False and are drawn with normal filled icons

AC2: Given the display has recovered When the next render cycle runs Then no stale outline rendering occurs for the recovered aircraft

AC3: Given a stale-then-recovery sequence When FileFixtureFetcher first returns [] (simulating stale) then returns a populated list Then the first cycle produces stale aircraft and the second produces normal fresh aircraft

Tasks / Subtasks

  • Task 1: Add recovery tests to tests/test_stale.py (AC: #1, #2, #3)

    • 1.1 Add test_recovery_after_timeout_returns_fresh
    • 1.2 Add test_recovery_after_empty_returns_fresh
    • 1.3 Verify no code changes needed in main.py or aircraft.py — recovery is already correct
  • Task 2: Run quality gates

    • 2.1 python -m pytest tests/ — all tests pass
    • 2.2 python -m ruff check . — zero violations
    • 2.3 python -m ruff format --check . — no formatting issues

Implementation Notes

Recovery logic already correct from story 3-1

The _run_one_cycle() stale detection logic in main.py is:

try:
    fresh = fetcher.fetch()
except requests.Timeout:
    log.warning("fetch timeout — using stale data")
    fresh = []
    stale_needed = True
else:
    stale_needed = (len(fresh) == 0 and len(last_aircraft) > 0)

if stale_needed:
    aircraft_list = [dataclasses.replace(a, is_stale=True) for a in last_aircraft]
else:
    aircraft_list = fresh

When len(fresh) > 0, stale_needed is False and aircraft_list = fresh. All Aircraft instances from a fresh fetch default is_stale=False, so recovery is automatic — no code changes required.

No changes to main.py or aircraft.py

Recovery is already handled correctly. The only deliverable is two additional tests in tests/test_stale.py confirming the recovery path for both the timeout-then-fresh and empty-then-fresh scenarios.

Files changed

File Change
tests/test_stale.py Add test_recovery_after_timeout_returns_fresh and test_recovery_after_empty_returns_fresh