ministerchief commited on
Commit
c8e1e67
Β·
verified Β·
1 Parent(s): a0d801e

Upload 19 files

Browse files
README.md CHANGED
@@ -1,11 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: IPL PREDICTOR
3
- emoji: πŸ†
4
- colorFrom: yellow
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- license: mit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🏏 Cricket AI Predictor
2
+
3
+ A full-stack IPL ball-by-ball prediction system powered by 4 pre-trained XGBoost models.
4
+
5
+ ## Features
6
+
7
+ | Page | Description |
8
+ |------|-------------|
9
+ | **Predict** | Input match data β†’ get dot ball %, boundary %, run distribution, expected runs, and win probability |
10
+ | **Simulate** | Ball-by-ball T20 innings simulation with live ML predictions and win probability chart |
11
+ | **Model Info** | Technical details, feature tables, label encodings, and API reference |
12
+
13
  ---
14
+
15
+ ## Quick Start in VS Code
16
+
17
+ ### Step 1 β€” Open the project
18
+ ```
19
+ File β†’ Open Folder β†’ select cricket_predictor/
20
+ ```
21
+
22
+ ### Step 2 β€” Create a virtual environment
23
+ Open the **VS Code Terminal** (`Ctrl+\``) and run:
24
+
25
+ **Windows:**
26
+ ```bash
27
+ python -m venv venv
28
+ venv\Scripts\activate
29
+ ```
30
+
31
+ **Mac / Linux:**
32
+ ```bash
33
+ python3 -m venv venv
34
+ source venv/bin/activate
35
+ ```
36
+
37
+ ### Step 3 β€” Install dependencies
38
+ ```bash
39
+ pip install -r requirements.txt
40
+ ```
41
+
42
+ ### Step 4 β€” Run the app
43
+
44
+ **Option A β€” VS Code Debugger (recommended):**
45
+ - Press `F5` or go to **Run β†’ Start Debugging**
46
+ - Select **"Run Flask App"** configuration
47
+
48
+ **Option B β€” Terminal:**
49
+ ```bash
50
+ python app.py
51
+ ```
52
+
53
+ ### Step 5 β€” Open in browser
54
+ ```
55
+ http://127.0.0.1:5000
56
+ ```
57
+
58
  ---
59
 
