Files
planeMapper/_bmad-output/implementation-artifacts/3-2-automatic-recovery-on-fresh-decode.md
T
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

64 lines
2.5 KiB
Markdown

# 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:
```python
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` |