File size: 5,252 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
import json
from pathlib import Path

from app.models import Match, TeamInfo
from app.services.cricket import ReplayData
from app.services.sample_data import demo_matches


class MatchRepository:
    def __init__(self, data_dir: Path):
        self._data = {match.match_id: match for match in demo_matches()}
        self._metadata = {
            "demo-t20-northern-coastal": Match(
                match_id="demo-t20-northern-coastal", tournament="MatchLens Demo T20 Cup",
                match_number="Match 14 of 24", teams=(TeamInfo(name="Northern Strikers", code="NS"), TeamInfo(name="Coastal Kings", code="CK")),
                venue="Harbour Oval, Demo City", match_date="2026-10-18",
            )
        }
        self._load_cricsheet(data_dir)

    def _load_cricsheet(self, data_dir: Path) -> None:
        if not data_dir.exists():
            return
        for path in data_dir.glob("*.json"):
            try:
                payload = json.loads(path.read_text(encoding="utf-8"))
                parsed = parse_cricsheet(payload, path.stem)
                self._data[parsed.match_id] = parsed
                self._metadata[parsed.match_id] = metadata_from_cricsheet(payload, parsed)
            except (OSError, ValueError, KeyError, TypeError):
                continue

    def all(self) -> list[Match]:
        return list(self._metadata.values())

    def get(self, match_id: str) -> ReplayData:
        if match_id not in self._data:
            raise KeyError(match_id)
        return self._data[match_id]

    def metadata(self, match_id: str) -> Match:
        if match_id not in self._metadata:
            raise KeyError(match_id)
        return self._metadata[match_id]


def parse_cricsheet(payload: dict, match_id: str = "cricsheet-match") -> ReplayData:
    info = payload["info"]
    teams = info["teams"]
    innings_data = payload["innings"]
    innings = innings_data[1] if len(innings_data) > 1 else innings_data[0]
    target = 0
    if len(innings_data) > 1:
        target = 1 + sum(
            delivery["runs"]["total"]
            for over_data in innings_data[0]["overs"]
            for delivery in over_data["deliveries"]
        )
    deliveries = []
    index = 0
    for over_data in innings["overs"]:
        over = over_data["over"]
        for delivery in over_data["deliveries"]:
            index += 1
            runs = delivery["runs"]
            extras_data = delivery.get("extras", {})
            extras = sum(extras_data.values())
            wicket_data = delivery.get("wickets", [])
            wicket = wicket_data[0] if wicket_data else None
            deliveries.append({
                "index": index, "over": over, "ball_in_over": len([d for d in deliveries if d["over"] == over]) + 1,
                "over_label": f"{over}.{len([d for d in deliveries if d['over'] == over]) + 1}",
                "bowler": delivery["bowler"], "striker": delivery["batter"], "non_striker": delivery["non_striker"],
                "runs_off_bat": runs["batter"], "extras": extras, "total_runs": runs["total"],
                "is_legal": not any(name in extras_data for name in ("wides", "noballs")),
                "outcome": "wicket" if wicket else ("wide" if "wides" in extras_data else ("no_ball" if "noballs" in extras_data else "run")),
                "wicket": {"batter": wicket["player_out"], "kind": wicket["kind"]} if wicket else None,
                "commentary": f"{runs['total']} run(s)",
            })
    from app.models import BallEvent

    outcome = info.get("outcome", {})
    winner = outcome.get("winner")
    by_data = outcome.get("by", {})
    if winner:
        if "runs" in by_data:
            r = by_data["runs"]
            result_str = f"{winner} won by {r} run{'s' if r > 1 else ''}"
        elif "wickets" in by_data:
            w = by_data["wickets"]
            result_str = f"{winner} won by {w} wicket{'s' if w > 1 else ''}"
        else:
            result_str = f"{winner} won"
    elif outcome.get("result") == "tie":
        result_str = "Match tied"
    elif outcome.get("result") == "no result":
        result_str = "No result"
    else:
        result_str = "Match completed"

    return ReplayData(
        match_id=match_id,
        batting_team=innings["team"], bowling_team=next(team for team in teams if team != innings["team"]),
        target=target, deliveries=[BallEvent.model_validate(item) for item in deliveries],
        batting_order=list(dict.fromkeys([d.striker for d in [BallEvent.model_validate(item) for item in deliveries]])),
        result=result_str,
        balls_per_innings=int(info.get("overs", 20)) * int(info.get("balls_per_over", 6)),
    )


def metadata_from_cricsheet(payload: dict, parsed: ReplayData) -> Match:
    info = payload["info"]
    teams = list(info["teams"])
    return Match(
        match_id=parsed.match_id, tournament=str(info.get("event", {}).get("name", "Cricket match")),
        match_number=str(info.get("event", {}).get("match_number", "")),
        teams=(TeamInfo(name=teams[0], code=teams[0][:3].upper()), TeamInfo(name=teams[1], code=teams[1][:3].upper())),
        venue=str(info.get("venue", "Unknown venue")), match_date=str(info.get("dates", [""])[0]),
        is_demo=False,
    )