""" Viperball Stats Export Exports season and dynasty statistics to CSV files for external analysis. Available exports: export_season_standings_csv(season, filepath) - One row per team: wins, losses, points, all sabermetric averages export_season_game_log_csv(season, filepath) - One row per completed game: teams, scores, both teams' metrics export_dynasty_standings_csv(dynasty, filepath) - One row per (year, team): all-time totals and per-season record export_dynasty_awards_csv(dynasty, filepath) - One row per year: all team and individual award winners export_injury_history_csv(dynasty, filepath) - One row per injury across all recorded seasons export_development_history_csv(dynasty, filepath) - One row per notable development event across all offseasons export_all_american_csv(dynasty, filepath) - One row per All-American selection across all seasons Usage: from engine.export import export_season_standings_csv export_season_standings_csv(season, "output/season_2026_standings.csv") """ from __future__ import annotations import csv import os from typing import TYPE_CHECKING if TYPE_CHECKING: from engine.season import Season from engine.dynasty import Dynasty def _ensure_dir(filepath: str): """Create parent directory if it doesn't exist.""" parent = os.path.dirname(filepath) if parent: os.makedirs(parent, exist_ok=True) # ────────────────────────────────────────────── # SEASON EXPORTS # ────────────────────────────────────────────── def export_season_standings_csv(season: "Season", filepath: str) -> str: """ Export end-of-season standings to CSV. Columns: team, conference, wins, losses, games_played, win_pct, points_for, points_against, point_differential, conf_wins, conf_losses, conf_win_pct, team_rating, ppd, conversion_pct, lateral_pct, kick_rating, to_margin, avg_start, offense_style, defense_style """ _ensure_dir(filepath) fieldnames = [ "team", "conference", "wins", "losses", "games_played", "win_pct", "points_for", "points_against", "point_differential", "conf_wins", "conf_losses", "conf_win_pct", "team_rating", "ppd", "conversion_pct", "lateral_pct", "kick_rating", "to_margin", "avg_start", "offense_style", "defense_style", ] rows = [] for team_name, record in season.standings.items(): rows.append({ "team": team_name, "conference": record.conference, "wins": record.wins, "losses": record.losses, "games_played": record.games_played, "win_pct": round(record.win_percentage, 4), "points_for": round(record.points_for, 1), "points_against": round(record.points_against, 1), "point_differential": round(record.point_differential, 2), "conf_wins": record.conf_wins, "conf_losses": record.conf_losses, "conf_win_pct": round(record.conf_win_percentage, 4), "team_rating": round(record.avg_team_rating, 2), "ppd": round(record.avg_ppd, 2), "conversion_pct": round(record.avg_conversion_pct, 2), "lateral_pct": round(record.avg_lateral_pct, 2), "kick_rating": round(record.avg_kicking, 2), "to_margin": round(record.avg_to_margin, 2), "avg_start": round(record.avg_start_position, 2), "offense_style": record.offense_style, "defense_style": record.defense_style, }) # Sort by win_pct descending rows.sort(key=lambda r: r["win_pct"], reverse=True) with open(filepath, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) return filepath def export_season_game_log_csv(season: "Season", filepath: str) -> str: """ Export every completed game in the season to CSV. Columns: week, home_team, away_team, home_score, away_score, winner, is_conference_game, home_team_rating, home_lateral_pct, home_conv_pct, home_kick_rating, away_team_rating, away_lateral_pct, away_conv_pct, away_kick_rating """ _ensure_dir(filepath) fieldnames = [ "week", "home_team", "away_team", "home_score", "away_score", "winner", "margin", "is_conference_game", "home_team_rating", "home_lateral_pct", "home_avg_start", "home_conv_pct", "home_kick_rating", "away_team_rating", "away_lateral_pct", "away_avg_start", "away_conv_pct", "away_kick_rating", ] rows = [] for game in season.schedule: if not game.completed: continue hs = game.home_score or 0 as_ = game.away_score or 0 winner = game.home_team if hs > as_ else game.away_team hm = game.home_metrics or {} am = game.away_metrics or {} rows.append({ "week": game.week, "home_team": game.home_team, "away_team": game.away_team, "home_score": round(hs, 1), "away_score": round(as_, 1), "winner": winner, "margin": round(abs(hs - as_), 1), "is_conference_game": int(game.is_conference_game), "home_team_rating": round(hm.get("team_rating", hm.get("opi", 0)), 2), "home_lateral_pct": round(hm.get("lateral_pct", hm.get("chaos_factor", 0)), 2), "home_avg_start": round(hm.get("avg_start", hm.get("territory_rating", 0)), 2), "home_conv_pct": round(hm.get("conversion_pct", hm.get("pressure_index", 0)), 2), "home_kick_rating": round(hm.get("kicking_efficiency", 0), 2), "away_team_rating": round(am.get("team_rating", am.get("opi", 0)), 2), "away_lateral_pct": round(am.get("lateral_pct", am.get("chaos_factor", 0)), 2), "away_avg_start": round(am.get("avg_start", am.get("territory_rating", 0)), 2), "away_conv_pct": round(am.get("conversion_pct", am.get("pressure_index", 0)), 2), "away_kick_rating": round(am.get("kicking_efficiency", 0), 2), }) rows.sort(key=lambda r: r["week"]) with open(filepath, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) return filepath # ────────────────────────────────────────────── # DYNASTY EXPORTS # ────────────────────────────────────────────── def export_dynasty_standings_csv(dynasty: "Dynasty", filepath: str) -> str: """ Export full dynasty season-by-season standings to CSV. Columns: year, team, conference, wins, losses, win_pct, points_for, points_against, team_rating, champion, playoff """ _ensure_dir(filepath) fieldnames = [ "year", "team", "conference", "wins", "losses", "win_pct", "points_for", "points_against", "team_rating", "champion", "playoff", ] rows = [] for year in sorted(dynasty.seasons.keys()): season = dynasty.seasons[year] for team_name, record in season.standings.items(): history = dynasty.team_histories.get(team_name) yr_record = history.season_records.get(year, {}) if history else {} rows.append({ "year": year, "team": team_name, "conference": record.conference, "wins": record.wins, "losses": record.losses, "win_pct": round(record.win_percentage, 4), "points_for": round(record.points_for, 1), "points_against": round(record.points_against, 1), "team_rating": round(record.avg_team_rating, 2), "champion": int(yr_record.get("champion", False)), "playoff": int(yr_record.get("playoff", False)), }) rows.sort(key=lambda r: (r["year"], -r["win_pct"])) with open(filepath, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) return filepath def export_dynasty_awards_csv(dynasty: "Dynasty", filepath: str) -> str: """ Export all team and individual award winners per year to CSV. Columns: year, award_type, award_name, winner, team, position, overall_rating """ _ensure_dir(filepath) fieldnames = [ "year", "award_type", "award_name", "winner", "team", "position", "overall_rating" ] rows = [] for year in sorted(dynasty.awards_history.keys()): awards = dynasty.awards_history[year] # Team-level awards team_awards = [ ("team", "CVL Champion", awards.champion, awards.champion), ("team", "Best Record", awards.best_record, awards.best_record), ("team", "Highest Scoring", awards.highest_scoring, awards.highest_scoring), ("team", "Best Defense", awards.best_defense, awards.best_defense), ("team", "Top Team Rating", awards.highest_opi, awards.highest_opi), ("team", "Best Kicking", awards.best_kicking, awards.best_kicking), ] for award_type, award_name, winner, team in team_awards: rows.append({ "year": year, "award_type": award_type, "award_name": award_name, "winner": winner, "team": team, "position": "", "overall_rating": 0, }) # Individual awards from honors honors = dynasty.honors_history.get(year, {}) for a in honors.get("individual_awards", []): rows.append({ "year": year, "award_type": "individual", "award_name": a.get("award_name", ""), "winner": a.get("player_name", ""), "team": a.get("team_name", ""), "position": a.get("position", ""), "overall_rating": a.get("overall_rating", 0), }) # Team-level honors for award_name, winner in [ ("Coach of the Year", honors.get("coach_of_year", "")), ("Most Improved Program", honors.get("most_improved", "")), ]: if winner: rows.append({ "year": year, "award_type": "team_honor", "award_name": award_name, "winner": winner, "team": winner, "position": "", "overall_rating": 0, }) with open(filepath, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) return filepath def export_injury_history_csv(dynasty: "Dynasty", filepath: str) -> str: """ Export all recorded injuries across all seasons to CSV. Columns: year, team, player, position, tier, description, week_injured, weeks_out, season_ending """ _ensure_dir(filepath) fieldnames = [ "year", "team", "player", "position", "tier", "description", "week_injured", "weeks_out", "season_ending", ] rows = [] for year in sorted(dynasty.injury_history.keys()): report = dynasty.injury_history[year] for team_name, injuries in report.items(): for inj in injuries: rows.append({ "year": year, "team": team_name, "player": inj.get("player_name", ""), "position": inj.get("position", ""), "tier": inj.get("tier", ""), "description": inj.get("description", ""), "week_injured": inj.get("week_injured", 0), "weeks_out": inj.get("weeks_out", 0), "season_ending": int(inj.get("is_season_ending", False)), }) rows.sort(key=lambda r: (r["year"], r["team"])) with open(filepath, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) return filepath def export_development_history_csv(dynasty: "Dynasty", filepath: str) -> str: """ Export notable player development events across all offseasons to CSV. Columns: offseason_year, team, player, event_type, description """ _ensure_dir(filepath) fieldnames = ["offseason_year", "team", "player", "event_type", "description"] rows = [] for year in sorted(dynasty.development_history.keys()): for ev in dynasty.development_history[year]: rows.append({ "offseason_year": year, "team": ev.get("team", ""), "player": ev.get("player", ""), "event_type": ev.get("event_type", ""), "description": ev.get("description", ""), }) with open(filepath, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) return filepath def export_all_american_csv(dynasty: "Dynasty", filepath: str) -> str: """ Export All-CVL selections (1st, 2nd, 3rd, HM, Freshman) across all seasons to CSV. Columns: year, team_level, position_slot, player, year_in_school, team, position, overall_rating """ _ensure_dir(filepath) fieldnames = [ "year", "team_level", "position_slot", "player", "year_in_school", "team", "position", "overall_rating", ] _tiers = [ ("all_american_first", "1st Team"), ("all_american_second", "2nd Team"), ("all_american_third", "3rd Team"), ("honorable_mention", "Honorable Mention"), ("all_freshman", "All-Freshman"), ] rows = [] for year in sorted(dynasty.honors_history.keys()): honors = dynasty.honors_history[year] for team_key, level_label in _tiers: team_data = honors.get(team_key) or {} for slot in team_data.get("slots", []): rows.append({ "year": year, "team_level": level_label, "position_slot": slot.get("award_name", ""), "player": slot.get("player_name", ""), "year_in_school": slot.get("year_in_school", ""), "team": slot.get("team_name", ""), "position": slot.get("position", ""), "overall_rating": slot.get("overall_rating", 0), }) with open(filepath, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) return filepath def export_all_conference_csv(dynasty: "Dynasty", filepath: str) -> str: """ Export All-Conference selections (1st and 2nd team) across all seasons to CSV. Columns: year, conference, team_level, position_slot, player, year_in_school, team, position, overall_rating """ _ensure_dir(filepath) fieldnames = [ "year", "conference", "team_level", "position_slot", "player", "year_in_school", "team", "position", "overall_rating", ] rows = [] for year in sorted(dynasty.honors_history.keys()): honors = dynasty.honors_history[year] for conf_name, tiers in (honors.get("all_conference_teams") or {}).items(): # tiers is {"first": {...}, "second": {...}} for tier_key, tier_label in [("first", "1st Team"), ("second", "2nd Team")]: conf_data = tiers.get(tier_key) if isinstance(tiers, dict) else None if not conf_data: continue slots = conf_data.get("slots", []) if isinstance(conf_data, dict) else [] for slot in slots: rows.append({ "year": year, "conference": conf_name, "team_level": tier_label, "position_slot": slot.get("award_name", ""), "player": slot.get("player_name", ""), "year_in_school": slot.get("year_in_school", ""), "team": slot.get("team_name", ""), "position": slot.get("position", ""), "overall_rating": slot.get("overall_rating", 0), }) with open(filepath, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) return filepath # ────────────────────────────────────────────── # CONVENIENCE: EXPORT EVERYTHING # ────────────────────────────────────────────── def export_dynasty_full(dynasty: "Dynasty", output_dir: str) -> dict: """ Export all dynasty stats to a directory. Creates: /standings.csv /awards.csv /injuries.csv /development.csv /all_american.csv /all_conference.csv Returns dict of {export_name: filepath}. """ os.makedirs(output_dir, exist_ok=True) exported = {} exported["standings"] = export_dynasty_standings_csv( dynasty, os.path.join(output_dir, "standings.csv") ) exported["awards"] = export_dynasty_awards_csv( dynasty, os.path.join(output_dir, "awards.csv") ) exported["injuries"] = export_injury_history_csv( dynasty, os.path.join(output_dir, "injuries.csv") ) exported["development"] = export_development_history_csv( dynasty, os.path.join(output_dir, "development.csv") ) exported["all_american"] = export_all_american_csv( dynasty, os.path.join(output_dir, "all_american.csv") ) exported["all_conference"] = export_all_conference_csv( dynasty, os.path.join(output_dir, "all_conference.csv") ) return exported def export_dynasty_classes_csv(dynasty: "Dynasty", season, filepath: str) -> str: """ Export all CVL roster classes (Freshman through Senior) to CSV. Year-agnostic — omits calendar year so the file can be imported into any WVL dynasty regardless of what year it is in. Columns: class_year, team, conference, prestige, player, position, overall, potential, development, speed, stamina, agility, power, awareness, hands, kicking, kick_power, kick_accuracy, lateral_skill, tackling, height, weight, hometown, high_school """ _ensure_dir(filepath) fieldnames = [ "class_year", "team", "conference", "prestige", "player", "position", "overall", "potential", "development", "speed", "stamina", "agility", "power", "awareness", "hands", "kicking", "kick_power", "kick_accuracy", "lateral_skill", "tackling", "height", "weight", "hometown", "high_school", ] from engine.player_card import player_to_card rows = [] for class_year in ("Freshman", "Sophomore", "Junior", "Senior"): for team_name, team in season.teams.items(): conf = "" prestige = 50 if dynasty: conf = dynasty.get_team_conference(team_name) prestige = dynasty.team_prestige.get(team_name, 50) for player in team.players: py = getattr(player, "year", "") if py == class_year or (class_year == "Senior" and py == "Graduate"): card = player_to_card(player, team_name) rows.append({ "class_year": class_year, "team": team_name, "conference": conf, "prestige": prestige, "player": card.full_name, "position": card.position, "overall": card.overall, "potential": card.potential, "development": card.development, "speed": card.speed, "stamina": card.stamina, "agility": card.agility, "power": card.power, "awareness": card.awareness, "hands": card.hands, "kicking": card.kicking, "kick_power": card.kick_power, "kick_accuracy": card.kick_accuracy, "lateral_skill": card.lateral_skill, "tackling": card.tackling, "height": card.height, "weight": card.weight, "hometown": f"{card.hometown_city}, {card.hometown_state}", "high_school": card.high_school, }) rows.sort(key=lambda r: (-{"Senior": 4, "Junior": 3, "Sophomore": 2, "Freshman": 1}.get(r["class_year"], 0), -r["overall"])) with open(filepath, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) return filepath def export_hs_pipeline_csv(pipeline, filepath: str) -> str: """ Export high school recruiting pipeline to CSV. Columns: grade, rank, name, position, scouted_stars, hometown, high_school, region, height, weight, position_rank, regional_rank, is_alpha, is_sleeper, breakout """ _ensure_dir(filepath) fieldnames = [ "grade", "rank", "name", "position", "scouted_stars", "hometown", "high_school", "region", "height", "weight", "position_rank", "regional_rank", "is_alpha", "is_sleeper", "breakout", ] rows = [] for grade in ("12th", "11th", "10th", "9th"): for p in pipeline.classes.get(grade, []): rows.append({ "grade": p.grade, "rank": p.national_rank, "name": p.recruit.full_name, "position": p.recruit.position, "scouted_stars": p.scouted_stars, "hometown": p.recruit.hometown, "high_school": p.recruit.high_school, "region": p.recruit.region, "height": p.recruit.height, "weight": p.recruit.weight, "position_rank": p.position_rank, "regional_rank": p.regional_rank, "is_alpha": int(p.is_alpha), "is_sleeper": int(p.is_sleeper), "breakout": int(p.breakout), }) with open(filepath, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) return filepath def export_season_full(season: "Season", output_dir: str) -> dict: """ Export all single-season stats to a directory. Creates: /season_standings.csv /season_games.csv Returns dict of {export_name: filepath}. """ os.makedirs(output_dir, exist_ok=True) return { "standings": export_season_standings_csv( season, os.path.join(output_dir, "season_standings.csv") ), "games": export_season_game_log_csv( season, os.path.join(output_dir, "season_games.csv") ), }