feat: improve scouting dashboard and demo data

This commit is contained in:
bisco
2026-06-03 22:42:08 +02:00
parent 22f4a9159a
commit 7101900e19
10 changed files with 633 additions and 160 deletions
@@ -33,82 +33,164 @@ class Command(BaseCommand):
code="LNB",
defaults={"name": "LNB Elite", "region": "Europe", "country": "France"},
)[0],
"ISBL": League.objects.update_or_create(
code="ISBL",
defaults={"name": "Israeli Basketball Premier League", "region": "Europe", "country": "Israel"},
)[0],
"NBL": League.objects.update_or_create(
code="NBL",
defaults={"name": "National Basketball League", "region": "Oceania", "country": "Australia"},
)[0],
"NZNBL": League.objects.update_or_create(
code="NZNBL",
defaults={"name": "New Zealand NBL", "region": "Oceania", "country": "New Zealand"},
)[0],
}
demo_rows = [
{
"first_name": "Luca",
"last_name": "Marini",
"position": "PG",
"role": "Primary ball handler",
"birth_year": 2001,
"height_cm": 190,
"weight_kg": 86,
"nationality": "Italy",
"team": "Milano",
"league": "LBA",
"points": 16.8,
"assists": 6.2,
"rebounds": 3.8,
"efficiency": 20.5,
"ts": 59.2,
"usage": 25.0,
},
{
"first_name": "Mateo",
"last_name": "Santos",
"position": "SF",
"role": "3 and D wing",
"birth_year": 1999,
"height_cm": 201,
"weight_kg": 96,
"nationality": "Spain",
"team": "Madrid",
"league": "ACB",
"points": 11.2,
"assists": 2.1,
"rebounds": 5.5,
"efficiency": 13.7,
"ts": 55.8,
"usage": 17.5,
},
{
"first_name": "Jonas",
"last_name": "Keller",
"position": "C",
"role": "Roll man",
"birth_year": 2000,
"height_cm": 211,
"weight_kg": 109,
"nationality": "Germany",
"team": "Berlin",
"league": "BBL",
"points": 13.1,
"assists": 1.4,
"rebounds": 8.6,
"efficiency": 18.9,
"ts": 63.1,
"usage": 19.0,
},
(
"Luca", "Marini", "PG", "Primary ball handler", 2001, 190, 86, "Italy",
"Milano", "LBA", 16.8, 6.2, 3.8, 20.5, 59.2, 25.0,
),
(
"Davide", "Rossi", "SG", "Movement shooter", 2002, 195, 88, "Italy",
"Bologna", "LBA", 14.6, 2.4, 3.1, 15.8, 61.0, 21.4,
),
(
"Nikola", "Petrovic", "PF", "Stretch four", 1998, 206, 103, "Serbia",
"Trieste", "LBA", 12.9, 2.7, 6.8, 17.2, 58.4, 18.8,
),
(
"Mateo", "Santos", "SF", "3 and D wing", 1999, 201, 96, "Spain",
"Madrid", "ACB", 11.2, 2.1, 5.5, 13.7, 55.8, 17.5,
),
(
"Iker", "Varela", "PG", "Tempo guard", 2000, 188, 82, "Spain",
"Valencia", "ACB", 13.8, 5.9, 2.9, 18.1, 57.3, 23.7,
),
(
"Moussa", "Diagne", "C", "Rim protector", 1997, 213, 111, "Senegal",
"Malaga", "ACB", 9.7, 1.1, 8.9, 16.4, 64.8, 15.2,
),
(
"Marko", "Ilic", "PG", "Pick and roll creator", 2001, 192, 87, "Serbia",
"Belgrade", "ABA", 15.1, 6.8, 3.3, 19.6, 56.6, 26.1,
),
(
"Luka", "Horvat", "SF", "Slashing wing", 1999, 202, 94, "Croatia",
"Zadar", "ABA", 13.4, 2.8, 5.9, 16.0, 55.1, 20.5,
),
(
"Amar", "Kovac", "C", "Low-post finisher", 1998, 210, 114, "Bosnia",
"Ljubljana", "ABA", 11.8, 1.6, 7.7, 15.5, 60.9, 18.0,
),
(
"Jonas", "Keller", "C", "Roll man", 2000, 211, 109, "Germany",
"Berlin", "BBL", 13.1, 1.4, 8.6, 18.9, 63.1, 19.0,
),
(
"Tobias", "Weber", "SG", "Secondary creator", 2002, 196, 91, "Germany",
"Munich", "BBL", 15.9, 3.7, 4.2, 17.8, 58.0, 22.9,
),
(
"Leon", "Schmidt", "PF", "Short-roll passer", 1999, 205, 101, "Germany",
"Ulm", "BBL", 10.6, 3.2, 6.4, 14.9, 56.7, 16.8,
),
(
"Emir", "Yilmaz", "PG", "Pressure guard", 2001, 189, 84, "Turkey",
"Istanbul", "BSL", 17.4, 5.5, 3.0, 20.1, 57.8, 27.2,
),
(
"Can", "Demir", "SF", "Transition wing", 2000, 200, 95, "Turkey",
"Ankara", "BSL", 12.7, 2.5, 5.1, 14.4, 54.6, 19.4,
),
(
"Kerem", "Arslan", "C", "Paint anchor", 1998, 212, 116, "Turkey",
"Izmir", "BSL", 10.2, 1.2, 9.3, 17.0, 62.3, 14.8,
),
(
"Noam", "Levi", "SG", "Pull-up shooter", 2002, 194, 88, "Israel",
"Tel Aviv", "ISBL", 16.1, 3.0, 3.5, 16.9, 59.5, 24.1,
),
(
"Amit", "Cohen", "PG", "Drive and kick guard", 2001, 187, 81, "Israel",
"Jerusalem", "ISBL", 14.2, 6.4, 2.7, 18.7, 56.9, 25.5,
),
(
"Eitan", "Mizrahi", "PF", "Switch defender", 1999, 204, 100, "Israel",
"Holon", "ISBL", 9.8, 2.0, 7.1, 13.9, 54.2, 15.7,
),
(
"Theo", "Moreau", "PG", "Change-of-pace guard", 2003, 186, 80, "France",
"Paris", "LNB", 12.5, 5.8, 2.5, 16.8, 55.5, 22.2,
),
(
"Bastien", "Girard", "SF", "Connector wing", 2000, 199, 92, "France",
"Monaco", "LNB", 10.9, 3.6, 4.8, 14.6, 57.1, 16.3,
),
(
"Yanis", "Traore", "C", "Vertical spacer", 2001, 214, 112, "France",
"Lyon", "LNB", 11.4, 1.0, 8.1, 16.7, 65.0, 15.0,
),
(
"Jayden", "Mills", "SG", "Off-screen scorer", 2000, 197, 92, "Australia",
"Sydney", "NBL", 18.2, 2.9, 4.0, 19.4, 60.1, 26.4,
),
(
"Cooper", "Reed", "PF", "Face-up forward", 1999, 206, 102, "Australia",
"Melbourne", "NBL", 13.6, 2.4, 7.3, 17.5, 58.6, 20.0,
),
(
"Hemi", "Walker", "PG", "Paint touch guard", 2002, 191, 85, "New Zealand",
"Auckland", "NZNBL", 15.7, 7.1, 3.9, 21.2, 57.5, 27.0,
),
(
"Tane", "Rangi", "SF", "Defensive playmaker", 2001, 203, 98, "New Zealand",
"Wellington", "NZNBL", 11.6, 3.4, 6.5, 15.9, 55.4, 18.7,
),
(
"Finn", "McKenzie", "C", "Glass cleaner", 1998, 211, 113, "New Zealand",
"Canterbury", "NZNBL", 9.4, 1.3, 10.2, 16.1, 61.8, 13.9,
),
]
for row in demo_rows:
league = leagues[row["league"]]
(
first_name,
last_name,
position,
role,
birth_year,
height_cm,
weight_kg,
nationality,
team_name,
league_code,
points,
assists,
rebounds,
efficiency,
true_shooting,
usage,
) = row
games_played = 28
minutes = round(18 + usage * 0.42, 2)
league = leagues[league_code]
team, _ = Team.objects.update_or_create(
name=row["team"],
name=team_name,
league=league,
defaults={"country": league.country},
)
player, _ = Player.objects.update_or_create(
first_name=row["first_name"],
last_name=row["last_name"],
birth_year=row["birth_year"],
nationality=row["nationality"],
first_name=first_name,
last_name=last_name,
birth_year=birth_year,
nationality=nationality,
defaults={
"position": row["position"],
"role": row["role"],
"height_cm": row["height_cm"],
"weight_kg": row["weight_kg"],
"position": position,
"role": role,
"height_cm": height_cm,
"weight_kg": weight_kg,
"current_team": team,
"external_source": "synthetic",
},
@@ -119,23 +201,23 @@ class Command(BaseCommand):
league=league,
season=season,
defaults={
"games_played": 28,
"minutes_per_game": 27.5,
"points_per_game": row["points"],
"assists_per_game": row["assists"],
"rebounds_per_game": row["rebounds"],
"steals_per_game": 1.0,
"blocks_per_game": 0.4,
"turnovers_per_game": 1.8,
"field_goal_percentage": 48.0,
"three_point_percentage": 37.5,
"free_throw_percentage": 81.0,
"efficiency_rating": row["efficiency"],
"true_shooting_percentage": row["ts"],
"usage_percentage": row["usage"],
"total_points": int(row["points"] * 28),
"total_assists": int(row["assists"] * 28),
"total_rebounds": int(row["rebounds"] * 28),
"games_played": games_played,
"minutes_per_game": minutes,
"points_per_game": points,
"assists_per_game": assists,
"rebounds_per_game": rebounds,
"steals_per_game": 0.7 + (assists / 10),
"blocks_per_game": 0.2 + (rebounds / 18),
"turnovers_per_game": 1.0 + (usage / 20),
"field_goal_percentage": 42.5 + (true_shooting / 5),
"three_point_percentage": 28.0 + (points / 2.3),
"free_throw_percentage": 70.0 + (usage / 2),
"efficiency_rating": efficiency,
"true_shooting_percentage": true_shooting,
"usage_percentage": usage,
"total_points": int(points * games_played),
"total_assists": int(assists * games_played),
"total_rebounds": int(rebounds * games_played),
},
)
PlayerGameLog.objects.update_or_create(
@@ -146,10 +228,24 @@ class Command(BaseCommand):
game_date="2026-01-10",
opponent="Top domestic opponent",
defaults={
"points": int(row["points"] + 10),
"assists": int(row["assists"] + 3),
"rebounds": int(row["rebounds"] + 2),
"efficiency_rating": row["efficiency"] + 10,
"points": int(points + 10),
"assists": int(assists + 3),
"rebounds": int(rebounds + 2),
"efficiency_rating": efficiency + 10,
},
)
PlayerGameLog.objects.update_or_create(
player=player,
team=team,
league=league,
season=season,
game_date="2026-01-17",
opponent="Physical road opponent",
defaults={
"points": max(0, int(points - 7)),
"assists": max(0, int(assists - 2)),
"rebounds": max(0, int(rebounds - 3)),
"efficiency_rating": max(0, efficiency - 9),
},
)
@@ -0,0 +1,33 @@
import pytest
from django.core.management import call_command
from scouting.models import League, Player, PlayerSeasonStat
@pytest.mark.django_db
def test_seed_demo_data_creates_a_useful_scouting_board():
call_command("seed_demo_data")
assert Player.objects.count() >= 24
assert PlayerSeasonStat.objects.count() >= 24
assert set(League.objects.values_list("code", flat=True)) >= {
"LBA",
"ACB",
"ABA",
"BBL",
"BSL",
"LNB",
"ISBL",
"NBL",
"NZNBL",
}
@pytest.mark.django_db
def test_seed_demo_data_is_idempotent():
call_command("seed_demo_data")
first_count = Player.objects.count()
call_command("seed_demo_data")
assert Player.objects.count() == first_count