File size: 4,993 Bytes
29fd274
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
from collections import defaultdict
from pathlib import Path

from fastapi import APIRouter, HTTPException, Query

from app.config import get_settings
from app.models import CatchUpRequest, ChatRequest, MatchState, ReplayAdvanceRequest
from app.services.cricket import ReplayData, state_at, timeline_at
from app.services.gemini import GeminiService
from app.services.match_data import MatchRepository
from app.services.rag import CricketRetriever
from app.services.workflow import answer
from app.services.intent import IntentClassifier
from app.models import StoryItem
from app.services.stories import build_match_stories

settings = get_settings()
repository = MatchRepository(settings.resolved_data_dir)
gemini = GeminiService(settings)
retriever = CricketRetriever(settings)
classifier = IntentClassifier(settings)
router = APIRouter(prefix="/api")


def get_data(match_id: str) -> ReplayData:
    try:
        return repository.get(match_id)
    except KeyError as exc:
        raise HTTPException(status_code=404, detail=f"Match {match_id} was not found") from exc


@router.get("/health")
def health() -> dict[str, str | None]:
    return {
        "status": "ok",
        "mode": "historical_replay",
        "gemini": gemini.status,
        "gemini_model": gemini.model,
        "gemini_error": gemini.error_kind,
        "gemini_error_summary": gemini.error_summary,
        "qdrant": retriever.status,
        "intent_classifier": classifier.source,
    }


@router.get("/matches")
def matches():
    return repository.all()


@router.get("/matches/{match_id}")
def match(match_id: str):
    try:
        return repository.metadata(match_id)
    except KeyError as exc:
        raise HTTPException(status_code=404, detail="Match was not found") from exc


@router.get("/matches/{match_id}/state")
def state(match_id: str, ball_index: int = Query(ge=0)) -> MatchState:
    return state_at(get_data(match_id), ball_index)


@router.get("/matches/{match_id}/timeline")
def timeline(match_id: str, ball_index: int = Query(ge=0)):
    return timeline_at(get_data(match_id), ball_index)


@router.post("/matches/{match_id}/replay/advance")
def advance(match_id: str, request: ReplayAdvanceRequest):
    data = get_data(match_id)
    return {"match_id": match_id, "ball_index": max(0, min(len(data.deliveries), request.from_index + request.steps)), "total_deliveries": len(data.deliveries)}


@router.post("/matches/{match_id}/catch-up")
def catch_up(match_id: str, request: CatchUpRequest):
    data = get_data(match_id)
    end = max(0, min(request.ball_index, len(data.deliveries)))
    start = max(0, end - request.overs * 6)
    events = data.deliveries[start:end]
    runs = sum(event.total_runs for event in events)
    wickets = sum(1 for event in events if event.wicket)
    legal = sum(event.is_legal for event in events)
    boundaries = sum(event.runs_off_bat in (4, 6) for event in events)
    dots = sum(event.runs_off_bat == 0 and event.extras == 0 for event in events)
    from app.models import CatchUpStatistic, CatchUpResponse, KeyMoment
    moments = [KeyMoment(title="Wicket", detail=event.commentary, over_label=event.over_label, tone="wicket") for event in events if event.wicket]
    moments += [KeyMoment(title="Boundary", detail=event.commentary, over_label=event.over_label, tone="boundary") for event in events if event.runs_off_bat in (4, 6)]
    return CatchUpResponse(
        match_id=match_id, from_over=events[0].over_label if events else "0.1",
        to_over=events[-1].over_label if events else "0.1",
        headline=f"{runs} runs from the last {request.overs} overs",
        summary=f"Historical replay interval produced {runs} runs and {wickets} wickets.",
        key_moments=moments[:6], important_events=events[-6:],
        statistics=[
            CatchUpStatistic(label="Runs", value=str(runs)),
            CatchUpStatistic(label="Wickets", value=str(wickets)),
            CatchUpStatistic(label="Boundaries", value=str(boundaries)),
            CatchUpStatistic(label="Dot balls", value=str(dots)),
            CatchUpStatistic(label="Scoring rate", value=f"{runs * 6 / legal:.2f}" if legal else "0.00"),
        ],
        key_players=[], evidence=[], source="backend", last_processed_ball_index=end,
    )


@router.post("/matches/{match_id}/ask")
@router.post("/chat")
def ask(request: ChatRequest):
    data = get_data(request.match_id)
    return answer(data, request.question, request.ball_index, gemini, retriever, classifier)


@router.get("/matches/{match_id}/insights")
def insights(match_id: str, ball_index: int = Query(ge=0)):
    current = state_at(get_data(match_id), ball_index)
    return [f"Scoring at {current.current_run_rate:.2f} runs per over", f"{current.remaining_runs} runs remain in this historical chase"]



@router.get(
    "/matches/{match_id}/stories",
    response_model=list[StoryItem],
)

def stories(match_id: str) -> list[StoryItem]:
    data = get_data(match_id)
    return build_match_stories(data)