Spaces:
Sleeping
Sleeping
| from flask import Flask, request, jsonify | |
| from flask_cors import CORS | |
| import numpy as np | |
| import math | |
| import pickle | |
| import os | |
| app = Flask(__name__) | |
| CORS(app) | |
| # Singleton Pattern for Model Loading | |
| class ModelService: | |
| _model = None | |
| def get_model(cls): | |
| if cls._model is None: | |
| print("Loading Production Shot Quality Model (139MB)...") | |
| # Path for HF Space/Docker environment | |
| model_path = os.path.join(os.path.dirname(__file__), 'shot_quality_model.pickle') | |
| if not os.path.exists(model_path): | |
| # Fallback for local testing if path differs | |
| model_path = 'shot_quality_model.pickle' | |
| with open(model_path, 'rb') as f: | |
| cls._model = pickle.load(f) | |
| return cls._model | |
| def calculate_features(x, y, shot_type): | |
| # 1. Shot Distance (Euclidean to 100, 50) | |
| dist = np.sqrt((100 - x) ** 2 + (50 - y) ** 2) | |
| # 2. Shot Angle (Law of Cosines with posts at 100,46 and 100,54) | |
| x2, y2 = 100, 46 # Left Post | |
| x3, y3 = 100, 54 # Right Post | |
| a = np.sqrt((x - x2)**2 + (y - y2)**2) | |
| b = np.sqrt((x - x3)**2 + (y - y3)**2) | |
| c = 8 # Distance between posts | |
| denominator = 2 * a * b | |
| if denominator == 0: | |
| angle_deg = 0.0 | |
| else: | |
| cos_angle = (a**2 + b**2 - c**2) / denominator | |
| cos_angle = np.clip(cos_angle, -1.0, 1.0) | |
| angle_rad = np.arccos(cos_angle) | |
| angle_deg = (angle_rad * 180) / np.pi | |
| # 3. Strong Foot Mapping (0: Weak, 1: Headers/Other, 2: Strong) | |
| mapping = { | |
| 'RightFoot': 2, | |
| 'LeftFoot': 0, | |
| 'Head': 1, | |
| 'OtherBodyPart': 1 | |
| } | |
| strong_foot = mapping.get(shot_type, 2) | |
| return dist, angle_deg, strong_foot | |
| def predict(): | |
| try: | |
| data = request.get_json() | |
| x = float(data.get('x')) | |
| y = float(data.get('y')) | |
| # Handle both shot_type and shotType | |
| shot_type = data.get('shot_type') or data.get('shotType') or 'RightFoot' | |
| dist, angle, strong_foot = calculate_features(x, y, shot_type) | |
| features = np.array([[dist, angle, strong_foot]]) | |
| model = ModelService.get_model() | |
| probability = model.predict_proba(features)[0][1] | |
| return jsonify({ | |
| 'shot_quality': float(probability), | |
| 'goal_prediction': 1 if probability > 0.5 else 0, | |
| 'features': { | |
| 'distance': float(dist), | |
| 'angle': float(angle), | |
| 'strong_foot': int(strong_foot) | |
| } | |
| }) | |
| except Exception as e: | |
| import traceback | |
| print(traceback.format_exc()) | |
| return jsonify({'error': str(e)}), 400 | |
| def health(): | |
| return jsonify({'status': 'healthy', 'model_loaded': ModelService._model is not None}) | |
| def home(): | |
| return jsonify({ | |
| 'name': 'Shot Quality xG API', | |
| 'status': 'online', | |
| 'endpoints': ['/predict', '/health'] | |
| }) | |
| if __name__ == '__main__': | |
| port = int(os.environ.get('PORT', 8080)) | |
| # Pre-load model | |
| ModelService.get_model() | |
| app.run(host='0.0.0.0', port=port) | |