Narendra9009 commited on
Commit
b0a8dea
·
1 Parent(s): 7f4a7eb

added new api getfen which is working percfectly

Browse files
__pycache__/main.cpython-311.pyc DELETED
Binary file (9.34 kB)
 
__pycache__/main.cpython-313.pyc ADDED
Binary file (8.2 kB). View file
 
main.py CHANGED
@@ -140,6 +140,7 @@ async def get_fen(file : UploadFile = File(), perspective : str = Form("w"), nex
140
 
141
  @app.post('/getReview')
142
  async def getReview(file: UploadFile = File(...)):
 
143
  print("call recieved")
144
 
145
  if not file.filename.endswith(".pgn"):
@@ -152,14 +153,14 @@ async def getReview(file: UploadFile = File(...)):
152
  tmp_file_path = tmp_file.name
153
 
154
  # Analyze the PGN file
155
- analysis_result = await analyze_pgn(tmp_file_path)
156
 
157
  # Clean up the temporary file
158
  os.remove(tmp_file_path)
159
 
160
  if not analysis_result:
161
  return JSONResponse(content={"error": "No game found in the PGN file"}, status_code=400)
162
-
163
- return JSONResponse(content=analysis_result, status_code=200)
164
  except Exception as e:
165
  return JSONResponse(content={"error": "Unexpected error occurred", "details": str(e)}, status_code=500)
 
140
 
141
  @app.post('/getReview')
142
  async def getReview(file: UploadFile = File(...)):
143
+ print(os.getcwd())
144
  print("call recieved")
145
 
146
  if not file.filename.endswith(".pgn"):
 
153
  tmp_file_path = tmp_file.name
154
 
155
  # Analyze the PGN file
156
+ analysis_result = analyze_pgn(tmp_file_path)
157
 
158
  # Clean up the temporary file
159
  os.remove(tmp_file_path)
160
 
161
  if not analysis_result:
162
  return JSONResponse(content={"error": "No game found in the PGN file"}, status_code=400)
163
+ return analysis_result
164
+
165
  except Exception as e:
166
  return JSONResponse(content={"error": "Unexpected error occurred", "details": str(e)}, status_code=500)
routes/__pycache__/chess_review.cpython-311.pyc DELETED
Binary file (10.2 kB)
 
routes/__pycache__/chess_review_helper.cpython-313.pyc ADDED
Binary file (14.2 kB). View file
 
routes/__pycache__/detection.cpython-311.pyc DELETED
Binary file (1.5 kB)
 
routes/__pycache__/detection.cpython-313.pyc ADDED
Binary file (1.57 kB). View file
 
routes/__pycache__/fen_generator.cpython-311.pyc DELETED
Binary file (5.36 kB)
 
routes/__pycache__/fen_generator.cpython-313.pyc ADDED
Binary file (4.62 kB). View file
 
routes/__pycache__/segmentation.cpython-311.pyc DELETED
Binary file (1.27 kB)
 
routes/__pycache__/segmentation.cpython-313.pyc ADDED
Binary file (1.3 kB). View file
 
routes/chess_review.py CHANGED
@@ -1,47 +1,16 @@
1
- import csv
 
 
2
  import chess.pgn
3
  import chess.engine
4
  from enum import Enum
5
  from typing import List, Dict
6
- import asyncio
 
7
  import json
8
- import sys
9
- import os
10
-
11
- if sys.platform == "win32":
12
- asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
13
-
14
-
15
- def load_opening_book(csv_path):
16
- opening_book = {}
17
- try:
18
- with open(csv_path, newline='', encoding='utf-8') as csvfile:
19
- reader = csv.reader(csvfile)
20
- next(reader)
21
- for row in reader:
22
- if len(row) < 3:
23
- continue
24
- pgn_moves = row[2]
25
- game = chess.pgn.Game()
26
- board = game.board()
27
- for move in pgn_moves.split():
28
- if "." in move:
29
- continue
30
- try:
31
- chess_move = board.push_san(move)
32
- fen = " ".join(board.fen().split()[:4])
33
- opening_book[fen] = chess_move.uci()
34
- except ValueError:
35
- break
36
- except Exception as e:
37
- print(f"Error loading opening book: {e}")
38
- return opening_book
39
-
40
-
41
- engine_path = os.path.join(os.getcwd(), "models", "stockfish_14_x64_avx2.exe")
42
- csv_path = os.path.join(os.getcwd(), "assets", "opening_book.csv")
43
- opening_book = load_opening_book(csv_path)
44
 
 
45
 
46
  class GamePhase(Enum):
47
  OPENING = "opening"
@@ -85,6 +54,7 @@ centipawn_classifications = [
85
  Classification.BLUNDER,
86
  ]
87
 
 
88
  FORCED_WIN_THRESHOLD = 500
89
  MISS_CENTIPAWN_LOSS = 300
90
  MISS_MATE_THRESHOLD = 3
@@ -94,64 +64,224 @@ QUEEN_VALUE = 9
94
  def detect_game_phase(board: chess.Board, in_opening: bool) -> GamePhase:
95
  if in_opening:
96
  return GamePhase.OPENING
97
- total_material = sum(
98
- len(board.pieces(p, color)) * {1: 1, 2: 3, 3: 3, 4: 5, 5: QUEEN_VALUE}[p]
99
- for color in [chess.WHITE, chess.BLACK] for p in chess.PIECE_TYPES if p != chess.KING
100
- )
101
- queens = sum(len(board.pieces(chess.QUEEN, color)) for color in [chess.WHITE, chess.BLACK])
102
- return GamePhase.ENDGAME if total_material <= ENDGAME_MATERIAL_THRESHOLD or (queens == 0 and total_material <= ENDGAME_MATERIAL_THRESHOLD * 2) else GamePhase.MIDDLEGAME
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
  def is_book_move(board, opening_book, max_depth=8):
105
- return None if board.fullmove_number > max_depth else opening_book.get(" ".join(board.fen().split()[:4]))
 
 
 
 
106
 
107
- async def analyze_pgn(pgn_file: str):
 
 
 
 
 
108
  with open(pgn_file) as pgn:
109
  game = chess.pgn.read_game(pgn)
 
110
  if not game:
111
- return json.dumps({"error": "No game found in PGN file."})
112
 
113
- results = {
114
- "moves": [],
115
- "phases": {},
116
- "players": {"white": {}, "black": {}}
117
  }
118
 
119
  with chess.engine.SimpleEngine.popen_uci(engine_path) as engine:
120
  board = game.board()
121
- classifications = {"white": {p.value: [] for p in GamePhase}, "black": {p.value: [] for p in GamePhase}}
 
 
 
 
122
  in_opening = True
123
 
124
  for move_number, node in enumerate(game.mainline(), start=1):
 
125
  pre_info = engine.analyse(board, chess.engine.Limit(depth=20))
126
  pre_eval = pre_info["score"].white().score(mate_score=10000) or 0
127
  best_move = pre_info.get("pv", [None])[0]
 
 
128
  move = node.move
129
  board.push(move)
 
 
130
  post_info = engine.analyse(board, chess.engine.Limit(depth=20))
131
  post_eval = post_info["score"].white().score(mate_score=10000) or 0
 
 
132
  book_move = is_book_move(board, opening_book)
133
  current_phase = detect_game_phase(board, in_opening)
134
  if not book_move and in_opening:
135
  in_opening = False
 
 
136
  eval_loss = abs(pre_eval - post_eval)
137
- classification = Classification.BOOK if book_move else Classification.BLUNDER
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  player = "white" if board.turn == chess.BLACK else "black"
139
- classifications[player][current_phase.value].append(classification.value)
140
- results["moves"].append({
 
 
 
141
  "move_number": move_number,
142
- "player": player,
143
  "move": move.uci(),
144
  "evaluation": post_eval / 100,
145
  "evaluation_loss": eval_loss / 100,
146
  "classification": classification.value
147
  })
148
 
 
149
  for phase in GamePhase:
150
- results["phases"][phase.value] = {
151
- "white": classifications["white"][phase.value],
152
- "black": classifications["black"][phase.value]
153
- }
154
- results["players"]["white"] = classifications["white"]
155
- results["players"]["black"] = classifications["black"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
- return json.dumps(results, indent=4)
 
1
+ from fastapi import FastAPI, File, UploadFile, HTTPException
2
+ import os
3
+ import tempfile
4
  import chess.pgn
5
  import chess.engine
6
  from enum import Enum
7
  from typing import List, Dict
8
+ from datetime import datetime
9
+ import csv
10
  import json
11
+ from fastapi.responses import JSONResponse
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ app = FastAPI()
14
 
15
  class GamePhase(Enum):
16
  OPENING = "opening"
 
54
  Classification.BLUNDER,
55
  ]
56
 
57
+ # Analysis parameters
58
  FORCED_WIN_THRESHOLD = 500
59
  MISS_CENTIPAWN_LOSS = 300
60
  MISS_MATE_THRESHOLD = 3
 
64
  def detect_game_phase(board: chess.Board, in_opening: bool) -> GamePhase:
65
  if in_opening:
66
  return GamePhase.OPENING
67
+
68
+ total_material = 0
69
+ queens = 0
70
+
71
+ for color in [chess.WHITE, chess.BLACK]:
72
+ for piece_type in chess.PIECE_TYPES:
73
+ if piece_type == chess.KING:
74
+ continue
75
+
76
+ count = len(board.pieces(piece_type, color))
77
+ value = {
78
+ chess.PAWN: 1,
79
+ chess.KNIGHT: 3,
80
+ chess.BISHOP: 3,
81
+ chess.ROOK: 5,
82
+ chess.QUEEN: QUEEN_VALUE
83
+ }[piece_type]
84
+
85
+ total_material += count * value
86
+ if piece_type == chess.QUEEN:
87
+ queens += count
88
+
89
+ endgame_conditions = [
90
+ total_material <= ENDGAME_MATERIAL_THRESHOLD,
91
+ queens == 0 and total_material <= ENDGAME_MATERIAL_THRESHOLD * 2,
92
+ ]
93
+
94
+ return GamePhase.ENDGAME if any(endgame_conditions) else GamePhase.MIDDLEGAME
95
+
96
+ def get_evaluation_loss_threshold(classif: Classification, prev_eval: float) -> float:
97
+ prev_eval = abs(prev_eval)
98
+ if classif == Classification.BEST:
99
+ return max(0.0001 * prev_eval**2 + 0.0236 * prev_eval - 3.7143, 0)
100
+ elif classif == Classification.EXCELLENT:
101
+ return max(0.0002 * prev_eval**2 + 0.1231 * prev_eval + 27.5455, 0)
102
+ elif classif == Classification.GOOD:
103
+ return max(0.0002 * prev_eval**2 + 0.2643 * prev_eval + 60.5455, 0)
104
+ elif classif == Classification.INACCURACY:
105
+ return max(0.0002 * prev_eval**2 + 0.3624 * prev_eval + 108.0909, 0)
106
+ elif classif == Classification.MISS:
107
+ return max(0.00025 * prev_eval**2 + 0.38255 * prev_eval + 166.9541, 0)
108
+ elif classif == Classification.MISTAKE:
109
+ return max(0.0003 * prev_eval**2 + 0.4027 * prev_eval + 225.8182, 0)
110
+ else:
111
+ return float("inf")
112
+
113
+ def load_opening_book(csv_path):
114
+ opening_book = {}
115
+ try:
116
+ with open(csv_path, newline='', encoding='utf-8') as csvfile:
117
+ reader = csv.reader(csvfile)
118
+ next(reader)
119
+ for row in reader:
120
+ if len(row) < 3:
121
+ continue
122
+ pgn_moves = row[2]
123
+ game = chess.pgn.Game()
124
+ board = game.board()
125
+ for move in pgn_moves.split():
126
+ if "." in move:
127
+ continue
128
+ try:
129
+ chess_move = board.push_san(move)
130
+ fen = " ".join(board.fen().split()[:4])
131
+ opening_book[fen] = chess_move.uci()
132
+ except ValueError:
133
+ break
134
+ except Exception as e:
135
+ print(f"Error loading opening book: {e}")
136
+ return opening_book
137
 
138
  def is_book_move(board, opening_book, max_depth=8):
139
+ if board.fullmove_number > max_depth:
140
+ return None
141
+ fen = " ".join(board.fen().split()[:4])
142
+ return opening_book.get(fen)
143
+
144
 
145
+ engine_path = os.path.join(os.getcwd(), "models", "stockfish-windows-x86-64-avx2.exe")
146
+ book_csv_path = os.path.join(os.getcwd(), "assets", "openings_master.csv")
147
+
148
+ def analyze_pgn(pgn_file: str) -> Dict:
149
+ opening_book = load_opening_book(book_csv_path)
150
+
151
  with open(pgn_file) as pgn:
152
  game = chess.pgn.read_game(pgn)
153
+
154
  if not game:
155
+ return {"error": "No game found in the PGN file."}
156
 
157
+ result = {
158
+ "move_analysis": [],
159
+ "phase_analysis": {},
160
+ "player_summaries": {}
161
  }
162
 
163
  with chess.engine.SimpleEngine.popen_uci(engine_path) as engine:
164
  board = game.board()
165
+ classifications = {
166
+ "white": {phase: [] for phase in GamePhase},
167
+ "black": {phase: [] for phase in GamePhase}
168
+ }
169
+ phase_data = {phase: [] for phase in GamePhase}
170
  in_opening = True
171
 
172
  for move_number, node in enumerate(game.mainline(), start=1):
173
+ # Analyze position before the move
174
  pre_info = engine.analyse(board, chess.engine.Limit(depth=20))
175
  pre_eval = pre_info["score"].white().score(mate_score=10000) or 0
176
  best_move = pre_info.get("pv", [None])[0]
177
+
178
+ # Make the move
179
  move = node.move
180
  board.push(move)
181
+
182
+ # Analyze position after the move
183
  post_info = engine.analyse(board, chess.engine.Limit(depth=20))
184
  post_eval = post_info["score"].white().score(mate_score=10000) or 0
185
+
186
+ # Determine game phase
187
  book_move = is_book_move(board, opening_book)
188
  current_phase = detect_game_phase(board, in_opening)
189
  if not book_move and in_opening:
190
  in_opening = False
191
+
192
+ # Calculate evaluation loss
193
  eval_loss = abs(pre_eval - post_eval)
194
+
195
+ # Initial classification
196
+ classification = Classification.BOOK if book_move else None
197
+ if not classification:
198
+ for classif in centipawn_classifications:
199
+ threshold = get_evaluation_loss_threshold(classif, pre_eval)
200
+ if eval_loss <= threshold:
201
+ classification = classif
202
+ break
203
+ classification = classification or Classification.BLUNDER
204
+
205
+ # Check for missed opportunities
206
+ is_winning = abs(pre_eval) >= FORCED_WIN_THRESHOLD
207
+ is_forced_win = pre_info["score"].is_mate() and pre_info["score"].relative.mate() <= MISS_MATE_THRESHOLD
208
+ if is_winning and move != best_move and (eval_loss >= MISS_CENTIPAWN_LOSS or is_forced_win):
209
+ classification = Classification.MISS
210
+
211
+ # Check for brilliant moves
212
+ if classification == Classification.BEST:
213
+ if pre_eval < -150 and post_eval >= 150:
214
+ classification = Classification.GREAT
215
+ elif pre_eval < -300 and post_eval >= 300:
216
+ classification = Classification.BRILLIANT
217
+
218
+ # Track classifications
219
  player = "white" if board.turn == chess.BLACK else "black"
220
+ classifications[player][current_phase].append(classification)
221
+ phase_data[current_phase].append(classification)
222
+
223
+ # Add move analysis to result
224
+ result["move_analysis"].append({
225
  "move_number": move_number,
226
+ "player": "White" if board.turn == chess.BLACK else "Black",
227
  "move": move.uci(),
228
  "evaluation": post_eval / 100,
229
  "evaluation_loss": eval_loss / 100,
230
  "classification": classification.value
231
  })
232
 
233
+ # Phase analysis
234
  for phase in GamePhase:
235
+ moves = phase_data[phase]
236
+ if moves:
237
+ rating = get_phase_rating(moves)
238
+ result["phase_analysis"][phase.value] = {
239
+ "rating": rating.value,
240
+ "move_count": len(moves)
241
+ }
242
+
243
+ # Player summaries
244
+ for color in ["white", "black"]:
245
+ player = game.headers["White" if color == "white" else "Black"]
246
+ counts = {c.value: 0 for c in Classification}
247
+
248
+ for phase in GamePhase:
249
+ phase_moves = classifications[color][phase]
250
+ for m in phase_moves:
251
+ counts[m.value] += 1
252
+
253
+ result["player_summaries"][player] = counts
254
+
255
+ def convert_enums(obj):
256
+ if isinstance(obj, Enum): # Convert Enum to its value
257
+ return obj.value
258
+ if isinstance(obj, dict): # Recursively handle dicts
259
+ return {k: convert_enums(v) for k, v in obj.items()}
260
+ if isinstance(obj, list): # Recursively handle lists
261
+ return [convert_enums(i) for i in obj]
262
+ return obj # Return other types as they are
263
+
264
+ json_result = convert_enums(result)
265
+
266
+ return JSONResponse(content=json_result)
267
+
268
+
269
+ def get_phase_rating(classified_moves: List[Classification]) -> Classification:
270
+ if not classified_moves:
271
+ return Classification.GOOD
272
+
273
+ total = sum(classification_values[m] for m in classified_moves)
274
+ average = total / len(classified_moves)
275
+
276
+ rating_order = [
277
+ (Classification.BRILLIANT, 0.95),
278
+ (Classification.GREAT, 0.85),
279
+ (Classification.BEST, 0.75),
280
+ (Classification.EXCELLENT, 0.65),
281
+ (Classification.GOOD, 0.5),
282
+ (Classification.INACCURACY, 0.35),
283
+ (Classification.MISS, 0.25),
284
+ (Classification.MISTAKE, 0.15)
285
+ ]
286
 
287
+ return next((c for c, t in rating_order if average >= t), Classification.BLUNDER)