mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 11:54:59 -04:00
217 lines
6.3 KiB
Markdown
217 lines
6.3 KiB
Markdown
# Backend Architecture
|
|
|
|
## Summary
|
|
|
|
The backend should move from direct page handlers calling OpenF1 into a layered
|
|
local-first architecture. Source clients fetch data, ingestion persists it,
|
|
store/query packages expose domain reads, and Web handlers return read models
|
|
with source and freshness metadata.
|
|
|
|
## Proposed Package Boundaries
|
|
|
|
### `internal/store`
|
|
|
|
Owns SQLite as the local domain database.
|
|
|
|
Responsibilities:
|
|
|
|
- Schema creation and migrations.
|
|
- Typed upsert methods for domain records.
|
|
- Typed read methods for screens and backend services.
|
|
- Raw payload storage.
|
|
- Ingestion metadata and provenance.
|
|
- Transactions and batch writes.
|
|
|
|
Non-goals:
|
|
|
|
- Calling OpenF1 directly.
|
|
- Knowing Web UI route behavior.
|
|
- Rendering derived frontend-specific structures unless they are shared read
|
|
models.
|
|
|
|
### `internal/ingest`
|
|
|
|
Coordinates backfill, refresh, and opportunistic fetches.
|
|
|
|
Responsibilities:
|
|
|
|
- Ingest year, meeting, or session.
|
|
- Fetch required endpoints through source clients.
|
|
- Persist raw payloads and normalized rows.
|
|
- Track partial successes and failures.
|
|
- Support resumable, idempotent runs.
|
|
- Respect rate limits and free-tier constraints.
|
|
|
|
Default ingestion modes:
|
|
|
|
- CLI bulk ingestion for years, meetings, and sessions.
|
|
- Opportunistic small fetches in Web mode when a user opens missing data.
|
|
- Explicit refresh mode for completed data when needed.
|
|
|
|
Rate-limit defaults:
|
|
|
|
- Bulk ingestion must be resumable and idempotent.
|
|
- Bulk ingestion should default to conservative sequential fetching with a
|
|
delay between OpenF1 requests.
|
|
- Failed requests should use bounded exponential backoff with jitter.
|
|
- HTTP 429 and live-session lockout should pause or stop the current run rather
|
|
than tight-loop retries.
|
|
- `--dry-run` should show planned datasets and estimated request count before a
|
|
large ingest.
|
|
|
|
### OpenF1 Source Client Layer
|
|
|
|
The current `internal/api` client can remain, but it should become one source
|
|
adapter rather than the main application data layer.
|
|
|
|
Responsibilities:
|
|
|
|
- Build OpenF1 URLs.
|
|
- Apply auth headers when `OPENF1_API_KEY` exists.
|
|
- Decode source payloads into source/domain structs.
|
|
- Preserve stale fallback behavior where useful.
|
|
|
|
Future direction:
|
|
|
|
- Make source fetches observable by ingestion metadata.
|
|
- Avoid direct UI route dependency on source calls.
|
|
|
|
### Live Timing Bridge
|
|
|
|
The current live parser should be extracted out of `internal/ui` into reusable
|
|
backend/domain logic.
|
|
|
|
Responsibilities:
|
|
|
|
- Connect to official F1 SignalR.
|
|
- Parse topic payloads into typed live events/state.
|
|
- Maintain current live snapshot.
|
|
- Broadcast snapshots to Web clients through SSE.
|
|
- Feed TUI live mode without coupling parser code to Bubble Tea.
|
|
- Persist live events/snapshots as an append-only stream once the bridge is
|
|
extracted.
|
|
|
|
Persistence policy:
|
|
|
|
- Live SignalR data should be stored separately from normalized post-session
|
|
OpenF1 records.
|
|
- Live data represents what was broadcast at the time, not necessarily the
|
|
corrected final historical record.
|
|
- A later reconciliation step can compare live stream data with OpenF1
|
|
post-session records.
|
|
|
|
### Web API Read Models
|
|
|
|
Web handlers should become thin adapters from query services to JSON.
|
|
|
|
Responsibilities:
|
|
|
|
- Validate route/query parameters.
|
|
- Call local-first query/read services.
|
|
- Return consistent response envelopes.
|
|
- Include source/freshness metadata.
|
|
|
|
Suggested response metadata:
|
|
|
|
- `source`: `local`, `api`, `cache`, `live`, or `missing`.
|
|
- `last_ingested_at`.
|
|
- `is_stale`.
|
|
- `missing_datasets`.
|
|
- `errors` where partial data is returned.
|
|
|
|
### CLI Ingestion Commands
|
|
|
|
CLI commands should make bulk ingestion explicit and user-controlled.
|
|
|
|
Candidate commands/flags:
|
|
|
|
- `--ingest-year 2024`
|
|
- `--ingest-meeting <meeting_key>`
|
|
- `--ingest-session <session_key>`
|
|
- `--refresh`
|
|
- `--dry-run`
|
|
|
|
CLI output should include:
|
|
|
|
- What will be fetched.
|
|
- What is already local.
|
|
- What succeeded.
|
|
- What failed.
|
|
- Whether the run is resumable.
|
|
|
|
## Local-First Read Behavior
|
|
|
|
Default rule:
|
|
|
|
1. Read from local domain DB.
|
|
2. If missing and request scope is small, optionally fetch from OpenF1.
|
|
3. Persist successful fetches.
|
|
4. Return local/read-model data with metadata.
|
|
5. If OpenF1 is unavailable, return partial local data and clear missing/stale
|
|
metadata rather than an empty page.
|
|
|
|
Examples:
|
|
|
|
- Opening a completed race with all local data should perform no OpenF1 calls.
|
|
- Opening a completed race with missing weather may opportunistically fetch only
|
|
weather.
|
|
- Opening a whole season should not silently trigger a large backfill.
|
|
- During live-session lockout, historical pages should still render from local
|
|
data.
|
|
|
|
## Opportunistic Fetch Policy
|
|
|
|
Allowed by default:
|
|
|
|
- Single meeting sessions.
|
|
- Single session results/grid/weather/race control.
|
|
- Small metadata gaps needed to render a screen.
|
|
|
|
Not allowed by default:
|
|
|
|
- Full season backfills.
|
|
- High-volume telemetry/location/car data.
|
|
- Repeated refresh loops during API lockout.
|
|
- Silent destructive refresh of completed local data.
|
|
|
|
## Migration Strategy
|
|
|
|
The existing SQLite HTTP cache should remain operational during the refactor.
|
|
The new domain database should be introduced without requiring users to delete
|
|
their current cache.
|
|
|
|
Default migration stance:
|
|
|
|
- Keep the current cache tables and stale fallback behavior intact.
|
|
- Introduce domain tables through `internal/store`.
|
|
- Prefer a separate domain database file at first if it materially reduces
|
|
migration risk; using the same SQLite file remains acceptable if table names
|
|
and migrations are carefully isolated.
|
|
- Do not attempt to transform arbitrary URL-keyed cache entries into domain rows
|
|
automatically.
|
|
- New ingestion runs should populate domain tables from fresh source fetches or
|
|
explicitly supported raw payloads.
|
|
- Web routes can migrate endpoint by endpoint from source-first to local-first.
|
|
|
|
## Failure Modes
|
|
|
|
The backend should explicitly represent:
|
|
|
|
- Local data available.
|
|
- Local data partial.
|
|
- Local data missing.
|
|
- OpenF1 locked/unavailable.
|
|
- Stale cache fallback used.
|
|
- Live feed connected/disconnected.
|
|
- Ingestion partial failure.
|
|
|
|
The Web UI should be able to show these states without guesswork.
|
|
|
|
## Open Questions
|
|
|
|
- Should API response envelopes be introduced globally or per endpoint during
|
|
migration?
|
|
- How should source schema drift be detected and surfaced?
|
|
- What is the minimum dataset required for a Race Hub to be considered
|
|
complete?
|