Files
Tatami/tests/conftest.py
T
lars 07f8415eb1 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.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:19:00 +02:00

68 lines
1.9 KiB
Python

"""Shared pytest fixtures and test setup for the tatami package.
``tatami.traveltimes`` raises at import time if ``GOOGLE_MAPS_API_KEY`` is unset,
so we set a dummy value here (conftest is imported before any test module, and
therefore before the package is imported) to make the modules importable without
a real API key. Tests never hit the live API; the HTTP layer is mocked.
"""
import os
os.environ.setdefault("GOOGLE_MAPS_API_KEY", "test-key")
import numpy as np
import pandas as pd
import pytest
from tatami.classes import Participant
@pytest.fixture(autouse=True)
def _seed_rng():
"""Make the randomized steps (shuffles, annealing) deterministic per test."""
np.random.seed(1234)
def make_participants(
n: int, kitchen_sizes: list[float] | None = None
) -> list[Participant]:
"""Create ``n`` participants with distinct addresses and uuids."""
if kitchen_sizes is None:
kitchen_sizes = [10.0] * n
return [
Participant(
name=f"P{i}",
address=f"address {i}",
phone=f"phone {i}",
kitchen_size=kitchen_sizes[i],
allergies="",
)
for i in range(n)
]
def make_timedelta_matrix(
participants: list[Participant], seconds: np.ndarray
) -> pd.DataFrame:
"""Build a uuid-indexed square matrix of ``pd.Timedelta`` from a seconds array.
Mirrors the shape produced by ``get_participant_distance_matrix``: object dtype
cells holding ``pd.Timedelta`` values, indexed and columned by participant uuid.
"""
uuids = [p.uuid for p in participants]
matrix = pd.DataFrame(index=pd.Index(uuids), columns=pd.Index(uuids), dtype=object)
for i, u in enumerate(uuids):
for j, v in enumerate(uuids):
matrix.at[u, v] = pd.to_timedelta(f"{int(seconds[i][j])}s")
return matrix
@pytest.fixture
def participants_factory():
return make_participants
@pytest.fixture
def matrix_factory():
return make_timedelta_matrix