Files
directus/.planning/phase-3-timing-and-penalty-tables/README.md
T
julian a8e808e71c Scaffold directus service planning structure
Initial commit. Establishes the .planning/ tree mirroring processor's
shape (ROADMAP.md as nav hub + per-phase folders with READMEs and
granular task files).

Six phases:

1. Slice 1 schema + deploy pipeline — what Rally Albania 2026 needs.
   Org catalog (orgs, users, vehicles, devices) + event participation
   (events, classes, entries, entry_crew, entry_devices). db-init/
   for the positions hypertable + faulty column. snapshot/apply
   tooling. Gitea CI dry-run. Dogfood seed of Rally Albania 2026.
   Nine task files with full Goal / Deliverables / Specification /
   Acceptance criteria / Risks / Done sections.

2. Course definition — stages, segments, geofences, waypoints, SLZs.
   PostGIS extension introduced here.

3. Timing & penalty tables — co-developed with processor Phase 2.
   entry_segment_starts, entry_crossings, entry_penalties,
   stage_results, penalty_formulas.

4. Permissions & policies — Directus 11 dynamic-filter Policies per
   logical role. Deployment-time work, deferred to keep early phases
   focused on the data model.

5. Custom extensions — TypeScript hooks/endpoints implementing the
   cross-plane workflows the schema implies (faulty-flag → Redis
   stream emit, stage-open materializer, etc.).

6. Future / optional — retroactivity preview UI, command-routing
   Flows, audit trails, federation rule import. Not committed.

Non-negotiable design rules captured in ROADMAP.md: schema authority
in Directus + snapshot-as-code + db-init for non-Directus DDL +
sequential idempotent migrations + entrypoint apply order + no
application logic in Flows + permissions deferred to Phase 4.

Architectural anchors point at the wiki at ../docs/wiki/ — the schema
draft, the Rally Albania 2025 source page, plus the existing
processor/postgres-timescaledb/live-channel pages. Each task file
calls out the wiki refs an implementing agent should read first.

README.md mirrors the processor service README structure: quick start,
local Docker test, prod/stage deployment notes, env vars, CI behavior.
2026-05-01 20:42:44 +02:00

30 lines
3.5 KiB
Markdown

# Phase 3 — Timing & penalty tables
**Status:** ⬜ Not started — co-developed with [[processor]] Phase 2
**Outcome:** The schema half of the paired schema/code work that produces real timing results. Adds `entry_segment_starts`, `entry_crossings`, `entry_penalties`, `stage_results`, and `penalty_formulas` collections. Penalty evaluator registry ships on the [[processor]] side; the rule numeric values ship here. After Phase 3, operators can review computed penalties, override them, and publish official stage results.
## Why this is co-developed
- **Schema and writer must land together.** `entry_crossings` rows are written by [[processor]] Phase 2; defining the collection without the writer is dead weight, defining the writer without the collection is broken code. Land both in the same release window.
- **Penalty formula seeding is event-specific.** Rally Albania 2026's SLZ brackets come from the Supplementary Regulation (published 60 days before the event per regs §1.8). Phase 3 needs that data, or a reasonable placeholder, before the rally.
- **Snapshot pattern requires care.** `entry_penalties` snapshots its inputs (peak speed, count, formula values). The schema must capture this faithfully so the recompute strategy in [[processor]] works.
## Tasks (sketched, not detailed)
| # | Task | Notes |
|---|------|-------|
| 3.1 | `penalty_formulas` collection | Per [[directus-schema-draft]]: `event_id`, `belongs_to_type`, `belongs_to_id`, `type`, `offence_min`, `offence_max`, `operator`, `penalty`, `retroactive`, `enabled`. Both bracket-style and flat-style coexist. |
| 3.2 | `entry_segment_starts` collection | `entry_id`, `segment_id`, `start_position`, `target_at`, `manual_override`. Materialized at stage open. |
| 3.3 | `entry_crossings` collection | Per-position-derived crossing events. Idempotent on `(entry_id, geofence_id, ts)`. Written by [[processor]]. |
| 3.4 | `entry_penalties` collection | `entry_id`, `type`, `formula_id`, `formula_snapshot` (JSONB), `inputs` (JSONB), `seconds`, `evaluated_at`, `recomputed_at`, `manual_override`. Snapshot inputs and rule rows for cheap recompute. |
| 3.5 | `stage_results` collection | `entry_id`, `stage_id`, `clean_time`, `penalty_seconds`, `total_time`, `position_in_class`, `position_overall`, `published_at`. The next-stage seeding input is `clean_time`. |
| 3.6 | Custom interface for penalty review | Operator-facing panel showing all `entry_penalties` rows for a stage, with diff between auto-computed and manual override. Likely a custom extension (Phase 5 territory). Phase 3 produces the schema; the UI follows. |
| 3.7 | Snapshot regeneration + CI verification | All new collections in the snapshot; CI dry-run still passes. |
| 3.8 | Rally Albania SLZ bracket seed | Once the Supplementary Regulation is published, seed `penalty_formulas` rows with the actual brackets. Single SQL/Directus-API script. |
## Open questions blocking task-level detail
1. Does `entry_crossings` need PostGIS metadata on each row (e.g. the exact geometry-relative position), or just `(entry_id, geofence_id, ts, kind)`? Default: minimal, the position is in `positions` and the join key gets you back to it.
2. Where does `position_in_class` and `position_overall` get computed — DB view, materialized view, or [[processor]]-written column? Trade-off: view is simpler but slower; column is faster but needs invalidation.
3. Penalty review workflow UX — is the operator approving each row individually, or bulk-approving the auto-computed set with manual exceptions? Drives whether `manual_override` is a single bool or a richer state.