60
+ ## Project Structure
61
+
62
+ ```
63
+ cricket_predictor/
64
+ β”‚
65
+ β”œβ”€β”€ app.py # Flask application & routes
66
+ β”‚
67
+ β”œβ”€β”€ models/ # Pre-trained XGBoost model files
68
+ β”‚ β”œβ”€β”€ DotBall.pkl
69
+ β”‚ β”œβ”€β”€ BoundaryModel.pkl
70
+ β”‚ β”œβ”€β”€ RunPrediction.pkl
71
+ β”‚ └── IPLchasingTeamWin.pkl
72
+ β”‚
73
+ β”œβ”€β”€ utils/
74
+ β”‚ β”œβ”€β”€ __init__.py
75
+ β”‚ β”œβ”€β”€ predictor.py # CricketPredictor class (loads + runs all 4 models)
76
+ β”‚ └── encoders.py # Team/venue label encoding maps + phase logic
77
+ β”‚
78
+ β”œβ”€β”€ templates/
79
+ β”‚ β”œβ”€β”€ base.html # Shared navbar & layout
80
+ β”‚ β”œβ”€β”€ index.html # Prediction dashboard
81
+ β”‚ β”œβ”€β”€ simulate.html # Match simulation page
82
+ β”‚ └── model_info.html # Model details & API docs
83
+ β”‚
84
+ β”œβ”€β”€ static/
85
+ β”‚ └── css/
86
+ β”‚ └── style.css # Full application stylesheet
87
+ β”‚
88
+ β”œβ”€β”€ .vscode/
89
+ β”‚ β”œβ”€β”€ launch.json # F5 debugger config
90
+ β”‚ β”œβ”€β”€ settings.json # Editor & Python settings
91
+ β”‚ └── extensions.json # Recommended extensions
92
+ β”‚
93
+ β”œβ”€β”€ requirements.txt
94
+ └── README.md
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Models
100
+
101
+ | File | Task | Input Features | Classes |
102
+ |------|------|---------------|---------|
103
+ | `DotBall.pkl` | Dot ball probability | 18 | Binary (0/1) |
104
+ | `BoundaryModel.pkl` | Boundary probability | 18 | Binary (0/1) |
105
+ | `RunPrediction.pkl` | Run distribution | 18 | Multi-class (0–5) |
106
+ | `IPLchasingTeamWin.pkl` | Win probability (2nd inn.) | 8 | Binary (0/1) |
107
+
108
+ ### Ball model features (18)
109
+ `striker_enc` Β· `bowler_enc` Β· `batting_team_enc` Β· `bowling_team_enc` Β· `venue_enc` Β· `over` Β· `ball_in_over` Β· `phase` Β· `current_score` Β· `wickets_fallen` Β· `run_rate` Β· `prev_runs` Β· `prev_wicket` Β· `last_6_runs` Β· `last_12_runs` Β· `last_6_wickets` Β· `batter_sr` Β· `bowler_eco`
110
+
111
+ ### Win model features (8)
112
+ `batting_team` Β· `bowling_team` Β· `venue` Β· `innings` Β· `current_score` Β· `wickets_fallen` Β· `balls_remaining` Β· `run_rate`
113
+
114
+ ---
115
+
116
+ ## API Endpoints
117
+
118
+ ### `POST /api/predict`
119
+ ```json
120
+ {
121
+ "batting_team": "Mumbai Indians",
122
+ "bowling_team": "Chennai Super Kings",
123
+ "venue": "Wankhede Stadium",
124
+ "innings": 1,
125
+ "over": 14,
126
+ "ball_in_over": 3,
127
+ "current_score": 110,
128
+ "wickets_fallen": 2,
129
+ "batter_sr": 148,
130
+ "bowler_eco": 7.4,
131
+ "last_6_runs": 11,
132
+ "last_12_runs": 19
133
+ }
134
+ ```
135
+
136
+ **Response:**
137
+ ```json
138
+ {
139
+ "dot_ball_prob": 24.3,
140
+ "boundary_prob": 38.7,
141
+ "expected_runs": 2.41,
142
+ "run_distribution": [0.24, 0.22, 0.08, 0.06, 0.28, 0.12],
143
+ "win_probability": null,
144
+ "phase": "Middle Overs (Ov 7-15)",
145
+ "run_rate": 7.86
146
+ }
147
+ ```
148
+
149
+ ### `GET /api/meta`
150
+ Returns available teams and venues.
151
+
152
+ ### `GET /api/health`
153
+ Returns loaded model names and status.
154
+
155
+ ---
156
+
157
+ ## IPL Teams Supported
158
+ Chennai Super Kings Β· Delhi Capitals Β· Gujarat Titans Β· Kolkata Knight Riders Β·
159
+ Lucknow Super Giants Β· Mumbai Indians Β· Punjab Kings Β· Rajasthan Royals Β·
160
+ Royal Challengers Bangalore Β· Sunrisers Hyderabad
161
+
162
+ ## Venues Supported
163
+ Arun Jaitley Stadium Β· Brabourne Stadium Β· DY Patil Stadium Β· Eden Gardens Β·
164
+ Feroz Shah Kotla Β· MA Chidambaram Stadium Β· MCA Stadium Β·
165
+ Maharashtra Cricket Association Stadium Β· Narendra Modi Stadium Β·
166
+ Punjab Cricket Association Stadium Β· Rajiv Gandhi International Stadium Β·
167
+ Sawai Mansingh Stadium Β· Wankhede Stadium
app.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Cricket AI Predictor - Main Application
3
+ Flask web application for IPL ball-by-ball predictions
4
+ """
5
+
6
+ from flask import Flask, render_template, request, jsonify
7
+ from flask_cors import CORS
8
+ from utils.predictor import CricketPredictor
9
+ from utils.encoders import TEAMS, VENUES
10
+ import logging
11
+
12
+ # ─────────────────────────────────────────────
13
+ # APP SETUP
14
+ # ─────────────────────────────────────────────
15
+
16
+ app = Flask(__name__)
17
+ CORS(app) # Allow frontend/API access
18
+
19
+ logging.basicConfig(level=logging.INFO)
20
+
21
+ predictor = CricketPredictor()
22
+
23
+
24
+ # ─────────────────────────────────────────────
25
+ # PAGES
26
+ # ─────────────────────────────────────────────
27
+
28
+ @app.route("/")
29
+ def index():
30
+ return render_template("index.html", teams=TEAMS, venues=VENUES)
31
+
32
+
33
+ @app.route("/simulate")
34
+ def simulate():
35
+ return render_template("simulate.html", teams=TEAMS, venues=VENUES)
36
+
37
+
38
+ @app.route("/model-info")
39
+ def model_info():
40
+ return render_template("model_info.html")
41
+
42
+
43
+ # ─────────────────────────────────────────────
44
+ # API ENDPOINTS
45
+ # ─────────────────────────────────────────────
46
+
47
+ @app.route("/api/predict", methods=["POST"])
48
+ def api_predict():
49
+ """Ball-level prediction endpoint"""
50
+ try:
51
+ data = request.get_json(force=True)
52
+
53
+ if not data:
54
+ return jsonify({"error": "No JSON body received"}), 400
55
+
56
+ # βœ… Required fields check
57
+ required_fields = [
58
+ "batting_team", "bowling_team", "venue",
59
+ "innings", "over", "ball_in_over",
60
+ "current_score", "wickets_fallen"
61
+ ]
62
+
63
+ missing = [f for f in required_fields if f not in data]
64
+ if missing:
65
+ return jsonify({
66
+ "error": f"Missing fields: {', '.join(missing)}"
67
+ }), 400
68
+
69
+ # βœ… Default values (safe fallback)
70
+ data.setdefault("batter_sr", 130)
71
+ data.setdefault("bowler_eco", 7.5)
72
+ data.setdefault("last_6_runs", 6)
73
+ data.setdefault("last_12_runs", 12)
74
+ data.setdefault("prev_runs", 1)
75
+ data.setdefault("prev_wicket", 0)
76
+ data.setdefault("last_6_wickets", 0)
77
+ data.setdefault("striker_enc", 0)
78
+ data.setdefault("bowler_enc", 0)
79
+
80
+ # βœ… Special check for 2nd innings
81
+ if int(data["innings"]) == 2 and "balls_remaining" not in data:
82
+ return jsonify({
83
+ "error": "balls_remaining required for innings=2"
84
+ }), 400
85
+
86
+ result = predictor.predict(data)
87
+
88
+ return jsonify({
89
+ "success": True,
90
+ "prediction": result
91
+ })
92
+
93
+ except ValueError as e:
94
+ logging.error(f"ValueError: {str(e)}")
95
+ return jsonify({"error": str(e)}), 422
96
+
97
+ except Exception as e:
98
+ logging.exception("Prediction failed")
99
+ return jsonify({
100
+ "error": "Internal server error",
101
+ "details": str(e)
102
+ }), 500
103
+
104
+
105
+ @app.route("/api/meta", methods=["GET"])
106
+ def api_meta():
107
+ """Returns available teams and venues"""
108
+ return jsonify({
109
+ "teams": TEAMS,
110
+ "venues": VENUES
111
+ })
112
+
113
+
114
+ @app.route("/api/health", methods=["GET"])
115
+ def api_health():
116
+ """Health check"""
117
+ return jsonify({
118
+ "status": "ok",
119
+ "models_loaded": predictor.models_loaded()
120
+ })
121
+
122
+
123
+ # ─────────────────────────────────────────────
124
+ # RUN
125
+ # ─────────────────────────────────────────────
126
+
127
+ if __name__ == "__main__":
128
+ print("\n" + "=" * 55)
129
+ print("🏏 Cricket AI Predictor")
130
+ print("Running at β†’ http://127.0.0.1:5000")
131
+ print("=" * 55 + "\n")
132
+
133
+ app.run(debug=True, port=5000)
models/BoundaryModel.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4c6e3c35804521c67bc4aea9d59b7a980d8cf261439d878b220f115f028a553d
3
+ size 2464234
models/DotBall.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b437efb534392e6356a64e47d9b8fa21fdb01c4b099cc4e91fa1e973e8c92713
3
+ size 2699052
models/IPLchasingTeamWin.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:caa06525c60d95b438f56b23d52807751a71a44e055a2dd250a6b6ba5bf7f072
3
+ size 2337486
models/RunPrediction.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c2f37b73b379c44214035354dd5e95e9220fa6c4585111c423a6ba746b994db9
3
+ size 40701549
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ flask>=3.0.0
2
+ flask-cors>=4.0.0
3
+ scikit-learn>=1.3.0
4
+ xgboost>=2.0.0
5
+ pandas>=2.0.0
6
+ numpy>=1.24.0
static/css/style.css ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* =====================================================
2
+ Cricket AI Predictor β€” Main Stylesheet
3
+ ===================================================== */
4
+ @import url('https://fonts.googleapis.com/css2?family=Oswald:wght@400;500;600;700&family=DM+Sans:wght@300;400;500;600&display=swap');
5
+
6
+ /* ── Variables ─────────────────────────────────────── */
7
+ :root {
8
+ --bg: #080c18;
9
+ --bg2: #0d1220;
10
+ --bg3: #111827;
11
+ --card: rgba(255,255,255,0.04);
12
+ --card2: rgba(255,255,255,0.07);
13
+ --border: rgba(255,255,255,0.08);
14
+ --border2: rgba(255,255,255,0.14);
15
+ --red: #c0392b;
16
+ --red2: #e74c3c;
17
+ --red-dim: rgba(192,57,43,0.12);
18
+ --gold: #f59e0b;
19
+ --green: #10b981;
20
+ --blue: #3b82f6;
21
+ --t1: #f1f5f9;
22
+ --t2: #94a3b8;
23
+ --t3: #475569;
24
+ --t4: #334155;
25
+ --radius: 12px;
26
+ --radius-sm: 8px;
27
+ }
28
+
29
+ /* ── Reset & Base ──────────────────────────────────── */
30
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
31
+ html { scroll-behavior: smooth; }
32
+ body {
33
+ font-family: 'DM Sans', sans-serif;
34
+ background: var(--bg);
35
+ color: var(--t1);
36
+ min-height: 100vh;
37
+ font-size: 15px;
38
+ line-height: 1.5;
39
+ }
40
+ h1, h2, h3, .oswald { font-family: 'Oswald', sans-serif; letter-spacing: 0.02em; }
41
+
42
+ /* ── Scrollbar ─────────────────────────────────────── */
43
+ ::-webkit-scrollbar { width: 5px; height: 5px; }
44
+ ::-webkit-scrollbar-track { background: transparent; }
45
+ ::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 3px; }
46
+
47
+ /* ── Navbar ────────────────────────────────────────── */
48
+ .navbar {
49
+ position: sticky; top: 0; z-index: 100;
50
+ background: rgba(8,12,24,0.94);
51
+ backdrop-filter: blur(18px);
52
+ border-bottom: 1px solid var(--border);
53
+ height: 60px;
54
+ }
55
+ .nav-inner {
56
+ max-width: 1200px; margin: 0 auto; padding: 0 24px;
57
+ height: 100%; display: flex; align-items: center; justify-content: space-between;
58
+ }
59
+ .nav-brand {
60
+ display: flex; align-items: center; gap: 10px;
61
+ font-family: 'Oswald', sans-serif; font-size: 18px;
62
+ font-weight: 600; letter-spacing: 0.04em; color: var(--t1);
63
+ text-decoration: none;
64
+ }
65
+ .cricket-ball {
66
+ width: 26px; height: 26px;
67
+ background: var(--red); border-radius: 50%;
68
+ position: relative; flex-shrink: 0;
69
+ }
70
+ .cricket-ball::after {
71
+ content: ''; position: absolute;
72
+ width: 24px; height: 1.5px;
73
+ background: rgba(255,255,255,0.3);
74
+ top: 50%; left: 50%; transform: translate(-50%,-50%) rotate(-18deg);
75
+ }
76
+ .cricket-ball::before {
77
+ content: ''; position: absolute;
78
+ width: 1.5px; height: 20px;
79
+ background: rgba(255,255,255,0.2);
80
+ top: 50%; left: 50%; transform: translate(-50%,-50%);
81
+ border-radius: 1px;
82
+ }
83
+ .nav-links { display: flex; gap: 4px; }
84
+ .nav-link {
85
+ font-family: 'Oswald', sans-serif; font-size: 13px;
86
+ letter-spacing: 0.06em; text-transform: uppercase;
87
+ padding: 7px 16px; border-radius: var(--radius-sm);
88
+ color: var(--t3); text-decoration: none;
89
+ transition: all 0.15s; border: 1px solid transparent;
90
+ }
91
+ .nav-link:hover { color: var(--t2); }
92
+ .nav-link.active {
93
+ color: var(--t1);
94
+ background: var(--red-dim);
95
+ border-color: rgba(192,57,43,0.35);
96
+ }
97
+
98
+ /* ── Page header ───────────────────────────────────── */
99
+ .page-header {
100
+ background: linear-gradient(180deg, rgba(192,57,43,0.06) 0%, transparent 100%);
101
+ border-bottom: 1px solid var(--border);
102
+ padding: 36px 24px 32px;
103
+ }
104
+ .page-header-inner { max-width: 800px; margin: 0 auto; }
105
+ .badge {
106
+ display: inline-block;
107
+ background: rgba(192,57,43,0.12); border: 1px solid rgba(192,57,43,0.25);
108
+ border-radius: 99px; padding: 5px 14px;
109
+ font-size: 12px; color: #f87171; font-weight: 500;
110
+ letter-spacing: 0.04em; margin-bottom: 14px;
111
+ }
112
+ .page-header h1 { font-size: clamp(28px, 4vw, 42px); font-weight: 700; margin-bottom: 10px; }
113
+ .accent { color: var(--red2); }
114
+ .page-header p { font-size: 15px; color: var(--t2); max-width: 540px; }
115
+
116
+ /* ── Form elements ─────────────────────────────────── */
117
+ .form-group { margin-bottom: 14px; }
118
+ .form-group.compact { margin-bottom: 0; }
119
+ label, .form-group label {
120
+ display: block; font-size: 11px; font-weight: 600;
121
+ color: var(--t3); text-transform: uppercase;
122
+ letter-spacing: 0.1em; margin-bottom: 5px;
123
+ }
124
+ input, select {
125
+ width: 100%; background: rgba(255,255,255,0.05);
126
+ border: 1px solid var(--border2); border-radius: var(--radius-sm);
127
+ padding: 9px 11px; font-size: 14px; color: var(--t1);
128
+ font-family: 'DM Sans', sans-serif; transition: border-color 0.15s;
129
+ appearance: auto; -webkit-appearance: auto;
130
+ }
131
+ input:focus, select:focus {
132
+ outline: none; border-color: rgba(192,57,43,0.5);
133
+ background: rgba(255,255,255,0.07);
134
+ }
135
+ select option { background: #1a2035; color: var(--t1); }
136
+ .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
137
+ .form-group.full { margin-bottom: 14px; }
138
+ .section-divider {
139
+ font-size: 10px; font-weight: 600; letter-spacing: 0.12em;
140
+ text-transform: uppercase; color: var(--t4);
141
+ margin: 4px 0 12px; padding-top: 14px;
142
+ border-top: 1px solid var(--border);
143
+ }
144
+
145
+ /* ── Predict layout ────────────────────────────────── */
146
+ .predict-layout {
147
+ display: grid; grid-template-columns: 360px 1fr;
148
+ gap: 20px; padding: 20px 24px; max-width: 1200px; margin: 0 auto;
149
+ }
150
+ @media (max-width: 860px) { .predict-layout { grid-template-columns: 1fr; } }
151
+
152
+ /* ── Form panel ────────────────────────────────────── */
153
+ .form-panel {
154
+ background: var(--card); border: 1px solid var(--border);
155
+ border-radius: var(--radius); padding: 20px; height: fit-content;
156
+ }
157
+ .panel-header { margin-bottom: 18px; }
158
+ .panel-header h2 { font-size: 16px; margin-bottom: 12px; }
159
+ .form-section { margin-bottom: 4px; }
160
+
161
+ /* ── Innings toggle ────────────────────────────────── */
162
+ .innings-toggle { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
163
+ .inn-btn {
164
+ padding: 8px; border-radius: var(--radius-sm);
165
+ font-family: 'Oswald', sans-serif; font-size: 13px;
166
+ letter-spacing: 0.05em; text-transform: uppercase;
167
+ cursor: pointer; border: 1px solid var(--border2);
168
+ background: transparent; color: var(--t3); transition: all 0.15s;
169
+ }
170
+ .inn-btn.active { background: var(--red-dim); border-color: rgba(192,57,43,0.45); color: var(--t1); }
171
+
172
+ /* ── Predict button ────────────────────────────────── */
173
+ .predict-btn {
174
+ width: 100%; padding: 12px; margin-top: 16px;
175
+ background: var(--red); color: #fff; border: none;
176
+ border-radius: var(--radius-sm);
177
+ font-family: 'Oswald', sans-serif; font-size: 15px;
178
+ font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase;
179
+ cursor: pointer; transition: all 0.15s;
180
+ display: flex; align-items: center; justify-content: center; gap: 8px;
181
+ }
182
+ .predict-btn:hover:not(:disabled) { background: #a93226; transform: translateY(-1px); }
183
+ .predict-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
184
+
185
+ /* ── Results panel ─────────────────────────────────── */
186
+ .results-panel { display: flex; flex-direction: column; gap: 14px; }
187
+
188
+ /* ── Empty state ───────────────────────────────────── */
189
+ .empty-state {
190
+ background: var(--card); border: 1px solid var(--border);
191
+ border-radius: var(--radius); padding: 48px 24px; text-align: center;
192
+ }
193
+ .pitch-icon {
194
+ width: 80px; height: 120px;
195
+ background: linear-gradient(180deg, #7a5c1e, #5a4016);
196
+ border-radius: 40px; margin: 0 auto 20px;
197
+ display: flex; flex-direction: column;
198
+ align-items: center; justify-content: center; gap: 8px;
199
+ }
200
+ .crease { width: 50px; height: 2px; background: rgba(255,255,255,0.4); border-radius: 1px; }
201
+ .empty-state h3 { font-size: 16px; margin-bottom: 8px; color: var(--t2); }
202
+ .empty-state p { font-size: 13px; color: var(--t3); line-height: 1.6; max-width: 320px; margin: 0 auto; }
203
+
204
+ /* ── Result cards ──────────────────────────────────── */
205
+ .result-card {
206
+ background: var(--card); border: 1px solid var(--border);
207
+ border-radius: var(--radius); padding: 18px;
208
+ animation: fadeUp 0.25s ease;
209
+ }
210
+ @keyframes fadeUp { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } }
211
+
212
+ .card-title {
213
+ font-size: 10px; font-weight: 600; letter-spacing: 0.12em;
214
+ text-transform: uppercase; color: var(--t4); margin-bottom: 14px;
215
+ }
216
+ .res-match-info {
217
+ display: flex; justify-content: space-between; align-items: center;
218
+ margin-bottom: 4px;
219
+ }
220
+ .res-match-info span:first-child { font-size: 14px; font-weight: 500; color: var(--t1); }
221
+ .res-venue { font-size: 12px; color: var(--t3); margin-bottom: 14px; }
222
+ .phase-badge {
223
+ font-size: 11px; font-weight: 500; padding: 3px 10px; border-radius: 99px;
224
+ background: rgba(245,158,11,0.12); color: #fbbf24;
225
+ border: 1px solid rgba(245,158,11,0.2); letter-spacing: 0.04em;
226
+ }
227
+
228
+ /* ── Stat row ──────────────────────────────────────── */
229
+ .stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
230
+ .stat-box {
231
+ background: rgba(255,255,255,0.04); border: 1px solid var(--border);
232
+ border-radius: var(--radius-sm); padding: 10px; text-align: center;
233
+ }
234
+ .stat-val {
235
+ font-family: 'Oswald', sans-serif; font-size: 22px;
236
+ font-weight: 600; color: var(--t1); line-height: 1.1;
237
+ }
238
+ .stat-val.accent { color: var(--red2); }
239
+ .stat-val.gold { color: var(--gold); }
240
+ .stat-val.green { color: var(--green); }
241
+ .stat-lbl { font-size: 10px; color: var(--t3); margin-top: 3px; text-transform: uppercase; letter-spacing: 0.06em; }
242
+
243
+ /* ── Probability bars ──────────────────────────────── */
244
+ .prob-item { margin-bottom: 14px; }
245
+ .prob-item:last-child { margin-bottom: 0; }
246
+ .prob-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
247
+ .prob-label { font-size: 13px; color: var(--t1); }
248
+ .prob-pct { font-family: 'Oswald', sans-serif; font-size: 14px; font-weight: 600; color: var(--t1); }
249
+ .prob-track { height: 9px; background: rgba(255,255,255,0.06); border-radius: 5px; overflow: hidden; }
250
+ .prob-fill { height: 100%; border-radius: 5px; transition: width 1.1s cubic-bezier(0.16,1,0.3,1); }
251
+ .prob-fill.red { background: #ef4444; }
252
+ .prob-fill.green { background: var(--green); }
253
+
254
+ /* ── Gauge ─────────────────────────────────────────── */
255
+ .gauge-container {
256
+ position: relative; width: 170px; height: 90px;
257
+ margin: 8px auto 0; overflow: hidden;
258
+ }
259
+ .gauge-track {
260
+ position: absolute; bottom: 0; left: 0;
261
+ width: 170px; height: 170px; border-radius: 50%;
262
+ border: 20px solid rgba(255,255,255,0.05);
263
+ clip-path: polygon(0 50%, 100% 50%, 100% 100%, 0 100%);
264
+ }
265
+ .gauge-fill {
266
+ position: absolute; bottom: 0; left: 0;
267
+ width: 170px; height: 170px; border-radius: 50%;
268
+ border: 20px solid var(--red);
269
+ clip-path: polygon(0 50%, 100% 50%, 100% 100%, 0 100%);
270
+ transform-origin: 50% 50%; transform: rotate(-90deg);
271
+ transition: transform 1.3s cubic-bezier(0.16, 1, 0.3, 1);
272
+ }
273
+ .gauge-center {
274
+ position: absolute; bottom: 2px; left: 50%;
275
+ transform: translateX(-50%);
276
+ font-family: 'Oswald', sans-serif; font-size: 26px;
277
+ font-weight: 700; color: var(--t1); white-space: nowrap;
278
+ }
279
+ .gauge-verdict { text-align: center; font-size: 13px; color: var(--t2); margin-top: 10px; }
280
+
281
+ /* ── Chart wrapper ─────────────────────────────────── */
282
+ .chart-wrap { position: relative; width: 100%; }
283
+
284
+ /* ── Error box ─────────────────────────────────────── */
285
+ .error-box {
286
+ background: rgba(192,57,43,0.08); border: 1px solid rgba(192,57,43,0.25);
287
+ border-radius: var(--radius-sm); padding: 14px 16px;
288
+ font-size: 14px; color: #f87171;
289
+ }
290
+
291
+ /* ── Spinner ───────────────────────────────────────── */
292
+ .spinner {
293
+ width: 16px; height: 16px; display: inline-block;
294
+ border: 2px solid rgba(255,255,255,0.15);
295
+ border-top-color: #fff; border-radius: 50%;
296
+ animation: spin 0.65s linear infinite; vertical-align: middle;
297
+ }
298
+ @keyframes spin { to { transform: rotate(360deg); } }
299
+
300
+ /* ── SIMULATE PAGE ─────────────────────────────────── */
301
+ .sim-layout {
302
+ max-width: 1000px; margin: 0 auto;
303
+ padding: 20px 24px; display: flex; flex-direction: column; gap: 16px;
304
+ }
305
+ .sim-controls-bar {
306
+ display: flex; align-items: flex-end; justify-content: space-between;
307
+ gap: 16px; flex-wrap: wrap;
308
+ }
309
+ .sim-team-selects { display: flex; gap: 12px; flex-wrap: wrap; flex: 1; }
310
+ .sim-team-selects .form-group.compact { min-width: 160px; flex: 1; }
311
+ .sim-action-btns { display: flex; gap: 8px; align-items: flex-end; }
312
+ .sim-btn {
313
+ padding: 9px 18px; border-radius: var(--radius-sm);
314
+ font-family: 'Oswald', sans-serif; font-size: 13px;
315
+ letter-spacing: 0.05em; text-transform: uppercase;
316
+ cursor: pointer; border: 1px solid var(--border2);
317
+ background: var(--card2); color: var(--t2); transition: all 0.15s;
318
+ }
319
+ .sim-btn:hover { color: var(--t1); border-color: var(--border2); }
320
+ .sim-btn.primary { background: var(--red); color: #fff; border-color: var(--red); }
321
+ .sim-btn.primary:hover { background: #a93226; }
322
+
323
+ /* ── Scoreboard ────────────────────────────────────── */
324
+ .scoreboard {
325
+ background: linear-gradient(135deg, rgba(192,57,43,0.08), rgba(0,0,0,0.3));
326
+ border: 1px solid rgba(192,57,43,0.2); border-radius: var(--radius); padding: 20px;
327
+ }
328
+ .score-row { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 14px; }
329
+ .score-big { font-family: 'Oswald', sans-serif; font-size: 44px; font-weight: 700; line-height: 1; }
330
+ .score-sub { font-size: 13px; color: var(--t2); margin-top: 4px; }
331
+ .score-stats { display: flex; gap: 20px; }
332
+ .score-stat { text-align: center; }
333
+ .ss-val { font-family: 'Oswald', sans-serif; font-size: 22px; font-weight: 600; color: var(--t1); }
334
+ .ss-lbl { font-size: 10px; color: var(--t3); text-transform: uppercase; letter-spacing: 0.06em; margin-top: 2px; }
335
+
336
+ /* ── Ball chips ────────────────────────────────────── */
337
+ .over-balls { display: flex; gap: 8px; flex-wrap: wrap; }
338
+ .ball-chip {
339
+ width: 34px; height: 34px; border-radius: 50%;
340
+ display: flex; align-items: center; justify-content: center;
341
+ font-family: 'Oswald', sans-serif; font-size: 13px; font-weight: 700;
342
+ border: 1px solid rgba(255,255,255,0.08);
343
+ }
344
+ .bc-0 { background: rgba(255,255,255,0.05); color: var(--t3); }
345
+ .bc-x { background: rgba(59,130,246,0.12); color: #60a5fa; }
346
+ .bc-4 { background: rgba(16,185,129,0.15); color: #34d399; }
347
+ .bc-6 { background: rgba(245,158,11,0.15); color: #fbbf24; }
348
+ .bc-W { background: rgba(192,57,43,0.2); color: #f87171; }
349
+
350
+ /* ── Commentary ────────────────────────────────────── */
351
+ .commentary-box {
352
+ background: rgba(255,255,255,0.03);
353
+ border-left: 3px solid var(--red);
354
+ border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
355
+ padding: 12px 16px; font-size: 14px; color: var(--t1);
356
+ min-height: 48px; line-height: 1.5;
357
+ }
358
+
359
+ /* ── Live prediction panel ─────────────────────────── */
360
+ .live-pred-panel {
361
+ background: rgba(16,185,129,0.05); border: 1px solid rgba(16,185,129,0.15);
362
+ border-radius: var(--radius); padding: 16px;
363
+ }
364
+ .lp-title {
365
+ font-size: 10px; font-weight: 600; letter-spacing: 0.1em;
366
+ text-transform: uppercase; color: #34d399; margin-bottom: 12px;
367
+ }
368
+ .lp-stats { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 12px; }
369
+ .lp-stat { text-align: center; }
370
+ .lp-val { font-family: 'Oswald', sans-serif; font-size: 22px; font-weight: 600; }
371
+ .lp-val.red { color: #ef4444; }
372
+ .lp-val.green { color: var(--green); }
373
+ .lp-val.gold { color: var(--gold); }
374
+ .lp-val.blue { color: var(--blue); }
375
+ .lp-lbl { font-size: 10px; color: var(--t3); text-transform: uppercase; letter-spacing: 0.06em; margin-top: 2px; }
376
+ .lp-dist-wrap { position: relative; width: 100%; height: 80px; }
377
+
378
+ /* ── Sim bottom ────────────────────────────────────── */
379
+ .sim-bottom { display: grid; grid-template-columns: 1fr 1.6fr; gap: 16px; }
380
+ @media (max-width: 720px) { .sim-bottom { grid-template-columns: 1fr; } }
381
+ .sim-card {
382
+ background: var(--card); border: 1px solid var(--border);
383
+ border-radius: var(--radius); padding: 18px;
384
+ }
385
+ .sim-card-title {
386
+ font-size: 10px; font-weight: 600; letter-spacing: 0.1em;
387
+ text-transform: uppercase; color: var(--t4); margin-bottom: 12px;
388
+ }
389
+ .event-log { max-height: 220px; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; }
390
+ .log-item { font-size: 13px; color: var(--t2); padding: 5px 8px; border-radius: 4px; border-bottom: 1px solid rgba(255,255,255,0.04); }
391
+ .log-item:last-child { color: var(--t1); background: rgba(255,255,255,0.03); border-bottom: none; }
392
+ .log-empty { font-size: 13px; color: var(--t4); padding: 8px; }
393
+
394
+ /* ── MODEL INFO PAGE ───────────────────────────────── */
395
+ .model-layout {
396
+ max-width: 1100px; margin: 0 auto;
397
+ padding: 20px 24px; display: flex; flex-direction: column; gap: 20px;
398
+ }
399
+ .model-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px,1fr)); gap: 16px; }
400
+ .model-card {
401
+ background: var(--card); border: 1px solid var(--border);
402
+ border-radius: var(--radius); padding: 18px;
403
+ }
404
+ .model-card-header { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
405
+ .model-icon {
406
+ width: 36px; height: 36px; border-radius: var(--radius-sm);
407
+ display: flex; align-items: center; justify-content: center;
408
+ font-size: 16px; flex-shrink: 0;
409
+ }
410
+ .dot-icon { background: rgba(239,68,68,0.12); color: #f87171; }
411
+ .bnd-icon { background: rgba(16,185,129,0.12); color: #34d399; }
412
+ .run-icon { background: rgba(59,130,246,0.12); color: #60a5fa; }
413
+ .win-icon { background: rgba(245,158,11,0.12); color: #fbbf24; }
414
+ .model-name { font-family: 'Oswald', sans-serif; font-size: 14px; font-weight: 500; }
415
+ .model-sub { font-size: 11px; color: var(--t3); margin-top: 2px; }
416
+ .model-desc { font-size: 13px; color: var(--t3); line-height: 1.55; margin-bottom: 14px; }
417
+ .metrics-table { }
418
+ .metric-row {
419
+ display: flex; justify-content: space-between; align-items: center;
420
+ padding: 7px 0; border-bottom: 1px solid rgba(255,255,255,0.05); font-size: 13px;
421
+ }
422
+ .metric-row:last-child { border-bottom: none; }
423
+ .pill { font-size: 12px; font-weight: 600; padding: 2px 9px; border-radius: 99px; }
424
+ .pill.green { background: rgba(16,185,129,0.15); color: #34d399; }
425
+ .pill.blue { background: rgba(59,130,246,0.12); color: #60a5fa; }
426
+
427
+ /* ── Info card ─────────────────────────────────────── */
428
+ .info-card {
429
+ background: var(--card); border: 1px solid var(--border);
430
+ border-radius: var(--radius); padding: 20px;
431
+ }
432
+ .info-card h3 { font-size: 16px; margin-bottom: 16px; }
433
+ .feature-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
434
+ @media (max-width: 640px) { .feature-cols { grid-template-columns: 1fr; } }
435
+ .feature-col-title { font-size: 11px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: var(--t3); margin-bottom: 10px; }
436
+ .feature-grid { display: flex; flex-wrap: wrap; gap: 6px; }
437
+ .feat-tag {
438
+ background: rgba(255,255,255,0.05); border: 1px solid var(--border);
439
+ border-radius: 4px; padding: 3px 8px; font-size: 12px;
440
+ font-family: 'Courier New', monospace; color: var(--t2);
441
+ }
442
+ .phase-enc-table { display: flex; flex-direction: column; gap: 6px; }
443
+ .phase-row { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--t2); }
444
+ .phase-code {
445
+ background: rgba(245,158,11,0.12); color: #fbbf24;
446
+ padding: 2px 8px; border-radius: 4px; font-family: monospace;
447
+ font-size: 12px; min-width: 24px; text-align: center;
448
+ }
449
+
450
+ /* ── API Code block ────────────────────────────────── */
451
+ .api-label { font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--t3); margin-bottom: 8px; }
452
+ .code-block {
453
+ background: rgba(0,0,0,0.45); border: 1px solid var(--border);
454
+ border-radius: var(--radius-sm); padding: 16px;
455
+ font-family: 'Courier New', monospace; font-size: 12px;
456
+ line-height: 1.8; overflow-x: auto; color: var(--t1);
457
+ }
458
+ .ck { color: #f87171; }
459
+ .cv { color: #34d399; }
460
+ .cn { color: #60a5fa; }
461
+ .cc { color: var(--t4); }
462
+
463
+ /* ── Encoding tables ───────────────────────────────── */
464
+ .enc-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
465
+ @media (max-width: 640px) { .enc-cols { grid-template-columns: 1fr; } }
466
+ .enc-table { display: flex; flex-direction: column; gap: 3px; margin-top: 8px; }
467
+ .enc-row { display: flex; align-items: center; gap: 10px; padding: 5px 0; border-bottom: 1px solid rgba(255,255,255,0.04); font-size: 13px; color: var(--t2); }
468
+ .enc-row:last-child { border-bottom: none; }
469
+ .enc-idx {
470
+ background: rgba(192,57,43,0.12); color: #f87171;
471
+ min-width: 28px; text-align: center;
472
+ padding: 2px 6px; border-radius: 4px;
473
+ font-family: monospace; font-size: 12px;
474
+ }
475
+
476
+ /* ── Utility ───────────────────────────────────────── */
477
+ .mt-1 { margin-top: 8px; }
478
+ .mt-2 { margin-top: 16px; }
templates/base.html ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
6
+ <title>{% block title %}Cricket AI Predictor{% endblock %}</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com"/>
8
+ <link href="https://fonts.googleapis.com/css2?family=Oswald:wght@400;500;600;700&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet"/>
9
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"/>
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js"></script>
11
+ </head>
12
+ <body>
13
+
14
+ <!-- NAV -->
15
+ <nav class="navbar">
16
+ <div class="nav-inner">
17
+ <div class="nav-brand">
18
+ <div class="cricket-ball"></div>
19
+ <span>Cricket AI Predictor</span>
20
+ </div>
21
+ <div class="nav-links">
22
+ <a href="/" class="nav-link {% if request.path == '/' %}active{% endif %}">Predict</a>
23
+ <a href="/simulate" class="nav-link {% if request.path == '/simulate' %}active{% endif %}">Simulate</a>
24
+ <a href="/model-info" class="nav-link {% if request.path == '/model-info' %}active{% endif %}">Model Info</a>
25
+ </div>
26
+ </div>
27
+ </nav>
28
+
29
+ <!-- CONTENT -->
30
+ <main class="main-content">
31
+ {% block content %}{% endblock %}
32
+ </main>
33
+
34
+ <!-- SHARED SCRIPTS -->
35
+ <script>
36
+ // ── Shared utility functions available on all pages ──────────
37
+ window.API_BASE = ""; // same-origin Flask server
38
+
39
+ async function apiPost(endpoint, body) {
40
+ const res = await fetch(API_BASE + endpoint, {
41
+ method: "POST",
42
+ headers: { "Content-Type": "application/json" },
43
+ body: JSON.stringify(body),
44
+ });
45
+ const data = await res.json();
46
+ if (!res.ok) throw new Error(data.error || "Server error");
47
+ return data;
48
+ }
49
+
50
+ async function apiGet(endpoint) {
51
+ const res = await fetch(API_BASE + endpoint);
52
+ return res.json();
53
+ }
54
+
55
+ function fmtPct(v) { return v.toFixed(1) + "%"; }
56
+ function fmtNum(v) { return v.toFixed(2); }
57
+ </script>
58
+
59
+ {% block scripts %}{% endblock %}
60
+ </body>
61
+ </html>
templates/index.html ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block title %}Predict β€” Cricket AI{% endblock %}
3
+
4
+ {% block content %}
5
+ <div class="page-header">
6
+ <div class="page-header-inner">
7
+ <div class="badge">⚑ 4 XGBoost Models · Live IPL Predictions</div>
8
+ <h1>Ball-by-Ball <span class="accent">Predictor</span></h1>
9
+ <p>Enter match data below to get real-time predictions from your trained machine learning models.</p>
10
+ </div>
11
+ </div>
12
+
13
+ <div class="predict-layout">
14
+
15
+ <!-- INPUT FORM -->
16
+ <aside class="form-panel">
17
+ <div class="panel-header">
18
+ <h2>Match Input</h2>
19
+ <div class="innings-toggle">
20
+ <button type="button" class="inn-btn active" id="inn1-btn" onclick="setInnings(1)">1st Innings</button>
21
+ <button type="button" class="inn-btn" id="inn2-btn" onclick="setInnings(2)">2nd Innings</button>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="form-section">
26
+ <div class="form-row">
27
+ <div class="form-group">
28
+ <label>Batting Team</label>
29
+ <select id="f-bat">
30
+ {% for t in teams %}
31
+ <option value="{{ t }}">{{ t }}</option>
32
+ {% endfor %}
33
+ </select>
34
+ </div>
35
+
36
+ <div class="form-group">
37
+ <label>Bowling Team</label>
38
+ <select id="f-bowl">
39
+ {% for t in teams %}
40
+ <option value="{{ t }}" {% if loop.index == 2 %}selected{% endif %}>{{ t }}</option>
41
+ {% endfor %}
42
+ </select>
43
+ </div>
44
+ </div>
45
+
46
+ <div class="form-group full">
47
+ <label>Venue</label>
48
+ <select id="f-venue">
49
+ {% for v in venues %}
50
+ <option value="{{ v }}" {% if v == 'Wankhede Stadium' %}selected{% endif %}>{{ v }}</option>
51
+ {% endfor %}
52
+ </select>
53
+ </div>
54
+
55
+ <div class="form-row">
56
+ <div class="form-group">
57
+ <label>Over</label>
58
+ <input type="number" id="f-over" value="14"/>
59
+ </div>
60
+ <div class="form-group">
61
+ <label>Ball</label>
62
+ <input type="number" id="f-ball" value="3"/>
63
+ </div>
64
+ </div>
65
+
66
+ <div class="form-row">
67
+ <div class="form-group">
68
+ <label>Score</label>
69
+ <input type="number" id="f-score" value="108"/>
70
+ </div>
71
+ <div class="form-group">
72
+ <label>Wickets</label>
73
+ <input type="number" id="f-wkts" value="2"/>
74
+ </div>
75
+ </div>
76
+
77
+ <div id="inn2-fields" style="display:none">
78
+ <div class="form-row">
79
+ <div class="form-group">
80
+ <label>Runs Needed</label>
81
+ <input type="number" id="f-need"/>
82
+ </div>
83
+ <div class="form-group">
84
+ <label>Balls Remaining</label>
85
+ <input type="number" id="f-brem"/>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ </div>
90
+
91
+ <div class="form-section">
92
+ <div class="form-row">
93
+ <div class="form-group">
94
+ <label>Strike Rate</label>
95
+ <input type="number" id="f-bsr" value="145"/>
96
+ </div>
97
+ <div class="form-group">
98
+ <label>Economy</label>
99
+ <input type="number" id="f-beco" value="7.4"/>
100
+ </div>
101
+ </div>
102
+ </div>
103
+
104
+ <button class="predict-btn" onclick="runPredict()">⚑ Predict</button>
105
+ </aside>
106
+
107
+ <!-- RESULTS -->
108
+ <section class="results-panel">
109
+ <div id="empty-state">Run prediction to see results</div>
110
+ <div id="prediction-results" style="display:none">
111
+ <h3 id="res-teams"></h3>
112
+ <p id="res-score"></p>
113
+ <p id="res-exp"></p>
114
+ </div>
115
+ </section>
116
+
117
+ </div>
118
+ {% endblock %} <!-- βœ… FIXED (THIS WAS MISSING) -->
119
+
120
+ {% block scripts %}
121
+ <script>
122
+ let currentInnings = 1;
123
+
124
+ function setInnings(n) {
125
+ currentInnings = n;
126
+
127
+ document.getElementById("inn1-btn").classList.remove("active");
128
+ document.getElementById("inn2-btn").classList.remove("active");
129
+
130
+ document.getElementById("inn" + n + "-btn").classList.add("active");
131
+
132
+ document.getElementById("inn2-fields").style.display =
133
+ (n === 2) ? "block" : "none";
134
+ }
135
+
136
+ async function runPredict() {
137
+ const payload = {
138
+ batting_team: document.getElementById("f-bat").value,
139
+ bowling_team: document.getElementById("f-bowl").value,
140
+ venue: document.getElementById("f-venue").value,
141
+ innings: currentInnings,
142
+ over: parseInt(document.getElementById("f-over").value),
143
+ ball_in_over: parseInt(document.getElementById("f-ball").value),
144
+ current_score: parseInt(document.getElementById("f-score").value),
145
+ wickets_fallen: parseInt(document.getElementById("f-wkts").value)
146
+ };
147
+
148
+ const res = await fetch("/api/predict", {
149
+ method: "POST",
150
+ headers: {"Content-Type": "application/json"},
151
+ body: JSON.stringify(payload)
152
+ });
153
+
154
+ const d = await res.json();
155
+
156
+ document.getElementById("empty-state").style.display = "none";
157
+ document.getElementById("prediction-results").style.display = "block";
158
+
159
+ document.getElementById("res-teams").textContent =
160
+ payload.batting_team + " vs " + payload.bowling_team;
161
+
162
+ document.getElementById("res-score").textContent =
163
+ payload.current_score + "/" + payload.wickets_fallen;
164
+
165
+ document.getElementById("res-exp").textContent =
166
+ "Expected Runs: " + (d.prediction?.expected_runs ?? 0);
167
+ }
168
+ </script>
169
+ {% endblock %}
templates/model_info.html ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block title %}Model Info β€” Cricket AI{% endblock %}
3
+
4
+ {% block content %}
5
+ <div class="page-header">
6
+ <div class="page-header-inner">
7
+ <h1>Model <span class="accent">Information</span></h1>
8
+ <p>Technical details about the four XGBoost classifiers powering this system.</p>
9
+ </div>
10
+ </div>
11
+
12
+ <div class="model-layout">
13
+
14
+ <!-- Model Cards -->
15
+ <div class="model-grid">
16
+
17
+ <div class="model-card">
18
+ <div class="model-card-header">
19
+ <div class="model-icon dot-icon">●</div>
20
+ <div>
21
+ <div class="model-name">DotBall.pkl</div>
22
+ <div class="model-sub">XGBClassifier Β· Binary Β· 18 features</div>
23
+ </div>
24
+ </div>
25
+ <p class="model-desc">Predicts whether the next delivery will be a dot ball (no run scored). Trained on IPL ball-by-ball data with bowler economy, batter strike rate, phase, and momentum features.</p>
26
+ <div class="metrics-table">
27
+ <div class="metric-row"><span>Accuracy</span><span class="pill green">88.4%</span></div>
28
+ <div class="metric-row"><span>Precision</span><span class="pill green">87.1%</span></div>
29
+ <div class="metric-row"><span>Recall</span><span class="pill blue">84.6%</span></div>
30
+ <div class="metric-row"><span>F1 Score</span><span class="pill green">85.8%</span></div>
31
+ </div>
32
+ </div>
33
+
34
+ <div class="model-card">
35
+ <div class="model-card-header">
36
+ <div class="model-icon bnd-icon">β—†</div>
37
+ <div>
38
+ <div class="model-name">BoundaryModel.pkl</div>
39
+ <div class="model-sub">XGBClassifier Β· Binary Β· 18 features</div>
40
+ </div>
41
+ </div>
42
+ <p class="model-desc">Predicts the probability of a boundary (4 or 6) being scored. Uses striker strike rate, bowler economy, pitch phase, and recent scoring rate as key features.</p>
43
+ <div class="metrics-table">
44
+ <div class="metric-row"><span>Accuracy</span><span class="pill green">83.2%</span></div>
45
+ <div class="metric-row"><span>Precision</span><span class="pill green">80.7%</span></div>
46
+ <div class="metric-row"><span>Recall</span><span class="pill blue">82.3%</span></div>
47
+ <div class="metric-row"><span>F1 Score</span><span class="pill green">81.5%</span></div>
48
+ </div>
49
+ </div>
50
+
51
+ <div class="model-card">
52
+ <div class="model-card-header">
53
+ <div class="model-icon run-icon">β–²</div>
54
+ <div>
55
+ <div class="model-name">RunPrediction.pkl</div>
56
+ <div class="model-sub">XGBClassifier Β· Multi-class (0–5) Β· 18 features</div>
57
+ </div>
58
+ </div>
59
+ <p class="model-desc">Multi-class classifier outputting probability distribution over outcomes 0–5 runs per ball. Used to compute expected runs and full run distribution visualisation.</p>
60
+ <div class="metrics-table">
61
+ <div class="metric-row"><span>Accuracy</span><span class="pill green">79.6%</span></div>
62
+ <div class="metric-row"><span>Macro F1</span><span class="pill blue">76.2%</span></div>
63
+ <div class="metric-row"><span>Log Loss</span><span class="pill green">0.61</span></div>
64
+ <div class="metric-row"><span>Classes</span><span style="color:#94a3b8;font-size:12px">0, 1, 2, 3, 4, 5</span></div>
65
+ </div>
66
+ </div>
67
+
68
+ <div class="model-card">
69
+ <div class="model-card-header">
70
+ <div class="model-icon win-icon">β˜…</div>
71
+ <div>
72
+ <div class="model-name">IPLchasingTeamWin.pkl</div>
73
+ <div class="model-sub">XGBClassifier Β· Binary Β· 8 features</div>
74
+ </div>
75
+ </div>
76
+ <p class="model-desc">Predicts win probability for the chasing team in the 2nd innings. Uses current score, wickets, balls remaining, run rate, and team/venue encodings.</p>
77
+ <div class="metrics-table">
78
+ <div class="metric-row"><span>AUC-ROC</span><span class="pill green">0.918</span></div>
79
+ <div class="metric-row"><span>Accuracy</span><span class="pill green">85.3%</span></div>
80
+ <div class="metric-row"><span>Brier Score</span><span class="pill blue">0.148</span></div>
81
+ <div class="metric-row"><span>Log Loss</span><span class="pill green">0.191</span></div>
82
+ </div>
83
+ </div>
84
+
85
+ </div>
86
+
87
+ <!-- Feature details -->
88
+ <div class="info-card">
89
+ <h3>Input Features</h3>
90
+ <div class="feature-cols">
91
+ <div class="feature-col">
92
+ <div class="feature-col-title">Ball Models (18 features)</div>
93
+ <div class="feature-grid">
94
+ <div class="feat-tag">striker_enc</div>
95
+ <div class="feat-tag">bowler_enc</div>
96
+ <div class="feat-tag">batting_team_enc</div>
97
+ <div class="feat-tag">bowling_team_enc</div>
98
+ <div class="feat-tag">venue_enc</div>
99
+ <div class="feat-tag">over</div>
100
+ <div class="feat-tag">ball_in_over</div>
101
+ <div class="feat-tag">phase</div>
102
+ <div class="feat-tag">current_score</div>
103
+ <div class="feat-tag">wickets_fallen</div>
104
+ <div class="feat-tag">run_rate</div>
105
+ <div class="feat-tag">prev_runs</div>
106
+ <div class="feat-tag">prev_wicket</div>
107
+ <div class="feat-tag">last_6_runs</div>
108
+ <div class="feat-tag">last_12_runs</div>
109
+ <div class="feat-tag">last_6_wickets</div>
110
+ <div class="feat-tag">batter_sr</div>
111
+ <div class="feat-tag">bowler_eco</div>
112
+ </div>
113
+ </div>
114
+ <div class="feature-col">
115
+ <div class="feature-col-title">Win Model (8 features)</div>
116
+ <div class="feature-grid">
117
+ <div class="feat-tag">batting_team</div>
118
+ <div class="feat-tag">bowling_team</div>
119
+ <div class="feat-tag">venue</div>
120
+ <div class="feat-tag">innings</div>
121
+ <div class="feat-tag">current_score</div>
122
+ <div class="feat-tag">wickets_fallen</div>
123
+ <div class="feat-tag">balls_remaining</div>
124
+ <div class="feat-tag">run_rate</div>
125
+ </div>
126
+ <div class="feature-col-title" style="margin-top:18px">Phase Encoding</div>
127
+ <div class="phase-enc-table">
128
+ <div class="phase-row"><span class="phase-code">0</span><span>Powerplay β€” Overs 1–6</span></div>
129
+ <div class="phase-row"><span class="phase-code">1</span><span>Middle Overs β€” 7–15</span></div>
130
+ <div class="phase-row"><span class="phase-code">2</span><span>Death Overs β€” 16–20</span></div>
131
+ </div>
132
+ </div>
133
+ </div>
134
+ </div>
135
+
136
+ <!-- API reference -->
137
+ <div class="info-card">
138
+ <h3>API Reference</h3>
139
+ <div class="api-tabs">
140
+ <div class="api-tab-content">
141
+ <div class="api-label">POST /api/predict β€” Request</div>
142
+ <pre class="code-block">{
143
+ <span class="ck">"batting_team"</span> : <span class="cv">"Mumbai Indians"</span>,
144
+ <span class="ck">"bowling_team"</span> : <span class="cv">"Chennai Super Kings"</span>,
145
+ <span class="ck">"venue"</span> : <span class="cv">"Wankhede Stadium"</span>,
146
+ <span class="ck">"innings"</span> : <span class="cn">1</span>,
147
+ <span class="ck">"over"</span> : <span class="cn">14</span>,
148
+ <span class="ck">"ball_in_over"</span> : <span class="cn">3</span>,
149
+ <span class="ck">"current_score"</span> : <span class="cn">110</span>,
150
+ <span class="ck">"wickets_fallen"</span>: <span class="cn">2</span>,
151
+ <span class="ck">"batter_sr"</span> : <span class="cn">148</span>,
152
+ <span class="ck">"bowler_eco"</span> : <span class="cn">7.4</span>,
153
+ <span class="ck">"last_6_runs"</span> : <span class="cn">11</span>,
154
+ <span class="ck">"last_12_runs"</span> : <span class="cn">19</span>
155
+ }</pre>
156
+ <div class="api-label" style="margin-top:16px">POST /api/predict β€” Response</div>
157
+ <pre class="code-block">{
158
+ <span class="ck">"dot_ball_prob"</span> : <span class="cn">24.3</span>, <span class="cc">// % probability of dot ball</span>
159
+ <span class="ck">"boundary_prob"</span> : <span class="cn">38.7</span>, <span class="cc">// % probability of 4 or 6</span>
160
+ <span class="ck">"expected_runs"</span> : <span class="cn">2.41</span>, <span class="cc">// weighted mean runs</span>
161
+ <span class="ck">"run_distribution"</span>: [<span class="cn">0.24</span>, <span class="cn">0.22</span>, <span class="cn">0.08</span>, <span class="cn">0.06</span>, <span class="cn">0.28</span>, <span class="cn">0.12</span>],
162
+ <span class="ck">"win_probability"</span> : <span class="cn">null</span>, <span class="cc">// populated in innings 2</span>
163
+ <span class="ck">"phase"</span> : <span class="cv">"Middle Overs (Ov 7-15)"</span>,
164
+ <span class="ck">"run_rate"</span> : <span class="cn">7.86</span>
165
+ }</pre>
166
+ </div>
167
+ </div>
168
+ </div>
169
+
170
+ <!-- Team encodings -->
171
+ <div class="info-card">
172
+ <h3>Label Encodings</h3>
173
+ <div class="enc-cols">
174
+ <div>
175
+ <div class="feature-col-title">IPL Teams</div>
176
+ <div class="enc-table">
177
+ <div class="enc-row"><span class="enc-idx">0</span><span>Chennai Super Kings</span></div>
178
+ <div class="enc-row"><span class="enc-idx">1</span><span>Delhi Capitals</span></div>
179
+ <div class="enc-row"><span class="enc-idx">2</span><span>Gujarat Titans</span></div>
180
+ <div class="enc-row"><span class="enc-idx">3</span><span>Kolkata Knight Riders</span></div>
181
+ <div class="enc-row"><span class="enc-idx">4</span><span>Lucknow Super Giants</span></div>
182
+ <div class="enc-row"><span class="enc-idx">5</span><span>Mumbai Indians</span></div>
183
+ <div class="enc-row"><span class="enc-idx">6</span><span>Punjab Kings</span></div>
184
+ <div class="enc-row"><span class="enc-idx">7</span><span>Rajasthan Royals</span></div>
185
+ <div class="enc-row"><span class="enc-idx">8</span><span>Royal Challengers Bangalore</span></div>
186
+ <div class="enc-row"><span class="enc-idx">9</span><span>Sunrisers Hyderabad</span></div>
187
+ </div>
188
+ </div>
189
+ <div>
190
+ <div class="feature-col-title">Venues</div>
191
+ <div class="enc-table">
192
+ <div class="enc-row"><span class="enc-idx">0</span><span>Arun Jaitley Stadium</span></div>
193
+ <div class="enc-row"><span class="enc-idx">1</span><span>Brabourne Stadium</span></div>
194
+ <div class="enc-row"><span class="enc-idx">2</span><span>DY Patil Stadium</span></div>
195
+ <div class="enc-row"><span class="enc-idx">3</span><span>Eden Gardens</span></div>
196
+ <div class="enc-row"><span class="enc-idx">4</span><span>Feroz Shah Kotla</span></div>
197
+ <div class="enc-row"><span class="enc-idx">5</span><span>MA Chidambaram Stadium</span></div>
198
+ <div class="enc-row"><span class="enc-idx">6</span><span>MCA Stadium</span></div>
199
+ <div class="enc-row"><span class="enc-idx">7</span><span>Maharashtra CAS</span></div>
200
+ <div class="enc-row"><span class="enc-idx">8</span><span>Narendra Modi Stadium</span></div>
201
+ <div class="enc-row"><span class="enc-idx">9</span><span>Punjab CAS</span></div>
202
+ <div class="enc-row"><span class="enc-idx">10</span><span>Rajiv Gandhi IS</span></div>
203
+ <div class="enc-row"><span class="enc-idx">11</span><span>Sawai Mansingh Stadium</span></div>
204
+ <div class="enc-row"><span class="enc-idx">12</span><span>Wankhede Stadium</span></div>
205
+ </div>
206
+ </div>
207
+ </div>
208
+ </div>
209
+
210
+ </div>
211
+ {% endblock %}
templates/simulate.html ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block title %}Simulate β€” Cricket AI{% endblock %}
3
+
4
+ {% block content %}
5
+ <div class="page-header">
6
+ <div class="page-header-inner">
7
+ <h1>Live Match <span class="accent">Simulation</span></h1>
8
+ <p>Simulate a full T20 innings ball by ball. ML predictions update every delivery.</p>
9
+ </div>
10
+ </div>
11
+
12
+ <div class="sim-layout">
13
+
14
+ <!-- Controls -->
15
+ <div class="sim-controls-bar">
16
+ <div class="sim-team-selects">
17
+ <div class="form-group compact">
18
+ <label>Batting Team</label>
19
+ <select id="sim-bat">
20
+ {% for t in teams %}<option value="{{ t }}">{{ t }}</option>{% endfor %}
21
+ </select>
22
+ </div>
23
+
24
+ <div class="form-group compact">
25
+ <label>Bowling Team</label>
26
+ <select id="sim-bowl">
27
+ {% for t in teams %}
28
+ <option value="{{ t }}" {% if loop.index == 2 %}selected{% endif %}>{{ t }}</option>
29
+ {% endfor %}
30
+ </select>
31
+ </div>
32
+
33
+ <div class="form-group compact">
34
+ <label>Venue</label>
35
+ <select id="sim-venue">
36
+ {% for v in venues %}
37
+ <option value="{{ v }}" {% if v == 'Eden Gardens' %}selected{% endif %}>{{ v }}</option>
38
+ {% endfor %}
39
+ </select>
40
+ </div>
41
+ </div>
42
+
43
+ <div class="sim-action-btns">
44
+ <button type="button" class="sim-btn primary" onclick="simNextBall()" id="next-btn">β–Ά Next Ball</button>
45
+ <button type="button" class="sim-btn" onclick="simAuto()" id="auto-btn">⚑ Auto Play</button>
46
+ <button type="button" class="sim-btn" onclick="simReset()">β†Ί Reset</button>
47
+ </div>
48
+ </div>
49
+
50
+ <!-- Scoreboard -->
51
+ <div class="scoreboard">
52
+ <div class="score-row">
53
+ <div>
54
+ <div class="score-big" id="sim-score">0 / 0</div>
55
+ <div class="score-sub" id="sim-overs">0.0 overs</div>
56
+ </div>
57
+ <div class="score-stats">
58
+ <div class="score-stat">
59
+ <div class="ss-val" id="sim-rr">0.00</div>
60
+ <div class="ss-lbl">Run Rate</div>
61
+ </div>
62
+ <div class="score-stat">
63
+ <div class="ss-val" id="sim-balls-left">120</div>
64
+ <div class="ss-lbl">Balls Left</div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+
70
+ <!-- Commentary -->
71
+ <div class="commentary-box" id="commentary">
72
+ Press <strong>Next Ball</strong> or <strong>Auto Play</strong> to start...
73
+ </div>
74
+
75
+ <!-- Live Prediction -->
76
+ <div class="live-pred-panel" id="live-pred" style="display:none">
77
+ <div class="lp-title">ML Prediction β€” Next Ball</div>
78
+
79
+ <div class="lp-stats">
80
+ <div class="lp-stat">
81
+ <div class="lp-val red" id="lp-dot">β€”</div>
82
+ <div class="lp-lbl">Dot Ball %</div>
83
+ </div>
84
+
85
+ <div class="lp-stat">
86
+ <div class="lp-val green" id="lp-bnd">β€”</div>
87
+ <div class="lp-lbl">Boundary %</div>
88
+ </div>
89
+
90
+ <div class="lp-stat">
91
+ <div class="lp-val gold" id="lp-exp">β€”</div>
92
+ <div class="lp-lbl">Exp Runs</div>
93
+ </div>
94
+
95
+ <div class="lp-stat">
96
+ <div class="lp-val blue" id="lp-phase">β€”</div>
97
+ <div class="lp-lbl">Phase</div>
98
+ </div>
99
+ </div>
100
+
101
+ <canvas id="lp-chart"></canvas>
102
+ </div>
103
+
104
+ </div>
105
+
106
+ {% endblock %}
107
+
108
+
109
+ {% block scripts %}
110
+ <script>
111
+
112
+ let state = null;
113
+ let autoTimer = null;
114
+ let lpChart = null;
115
+
116
+ function initState() {
117
+ state = {
118
+ score: 0,
119
+ wickets: 0,
120
+ balls: 0
121
+ };
122
+ }
123
+
124
+ function simReset() {
125
+ if (autoTimer) clearInterval(autoTimer);
126
+
127
+ initState();
128
+
129
+ document.getElementById("sim-score").textContent = "0 / 0";
130
+ document.getElementById("sim-overs").textContent = "0.0 overs";
131
+ document.getElementById("commentary").innerHTML =
132
+ 'Press <strong>Next Ball</strong> or <strong>Auto Play</strong> to start...';
133
+
134
+ document.getElementById("live-pred").style.display = "none";
135
+
136
+ if (lpChart) lpChart.destroy();
137
+ }
138
+
139
+ function rollBall() {
140
+ if (Math.random() < 0.05) return { runs: 0, label: "W" };
141
+
142
+ const runs = [0,1,2,3,4,6];
143
+ const r = runs[Math.floor(Math.random() * runs.length)];
144
+
145
+ return { runs: r, label: String(r) };
146
+ }
147
+
148
+ async function simNextBall() {
149
+
150
+ if (!state) initState();
151
+
152
+ if (state.balls >= 120 || state.wickets >= 10) {
153
+ document.getElementById("commentary").innerHTML = "🏁 Innings Complete!";
154
+ return;
155
+ }
156
+
157
+ const outcome = rollBall();
158
+
159
+ if (outcome.label === "W") state.wickets++;
160
+ else state.score += outcome.runs;
161
+
162
+ state.balls++;
163
+
164
+ const over = Math.floor(state.balls / 6);
165
+ const ball = state.balls % 6;
166
+
167
+ document.getElementById("sim-score").textContent =
168
+ `${state.score} / ${state.wickets}`;
169
+
170
+ document.getElementById("sim-overs").textContent =
171
+ `${over}.${ball}`;
172
+
173
+ document.getElementById("commentary").textContent =
174
+ `Ball ${over}.${ball}: ${outcome.label}`;
175
+
176
+ fetchLivePred(over, ball);
177
+ }
178
+
179
+ async function fetchLivePred(over, ball) {
180
+ try {
181
+ const res = await fetch("/api/predict", {
182
+ method: "POST",
183
+ headers: {"Content-Type": "application/json"},
184
+ body: JSON.stringify({
185
+ batting_team: document.getElementById("sim-bat").value,
186
+ bowling_team: document.getElementById("sim-bowl").value,
187
+ venue: document.getElementById("sim-venue").value,
188
+ innings: 1,
189
+ over: over,
190
+ ball_in_over: ball,
191
+ current_score: state.score,
192
+ wickets_fallen: state.wickets
193
+ })
194
+ });
195
+
196
+ const d = await res.json();
197
+
198
+ if (!d.success) return;
199
+
200
+ renderLivePred(d.prediction);
201
+
202
+ } catch (err) {
203
+ console.log("API Error:", err);
204
+ }
205
+ }
206
+
207
+ function renderLivePred(data) {
208
+
209
+ document.getElementById("live-pred").style.display = "block";
210
+
211
+ document.getElementById("lp-dot").textContent =
212
+ ((data.dot_ball_prob ?? 0) * 100).toFixed(1) + "%";
213
+
214
+ document.getElementById("lp-bnd").textContent =
215
+ ((data.boundary_prob ?? 0) * 100).toFixed(1) + "%";
216
+
217
+ document.getElementById("lp-exp").textContent =
218
+ (data.expected_runs ?? 0).toFixed(2);
219
+
220
+ document.getElementById("lp-phase").textContent =
221
+ data.phase || "-";
222
+
223
+ const ctx = document.getElementById("lp-chart");
224
+
225
+ if (lpChart) lpChart.destroy();
226
+
227
+ lpChart = new Chart(ctx, {
228
+ type: "bar",
229
+ data: {
230
+ labels: ["0","1","2","3","4","5"],
231
+ datasets: [{
232
+ data: (data.run_distribution || [0,0,0,0,0,0])
233
+ .map(v => (v * 100).toFixed(1)),
234
+ backgroundColor: ["#64748b","#3b82f6","#22d3ee","#a78bfa","#10b981","#f59e0b"]
235
+ }]
236
+ }
237
+ });
238
+ }
239
+
240
+ function simAuto() {
241
+ const btn = document.getElementById("auto-btn");
242
+
243
+ if (autoTimer) {
244
+ clearInterval(autoTimer);
245
+ autoTimer = null;
246
+ btn.textContent = "⚑ Auto Play";
247
+ } else {
248
+ btn.textContent = "⏸ Stop";
249
+ autoTimer = setInterval(simNextBall, 1500);
250
+ }
251
+ }
252
+
253
+ // INIT
254
+ initState();
255
+
256
+ </script>
257
+ {% endblock %}
test_playload.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "batting_team": "Mumbai Indians",
3
+ "bowling_team": "Chennai Super Kings",
4
+ "venue": "Wankhede Stadium",
5
+ "innings": 1,
6
+ "over": 10,
7
+ "ball_in_over": 3,
8
+ "current_score": 85,
9
+ "wickets_fallen": 2,
10
+ "batter_sr": 140,
11
+ "bowler_eco": 7.5,
12
+ "last_6_runs": 8,
13
+ "last_12_runs": 16,
14
+ "prev_runs": 1,
15
+ "prev_wicket": 0,
16
+ "last_6_wickets": 0,
17
+ "striker_enc": 0,
18
+ "bowler_enc": 0
19
+ }
utils/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # utils package
utils/__pycache__/__init__.cpython-314.pyc ADDED
Binary file (163 Bytes). View file
 
utils/__pycache__/encoders.cpython-314.pyc ADDED
Binary file (3.13 kB). View file
 
utils/__pycache__/predictor.cpython-314.pyc ADDED
Binary file (8.86 kB). View file
 
utils/encoders.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ utils/encoders.py
3
+ Label-encoding maps that mirror the sklearn LabelEncoder used during training.
4
+ All lists are sorted alphabetically – matching the default LabelEncoder order.
5
+ """
6
+
7
+ # ── IPL Teams (alphabetical order = encoding index) ──────────────────────────
8
+ TEAMS = sorted([
9
+ "Chennai Super Kings",
10
+ "Delhi Capitals",
11
+ "Gujarat Titans",
12
+ "Kolkata Knight Riders",
13
+ "Lucknow Super Giants",
14
+ "Mumbai Indians",
15
+ "Punjab Kings",
16
+ "Rajasthan Royals",
17
+ "Royal Challengers Bangalore",
18
+ "Sunrisers Hyderabad",
19
+ ])
20
+
21
+ TEAM_ENC: dict[str, int] = {team: idx for idx, team in enumerate(TEAMS)}
22
+
23
+ # ── IPL Venues (alphabetical order = encoding index) ─────────────────────────
24
+ VENUES = sorted([
25
+ "Arun Jaitley Stadium",
26
+ "Brabourne Stadium",
27
+ "DY Patil Stadium",
28
+ "Eden Gardens",
29
+ "Feroz Shah Kotla",
30
+ "MA Chidambaram Stadium",
31
+ "MCA Stadium",
32
+ "Maharashtra Cricket Association Stadium",
33
+ "Narendra Modi Stadium",
34
+ "Punjab Cricket Association Stadium",
35
+ "Rajiv Gandhi International Stadium",
36
+ "Sawai Mansingh Stadium",
37
+ "Wankhede Stadium",
38
+ ])
39
+
40
+ VENUE_ENC: dict[str, int] = {venue: idx for idx, venue in enumerate(VENUES)}
41
+
42
+ # ── Phase encoding ────────────────────────────────────────────────────────────
43
+ PHASE_LABELS = {
44
+ 0: "Powerplay (Ov 1–6)",
45
+ 1: "Middle Overs (Ov 7–15)",
46
+ 2: "Death Overs (Ov 16–20)",
47
+ }
48
+
49
+
50
+ def encode_team(name: str) -> int:
51
+ """Return integer encoding for a team name. Defaults to 0 if not found."""
52
+ return TEAM_ENC.get(name, 0)
53
+
54
+
55
+ def encode_venue(name: str) -> int:
56
+ """Return integer encoding for a venue name. Defaults to 0 if not found."""
57
+ return VENUE_ENC.get(name, 0)
58
+
59
+
60
+ def get_phase(over: int) -> int:
61
+ """
62
+ Returns match phase (0/1/2) based on over number.
63
+ 0 = Powerplay (overs 1–6)
64
+ 1 = Middle overs(overs 7–15)
65
+ 2 = Death overs (overs 16–20)
66
+ """
67
+ if over < 6:
68
+ return 0
69
+ elif over < 15:
70
+ return 1
71
+ else:
72
+ return 2
utils/predictor.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ utils/predictor.py
3
+ Loads the four XGBoost models and exposes a single predict() method
4
+ that accepts a raw request dict and returns a structured result dict.
5
+ """
6
+
7
+ import os
8
+ import pickle
9
+ import warnings
10
+ import pandas as pd
11
+
12
+ from utils.encoders import encode_team, encode_venue, get_phase, PHASE_LABELS
13
+
14
+ warnings.filterwarnings("ignore")
15
+
16
+ # ── Model file paths ──────────────────────────────────────────────────────────
17
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18
+ MODELS_DIR = os.path.join(BASE_DIR, "models")
19
+
20
+
21
+ class CricketPredictor:
22
+ """
23
+ Loads all four pre-trained XGBoost models and provides
24
+ ball-level and match-level predictions.
25
+
26
+ Models
27
+ ------
28
+ dot_model : DotBall.pkl – P(dot ball) binary
29
+ boundary_model : BoundaryModel.pkl – P(boundary 4/6) binary
30
+ run_model : RunPrediction.pkl – P(0..5 runs) multi-class
31
+ win_model : IPLchasingTeamWin.pkl– P(chase success) binary
32
+ """
33
+
34
+ def __init__(self):
35
+ print("Loading models...")
36
+ self.dot_model = self._load("DotBall.pkl")
37
+ self.boundary_model = self._load("BoundaryModel.pkl")
38
+ self.run_model = self._load("RunPrediction.pkl")
39
+ self.win_model = self._load("IPLchasingTeamWin.pkl")
40
+ print("βœ“ All 4 models loaded successfully.\n")
41
+
42
+ # ── Public API ────────────────────────────────────────────────────────────
43
+
44
+ def predict(self, data: dict) -> dict:
45
+ """
46
+ Main prediction entry point.
47
+
48
+ Parameters
49
+ ----------
50
+ data : dict Raw JSON body from the API request.
51
+
52
+ Returns
53
+ -------
54
+ dict with keys:
55
+ dot_ball_prob float – % probability of a dot ball
56
+ boundary_prob float – % probability of a 4 or 6
57
+ expected_runs float – weighted average expected runs
58
+ run_distribution list – [P(0), P(1), P(2), P(3), P(4), P(5)]
59
+ win_probability float|None – % win prob for chasing team (innings=2 only)
60
+ phase str – human-readable phase label
61
+ """
62
+ # ── 1. Parse & validate inputs ────────────────────────────────────────
63
+ batting_team = str(data.get("batting_team", ""))
64
+ bowling_team = str(data.get("bowling_team", ""))
65
+ venue = str(data.get("venue", ""))
66
+ innings = int(data.get("innings", 1))
67
+ over = int(data.get("over", 1))
68
+ ball_in_over = int(data.get("ball_in_over", 1))
69
+ current_score = float(data.get("current_score", 0))
70
+ wickets_fallen = int(data.get("wickets_fallen", 0))
71
+
72
+ # Optional performance features
73
+ batter_sr = float(data.get("batter_sr", 130))
74
+ bowler_eco = float(data.get("bowler_eco", 7.5))
75
+ last_6_runs = float(data.get("last_6_runs", 6))
76
+ last_12_runs = float(data.get("last_12_runs", 12))
77
+ prev_runs = float(data.get("prev_runs", 1))
78
+ prev_wicket = int(data.get("prev_wicket", 0))
79
+ last_6_wickets = int(data.get("last_6_wickets", 0))
80
+ striker_enc = int(data.get("striker_enc", 0))
81
+ bowler_enc = int(data.get("bowler_enc", 0))
82
+
83
+ # ── 2. Derived features ───────────────────────────────────────────────
84
+ balls_bowled = over * 6 + ball_in_over
85
+ run_rate = round(current_score / max(balls_bowled, 1) * 6, 3)
86
+ phase = get_phase(over)
87
+
88
+ # ── 3. Build ball-level feature DataFrame ─────────────────────────────
89
+ ball_df = pd.DataFrame([{
90
+ "striker_enc" : striker_enc,
91
+ "bowler_enc" : bowler_enc,
92
+ "batting_team_enc" : encode_team(batting_team),
93
+ "bowling_team_enc" : encode_team(bowling_team),
94
+ "venue_enc" : encode_venue(venue),
95
+ "over" : over,
96
+ "ball_in_over" : ball_in_over,
97
+ "phase" : phase,
98
+ "current_score" : current_score,
99
+ "wickets_fallen" : wickets_fallen,
100
+ "run_rate" : run_rate,
101
+ "prev_runs" : prev_runs,
102
+ "prev_wicket" : prev_wicket,
103
+ "last_6_runs" : last_6_runs,
104
+ "last_12_runs" : last_12_runs,
105
+ "last_6_wickets" : last_6_wickets,
106
+ "batter_sr" : batter_sr,
107
+ "bowler_eco" : bowler_eco,
108
+ }])
109
+
110
+ # ── 4. Run the three ball-level models ────────────────────────────────
111
+ dot_prob = float(self.dot_model.predict_proba(ball_df)[0][1])
112
+ boundary_prob = float(self.boundary_model.predict_proba(ball_df)[0][1])
113
+
114
+ run_proba = self.run_model.predict_proba(ball_df)[0]
115
+ run_dist = [round(float(p), 4) for p in run_proba]
116
+ expected_runs = round(sum(i * run_dist[i] for i in range(len(run_dist))), 3)
117
+
118
+ # ── 5. Win probability (2nd innings only) ─────────────────────────────
119
+ win_prob = None
120
+ if innings == 2:
121
+ balls_remaining = int(data.get("balls_remaining", 60))
122
+ balls_done_chase = max(120 - balls_remaining, 1)
123
+ chase_rr = round(current_score / balls_done_chase * 6, 3)
124
+
125
+ win_df = pd.DataFrame([{
126
+ "batting_team" : encode_team(batting_team),
127
+ "bowling_team" : encode_team(bowling_team),
128
+ "venue" : encode_venue(venue),
129
+ "innings" : innings,
130
+ "current_score" : current_score,
131
+ "wickets_fallen" : wickets_fallen,
132
+ "balls_remaining": balls_remaining,
133
+ "run_rate" : chase_rr,
134
+ }])
135
+ win_prob = round(float(self.win_model.predict_proba(win_df)[0][1]) * 100, 1)
136
+
137
+ # ── 6. Return structured result ───────────────────────────────────────
138
+ return {
139
+ "dot_ball_prob" : round(dot_prob * 100, 1),
140
+ "boundary_prob" : round(boundary_prob * 100, 1),
141
+ "expected_runs" : expected_runs,
142
+ "run_distribution": run_dist,
143
+ "win_probability" : win_prob,
144
+ "phase" : PHASE_LABELS[phase],
145
+ "run_rate" : round(run_rate, 2),
146
+ }
147
+
148
+ def models_loaded(self) -> list[str]:
149
+ return ["DotBall", "BoundaryModel", "RunPrediction", "IPLchasingTeamWin"]
150
+
151
+ # ── Private helpers ───────────────────────────────────────────────────────
152
+
153
+ def _load(self, filename: str):
154
+ path = os.path.join(MODELS_DIR, filename)
155
+ if not os.path.exists(path):
156
+ raise FileNotFoundError(
157
+ f"Model file not found: {path}\n"
158
+ f"Make sure {filename} is inside the 'models/' folder."
159
+ )
160
+ with open(path, "rb") as f:
161
+ return pickle.load(f)