Problem
Team identity is currently the free-form string "North-South" / "East-West", and it leaks across layers the same way position strings do:
- All score bookkeeping is keyed by
team.name: Game.scores (packages/contrai-engine/src/contrai_engine/model/game.py), Round.team_tricks / round_scores (model/round/round.py), and the whole of model/round/scoring.py passes team_name strings around.
Game.winner is a team name string, not a team.
- The view re-derives team from position strings (
view/formatting.py — position in ("North", "South")) and keys TEAM_ABBR on name strings (view/theme.py).
Team (packages/contrai-core/src/contrai_core/team.py) has no __eq__/__hash__, so call sites silently rely on identity semantics.
Proposal
- Add a two-member
TeamSide enum (NS, EW) in contrai-core, plus a Position.team_side property (North/South → NS, East/West → EW). Depends on the planned Position enum.
- Rekey score dicts as
dict[TeamSide, int]; team display strings ("North-South", "N-S") become presentation mappings in the view.
- Cleanup rider:
Team.get_partner and Team.contains_player are dead code in production and become fully redundant once Position.partner exists — remove them.
Why (roadmap)
Training rewards (RL), scraped-game labels (supervised learning), and the future web server's wire format all need a stable, serializable team identity. Name strings that happen to match dict keys break silently once games are persisted.
Acceptance criteria
- No
"North-South"/"East-West" literals outside view presentation mappings and prose.
- Scoring,
Game.scores, Round.team_tricks keyed by TeamSide with tests updated.
pytest green in both core and engine packages.
Problem
Team identity is currently the free-form string
"North-South"/"East-West", and it leaks across layers the same way position strings do:team.name:Game.scores(packages/contrai-engine/src/contrai_engine/model/game.py),Round.team_tricks/round_scores(model/round/round.py), and the whole ofmodel/round/scoring.pypassesteam_namestrings around.Game.winneris a team name string, not a team.view/formatting.py—position in ("North", "South")) and keysTEAM_ABBRon name strings (view/theme.py).Team(packages/contrai-core/src/contrai_core/team.py) has no__eq__/__hash__, so call sites silently rely on identity semantics.Proposal
TeamSideenum (NS,EW) incontrai-core, plus aPosition.team_sideproperty (North/South → NS, East/West → EW). Depends on the plannedPositionenum.dict[TeamSide, int]; team display strings ("North-South","N-S") become presentation mappings in the view.Team.get_partnerandTeam.contains_playerare dead code in production and become fully redundant oncePosition.partnerexists — remove them.Why (roadmap)
Training rewards (RL), scraped-game labels (supervised learning), and the future web server's wire format all need a stable, serializable team identity. Name strings that happen to match dict keys break silently once games are persisted.
Acceptance criteria
"North-South"/"East-West"literals outside view presentation mappings and prose.Game.scores,Round.team_trickskeyed byTeamSidewith tests updated.pytestgreen in both core and engine packages.