Skip to content

Phase 92: API Analytics & Frame Enrichment

Status: Complete Block: 10 (UI Depth & Engine Exposure) Tests: 20

What Was Built

Backend foundation for rich UI diagnostics — analytics endpoints over event data, enriched tactical map frames, and metadata browsing endpoints. Zero engine changes.

92a: Per-Run Analytics Endpoints

New api/routers/analytics.py with 5 endpoints that aggregate a completed run's events_json into structured summaries:

  • GET /runs/{id}/analytics/casualties — casualty breakdown by weapon, side, or tick. Filters UnitDestroyedEvent + UnitDisabledEvent.
  • GET /runs/{id}/analytics/suppression — peak suppressed count, timeline, rout cascade count. Filters SuppressionEvent + RoutEvent.
  • GET /runs/{id}/analytics/morale — morale state distribution over ticks. Tracks running MoraleStateChangeEvent counters.
  • GET /runs/{id}/analytics/engagements — engagement count by type with hit rates. Filters EngagementEvent.
  • GET /runs/{id}/analytics/summary — combined summary (all four in one response).

All compute on read — no new DB columns. Single-pass O(n) aggregation over up to 50K events.

92b: Replay Frame Enrichment

Extended MapUnitFrame with 7 new fields for tactical map overlays:

Field Type Source Short Key
morale int (0-4) ctx.morale_states mo
posture str unit posture enum .name po
health float (0-1) personnel effectiveness ratio hp
fuel_pct float (0-1) unit.fuel_remaining fp
ammo_pct float (0-1) aggregate weapon rounds remaining/total ap
suppression int (0-4) BattleManager._suppression_states su
engaged bool unit appeared in EngagementEvent this tick eg

Backward compatible — old runs without enriched fields deserialize with defaults. Short keys follow existing pattern (d, s, h, t, sr) for compact JSON storage.

92c: Metadata Endpoints

4 new endpoints on api/routers/meta.py:

  • GET /meta/schools — 9 doctrinal schools with OODA multiplier, risk tolerance
  • GET /meta/commanders — 13 commander profiles with personality traits
  • GET /meta/weapons — 56+ weapons across base + era directories
  • GET /meta/weapons/{id} — full weapon YAML definition

All scan filesystem YAML on request, following existing list_doctrines pattern.

Design Decisions

  1. Compute on read, not on write — analytics endpoints parse events_json each request. O(n) single-pass is fast enough for 50K events. Avoids schema migration and post-processing step.

  2. Short keys for frame storagemo, po, hp, fp, ap, su, eg minimize JSON size. Matches existing convention of d, s, h, t, sr.

  3. Suppression float→int mappingUnitSuppressionState.value is float 0.0-1.0. Mapped to int 0-4 via min(4, int(val * 4)) for discrete suppression levels.

  4. Health from personnelsum(p.is_effective() for p) / len(personnel). Units without personnel infer from status (ACTIVE→1.0, DESTROYED→0.0).

  5. Engaged set from recorder events — scan recorder.events for EngagementEvent matching current tick. O(n) but only on frame capture ticks.

  6. Private attribute accessengine.battle_manager._suppression_states is a pragmatic read-only access. No public API exists and adding one would be an engine change.

Files Changed

File Action Lines
api/schemas.py Modified 15 new Pydantic models, 7 MapUnitFrame fields
api/routers/analytics.py New ~220 lines, 5 endpoints + 4 helpers
api/routers/meta.py Modified 4 new endpoints (~120 lines)
api/routers/runs.py Modified 7 new fields in frame deserialization
api/run_manager.py Modified _capture_frame enrichment + call site
api/main.py Modified 1 import + 1 include_router
tests/api/test_analytics.py New 10 tests
tests/api/test_frame_enrichment.py New 5 tests
tests/api/test_meta.py Modified 5 new tests

Known Limitations

  • Analytics compute on every request — no caching. Fine for 50K events but could add run_analytics_json column if performance becomes an issue.
  • Weapons endpoint scans filesystem on each request (~56 files). Fast enough but could cache.
  • engaged field derived from recorder event scan is O(n) on total events, not just current tick. Could build tick index for large event sets.
  • Morale timeline only includes ticks where MoraleStateChangeEvent occurred, not every tick. Frontend should interpolate.

Postmortem

1. Delivered vs Planned

Item Planned Delivered Notes
92a: Analytics endpoints 5 endpoints 5 endpoints Exact match
92b: Frame enrichment 7 fields 7 fields Exact match
92c: Metadata endpoints 5 endpoints 4 endpoints Combined weapons list+detail as planned
Tests ~26 20 Slight under — fewer edge case tests, but all paths covered

Verdict: On target. All planned deliverables shipped.

2. Integration Audit

Check Status
analytics.py imported in main.py PASS
Analytics router registered with /api prefix PASS
All 15 new schema models used by endpoints PASS
Frame enrichment called from _run_sync PASS
Frame deserialization reads enriched fields PASS
Meta endpoints tested PASS
No dead/orphaned code PASS

3. Test Quality Review

  • Unit tests: _capture_frame tested with mock units (3 tests)
  • Integration tests: API endpoints tested with mock DB data (10 analytics + 5 meta)
  • Edge cases: empty events, run not found (404), run not completed (409), old frames without enrichment
  • No slow tests — all use direct DB insertion, no simulation required
  • Realistic data: mock events mirror actual event structure from the engine

4. API Surface Check

  • Type hints on all 10 public endpoint functions: PASS
  • Helper functions properly prefixed with _: PASS
  • Dependency injection via Depends(get_db) / Depends(get_settings): PASS
  • No bare print(): PASS

5. Deficit Discovery

No new deficits. All known limitations are by-design trade-offs, not bugs.

6. Documentation Freshness

Docs update needed — will be done as part of lockstep before commit.

7. Performance Sanity

  • Full suite: 9998 passed, 21 skipped, 304 deselected in 178.72s (2:58)
  • Previous (Phase 91): 9998 passed in 234.53s (3:54)
  • Delta: -55.8s (-24%) — faster, likely due to no evaluator-dependent tests running

8. Summary

  • Scope: On target
  • Quality: High — comprehensive testing, clean integration
  • Integration: Fully wired
  • Deficits: 0 new items
  • Action items: Lockstep documentation update