6-module Python app · Claude Haiku · Streamlit · Multi-agent player evaluation
MODEL = "claude-haiku-4-5-20251001" # Scoring maximums MAX_INDIVIDUAL = 35 MAX_CHEMISTRY = 50 MAX_UNIVERSAL = 15 MAX_TOTAL = 100 # Session weights (must sum to 1.0) SESSION_WEIGHTS = { "Monday": 0.15, "Wednesday": 0.25, "Thursday": 0.25, "Friday": 0.35, } # Session focus: weights for (individual, chemistry, universal) scoring SESSION_FOCUS = { "Monday": {"individual": 0.30, "chemistry": 0.10, "universal": 0.60}, "Wednesday": {"individual": 0.70, "chemistry": 0.10, "universal": 0.20}, "Thursday": {"individual": 0.10, "chemistry": 0.70, "universal": 0.20}, "Friday": {"individual": 0.333, "chemistry": 0.333, "universal": 0.334}, } FORMATIONS = { "4-3-3": {"GK": 1, "DEF": 4, "MID": 3, "FWD": 3}, "4-4-2": {"GK": 1, "DEF": 4, "MID": 4, "FWD": 2}, "3-5-2": {"GK": 1, "DEF": 3, "MID": 5, "FWD": 2}, } THRESHOLD_LINEUP = 65 THRESHOLD_BENCH = 50 THRESHOLD_UNUSED = 40
import os, json, random import anthropic from config import MODEL, MAX_INDIVIDUAL, MAX_CHEMISTRY, MAX_UNIVERSAL, SESSION_FOCUS client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY")) POSITION_PERSONAS = { "GK": "You are a goalkeeping coach. Evaluate goalkeeper performance on shot-stopping, " "command of area, distribution, communication, and organising the defensive line.", "DEF": "You are a defensive coach. Evaluate defenders on positioning, tackling, " "aerial duels, pressing discipline, and how well they hold their shape.", "MID": "You are a midfield coach. Evaluate midfielders on passing range, pressing, " "defensive cover, attacking transitions, and combination play with teammates.", "FWD": "You are a forward coach. Evaluate forwards on finishing, movement, " "pressing triggers, hold-up play, and runs in behind the defensive line.", } def evaluate_player(player, session, session_desc): focus = SESSION_FOCUS[session] prompt = f"""Evaluate {player['name']}, a {player['position']} (age {player['age']}). Style: {player['style']} Strength: {player['strength']} Weakness: {player['weakness']} Session: {session} — {session_desc} Focus: Individual {int(focus['individual']*100)}%, Chemistry {int(focus['chemistry']*100)}%, Universal {int(focus['universal']*100)}% Scoring: - individual_score: 0-{MAX_INDIVIDUAL} (position-specific technical performance) - chemistry_score: 0-{MAX_CHEMISTRY} (selflessness, support runs, pressing coordination) - universal_score: 0-{MAX_UNIVERSAL} (fitness, attitude, discipline) Respond ONLY with valid JSON: {{"individual_score": int, "chemistry_score": int, "universal_score": int, "highlight": "one sentence", "concern": "one sentence or No concerns today"}}""" response = client.messages.create( model=MODEL, max_tokens=300, system=POSITION_PERSONAS[player["position"]], messages=[{"role": "user", "content": prompt}], ) result = json.loads(response.content[0].text.strip()) result["individual_score"] = max(0, min(MAX_INDIVIDUAL, int(result["individual_score"]))) result["chemistry_score"] = max(0, min(MAX_CHEMISTRY, int(result["chemistry_score"]))) result["universal_score"] = max(0, min(MAX_UNIVERSAL, int(result["universal_score"]))) result["total_score"] = ( result["individual_score"] + result["chemistry_score"] + result["universal_score"] ) return result
from config import SESSION_WEIGHTS, SESSION_DESCRIPTIONS, FORM_THIS_WEEK, FORM_LAST_WEEK from coach import evaluate_player SESSIONS_ORDER = ["Monday", "Wednesday", "Thursday", "Friday"] def run_week(players, last_week_scores=None): results = {} for player in players: pid = player["id"] session_results = {} for session in SESSIONS_ORDER: session_results[session] = evaluate_player( player, session, SESSION_DESCRIPTIONS[session] ) # Weighted weekly score weekly_score = sum( session_results[s]["total_score"] * SESSION_WEIGHTS[s] for s in SESSIONS_ORDER ) # Rolling form: this week 60% + last week 40% if last_week_scores and pid in last_week_scores: form_score = weekly_score * FORM_THIS_WEEK + last_week_scores[pid] * FORM_LAST_WEEK else: form_score = weekly_score results[pid] = { "name": player["name"], "position": player["position"], "sessions": session_results, "weekly_score": round(weekly_score, 1), "form_score": round(form_score, 1), "highlight": session_results["Friday"]["highlight"], "concern": session_results["Friday"]["concern"], } return results
from config import FORMATIONS, THRESHOLD_LINEUP, THRESHOLD_BENCH def score_formation_fit(week_results, formation): """Sum of top-N form scores per position slot — higher = better natural fit.""" quotas = FORMATIONS[formation] by_pos = {"GK": [], "DEF": [], "MID": [], "FWD": []} for d in week_results.values(): by_pos[d["position"]].append(d["form_score"]) for pos in by_pos: by_pos[pos].sort(reverse=True) return round(sum(sum(by_pos[p][:n]) for p, n in quotas.items()), 1) def recommend_formation(week_results): fit_scores = {f: score_formation_fit(week_results, f) for f in FORMATIONS} best = max(fit_scores, key=fit_scores.get) return best, fit_scores def select_squad(week_results, formation, transfer_risk_ids): quotas = FORMATIONS[formation] by_pos = {pos: [] for pos in quotas} for pid, d in week_results.items(): by_pos[d["position"]].append({**d, "id": pid}) for pos in by_pos: by_pos[pos].sort(key=lambda x: x["form_score"], reverse=True) lineup, remaining = [], {pos: [] for pos in quotas} for pos, count in quotas.items(): eligible = [p for p in by_pos[pos] if p["form_score"] >= THRESHOLD_LINEUP] selected = (eligible + [p for p in by_pos[pos] if p not in eligible])[:count] lineup.extend(selected) remaining[pos] = [p for p in by_pos[pos] if p not in selected] # Bench: 1 GK + best 4 outfield bench = [] if remaining["GK"]: bench.append(remaining["GK"].pop(0)) outfield = sorted( [p for pos in ["DEF","MID","FWD"] for p in remaining[pos]], key=lambda x: x["form_score"], reverse=True ) bench.extend(outfield[:5 - len(bench)]) used_ids = {p["id"] for p in lineup + bench} all_ids = set(week_results.keys()) unused_ids = all_ids - used_ids unused = sorted( [{**week_results[pid], "id": pid} for pid in unused_ids], key=lambda x: x["form_score"], reverse=True ) transfer_shortlist = [p for p in unused if p["id"] in transfer_risk_ids] return {"formation": formation, "quotas": quotas, "lineup": lineup, "bench": bench, "unused": unused, "transfer_shortlist": transfer_shortlist}