501e1b7e23
## Summary This PR fixes the release workflow to properly handle version bumping on PR merge and uses the new Docker registry authentication secrets. ## Changes ### Release Workflow (release.yml) - **Version Bumping**: Now automatically bumps version on PR merge - Determines bump type from commit messages (major/minor/patch) - Commits version bump to `package.json` and `CHANGELOG.md` - Creates git tag for the release - **Docker Registry Auth**: Uses `DOCKER_LOGIN` and `DOCKER_PASSWORD` secrets - Falls back gracefully if secrets are not configured - **Tag Handling**: Checks if tag exists before creating (prevents failures) ### PR Workflow (pr.yml) - NEW - Runs unit tests on every PR - Analyzes commits to suggest bump type - Comments the suggested bump type on the PR ### Documentation - Added `WORKFLOW_ARCHITECTURE.md` explaining the workflow design ## Workflow Architecture **Two-step process:** 1. **PR Workflow** (on PR): Analyzes commits and suggests bump type 2. **Release Workflow** (on merge): Bumps version, creates tag, builds Docker image ## Benefits 1. **No CI Loops**: Version bump commits are detected and skipped 2. **Clear Communication**: PR comments inform developers of version impact 3. **Semantic Versioning**: Automated adherence to semver rules 4. **Traceability**: Git tags and changelog reflect all changes ## Testing The new workflows will be tested when this PR is merged. Closes #13 (Add database test safety configuration) Reviewed-on: #17 Co-authored-by: David Gwilliam <dhgwilliam@gmail.com> Co-committed-by: David Gwilliam <dhgwilliam@gmail.com>
235 lines
6.5 KiB
Python
235 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Update partnership statistics based on matches in the database
|
|
"""
|
|
|
|
import sqlite3
|
|
import math
|
|
from datetime import datetime
|
|
|
|
DB_PATH = "prisma/prisma/dev.db"
|
|
K_FACTOR = 32
|
|
|
|
|
|
def get_all_matches():
|
|
"""Get all matches from the database"""
|
|
conn = sqlite3.connect(DB_PATH)
|
|
cursor = conn.cursor()
|
|
cursor.execute("""
|
|
SELECT id, team1P1Id, team1P2Id, team2P1Id, team2P2Id,
|
|
team1Score, team2Score, playedAt
|
|
FROM matches
|
|
ORDER BY playedAt
|
|
""")
|
|
matches = cursor.fetchall()
|
|
conn.close()
|
|
return matches
|
|
|
|
|
|
def get_or_create_partnership(player1_id, player2_id):
|
|
"""Get or create a partnership record"""
|
|
conn = sqlite3.connect(DB_PATH)
|
|
cursor = conn.cursor()
|
|
|
|
# Sort IDs to ensure consistent partnership lookup
|
|
p1 = min(player1_id, player2_id)
|
|
p2 = max(player1_id, player2_id)
|
|
|
|
cursor.execute(
|
|
"""
|
|
SELECT id, gamesPlayed, wins, losses, totalEloChange
|
|
FROM partnership_stats
|
|
WHERE player1Id = ? AND player2Id = ?
|
|
""",
|
|
(p1, p2),
|
|
)
|
|
|
|
result = cursor.fetchone()
|
|
if result:
|
|
conn.close()
|
|
return result
|
|
|
|
# Create new partnership record
|
|
cursor.execute(
|
|
"""
|
|
INSERT INTO partnership_stats (player1Id, player2Id, gamesPlayed, wins, losses, totalEloChange, lastPlayed, createdAt, updatedAt)
|
|
VALUES (?, ?, 0, 0, 0, 0, NULL, ?, ?)
|
|
""",
|
|
(p1, p2, datetime.now().isoformat() + "Z", datetime.now().isoformat() + "Z"),
|
|
)
|
|
|
|
partnership_id = cursor.lastrowid
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
return (partnership_id, 0, 0, 0, 0)
|
|
|
|
|
|
def update_partnership_stats(player1_id, player2_id, won, elo_change):
|
|
"""Update partnership statistics"""
|
|
conn = sqlite3.connect(DB_PATH)
|
|
cursor = conn.cursor()
|
|
|
|
# Sort IDs to ensure consistent partnership lookup
|
|
p1 = min(player1_id, player2_id)
|
|
p2 = max(player1_id, player2_id)
|
|
|
|
# Get current partnership stats
|
|
cursor.execute(
|
|
"""
|
|
SELECT id, gamesPlayed, wins, losses, totalEloChange
|
|
FROM partnership_stats
|
|
WHERE player1Id = ? AND player2Id = ?
|
|
""",
|
|
(p1, p2),
|
|
)
|
|
|
|
result = cursor.fetchone()
|
|
if not result:
|
|
# Create new partnership record
|
|
cursor.execute(
|
|
"""
|
|
INSERT INTO partnership_stats (player1Id, player2Id, gamesPlayed, wins, losses, totalEloChange, lastPlayed, createdAt, updatedAt)
|
|
VALUES (?, ?, 1, ?, ?, ?, ?, ?, ?)
|
|
""",
|
|
(
|
|
p1,
|
|
p2,
|
|
1 if won else 0,
|
|
0 if won else 1,
|
|
elo_change,
|
|
datetime.now().isoformat() + "Z",
|
|
datetime.now().isoformat() + "Z",
|
|
datetime.now().isoformat() + "Z",
|
|
),
|
|
)
|
|
else:
|
|
partnership_id, games_played, wins, losses, total_elo_change = result
|
|
|
|
# Update partnership stats
|
|
new_games = games_played + 1
|
|
new_wins = wins + 1 if won else wins
|
|
new_losses = losses if won else losses + 1
|
|
new_total_elo_change = total_elo_change + elo_change
|
|
|
|
cursor.execute(
|
|
"""
|
|
UPDATE partnership_stats
|
|
SET gamesPlayed = ?, wins = ?, losses = ?, totalEloChange = ?, lastPlayed = ?, updatedAt = ?
|
|
WHERE id = ?
|
|
""",
|
|
(
|
|
new_games,
|
|
new_wins,
|
|
new_losses,
|
|
new_total_elo_change,
|
|
datetime.now().isoformat() + "Z",
|
|
datetime.now().isoformat() + "Z",
|
|
partnership_id,
|
|
),
|
|
)
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
|
|
def main():
|
|
print("Updating partnership statistics based on matches...")
|
|
print("=" * 60)
|
|
|
|
# Get all matches
|
|
matches = get_all_matches()
|
|
print(f"Found {len(matches)} matches in database")
|
|
|
|
# Reset partnership stats
|
|
conn = sqlite3.connect(DB_PATH)
|
|
cursor = conn.cursor()
|
|
cursor.execute("DELETE FROM partnership_stats")
|
|
conn.commit()
|
|
conn.close()
|
|
print("Reset all partnership statistics")
|
|
|
|
# Process each match
|
|
match_count = 0
|
|
for (
|
|
match_id,
|
|
team1_p1,
|
|
team1_p2,
|
|
team2_p1,
|
|
team2_p2,
|
|
team1_score,
|
|
team2_score,
|
|
played_at,
|
|
) in matches:
|
|
# Determine winners
|
|
team1_won = team1_score > team2_score
|
|
team2_won = team2_score > team1_score
|
|
|
|
# Update partnership stats for Team 1
|
|
if team1_won:
|
|
update_partnership_stats(
|
|
team1_p1, team1_p2, True, 0
|
|
) # Elo change will be calculated separately
|
|
else:
|
|
update_partnership_stats(team1_p1, team1_p2, False, 0)
|
|
|
|
# Update partnership stats for Team 2
|
|
if team2_won:
|
|
update_partnership_stats(team2_p1, team2_p2, True, 0)
|
|
else:
|
|
update_partnership_stats(team2_p1, team2_p2, False, 0)
|
|
|
|
match_count += 1
|
|
if match_count % 20 == 0:
|
|
print(f"Processed {match_count}/{len(matches)} matches...")
|
|
|
|
print(f"Processed {match_count} matches")
|
|
|
|
# Display partnership stats for top players
|
|
print("\n" + "=" * 60)
|
|
print("Partnership Stats for Top Players:")
|
|
print("-" * 60)
|
|
|
|
conn = sqlite3.connect(DB_PATH)
|
|
cursor = conn.cursor()
|
|
|
|
# Get top players by ELO
|
|
cursor.execute("SELECT id, name FROM players ORDER BY currentElo DESC LIMIT 5")
|
|
top_players = cursor.fetchall()
|
|
|
|
for player_id, player_name in top_players:
|
|
print(f"\n{player_name}:")
|
|
|
|
# Get partnership stats for this player
|
|
cursor.execute(
|
|
"""
|
|
SELECT
|
|
CASE
|
|
WHEN player1Id = ? THEN (SELECT name FROM players WHERE id = player2Id)
|
|
ELSE (SELECT name FROM players WHERE id = player1Id)
|
|
END as partner_name,
|
|
gamesPlayed, wins, losses, totalEloChange
|
|
FROM partnership_stats
|
|
WHERE player1Id = ? OR player2Id = ?
|
|
ORDER BY gamesPlayed DESC
|
|
LIMIT 3
|
|
""",
|
|
(player_id, player_id, player_id),
|
|
)
|
|
|
|
partnerships = cursor.fetchall()
|
|
for partner_name, games, wins, losses, elo_change in partnerships:
|
|
win_rate = (wins / games * 100) if games > 0 else 0
|
|
print(
|
|
f" - with {partner_name}: {games} games, {wins}/{losses} ({win_rate:.1f}%) ELO: {elo_change:+d}"
|
|
)
|
|
|
|
conn.close()
|
|
|
|
print("\n" + "=" * 60)
|
|
print("Partnership statistics updated successfully!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|