Coverage for models/live_match.py: 98.51%
67 statements
« prev ^ index » next coverage.py v7.10.6, created at 2026-04-15 12:24 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2026-04-15 12:24 +0000
1"""
2Live Match Pydantic models.
4Models for managing live match state, clock, and events.
5"""
7from datetime import datetime
9from pydantic import BaseModel, Field
12class LiveMatchClock(BaseModel):
13 """Model for clock management actions during a live match."""
15 action: str = Field(
16 ...,
17 description="Clock action: start_first_half, start_halftime, etc.",
18 )
19 half_duration: int | None = Field(
20 None,
21 ge=20,
22 le=60,
23 description="Duration of each half in minutes (only for start_first_half)",
24 )
26 @property
27 def valid_actions(self) -> list[str]:
28 return ["start_first_half", "start_halftime", "start_second_half", "end_match"]
31class GoalEvent(BaseModel):
32 """Model for recording a goal event."""
34 team_id: int = Field(..., description="ID of the team that scored")
35 player_name: str | None = Field(None, max_length=200, description="Name of the goal scorer (legacy)")
36 player_id: int | None = Field(None, description="ID of the goal scorer from roster (preferred)")
37 message: str | None = Field(None, max_length=500, description="Optional description")
40class LiveCardEvent(BaseModel):
41 """Model for recording a card event during a live match."""
43 team_id: int = Field(..., description="ID of the team the player belongs to")
44 player_id: int = Field(..., description="ID of the carded player from roster")
45 card_type: str = Field(..., pattern="^(yellow_card|red_card)$", description="Type of card: yellow_card or red_card")
46 message: str | None = Field(None, max_length=500, description="Optional description (e.g., reason for card)")
49class MessageEvent(BaseModel):
50 """Model for posting a chat message."""
52 message: str = Field(..., min_length=1, max_length=500, description="Message content")
55class GoalEventUpdate(BaseModel):
56 """Model for updating a goal event (admin corrections)."""
58 match_minute: int | None = Field(None, ge=0, le=120, description="Match minute when goal was scored")
59 extra_time: int | None = Field(None, ge=0, le=30, description="Extra/stoppage time minutes")
60 player_name: str | None = Field(None, max_length=200, description="Name of the goal scorer")
61 player_id: int | None = Field(None, description="ID of the goal scorer from roster")
64class MatchEventResponse(BaseModel):
65 """Response model for a match event."""
67 id: int
68 match_id: int
69 event_type: str
70 team_id: int | None = None
71 player_name: str | None = None
72 player_id: int | None = None
73 match_minute: int | None = None
74 extra_time: int | None = None
75 message: str
76 created_by: str | None = None
77 created_by_username: str | None = None
78 created_at: datetime
79 is_deleted: bool = False
82class LiveMatchState(BaseModel):
83 """Full live match state returned to clients."""
85 match_id: int
86 match_status: str
87 home_score: int | None = None
88 away_score: int | None = None
90 # Clock timestamps
91 kickoff_time: datetime | None = None
92 halftime_start: datetime | None = None
93 second_half_start: datetime | None = None
94 match_end_time: datetime | None = None
95 half_duration: int = 45 # Minutes per half (default 45 for 90-min games)
97 # Team info
98 home_team_id: int
99 home_team_name: str
100 away_team_id: int
101 away_team_name: str
103 # Match metadata
104 match_date: str
105 age_group_name: str | None = None
106 match_type_name: str | None = None
107 division_name: str | None = None
109 # Recent events (last N)
110 recent_events: list[MatchEventResponse] = []
113class LiveMatchSummary(BaseModel):
114 """Summary info for the live matches list (for tab polling)."""
116 match_id: int
117 match_status: str
118 home_team_name: str
119 away_team_name: str
120 home_score: int | None = None
121 away_score: int | None = None
122 kickoff_time: datetime | None = None
123 match_date: str