Spaces:
Sleeping
Sleeping
Commit
·
46c25c8
1
Parent(s):
f1de4a9
changes made for getting text based review
Browse files
__pycache__/main.cpython-313.pyc
CHANGED
Binary files a/__pycache__/main.cpython-313.pyc and b/__pycache__/main.cpython-313.pyc differ
|
|
main.py
CHANGED
@@ -85,6 +85,7 @@ async def get_fen(file : UploadFile = File(), perspective : str = Form("w"), nex
|
|
85 |
|
86 |
@app.post('/getReview')
|
87 |
async def getReview(file_upload: FileUpload):
|
|
|
88 |
print(os.getcwd())
|
89 |
print("call recieved")
|
90 |
|
|
|
85 |
|
86 |
@app.post('/getReview')
|
87 |
async def getReview(file_upload: FileUpload):
|
88 |
+
# this function returns text based and overall review of the game by taking base64 encoded pgn file as input
|
89 |
print(os.getcwd())
|
90 |
print("call recieved")
|
91 |
|
routes/__pycache__/chess_review.cpython-313.pyc
CHANGED
Binary files a/routes/__pycache__/chess_review.cpython-313.pyc and b/routes/__pycache__/chess_review.cpython-313.pyc differ
|
|
routes/__pycache__/tex_based_review.cpython-313.pyc
ADDED
Binary file (3.93 kB). View file
|
|
routes/chess_review.py
CHANGED
@@ -9,8 +9,14 @@ from datetime import datetime
|
|
9 |
import csv
|
10 |
import json
|
11 |
from fastapi.responses import JSONResponse
|
|
|
|
|
|
|
12 |
|
13 |
-
|
|
|
|
|
|
|
14 |
|
15 |
class GamePhase(Enum):
|
16 |
OPENING = "opening"
|
@@ -147,6 +153,7 @@ 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)
|
@@ -157,7 +164,8 @@ def analyze_pgn(pgn_file: str) -> Dict:
|
|
157 |
result = {
|
158 |
"move_analysis": [],
|
159 |
"phase_analysis": {},
|
160 |
-
"player_summaries": {}
|
|
|
161 |
}
|
162 |
|
163 |
with chess.engine.SimpleEngine.popen_uci(engine_path) as engine:
|
@@ -171,18 +179,30 @@ def analyze_pgn(pgn_file: str) -> Dict:
|
|
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(
|
|
|
175 |
pre_eval = pre_info["score"].white().score(mate_score=10000) or 0
|
176 |
-
|
|
|
177 |
|
178 |
-
#
|
|
|
|
|
|
|
|
|
179 |
move = node.move
|
180 |
-
board.push(move)
|
181 |
-
|
182 |
# Analyze position after the move
|
183 |
-
post_info = engine.analyse(board,
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
@@ -191,7 +211,7 @@ def analyze_pgn(pgn_file: str) -> Dict:
|
|
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:
|
@@ -205,7 +225,7 @@ def analyze_pgn(pgn_file: str) -> Dict:
|
|
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 !=
|
209 |
classification = Classification.MISS
|
210 |
|
211 |
# Check for brilliant moves
|
@@ -220,14 +240,18 @@ def analyze_pgn(pgn_file: str) -> Dict:
|
|
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 |
-
"
|
228 |
"evaluation": post_eval / 100,
|
229 |
"evaluation_loss": eval_loss / 100,
|
230 |
-
"classification": classification.value
|
|
|
|
|
|
|
|
|
231 |
})
|
232 |
|
233 |
# Phase analysis
|
@@ -248,7 +272,8 @@ def analyze_pgn(pgn_file: str) -> Dict:
|
|
248 |
for phase in GamePhase:
|
249 |
phase_moves = classifications[color][phase]
|
250 |
for m in phase_moves:
|
251 |
-
|
|
|
252 |
|
253 |
result["player_summaries"][player] = counts
|
254 |
|
@@ -267,8 +292,11 @@ def analyze_pgn(pgn_file: str) -> Dict:
|
|
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)
|
|
|
9 |
import csv
|
10 |
import json
|
11 |
from fastapi.responses import JSONResponse
|
12 |
+
import asyncio
|
13 |
+
import sys
|
14 |
+
from routes.tex_based_review import review_chess_game, validate_json
|
15 |
|
16 |
+
|
17 |
+
|
18 |
+
if sys.platform == "win32":
|
19 |
+
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
20 |
|
21 |
class GamePhase(Enum):
|
22 |
OPENING = "opening"
|
|
|
153 |
|
154 |
def analyze_pgn(pgn_file: str) -> Dict:
|
155 |
opening_book = load_opening_book(book_csv_path)
|
156 |
+
text_based_result = review_chess_game(pgn_file)
|
157 |
|
158 |
with open(pgn_file) as pgn:
|
159 |
game = chess.pgn.read_game(pgn)
|
|
|
164 |
result = {
|
165 |
"move_analysis": [],
|
166 |
"phase_analysis": {},
|
167 |
+
"player_summaries": {},
|
168 |
+
"test_based_review": text_based_result
|
169 |
}
|
170 |
|
171 |
with chess.engine.SimpleEngine.popen_uci(engine_path) as engine:
|
|
|
179 |
|
180 |
for move_number, node in enumerate(game.mainline(), start=1):
|
181 |
# Analyze position before the move
|
182 |
+
pre_info = engine.analyse(board, chess.engine.Limit(time=0.3), multipv=3)[0]
|
183 |
+
|
184 |
pre_eval = pre_info["score"].white().score(mate_score=10000) or 0
|
185 |
+
|
186 |
+
pre_pv_moves = pre_info.get("pv", [])
|
187 |
|
188 |
+
# Get best move and follow-up moves in UCI notation
|
189 |
+
best_move_pre = pre_pv_moves[0].uci() if pre_pv_moves else None
|
190 |
+
follow_up_pre = [m.uci() for m in pre_pv_moves[:min(len(pre_pv_moves), 5)]]
|
191 |
+
|
192 |
+
# Make the user move
|
193 |
move = node.move
|
194 |
+
board.push(move) # Update the board state
|
195 |
+
|
196 |
# Analyze position after the move
|
197 |
+
post_info = engine.analyse(board, chess.engine.Limit(time=0.3), multipv=3)[0]
|
198 |
+
|
199 |
+
# Get best move and follow-up moves AFTER move is played (in UCI notation)
|
200 |
+
post_pv_moves = post_info.get("pv", [])
|
201 |
+
best_move_post = post_pv_moves[0].uci() if post_pv_moves else None
|
202 |
+
follow_up_post = [m.uci() for m in post_pv_moves[:min(len(post_pv_moves), 5)]]
|
203 |
+
|
204 |
post_eval = post_info["score"].white().score(mate_score=10000) or 0
|
205 |
+
|
206 |
# Determine game phase
|
207 |
book_move = is_book_move(board, opening_book)
|
208 |
current_phase = detect_game_phase(board, in_opening)
|
|
|
211 |
|
212 |
# Calculate evaluation loss
|
213 |
eval_loss = abs(pre_eval - post_eval)
|
214 |
+
|
215 |
# Initial classification
|
216 |
classification = Classification.BOOK if book_move else None
|
217 |
if not classification:
|
|
|
225 |
# Check for missed opportunities
|
226 |
is_winning = abs(pre_eval) >= FORCED_WIN_THRESHOLD
|
227 |
is_forced_win = pre_info["score"].is_mate() and pre_info["score"].relative.mate() <= MISS_MATE_THRESHOLD
|
228 |
+
if is_winning and move != best_move_pre and (eval_loss >= MISS_CENTIPAWN_LOSS or is_forced_win):
|
229 |
classification = Classification.MISS
|
230 |
|
231 |
# Check for brilliant moves
|
|
|
240 |
classifications[player][current_phase].append(classification)
|
241 |
phase_data[current_phase].append(classification)
|
242 |
|
243 |
+
# Add move analysis to result (using UCI notation)
|
244 |
result["move_analysis"].append({
|
245 |
"move_number": move_number,
|
246 |
"player": "White" if board.turn == chess.BLACK else "Black",
|
247 |
+
"user_move": move.uci(),
|
248 |
"evaluation": post_eval / 100,
|
249 |
"evaluation_loss": eval_loss / 100,
|
250 |
+
"classification": classification.value,
|
251 |
+
"best_move_pre": best_move_pre, # Best move BEFORE move is played (UCI)
|
252 |
+
"follow_up_pre": follow_up_pre, # Follow-up moves BEFORE move is played (UCI)
|
253 |
+
"best_move_post": best_move_post, # Best move AFTER move is played (UCI)
|
254 |
+
"follow_up_post": follow_up_post # Follow-up moves AFTER move is played (UCI)
|
255 |
})
|
256 |
|
257 |
# Phase analysis
|
|
|
272 |
for phase in GamePhase:
|
273 |
phase_moves = classifications[color][phase]
|
274 |
for m in phase_moves:
|
275 |
+
m_enum = Classification(m) if isinstance(m, str) else m # Convert if needed
|
276 |
+
counts[m_enum.value] += 1
|
277 |
|
278 |
result["player_summaries"][player] = counts
|
279 |
|
|
|
292 |
|
293 |
|
294 |
def get_phase_rating(classified_moves: List[Classification]) -> Classification:
|
295 |
+
|
296 |
if not classified_moves:
|
297 |
return Classification.GOOD
|
298 |
+
|
299 |
+
classified_moves = [Classification(m) if isinstance(m, str) else m for m in classified_moves]
|
300 |
|
301 |
total = sum(classification_values[m] for m in classified_moves)
|
302 |
average = total / len(classified_moves)
|
routes/tex_based_review.py
ADDED
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
from dotenv import load_dotenv
|
4 |
+
from groq import Groq
|
5 |
+
import re
|
6 |
+
|
7 |
+
def review_chess_game(pgn_file_path):
|
8 |
+
# Load environment variables and initialize Groq client
|
9 |
+
load_dotenv()
|
10 |
+
API_KEY = "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"
|
11 |
+
if not API_KEY:
|
12 |
+
raise ValueError("API key not found. Please set GROQ_API_KEY in the .env file.")
|
13 |
+
client = Groq(api_key=API_KEY)
|
14 |
+
|
15 |
+
# JSON structured prompt
|
16 |
+
template = (
|
17 |
+
"You are tasked with reviewing a chess game in PGN format: {pgn_content}. "
|
18 |
+
"Please provide the analysis in JSON format with the following structure:\n"
|
19 |
+
"{{\n"
|
20 |
+
' "summary": "Brief game summary",\n'
|
21 |
+
' "move_reviews": [\n'
|
22 |
+
' {{"move": "e4", "evaluation": "Good", "commentary": "Solid central control"}},\n'
|
23 |
+
' {{"move": "d5", "evaluation": "Brilliant", "commentary": "Aggressive center contest"}}\n'
|
24 |
+
' ],\n'
|
25 |
+
' "biggest_blunders": {{\n'
|
26 |
+
' "player1": "Qxb7",\n'
|
27 |
+
' "player2": "None"\n'
|
28 |
+
' }},\n'
|
29 |
+
' "recommendations": {{\n'
|
30 |
+
' "player1": "Focus on central control",\n'
|
31 |
+
' "player2": "Continue aggressive play"\n'
|
32 |
+
' }}\n'
|
33 |
+
"}}\n"
|
34 |
+
"Make sure the JSON is well-formatted and does not contain any invalid content."
|
35 |
+
)
|
36 |
+
|
37 |
+
# Read PGN content
|
38 |
+
try:
|
39 |
+
with open(pgn_file_path, 'r') as file:
|
40 |
+
pgn_content = file.read()
|
41 |
+
except FileNotFoundError:
|
42 |
+
return {"error": f"File '{pgn_file_path}' not found."}
|
43 |
+
|
44 |
+
# Format the prompt
|
45 |
+
prompt = template.format(pgn_content=pgn_content)
|
46 |
+
|
47 |
+
# Interact with Groq API
|
48 |
+
try:
|
49 |
+
completion = client.chat.completions.create(
|
50 |
+
model="llama-3.3-70b-versatile",
|
51 |
+
messages=[{"role": "user", "content": prompt}],
|
52 |
+
temperature=1,
|
53 |
+
max_tokens=4096,
|
54 |
+
top_p=1,
|
55 |
+
stream=True,
|
56 |
+
stop=None,
|
57 |
+
)
|
58 |
+
|
59 |
+
response_text = ""
|
60 |
+
for message in completion:
|
61 |
+
if message.choices and message.choices[0].delta.content:
|
62 |
+
response_text += message.choices[0].delta.content
|
63 |
+
response_text = response_text.strip()
|
64 |
+
|
65 |
+
# Remove unnecessary text before JSON starts
|
66 |
+
response_text = re.sub(r"(?s)^.*?\{", "{", response_text).strip()
|
67 |
+
response_text = re.sub(r"```json|```", "", response_text).strip()
|
68 |
+
|
69 |
+
|
70 |
+
# Ensure the response is valid JSON
|
71 |
+
try:
|
72 |
+
structured_data = json.loads(response_text)
|
73 |
+
return structured_data
|
74 |
+
except json.JSONDecodeError as e:
|
75 |
+
return {"error": f"Failed to parse JSON: {str(e)}", "raw_response": response_text}
|
76 |
+
|
77 |
+
|
78 |
+
except Exception as e:
|
79 |
+
return {"error": f"Error processing PGN: {str(e)}"}
|
80 |
+
|
81 |
+
def validate_json(review):
|
82 |
+
"""Check if the input is a valid JSON string or dictionary."""
|
83 |
+
if isinstance(review, dict):
|
84 |
+
print("Valid JSON (dictionary)")
|
85 |
+
return True # Already a valid Python dictionary
|
86 |
+
|
87 |
+
try:
|
88 |
+
json.loads(review) # Attempt to parse JSON string
|
89 |
+
print("Valid JSON (string)")
|
90 |
+
return True
|
91 |
+
except json.JSONDecodeError:
|
92 |
+
print("Invalid JSON response")
|
93 |
+
return False # Return False if JSON is invalid
|
94 |
+
|