Move Participant/Group to pydantic and add a saveable/editable Plan model

Participant and Group are now pydantic BaseModels. Group.hosts can form
cycles between groups, so it's kept as a private, non-persisted live list
(set via set_hosts()/add_host()) backed by a serializable host_uuids field,
re-linked via resolve_hosts() after a reload.

A new Plan model (src/tatami/plan.py) bundles groups, the after-party group,
and the event config (course_times, organizer_contacts, info_text,
spreadsheet_id) and supports save()/load() to/from JSON. tatami_masterplan's
__main__ now saves to masterplan.json (PLAN_FILE env var to override) on
first run and loads it on later runs instead of recomputing, so the plan can
be hand-edited (move a member between groups, change a course, fill in
spreadsheet_id) and picked up on rerun without hitting the Routes API again.
spreadsheet_id moves out of .env (GOOGLE_SHEETS_SPREADSHEET_ID) onto the plan
itself, since it's part of the plan rather than a secret.
This commit is contained in:
2026-06-19 16:07:47 +02:00
parent 91dc7729f9
commit 6b0fca0100
12 changed files with 538 additions and 157 deletions
+45 -33
View File
@@ -71,57 +71,69 @@ def _make_mock_groups() -> tuple[list[Group], Group]:
"""3 groups of 3 fictional participants with addresses around Karlsruhe."""
hosts = [
Participant(
"Anna Wagner", "Kaiserstraße 12, 76131 Karlsruhe", "0721-1000001", 9, "none"
name="Anna Wagner",
address="Kaiserstraße 12, 76131 Karlsruhe",
phone="0721-1000001",
kitchen_size=9,
allergies="none",
),
Participant(
"Jonas Becker",
"Waldstraße 5, 76133 Karlsruhe",
"0721-1000002",
7,
"lactose",
name="Jonas Becker",
address="Waldstraße 5, 76133 Karlsruhe",
phone="0721-1000002",
kitchen_size=7,
allergies="lactose",
),
Participant(
"Mira Hofmann", "Yorckstraße 22, 76185 Karlsruhe", "0721-1000003", 8, "none"
name="Mira Hofmann",
address="Yorckstraße 22, 76185 Karlsruhe",
phone="0721-1000003",
kitchen_size=8,
allergies="none",
),
]
semi_hosts = [
Participant(
"Lukas Schreiber",
"Sophienstraße 40, 76135 Karlsruhe",
"0721-1000004",
5,
"nuts",
name="Lukas Schreiber",
address="Sophienstraße 40, 76135 Karlsruhe",
phone="0721-1000004",
kitchen_size=5,
allergies="nuts",
),
Participant(
"Sophie Lindner",
"Beiertheimer Allee 18, 76137 Karlsruhe",
"0721-1000005",
6,
"none",
name="Sophie Lindner",
address="Beiertheimer Allee 18, 76137 Karlsruhe",
phone="0721-1000005",
kitchen_size=6,
allergies="none",
),
Participant(
"Tom Vogel",
"Durlacher Allee 75, 76131 Karlsruhe",
"0721-1000006",
4,
"none",
name="Tom Vogel",
address="Durlacher Allee 75, 76131 Karlsruhe",
phone="0721-1000006",
kitchen_size=4,
allergies="none",
),
Participant(
"Lea Brandt",
"Moltkestraße 30, 76133 Karlsruhe",
"0721-1000007",
3,
"gluten",
name="Lea Brandt",
address="Moltkestraße 30, 76133 Karlsruhe",
phone="0721-1000007",
kitchen_size=3,
allergies="gluten",
),
Participant(
"Felix Krause", "Adlerstraße 14, 76133 Karlsruhe", "0721-1000008", 8, "none"
name="Felix Krause",
address="Adlerstraße 14, 76133 Karlsruhe",
phone="0721-1000008",
kitchen_size=8,
allergies="none",
),
Participant(
"Nora Fink",
"Rüppurrer Straße 60, 76137 Karlsruhe",
"0721-1000009",
5,
"none",
name="Nora Fink",
address="Rüppurrer Straße 60, 76137 Karlsruhe",
phone="0721-1000009",
kitchen_size=5,
allergies="none",
),
]