fix: version bumping and Docker registry authentication (#17)
## 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>
This commit was merged in pull request #17.
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Update player statistics (ELO, gamesPlayed, wins, losses) based on matches in the database
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import math
|
||||
|
||||
DB_PATH = "prisma/prisma/dev.db"
|
||||
K_FACTOR = 32 # Standard K-factor for Elo calculations
|
||||
|
||||
|
||||
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_player(player_id):
|
||||
"""Get a player by ID"""
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"SELECT id, name, currentElo, gamesPlayed, wins, losses FROM players WHERE id = ?",
|
||||
(player_id,),
|
||||
)
|
||||
player = cursor.fetchone()
|
||||
conn.close()
|
||||
return player
|
||||
|
||||
|
||||
def update_player(player_id, current_elo, games_played, wins, losses):
|
||||
"""Update a player's statistics"""
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
UPDATE players
|
||||
SET currentElo = ?, gamesPlayed = ?, wins = ?, losses = ?
|
||||
WHERE id = ?
|
||||
""",
|
||||
(current_elo, games_played, wins, losses, player_id),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def calculate_elo_change(rating_a, rating_b, score_a, score_b):
|
||||
"""Calculate Elo change for a match"""
|
||||
# Calculate expected scores
|
||||
expected_a = 1 / (1 + math.pow(10, (rating_b - rating_a) / 400))
|
||||
expected_b = 1 - expected_a
|
||||
|
||||
# Actual scores (1 for win, 0.5 for tie, 0 for loss)
|
||||
actual_a = 0.5 if score_a == score_b else (1 if score_a > score_b else 0)
|
||||
actual_b = 0.5 if score_a == score_b else (1 if score_b > score_a else 0)
|
||||
|
||||
# Calculate Elo change
|
||||
elo_change_a = K_FACTOR * (actual_a - expected_a)
|
||||
elo_change_b = K_FACTOR * (actual_b - expected_b)
|
||||
|
||||
return elo_change_a, elo_change_b
|
||||
|
||||
|
||||
def main():
|
||||
print("Updating player statistics based on matches in database...")
|
||||
print("=" * 60)
|
||||
|
||||
# Get all matches
|
||||
matches = get_all_matches()
|
||||
print(f"Found {len(matches)} matches in database")
|
||||
|
||||
# Reset all player stats to 0 before recalculating
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"UPDATE players SET currentElo = 1000, gamesPlayed = 0, wins = 0, losses = 0"
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print("Reset all player statistics to initial values (ELO: 1000, games: 0)")
|
||||
|
||||
# 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:
|
||||
# Get player data
|
||||
p1 = get_player(team1_p1)
|
||||
p2 = get_player(team1_p2)
|
||||
p3 = get_player(team2_p1)
|
||||
p4 = get_player(team2_p2)
|
||||
|
||||
if not all([p1, p2, p3, p4]):
|
||||
print(f"Warning: Could not find all players for match {match_id}")
|
||||
continue
|
||||
|
||||
# Calculate team ratings
|
||||
team1_rating = (p1[2] + p2[2]) / 2 # currentElo
|
||||
team2_rating = (p3[2] + p4[2]) / 2 # currentElo
|
||||
|
||||
# Calculate Elo changes
|
||||
team1_elo_change, team2_elo_change = calculate_elo_change(
|
||||
team1_rating, team2_rating, team1_score, team2_score
|
||||
)
|
||||
|
||||
# Individual Elo changes (split evenly between team members)
|
||||
p1_elo_change = team1_elo_change / 2
|
||||
p2_elo_change = team1_elo_change / 2
|
||||
p3_elo_change = team2_elo_change / 2
|
||||
p4_elo_change = team2_elo_change / 2
|
||||
|
||||
# Determine winners
|
||||
team1_won = team1_score > team2_score
|
||||
team2_won = team2_score > team1_score
|
||||
|
||||
# Update player 1 stats
|
||||
p1_new_elo = int(p1[2] + p1_elo_change)
|
||||
p1_new_games = p1[3] + 1
|
||||
p1_new_wins = p1[4] + 1 if team1_won else p1[4]
|
||||
p1_new_losses = p1[5] if team1_won else p1[5] + 1
|
||||
update_player(p1[0], p1_new_elo, p1_new_games, p1_new_wins, p1_new_losses)
|
||||
|
||||
# Update player 2 stats
|
||||
p2_new_elo = int(p2[2] + p2_elo_change)
|
||||
p2_new_games = p2[3] + 1
|
||||
p2_new_wins = p2[4] + 1 if team1_won else p2[4]
|
||||
p2_new_losses = p2[5] if team1_won else p2[5] + 1
|
||||
update_player(p2[0], p2_new_elo, p2_new_games, p2_new_wins, p2_new_losses)
|
||||
|
||||
# Update player 3 stats
|
||||
p3_new_elo = int(p3[2] + p3_elo_change)
|
||||
p3_new_games = p3[3] + 1
|
||||
p3_new_wins = p3[4] + 1 if team2_won else p3[4]
|
||||
p3_new_losses = p3[5] if team2_won else p3[5] + 1
|
||||
update_player(p3[0], p3_new_elo, p3_new_games, p3_new_wins, p3_new_losses)
|
||||
|
||||
# Update player 4 stats
|
||||
p4_new_elo = int(p4[2] + p4_elo_change)
|
||||
p4_new_games = p4[3] + 1
|
||||
p4_new_wins = p4[4] + 1 if team2_won else p4[4]
|
||||
p4_new_losses = p4[5] if team2_won else p4[5] + 1
|
||||
update_player(p4[0], p4_new_elo, p4_new_games, p4_new_wins, p4_new_losses)
|
||||
|
||||
match_count += 1
|
||||
if match_count % 20 == 0:
|
||||
print(f"Processed {match_count}/{len(matches)} matches...")
|
||||
|
||||
print(f"Processed {match_count} matches")
|
||||
|
||||
# Display updated player rankings
|
||||
print("\n" + "=" * 60)
|
||||
print("Top 10 Players by ELO Rating:")
|
||||
print("-" * 60)
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT id, name, currentElo, gamesPlayed, wins, losses
|
||||
FROM players
|
||||
ORDER BY currentElo DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
top_players = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
for rank, (player_id, name, elo, games, wins, losses) in enumerate(top_players, 1):
|
||||
win_rate = (wins / games * 100) if games > 0 else 0
|
||||
print(
|
||||
f"{rank:2}. {name:15} | ELO: {elo:4} | Games: {games:3} | W/L: {wins}/{losses} ({win_rate:.1f}%)"
|
||||
)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Player statistics updated successfully!")
|
||||
print("Now run the application to see updated rankings.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user