Spaces:
Running
Running
| from __future__ import annotations | |
| import random | |
| import uuid | |
| from typing import Any | |
| def _fallback_commentary_text(event: dict) -> str: | |
| """Generate engaging fallback commentary when AI is unavailable.""" | |
| minute = event.get("minute", 0) | |
| etype = event.get("type", "play") | |
| player = event.get("player", "") | |
| team = event.get("team", "") | |
| details = event.get("details", "") | |
| templates = { | |
| "goal": [ | |
| f"⚽ GOAL at {minute}'! {player} finds the back of the net for {team}! What a moment!", | |
| f"⚽ {minute}' — GOOOAL! {player} scores! The crowd erupts as {team} celebrate!", | |
| f"⚽ {minute}' — It's in! {player} makes no mistake! {team} take the lead!", | |
| ], | |
| "yellow_card": [ | |
| f"🟨 {minute}' — Yellow card shown to {player} ({team}). That's a booking.", | |
| f"🟨 {minute}' — {player} goes into the book. Reckless challenge there.", | |
| ], | |
| "red_card": [ | |
| f"🟥 {minute}' — RED CARD! {player} is sent off! {team} down to 10 men!", | |
| f"🟥 {minute}' — It's a red! {player} has to go! Disaster for {team}!", | |
| ], | |
| "penalty_awarded": [ | |
| f"⚠️ {minute}' — PENALTY to {team}! The referee points to the spot!", | |
| f"⚠️ {minute}' — Penalty! {player} goes down and the ref has no hesitation!", | |
| ], | |
| "shot": [ | |
| f"🎯 {minute}' — {player} shoots! Good effort from the {team} player.", | |
| f"🎯 {minute}' — {player} lets fly! The goalkeeper watches it carefully.", | |
| ], | |
| "substitution": [ | |
| f"🔄 {minute}' — Substitution for {team}: {player} comes on.", | |
| f"🔄 {minute}' — Tactical change by {team}. {player} enters the fray.", | |
| ], | |
| "possession": [ | |
| f"📊 {minute}' — {team} dominating possession right now. Patient build-up play.", | |
| f"📊 {minute}' — {team} keeping the ball well. Looking for that opening.", | |
| ], | |
| "half_time": [ | |
| f"⏸️ Half-time! The referee blows for the break.", | |
| f"⏸️ That's half-time. Both teams head to the dressing room.", | |
| ], | |
| "full_time": [ | |
| f"🏁 Full time! The final whistle blows!", | |
| f"🏁 It's all over! The referee brings proceedings to a close.", | |
| ], | |
| } | |
| if etype in templates: | |
| return random.choice(templates[etype]) | |
| return f"[{minute}'] {details or etype}" | |
| # Rolling set to avoid repeating recent fallback questions (stores question text hashes) | |
| _recent_quiz_hashes: list[str] = [] | |
| _QUIZ_DEDUP_WINDOW = 20 | |
| _QUIZ_TEMPLATES: dict[str, list] = { | |
| "goal": [ | |
| ("Will {team} score again before the next break?", ["Yes, within 10 minutes", "No, the momentum will shift", "The match goes to half-time level", "Opposition equalises first"]), | |
| ("How will {player}'s goal affect the match tempo?", ["The scoring team will sit deep", "The scoring team push for more goals", "The losing team will substitute immediately", "Both teams reset with caution"]), | |
| ("After {player}'s goal, who is most likely to score next?", ["{team} top scorer", "Opposition striker", "A set-piece goal", "No more goals this half"]), | |
| ("What is {team}'s most likely next action after the goal?", ["High defensive press", "Sit back and protect the lead", "Immediate kick-off attack", "Tactical substitution"]), | |
| ], | |
| "red_card": [ | |
| ("With {team} down to 10 men, what happens?", ["The opposition scores within 15 min", "{team} holds on defensively", "A penalty is awarded next", "Both teams cancel each other out"]), | |
| ("How will the red card on {player} change this game?", ["Completely shifts momentum", "Little impact — {team} defend well", "Leads to another card", "Opposition dominate possession"]), | |
| ], | |
| "yellow_card": [ | |
| ("Will {player} receive a second yellow this match?", ["Yes — they're reckless", "No — they'll be cautious now", "They'll be substituted to avoid it", "Manager will issue a strong warning"]), | |
| ("How does {player}'s yellow card affect the next 5 minutes?", ["Play gets more physical", "Both teams ease off the tackles", "Manager subs {player} off immediately", "No significant change"]), | |
| ], | |
| "penalty_awarded": [ | |
| ("Will the penalty be scored?", ["Yes — clean finish", "No — goalkeeper saves it", "Hit the post or crossbar", "Retake ordered by referee"]), | |
| ("What happens immediately after the penalty decision?", ["Players protest the decision", "Quick VAR check", "Penalty scored calmly", "Wild scenes — red card for protests"]), | |
| ], | |
| "shot": [ | |
| ("Will {team} score from their next attack?", ["Yes — they're in great form", "No — opposition defence holds", "Corner kick leads to a goal", "Another shot but off target"]), | |
| ("How does {player}'s shot change the game dynamics?", ["Increases pressure on opposition", "Opposition counter-attacks immediately", "Brings the crowd alive", "No major tactical change"]), | |
| ], | |
| "substitution": [ | |
| ("Will {player}'s substitution make an immediate impact?", ["Yes — goal within 15 min", "No — takes time to settle", "Causes a tactical reshuffle", "The sub gets booked quickly"]), | |
| ("What's the tactical reason for this substitution?", ["Fatigue management", "Chasing the game", "Protecting a lead", "Injury concern"]), | |
| ], | |
| "default": [ | |
| ("What happens in the next 5 minutes of play?", ["A goal is scored", "A yellow or red card", "A penalty awarded", "Nothing — tight and tactical"]), | |
| ("Which team controls possession next?", ["{team}", "The opposition", "50/50 midfield battle", "Long-ball game from both sides"]), | |
| ("What is the next key event in this match?", ["Goal", "Card", "Injury", "VAR Review"]), | |
| ], | |
| } | |
| def _fallback_quiz(events: list[dict[str, Any]]) -> dict[str, Any]: | |
| """Build a varied, grounded quiz question from a pool, avoiding recent repeats.""" | |
| event = events[-1] | |
| player = event.get("player") or event.get("team") or "the player" | |
| event_type = str(event.get("type", "default")) | |
| minute = event.get("minute", 0) | |
| team = event.get("team", "the team") | |
| # Pick template pool by event type, fall back to default | |
| pool = _QUIZ_TEMPLATES.get(event_type, _QUIZ_TEMPLATES["default"]) | |
| # Shuffle and pick first template not in recent hashes | |
| random.shuffle(pool) | |
| chosen_q, chosen_opts = pool[0] | |
| for q_template, opts in pool: | |
| q_text = q_template.format(player=player, team=team, minute=minute) | |
| h = str(hash(q_text) & 0xFFFFFFFF) | |
| if h not in _recent_quiz_hashes: | |
| chosen_q, chosen_opts = q_template, opts | |
| break | |
| question_text = chosen_q.format(player=player, team=team, minute=minute) | |
| options = [o.format(player=player, team=team) for o in chosen_opts] | |
| # Track this question hash to avoid repeating it | |
| h = str(hash(question_text) & 0xFFFFFFFF) | |
| _recent_quiz_hashes.append(h) | |
| if len(_recent_quiz_hashes) > _QUIZ_DEDUP_WINDOW: | |
| _recent_quiz_hashes.pop(0) | |
| return { | |
| "id": str(uuid.uuid4()), | |
| "question": question_text, | |
| "options": options, | |
| "correctIndex": None, | |
| "expiresIn": 75, | |
| "groundedEvent": f"[{minute}'] {player} ({team}): {event_type.replace('_', ' ')}", | |
| "fallback": True, | |
| } | |