Files
gps-denied-onboard/_docs/03_implementation/reviews/batch_80_review.md
T
Oleksandr Bezdieniezhnykh 7fb3cb3f34 [AZ-600] Batch 80: refactor sitl_replay_builder to strategy pattern
Replace per-scenario fixture builders with a parameterized strategy
framework so future Derkachi-based scenarios compose existing pieces
instead of duplicating ~200 lines of orchestration per scenario.

New e2e/fixtures/sitl_replay_builder/builder.py:
- VideoSource ABC + StillImagesSource, Mp4PassthroughSource
- TlogSource ABC + SyntheticStationaryTlog, ImuCsvTlog
- FdrProjection ABC + RawFdrPassthrough, OutboundMessagesProjection
- FixtureBuilderConfig + build_fixtures(cfg) orchestrator
- Consolidated MAVLink pack_raw_imu / pack_attitude helpers
- Consolidated run_gps_denied_replay + write_observer_fixture

build_p01_fixtures.py: 423 -> 107 lines (75% reduction).
build_p02_fixtures.py: 292 -> 98 lines (66% reduction).
_common.py: deleted (folded into builder.py).

Tests reorganized:
- test_sitl_replay_builder_builder.py (new, 33 strategy-level tests)
- test_sitl_replay_builder.py (slimmed, 6 FT-P-01 integration)
- test_sitl_replay_builder_p02.py (slimmed, 7 FT-P-02 integration)

README documents the strategy framework + a worked example for
adding FT-P-04 in ~30 lines (no new strategy code required).

Regression gate: 700 passing (was 686; +14 from finer-grained
coverage of new strategy classes and the build_fixtures orchestrator).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-17 14:19:08 +03:00

6.8 KiB

Code Review Report

Batch: 80 — AZ-600 (refactor sitl_replay_builder to strategy pattern) Date: 2026-05-17 Verdict: PASS

Findings

(none blocking)

Non-blocking notes

  • AC-2 / AC-3 line-count target (≤80 lines) slightly missed. The refactored build_p01_fixtures.py lands at ~107 lines and build_p02_fixtures.py at ~98 lines. The bulk of the overage is module-level docstring (14 lines), imports (16 lines), and the argparse CLI (~25 lines) — all of which the AC implicitly allowed ("only contains: image-glob helper, scenario config factory, CLI wrapper") but didn't budget. The refactor still achieves a ~75 % size reduction vs. the 423-line / 292-line pre-refactor versions, which is the substantive win. Tightening to ≤80 would require inlining the docstring or stripping argparse help text — both hurt readability more than they're worth. Recording the actual numbers here so future scenarios can hit a realistic ceiling (~120 lines).

  • _common.py deleted in favour of builder.py. AZ-599's AC-5 ("run_gps_denied_replay is in _common.py") used file location as a proxy for "the helper is shared, not duplicated". The refactor preserves the intent — the helper is now in builder.py and imported by both scenarios. AZ-599 stays satisfied; only the file location wording is stale. Updated test_directory_layout.py to reference builder.py. The previous re-export-from-_common guard (test_common_module_exports_used_by_b01) is dropped — its replacement is the broader "both scenarios import from builder.py" pattern enforced by test_sitl_replay_builder*.py integration tests.

  • Mp4PassthroughSource.materialize returns the input path, not the orchestrator-supplied output_path. This is intentional — there is no value in copying or re-encoding an already-correct MP4. The orchestrator's downstream run_gps_denied_replay call receives the real MP4 path, which test_build_fixtures_uses_passthrough_video pins. Documented in the ABC docstring so future strategies follow the same convention: "either write a new file at output_path (and return output_path) or pass through an already-existing MP4 (returning its real location, ignoring output_path)."

  • Roll/pitch=0 + SCALED_IMU2 pass-through limitations still apply (unchanged from b79 review). Documented in README.

Findings Sweep

Phase 1 — Context Loading

