a8e808e71c
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.
126 lines
6.5 KiB
Markdown
126 lines
6.5 KiB
Markdown
# Task 1.5 — Event-participation collections
|
||
|
||
**Phase:** 1 — Slice 1 schema + deploy pipeline
|
||
**Status:** ⬜ Not started
|
||
**Depends on:** 1.4
|
||
**Wiki refs:** `docs/wiki/synthesis/directus-schema-draft.md` (Event-level participation section), `docs/wiki/sources/rally-albania-regulations-2025.md` (§2.2–§2.5 for class taxonomy reference)
|
||
|
||
## Goal
|
||
|
||
Create the per-event participation collections in the Directus admin UI: `events`, `classes`, `entries`, `entry_crew`, `entry_devices`. These are scoped to a single event and form the unit of timing.
|
||
|
||
## Deliverables
|
||
|
||
Create the following collections via the admin UI. Field shapes per [[directus-schema-draft]].
|
||
|
||
### `events`
|
||
|
||
| Field | Type | Notes |
|
||
|---|---|---|
|
||
| `id` * | UUID | |
|
||
| `organization_id` * | M2O → organizations | event lives in exactly one org |
|
||
| `name` * | string | "Rally Albania 2026" |
|
||
| `slug` * | string | unique within an org |
|
||
| `discipline` * | string (dropdown) | enum: `rally`, `time-trial`, `regatta`, `trail-run`, `hike` — drives validation |
|
||
| `starts_at` * | timestamp | event window begin |
|
||
| `ends_at` * | timestamp | event window end |
|
||
| `regulation_doc_url` | string | external URL to the rulebook PDF/page (e.g. `wiki/sources/rally-albania-regulations-2025.md`) |
|
||
| `notes` | text | |
|
||
|
||
Unique constraint: `(organization_id, slug)`.
|
||
|
||
### `classes`
|
||
|
||
| Field | Type | Notes |
|
||
|---|---|---|
|
||
| `id` * | UUID | |
|
||
| `event_id` * | M2O → events | classes are per-event |
|
||
| `code` * | string | "M-1", "C-2", "S-1", … |
|
||
| `name` * | string | human-readable |
|
||
| `description` | text | eligibility rules in plain text |
|
||
| `sort_order` | integer | for display ordering |
|
||
|
||
Unique constraint: `(event_id, code)`.
|
||
|
||
### `entries`
|
||
|
||
The unit of timing. One row per (vehicle or solo participant) registered for an event.
|
||
|
||
| Field | Type | Notes |
|
||
|---|---|---|
|
||
| `id` * | UUID | |
|
||
| `event_id` * | M2O → events | |
|
||
| `vehicle_id` | M2O → vehicles | nullable — null for foot races (trail-run, hike) |
|
||
| `team_id` | M2O → teams | nullable — for now, no `teams` collection in Phase 1, leave the field nullable and unwired (`teams` collection is Phase 2 territory if needed; per the schema draft, teams are an org-level catalog item) |
|
||
| `class_id` * | M2O → classes | required: every entry has a class |
|
||
| `race_number` * | integer | per Rally Albania §5: 1–199 moto, 2xx quad, 3xx car, 4xx SSV |
|
||
| `status` * | string (dropdown) | enum: `registered`, `confirmed`, `started`, `finished`, `dnf`, `dns`, `dq`, `withdrawn` |
|
||
| `registered_at` | timestamp | default `now()` |
|
||
| `notes` | text | |
|
||
|
||
Unique constraint: `(event_id, race_number)` — no two entries share a race number in the same event.
|
||
|
||
> **Status enum semantics** (from the schema draft):
|
||
> - `registered` — paid, not yet confirmed at scrutineering
|
||
> - `confirmed` — passed scrutineering, eligible to start
|
||
> - `started` — has begun the first stage
|
||
> - `finished` — completed all stages within MTA
|
||
> - `dnf` — did not finish (started but couldn't complete)
|
||
> - `dns` — did not start (confirmed but absent at start)
|
||
> - `dq` — disqualified (rule violation, see Rally Albania §12.13)
|
||
> - `withdrawn` — voluntary withdraw (Rally Albania §12.15 — MTA penalty for remaining stages)
|
||
|
||
> **`teams` deferred:** Phase 1 doesn't define a `teams` collection. The `team_id` field on `entries` is nullable and the FK target is intentionally unwired in Phase 1. Drop the field entirely if it complicates the snapshot — re-add in Phase 2 if a real team relationship is needed.
|
||
|
||
### `entry_crew` (junction)
|
||
|
||
| Field | Type | Notes |
|
||
|---|---|---|
|
||
| `id` * | UUID | |
|
||
| `entry_id` * | M2O → entries | |
|
||
| `user_id` * | M2O → directus_users | |
|
||
| `role` * | string (dropdown) | enum: `pilot`, `co-pilot`, `navigator`, `mechanic`, `rider`, `runner`, `hiker` |
|
||
|
||
Unique constraint: `(entry_id, user_id)` — a user can't appear twice in the same entry's crew.
|
||
|
||
### `entry_devices` (junction)
|
||
|
||
| Field | Type | Notes |
|
||
|---|---|---|
|
||
| `id` * | UUID | |
|
||
| `entry_id` * | M2O → entries | |
|
||
| `device_id` * | M2O → devices | |
|
||
| `assigned_user_id` | M2O → directus_users | nullable. null = vehicle-mounted; set = body-worn on this crew member |
|
||
| `mount_position` | string | optional free text: "panic_button_pilot", "hardwired_dash", "backup_chassis" |
|
||
|
||
Unique constraint: `(entry_id, device_id)` — a device can't appear twice in the same entry.
|
||
|
||
## Specification
|
||
|
||
- **All M2O `ON DELETE`:** `RESTRICT` by default. Cascading from event → entries is appealing but risky for audit/historical purposes — leave `RESTRICT` and require explicit operator action.
|
||
- **`status` enum order matters for display.** Set the dropdown's option order to match the lifecycle: `registered` → `confirmed` → `started` → `finished` → `dnf` → `dns` → `dq` → `withdrawn`.
|
||
- **`race_number` is integer**, not string. Plate background color (white/yellow/green/red per Rally Albania §5.5) is derivable from the number range; not a stored field.
|
||
- **No permission policies yet** — Phase 4 territory. Admin-only access.
|
||
- **No `team_id` field if it adds complexity** — the schema draft leaves teams as an org-level catalog item that's not yet defined. Phase 1 ships entries without team support.
|
||
|
||
## Acceptance criteria
|
||
|
||
- [ ] All five collections exist in the admin UI with the fields listed above.
|
||
- [ ] Required fields flagged required.
|
||
- [ ] Unique constraints enforced.
|
||
- [ ] M2O relations work in the admin UI.
|
||
- [ ] `entries.status` dropdown shows all eight values in lifecycle order.
|
||
- [ ] Manually walk through the registration: create an event → create classes → create one entry referencing a vehicle, class, and race number → add two `entry_crew` rows (pilot + co-pilot) → add three `entry_devices` rows (one with `assigned_user_id` set, two with null). All FKs resolve.
|
||
- [ ] Try to create a second entry with the same `race_number` in the same event → error.
|
||
- [ ] `pnpm run schema:snapshot` produces a snapshot containing the new collections.
|
||
- [ ] Cross-checked against the schema draft: every field that should exist does, every nullable field is nullable, every unique constraint is in place.
|
||
|
||
## Risks / open questions
|
||
|
||
- **`assigned_user_id` on entry_devices** — Directus represents this as an M2O. Verify the snapshot encodes the nullable / non-required nature correctly.
|
||
- **Cascading deletes vs RESTRICT** — RESTRICT is the safe default but may make admin UX painful (you can't delete an event without first deleting all its entries, etc.). Phase 4 / Phase 5 may revisit with custom Flows that walk the dependency graph.
|
||
|
||
## Done
|
||
|
||
(Fill in commit SHA + one-line note when this lands.)
|