Files
directus/.planning/phase-1-slice-1-schema/05-event-participation-collections.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

126 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: 1199 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.)