Commit Graph

5 Commits

Author SHA1 Message Date
lars 6b0fca0100 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.
2026-06-19 16:07:47 +02:00
lars 91dc7729f9 Add optional Google Sheets export for the masterplan
Lets organizers automatically populate a shared Google Sheet (the same kind
they previously built by hand) with an Overview tab and one tab per group,
instead of printing dicts. Group tabs are named after all their members
(not a single host) and show each group's route, course times, and guests
with allergies. The Overview tab also gets configurable Meal Times, Support
Contacts, and Info sections, passed through as plain data from
tatami_masterplan.py. Export is fully opt-in via GOOGLE_SHEETS_CREDENTIALS_FILE
and GOOGLE_SHEETS_SPREADSHEET_ID; without them, behavior is unchanged.
2026-06-19 15:17:37 +02:00
lars 2c60b83d8a Load Google Maps API key from a .env file
- Add python-dotenv and call load_dotenv() in traveltimes.py before reading
  GOOGLE_MAPS_API_KEY (exported env vars still take precedence).
- Add .env.example template; the real .env stays gitignored.
2026-06-19 14:24:54 +02:00
lars 4da624bfcf Fix route optimizer and group assignment; add test suite
Route building / optimization (tatami_masterplan.py):
- fast_total_time was permutation-invariant: it applied get_courses to
  group *values* instead of slots and ignored the permutation, so every
  ordering scored identically and the annealing optimized nothing. It now
  maps each rotation slot to its assigned group via the permutation.
- Replaced the broken next_permutation/simulated_annealing (enumerated n!
  orderings per iteration, fed unnormalized Boltzmann weights to
  np.random.choice -> ValueError, and returned the last random sample) with
  a standard neighbor-swap annealer that tracks and returns the best
  solution and handles <2 slots.
- Convert the reduced Timedelta matrix to float seconds before annealing
  (np.exp can't operate on Timedelta).

Group building (tatami_masterplan.py):
- assign_courses set each group's hosts (sorted by course) before all
  courses were assigned, so hosts whose course was still None got
  mis-ordered. Assign all courses first, then wire up hosts.
- get_masterplan no longer mutates the caller's participant list.

classes.py:
- Narrow casts on distance-matrix lookups to satisfy the mypy gate
  (pre-existing failures).

Tests:
- Add pytest suite (74 tests) covering the rotation topology, route cost
  and optimization, the domain model, the masterplan pipeline, and the
  Routes API wrapper (HTTP mocked). The cost cross-check caught the
  assign_courses ordering bug above.
2026-06-19 14:19:00 +02:00
lars 51bc491cc8 Initial Commit. I'm afraid of testing it. 2025-05-01 15:48:11 +02:00