Spaces:
Sleeping
Sleeping
| """ | |
| Comprehensive API testing with known positions. | |
| Tests tactical-vision and game-review endpoints. | |
| """ | |
| import requests | |
| import json | |
| import time | |
| BASE_URL = "https://anandu467-maia3-chess-api.hf.space" | |
| API_KEY = "sk-maia3-2026" | |
| HEADERS = { | |
| "Content-Type": "application/json", | |
| "x-api-key": API_KEY | |
| } | |
| def test_position(name, fen, player_color, expected_checks=None): | |
| """Test a position and verify expectations""" | |
| print(f"\n{'='*70}") | |
| print(f"TEST: {name}") | |
| print(f"{'='*70}") | |
| print(f"FEN: {fen}") | |
| print(f"Player: {player_color}") | |
| payload = { | |
| "fen": fen, | |
| "elo_self": 1500, | |
| "elo_oppo": 1500, | |
| "top_n": 5, | |
| "player_color": player_color, | |
| "compute_value_delta": True | |
| } | |
| t0 = time.time() | |
| r = requests.post(f"{BASE_URL}/api/tactical-vision", json=payload, headers=HEADERS, timeout=120) | |
| elapsed = (time.time() - t0) * 1000 | |
| print(f"Status: {r.status_code} ({elapsed:.0f}ms)") | |
| if r.status_code != 200: | |
| print(f"ERROR: {r.text[:200]}") | |
| return False | |
| data = r.json() | |
| # Display key information | |
| threats = data.get("threats", {}) | |
| alerts = data.get("tactical_alerts", []) | |
| top_moves = data.get("top_moves", []) | |
| position = data.get("position", {}) | |
| print(f"\nPosition Analysis:") | |
| print(f" Win probability: {position.get('win_probability', 0):.1%}") | |
| print(f" Eval: {position.get('eval_cp', 0)}cp") | |
| print(f" Confidence: {position.get('confidence', 'unknown')}") | |
| print(f" Danger level: {position.get('danger_level', 'unknown')}") | |
| print(f"\nTop 3 moves:") | |
| for i, move in enumerate(top_moves[:3], 1): | |
| tags = f" [{', '.join(move.get('tactical_tags', []))}]" if move.get('tactical_tags') else "" | |
| delta = f" (delta: {move.get('eval_delta_cp', 0):+d}cp)" if move.get('eval_delta_cp') is not None else "" | |
| print(f" {i}. {move['san']} - {move['probability']:.1%} - {move['classification']}{tags}{delta}") | |
| print(f"\nThreats:") | |
| print(f" Hanging pieces: {threats.get('hanging_pieces', [])}") | |
| print(f" Attacked pieces: {threats.get('attacked_pieces', [])}") | |
| print(f" Underdefended: {threats.get('underdefended_squares', [])}") | |
| print(f" King safety: {threats.get('king_safety_concern', False)}") | |
| print(f"\nTactical alerts: {len(alerts)}") | |
| for alert in alerts[:5]: # Show first 5 | |
| print(f" - {alert['type']}: {alert['description']}") | |
| if len(alerts) > 5: | |
| print(f" ... and {len(alerts) - 5} more") | |
| # Verify expectations | |
| if expected_checks: | |
| print(f"\nVerification:") | |
| all_pass = True | |
| for check_name, check_func in expected_checks.items(): | |
| try: | |
| result = check_func(data) | |
| status = "PASS" if result else "FAIL" | |
| print(f" {check_name}: {status}") | |
| if not result: | |
| all_pass = False | |
| except Exception as e: | |
| print(f" {check_name}: ERROR - {e}") | |
| all_pass = False | |
| return all_pass | |
| return True | |
| # Test 1: Starting position | |
| test_position( | |
| "Starting Position", | |
| "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", | |
| "white", | |
| { | |
| "No hanging pieces": lambda d: len(d.get("threats", {}).get("hanging_pieces", [])) == 0, | |
| "No tactical alerts": lambda d: len(d.get("tactical_alerts", [])) == 0, | |
| "Has top moves": lambda d: len(d.get("top_moves", [])) >= 3, | |
| "First move is e4 or d4": lambda d: d["top_moves"][0]["san"] in ["e4", "d4", "Nf3", "c4"] | |
| } | |
| ) | |
| # Test 2: Scholar's mate setup (Qh5 threatening Qxf7#) | |
| test_position( | |
| "Scholar's Mate Setup (White to play Qxf7#)", | |
| "r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/5Q2/PPPP1PPP/RNB1K1NR w KQkq - 4 4", | |
| "white", | |
| { | |
| "Has checkmate move": lambda d: any(m.get("is_mate") for m in d.get("top_moves", [])), | |
| "Qxf7 is top move": lambda d: d["top_moves"][0]["uci"] == "f3f7", | |
| "High win probability": lambda d: d.get("position", {}).get("win_probability", 0) > 0.9 | |
| } | |
| ) | |
| # Test 3: Hanging piece (knight on e5 attacked by d6 pawn, no defenders) | |
| test_position( | |
| "Hanging Knight on e5", | |
| "rnbqkbnr/pppp1ppp/3p4/4N3/8/8/PPPPPPPP/RNBQKB1R w KQkq - 0 1", | |
| "black", | |
| { | |
| "Detects hanging piece": lambda d: "e5" in d.get("threats", {}).get("hanging_pieces", []), | |
| "Has hanging_piece alert": lambda d: any(a["type"] == "hanging_piece" and a["square"] == "e5" for a in d.get("tactical_alerts", [])) | |
| } | |
| ) | |
| # Test 4: Fork opportunity (knight can fork king and rook) | |
| test_position( | |
| "Knight Fork Opportunity (Nc7+ forking king and rook)", | |
| "r3k2r/ppp2ppp/2n1b3/3q4/3P4/2N1B3/PPP2PPP/R2QK2R w KQkq - 0 1", | |
| "white", | |
| { | |
| "Has tactical tags": lambda d: any(m.get("tactical_tags") for m in d.get("top_moves", [])), | |
| "Fork detected": lambda d: any("fork" in m.get("tactical_tags", []) for m in d.get("top_moves", [])) | |
| } | |
| ) | |
| # Test 5: Pin (bishop pins knight to king) | |
| test_position( | |
| "Absolute Pin (Bb5 pins Nc6 to king)", | |
| "r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 3 3", | |
| "black", | |
| { | |
| "Has top moves": lambda d: len(d.get("top_moves", [])) >= 3, | |
| "Reasonable eval": lambda d: abs(d.get("position", {}).get("eval_cp", 0)) < 200 | |
| } | |
| ) | |
| # Test 6: Back rank mate threat | |
| test_position( | |
| "Back Rank Mate Threat", | |
| "6k1/5ppp/8/8/8/8/5PPP/R5K1 w - - 0 1", | |
| "white", | |
| { | |
| "Has top moves": lambda d: len(d.get("top_moves", [])) >= 3, | |
| "Ra8 might be suggested": lambda d: any(m["uci"] == "a1a8" for m in d.get("top_moves", [])) | |
| } | |
| ) | |
| # Test 7: Endgame position (king and pawn vs king) | |
| test_position( | |
| "King and Pawn Endgame", | |
| "8/8/8/8/8/5k2/5P2/6K1 w - - 0 1", | |
| "white", | |
| { | |
| "Has top moves": lambda d: len(d.get("top_moves", [])) >= 1, | |
| "Pawn push is good": lambda d: any(m["uci"].startswith("f2") for m in d.get("top_moves", [])) | |
| } | |
| ) | |
| # Test 8: Position with multiple tactical themes | |
| test_position( | |
| "Complex Tactical Position", | |
| "r1bq1rk1/pp2ppbp/2np1np1/8/3NP3/2N1B3/PPP1BPPP/R2Q1RK1 w - - 0 9", | |
| "white", | |
| { | |
| "Has top moves": lambda d: len(d.get("top_moves", [])) >= 3, | |
| "Reasonable confidence": lambda d: d.get("position", {}).get("confidence") in ["clear", "complex", "confusing"] | |
| } | |
| ) | |
| # Test 9: Game review with known game (Immortal Game excerpt) | |
| print(f"\n{'='*70}") | |
| print("TEST: Game Review (Short Tactical Game)") | |
| print(f"{'='*70}") | |
| moves = ["e2e4", "e7e5", "f2f4", "e5f4", "f1c4", "d8h4", "e1f1", "b7b5"] | |
| payload = { | |
| "moves": moves, | |
| "elo_white": 1800, | |
| "elo_black": 1800 | |
| } | |
| t0 = time.time() | |
| r = requests.post(f"{BASE_URL}/api/game-review", json=payload, headers=HEADERS, timeout=300) | |
| elapsed = (time.time() - t0) * 1000 | |
| print(f"Status: {r.status_code} ({elapsed:.0f}ms)") | |
| if r.status_code == 200: | |
| data = r.json() | |
| summary = data.get("summary", {}) | |
| print(f"\nGame Summary:") | |
| print(f" Consistency: {summary.get('consistency_score', 0):.2f}") | |
| print(f" Label: {summary.get('consistency_label', 'unknown')}") | |
| print(f" Blunders: {summary.get('blunder_count', 0)}") | |
| print(f" Mistakes: {summary.get('mistake_count', 0)}") | |
| print(f" Good moves: {summary.get('good_count', 0)}") | |
| print(f" Excellent moves: {summary.get('excellent_count', 0)}") | |
| print(f"\nPhase Breakdown:") | |
| for phase in ["opening", "middlegame", "endgame"]: | |
| stats = data.get("phase_breakdown", {}).get(phase, {}) | |
| if stats.get("moves", 0) > 0: | |
| print(f" {phase}: {stats['moves']} moves, consistency {stats['consistency_score']:.2f}") | |
| print(f"\nMove Analysis (first 4):") | |
| for move in data.get("moves", [])[:4]: | |
| print(f" {move['move_number']}. {move['side']} {move['san']} - {move['classification']} ({move['probability']:.1%})") | |
| print(f"\nRecommendations:") | |
| for rec in data.get("recommendations", [])[:3]: | |
| print(f" - {rec}") | |
| else: | |
| print(f"ERROR: {r.text[:200]}") | |
| print(f"\n{'='*70}") | |
| print("All tests completed!") | |
| print(f"{'='*70}") | |