Coverage for dao/lineup_dao.py: 9.78%
70 statements
« prev ^ index » next coverage.py v7.10.6, created at 2026-04-12 14:12 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2026-04-12 14:12 +0000
1"""
2Lineup Data Access Object.
4Handles all database operations for match lineups/formations:
5- Get lineup for a team in a match
6- Save/update lineup (upsert)
7"""
9import structlog
11from dao.base_dao import BaseDAO, invalidates_cache
13logger = structlog.get_logger()
15# Cache patterns for invalidation
16LINEUP_CACHE_PATTERN = "mt:dao:lineup:*"
19class LineupDAO(BaseDAO):
20 """Data access object for match lineup operations."""
22 def get_lineup(self, match_id: int, team_id: int) -> dict | None:
23 """
24 Get lineup for a team in a specific match.
26 Args:
27 match_id: Match ID
28 team_id: Team ID
30 Returns:
31 Lineup dict with enriched player details or None if not found
32 """
33 try:
34 response = (
35 self.client.table("match_lineups")
36 .select("*")
37 .eq("match_id", match_id)
38 .eq("team_id", team_id)
39 .execute()
40 )
42 if not response.data or len(response.data) == 0:
43 return None
45 lineup = response.data[0]
47 # Enrich positions with player details
48 positions = lineup.get("positions", [])
49 if positions:
50 player_ids = [p.get("player_id") for p in positions if p.get("player_id")]
51 if player_ids:
52 players_response = (
53 self.client.table("players")
54 .select("id, jersey_number, first_name, last_name")
55 .in_("id", player_ids)
56 .execute()
57 )
58 players_map = {p["id"]: p for p in (players_response.data or [])}
60 # Enrich each position with player info
61 for pos in positions:
62 player_id = pos.get("player_id")
63 if player_id and player_id in players_map:
64 player = players_map[player_id]
65 pos["jersey_number"] = player.get("jersey_number")
66 pos["first_name"] = player.get("first_name")
67 pos["last_name"] = player.get("last_name")
68 first = player.get("first_name", "")
69 last = player.get("last_name", "")
70 pos["display_name"] = f"{first} {last}".strip()
72 lineup["positions"] = positions
74 return lineup
76 except Exception as e:
77 logger.error("lineup_get_error", match_id=match_id, team_id=team_id, error=str(e))
78 return None
80 @invalidates_cache(LINEUP_CACHE_PATTERN)
81 def save_lineup(
82 self,
83 match_id: int,
84 team_id: int,
85 formation_name: str,
86 positions: list[dict],
87 user_id: str | None = None,
88 ) -> dict | None:
89 """
90 Save or update lineup for a team in a match (upsert).
92 Args:
93 match_id: Match ID
94 team_id: Team ID
95 formation_name: Formation name (e.g., "4-3-3")
96 positions: List of {player_id, position} dicts
97 user_id: ID of user making the change
99 Returns:
100 Saved lineup dict
101 """
102 try:
103 # Check if lineup exists
104 existing = (
105 self.client.table("match_lineups")
106 .select("id")
107 .eq("match_id", match_id)
108 .eq("team_id", team_id)
109 .execute()
110 )
112 lineup_data = {
113 "match_id": match_id,
114 "team_id": team_id,
115 "formation_name": formation_name,
116 "positions": positions,
117 }
119 if existing.data and len(existing.data) > 0:
120 # Update existing
121 lineup_data["updated_by"] = user_id
122 response = (
123 self.client.table("match_lineups")
124 .update(lineup_data)
125 .eq("match_id", match_id)
126 .eq("team_id", team_id)
127 .execute()
128 )
129 else:
130 # Insert new
131 lineup_data["created_by"] = user_id
132 lineup_data["updated_by"] = user_id
133 response = self.client.table("match_lineups").insert(lineup_data).execute()
135 if response.data and len(response.data) > 0:
136 logger.info(
137 "lineup_saved",
138 match_id=match_id,
139 team_id=team_id,
140 formation=formation_name,
141 player_count=len(positions),
142 )
143 return response.data[0]
144 return None
146 except Exception as e:
147 logger.error(
148 "lineup_save_error",
149 match_id=match_id,
150 team_id=team_id,
151 error=str(e),
152 )
153 return None
155 def get_lineups_for_match(self, match_id: int) -> dict:
156 """
157 Get lineups for both teams in a match.
159 Args:
160 match_id: Match ID
162 Returns:
163 Dict with 'home' and 'away' keys containing lineups (or None)
164 """
165 try:
166 response = (
167 self.client.table("match_lineups")
168 .select("*")
169 .eq("match_id", match_id)
170 .execute()
171 )
173 # Get match to determine home/away team IDs
174 match_response = (
175 self.client.table("matches")
176 .select("home_team_id, away_team_id")
177 .eq("id", match_id)
178 .execute()
179 )
181 result = {"home": None, "away": None}
183 if not match_response.data:
184 return result
186 match = match_response.data[0]
187 home_team_id = match.get("home_team_id")
188 away_team_id = match.get("away_team_id")
190 for lineup in (response.data or []):
191 team_id = lineup.get("team_id")
192 if team_id == home_team_id:
193 result["home"] = lineup
194 elif team_id == away_team_id:
195 result["away"] = lineup
197 return result
199 except Exception as e:
200 logger.error("lineup_get_match_error", match_id=match_id, error=str(e))
201 return {"home": None, "away": None}