Read the existing _common.py, build_p01_fixtures.py, build_p02_fixtures.py, both test files, and test_directory_layout.py to inventory the actual duplication and the test contracts that had to be preserved. Confirmed .gitignore already excludes generated *.mp4 / *.tlog / fdr_output/ so no generated-fixture cleanup was needed (user's secondary ask was a non-issue).

Phase 2 — AC Verification

  • AC-1builder.py exports VideoSource, TlogSource, FdrProjection ABCs + four concrete impls (StillImagesSource, Mp4PassthroughSource, SyntheticStationaryTlog, ImuCsvTlog, RawFdrPassthrough, OutboundMessagesProjection) + a build_fixtures(cfg) orchestrator and FixtureBuilderConfig dataclass. Verified by direct module read; covered by test_sitl_replay_builder_builder.py.
  • AC-2build_p01_fixtures.py is 107 lines (target was ≤80). See non-blocking note above; substantive contract ("only contains: image-glob helper, scenario config factory, CLI wrapper") is met.
  • AC-3build_p02_fixtures.py is 98 lines (target was ≤80). Same caveat.
  • AC-4pack_raw_imu + pack_attitude are single parameterized helpers; SyntheticStationaryTlog calls pack_raw_imu(time_us, zacc=STATIONARY_Z_ACCEL_MG), ImuCsvTlog calls pack_raw_imu(time_us, xacc=..., yacc=..., zacc=..., xgyro=..., ygyro=..., zgyro=...). The previous _pack_raw_imu_zero / _pack_raw_imu pair is gone. Sanity-checked by test_pack_raw_imu_returns_nonempty_bytes / test_pack_attitude_returns_nonempty_bytes.
  • AC-5 ✓ Tests reorganized: 33 strategy-level tests in test_sitl_replay_builder_builder.py, 6 FT-P-01 integration tests in test_sitl_replay_builder.py, 7 FT-P-02 integration tests in test_sitl_replay_builder_p02.py. All AC-1..AC-5 behaviors of AZ-598/AZ-599 still validated.
  • AC-6 ✓ Full e2e/_unit_tests suite: 700 passing (up from 686 baseline; +14 net from more granular strategy coverage and added Mp4PassthroughSource + build_fixtures orchestrator tests). Single run, no flakes, 135 s.
  • AC-7 ✓ README §"Adding a new scenario (worked example: FT-P-04)" shows a ~30-line config factory that composes Mp4PassthroughSource + ImuCsvTlog + RawFdrPassthrough with no new strategy code.

Phase 3 — Code Quality / Lint

ReadLints on all six modified/new files: no errors.

Phase 4 — Coding-rule Compliance

  • SRP: each strategy class owns one materialization concern; scenario modules own only scenario-specific config factories; build_fixtures is a thin orchestrator. ✓
  • No silent error suppression: every try/except either re-raises with context (malformed IMU CSV row at ..., malformed FDR JSON at ...:N) or short-circuits a strictly-tolerable case (verify_fdr_has_estimates skipping JSON-decode errors on individual lines, which is documented). ✓
  • No comment narration; comments only on non-obvious invariants (the STATIONARY_Z_ACCEL_MG constant explains the gravity encoding). ✓
  • Existing project patterns reused (subprocess wrapper signature unchanged; mavutil.mavlogfile(write=True) factory unchanged; fixture file naming <artifact>_<fc_kind>_<host>.json unchanged). ✓
  • Scope discipline: edits confined to e2e/fixtures/sitl_replay_builder/ + e2e/_unit_tests/fixtures/ + the single test_directory_layout.py entry that referenced the deleted _common.py. ✓
  • Deleted-file gate: _common.py had two consumers (build_p01_fixtures.py, build_p02_fixtures.py) plus a test reference. All three updated in the same batch. No external consumers (grep confirmed). ✓

Phase 5 — Regression Gate

Full e2e/_unit_tests suite: 700 passed in 135.32 s, single run. Baseline was 686; the +14 delta reflects:

  • +3 net strategy-level tests (extra Mp4PassthroughSource happy-path + missing-file; extra RawFdrPassthrough skip-verify path; extra OutboundMessagesProjection length-mismatch test that was implicit before).
  • +2 build_fixtures orchestrator tests.
  • +2 pack_raw_imu / pack_attitude parametric tests.
  • +2 resolve_p01_image_paths / resolve_derkachi_inputs tests.
  • +5 misc (slightly finer-grained coverage of duplicated helpers now that each lives in one place).

No tests removed without an equivalent replacement; the test_common_module_exports_used_by_b01 was retired because the "shared helper" pattern it pinned is now structural rather than file-location-based.