Version control Claude Code configuration including: - Global instructions (CLAUDE.md) - User settings (settings.json) - Custom agents (architect, designer, engineer, etc.) - Custom skills (create-skill templates and workflows) Excludes session data, secrets, cache, and temporary files per .gitignore. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
281 lines
8.4 KiB
Python
Executable File
281 lines
8.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Generate summary report for Paper Dynasty card update
|
|
|
|
Collects statistics and notable changes for release notes.
|
|
|
|
Usage:
|
|
python generate_summary.py <database_path> [--previous-db <path>]
|
|
"""
|
|
|
|
import sqlite3
|
|
import sys
|
|
import json
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
from typing import List, Dict, Optional, Tuple
|
|
|
|
|
|
RARITY_TIERS = {
|
|
"Reserve": 1,
|
|
"Replacement": 2,
|
|
"Starter": 3,
|
|
"All-Star": 4,
|
|
"MVP": 5,
|
|
"Hall of Fame": 6
|
|
}
|
|
|
|
|
|
def get_card_counts(cursor: sqlite3.Cursor) -> Dict[str, int]:
|
|
"""Get total card counts"""
|
|
batting = cursor.execute("SELECT COUNT(*) FROM batting_cards").fetchone()[0]
|
|
pitching = cursor.execute("SELECT COUNT(*) FROM pitching_cards").fetchone()[0]
|
|
return {"batting": batting, "pitching": pitching, "total": batting + pitching}
|
|
|
|
|
|
def get_new_players(cursor: sqlite3.Cursor, since_date: Optional[str] = None) -> int:
|
|
"""Count new players added since date"""
|
|
if not since_date:
|
|
# Default to last 7 days
|
|
query = """
|
|
SELECT COUNT(DISTINCT player_name)
|
|
FROM (
|
|
SELECT player_name, created_date FROM batting_cards
|
|
WHERE created_date >= date('now', '-7 days')
|
|
UNION
|
|
SELECT player_name, created_date FROM pitching_cards
|
|
WHERE created_date >= date('now', '-7 days')
|
|
)
|
|
"""
|
|
else:
|
|
query = f"""
|
|
SELECT COUNT(DISTINCT player_name)
|
|
FROM (
|
|
SELECT player_name FROM batting_cards
|
|
WHERE created_date >= '{since_date}'
|
|
UNION
|
|
SELECT player_name FROM pitching_cards
|
|
WHERE created_date >= '{since_date}'
|
|
)
|
|
"""
|
|
|
|
try:
|
|
return cursor.execute(query).fetchone()[0]
|
|
except sqlite3.OperationalError:
|
|
# created_date column might not exist
|
|
return 0
|
|
|
|
|
|
def get_rarity_changes(
|
|
current_cursor: sqlite3.Cursor,
|
|
previous_db_path: Optional[Path] = None,
|
|
threshold: int = 2
|
|
) -> Tuple[List[Dict], List[Dict]]:
|
|
"""
|
|
Compare rarity changes between current and previous database.
|
|
|
|
Returns (upgrades, downgrades) where each is a list of dicts with:
|
|
- player_name
|
|
- card_id
|
|
- old_rarity
|
|
- new_rarity
|
|
- change (tier difference)
|
|
"""
|
|
if not previous_db_path or not previous_db_path.exists():
|
|
return [], []
|
|
|
|
prev_conn = sqlite3.connect(previous_db_path)
|
|
prev_cursor = prev_conn.cursor()
|
|
|
|
upgrades = []
|
|
downgrades = []
|
|
|
|
# Compare batting cards
|
|
query = """
|
|
SELECT
|
|
c.player_name,
|
|
c.card_id,
|
|
p.rarity as old_rarity,
|
|
c.rarity as new_rarity
|
|
FROM batting_cards c
|
|
JOIN prev.batting_cards p ON c.card_id = p.card_id
|
|
WHERE c.rarity != p.rarity
|
|
"""
|
|
|
|
try:
|
|
current_cursor.execute("ATTACH DATABASE ? AS prev", (str(previous_db_path),))
|
|
changes = current_cursor.execute(query).fetchall()
|
|
|
|
for name, card_id, old_rarity, new_rarity in changes:
|
|
old_tier = RARITY_TIERS.get(old_rarity, 0)
|
|
new_tier = RARITY_TIERS.get(new_rarity, 0)
|
|
change = new_tier - old_tier
|
|
|
|
if abs(change) >= threshold:
|
|
record = {
|
|
"player_name": name,
|
|
"card_id": card_id,
|
|
"old_rarity": old_rarity,
|
|
"new_rarity": new_rarity,
|
|
"change": change
|
|
}
|
|
if change > 0:
|
|
upgrades.append(record)
|
|
else:
|
|
downgrades.append(record)
|
|
|
|
current_cursor.execute("DETACH DATABASE prev")
|
|
except sqlite3.OperationalError as e:
|
|
print(f"Warning: Could not compare rarity changes: {e}", file=sys.stderr)
|
|
|
|
prev_conn.close()
|
|
|
|
# Sort by magnitude of change
|
|
upgrades.sort(key=lambda x: x['change'], reverse=True)
|
|
downgrades.sort(key=lambda x: x['change'])
|
|
|
|
return upgrades, downgrades
|
|
|
|
|
|
def get_date_range(card_creation_dir: Path) -> Tuple[str, str]:
|
|
"""Extract date range from retrosheet_data.py"""
|
|
retrosheet_file = card_creation_dir / "retrosheet_data.py"
|
|
|
|
if not retrosheet_file.exists():
|
|
return "Unknown", "Unknown"
|
|
|
|
content = retrosheet_file.read_text()
|
|
|
|
start_date = "Unknown"
|
|
end_date = "Unknown"
|
|
|
|
for line in content.split('\n'):
|
|
if 'START_DATE' in line and '=' in line:
|
|
start_date = line.split('=')[1].strip().strip('"\'')
|
|
elif 'END_DATE' in line and '=' in line:
|
|
end_date = line.split('=')[1].strip().strip('"\'')
|
|
|
|
return start_date, end_date
|
|
|
|
|
|
def generate_markdown_summary(
|
|
counts: Dict[str, int],
|
|
new_players: int,
|
|
upgrades: List[Dict],
|
|
downgrades: List[Dict],
|
|
date_range: Tuple[str, str],
|
|
csv_files: List[str]
|
|
) -> str:
|
|
"""Generate markdown summary report"""
|
|
|
|
today = datetime.now().strftime('%Y-%m-%d')
|
|
start_date, end_date = date_range
|
|
|
|
lines = [
|
|
f"# Paper Dynasty Card Update - {today}",
|
|
"",
|
|
"## Overview",
|
|
f"- **Total Cards**: {counts['batting']} batting, {counts['pitching']} pitching",
|
|
f"- **New Players**: {new_players}",
|
|
f"- **Data Range**: {start_date} to {end_date}",
|
|
"",
|
|
]
|
|
|
|
if upgrades or downgrades:
|
|
lines.append("## Notable Rarity Changes")
|
|
lines.append("")
|
|
|
|
if upgrades:
|
|
lines.append("### Upgrades")
|
|
for player in upgrades[:10]: # Max 10
|
|
tier_change = f"+{player['change']}" if player['change'] > 0 else str(player['change'])
|
|
lines.append(
|
|
f"- **{player['player_name']}** (ID: {player['card_id']}): "
|
|
f"{player['old_rarity']} → {player['new_rarity']} ({tier_change} tiers)"
|
|
)
|
|
if len(upgrades) > 10:
|
|
lines.append(f"- *...and {len(upgrades) - 10} more*")
|
|
lines.append("")
|
|
|
|
if downgrades:
|
|
lines.append("### Downgrades")
|
|
for player in downgrades[:10]: # Max 10
|
|
tier_change = str(player['change']) # Already negative
|
|
lines.append(
|
|
f"- **{player['player_name']}** (ID: {player['card_id']}): "
|
|
f"{player['old_rarity']} → {player['new_rarity']} ({tier_change} tiers)"
|
|
)
|
|
if len(downgrades) > 10:
|
|
lines.append(f"- *...and {len(downgrades) - 10} more*")
|
|
lines.append("")
|
|
|
|
lines.extend([
|
|
"## Files Generated",
|
|
"- ✅ Card images uploaded to S3",
|
|
"- ✅ Scouting CSVs transferred to database server",
|
|
])
|
|
|
|
for csv_file in csv_files:
|
|
lines.append(f" - {csv_file}")
|
|
|
|
lines.extend([
|
|
"",
|
|
"## Validation",
|
|
"- ✅ No negative groundball_b values",
|
|
"- ✅ All required fields populated",
|
|
"- ✅ Database integrity checks passed",
|
|
"",
|
|
"---",
|
|
"Generated by Claude Code - Paper Dynasty Cards Skill",
|
|
])
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python generate_summary.py <database_path> [--previous-db <path>]")
|
|
sys.exit(1)
|
|
|
|
db_path = Path(sys.argv[1])
|
|
previous_db = Path(sys.argv[3]) if len(sys.argv) > 3 and sys.argv[2] == '--previous-db' else None
|
|
|
|
if not db_path.exists():
|
|
print(f"❌ Database not found: {db_path}")
|
|
sys.exit(1)
|
|
|
|
# Connect to database
|
|
conn = sqlite3.connect(db_path)
|
|
cursor = conn.cursor()
|
|
|
|
# Collect data
|
|
counts = get_card_counts(cursor)
|
|
new_players = get_new_players(cursor)
|
|
upgrades, downgrades = get_rarity_changes(cursor, previous_db, threshold=2)
|
|
|
|
# Get date range from retrosheet_data.py
|
|
card_creation_dir = db_path.parent
|
|
date_range = get_date_range(card_creation_dir)
|
|
|
|
# Get CSV files from scouting directory
|
|
scouting_dir = card_creation_dir / "scouting"
|
|
csv_files = sorted([f.name for f in scouting_dir.glob("*.csv")]) if scouting_dir.exists() else []
|
|
|
|
# Generate summary
|
|
summary = generate_markdown_summary(counts, new_players, upgrades, downgrades, date_range, csv_files)
|
|
|
|
# Print to stdout
|
|
print(summary)
|
|
|
|
# Save to file
|
|
output_file = Path.home() / ".claude" / "scratchpad" / f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_card_update_summary.md"
|
|
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
output_file.write_text(summary)
|
|
print(f"\n✅ Summary saved to: {output_file}", file=sys.stderr)
|
|
|
|
conn.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|