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.
- 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.
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.