feat(ingestion): add first public LBA Serie A importer

This commit is contained in:
bisco
2026-04-11 00:45:33 +02:00
parent 9524c928e2
commit 88ca5cde10
5 changed files with 702 additions and 1 deletions

View File

@ -1,9 +1,10 @@
from datetime import date
from decimal import Decimal
from pathlib import Path
from urllib.parse import urlencode
from django.contrib.auth import get_user_model
from django.core.management import call_command
from django.core.management import CommandError, call_command
from django.db import connection
from django.db.migrations.executor import MigrationExecutor
from django.test import TestCase, TransactionTestCase
@ -526,6 +527,135 @@ class FirstRealIngestionFlowTests(TestCase):
self.assertContains(detail_response, "PTS 17.2")
class FirstPublicEuropeanImporterTests(TestCase):
COMMAND_NAME = "import_lba_public_serie_a"
SOURCE_NAME = "lba_public"
FIXTURE_PATH = Path(__file__).resolve().parent / "sample_data" / "imports" / "lba_public_serie_a_fixture.json"
def run_import(self):
call_command(self.COMMAND_NAME, fixture=str(self.FIXTURE_PATH), season=2025)
def test_importer_command_runs_successfully_for_lba_scope(self):
self.run_import()
self.assertGreaterEqual(Player.objects.count(), 2)
def test_importer_creates_expected_entities_contexts_and_stats(self):
self.run_import()
self.assertTrue(Competition.objects.filter(name="Lega Basket Serie A").exists())
self.assertTrue(Season.objects.filter(name="2025-2026", start_year=2025, end_year=2026).exists())
self.assertTrue(Player.objects.filter(full_name="Muhammad-Ali Abdur-Rahkman").exists())
self.assertTrue(Player.objects.filter(full_name="Nicola Akele").exists())
self.assertTrue(Team.objects.filter(name="NutriBullet Treviso Basket", country="IT").exists())
self.assertTrue(PlayerSeason.objects.filter(player__full_name="Muhammad-Ali Abdur-Rahkman").exists())
self.assertTrue(PlayerSeasonStats.objects.filter(player_season__player__full_name="Muhammad-Ali Abdur-Rahkman").exists())
def test_importer_is_idempotent_for_same_input(self):
self.run_import()
first_counts = {
"players": Player.objects.count(),
"teams": Team.objects.count(),
"contexts": PlayerSeason.objects.count(),
"stats": PlayerSeasonStats.objects.count(),
"mappings": ExternalEntityMapping.objects.filter(source_name=self.SOURCE_NAME).count(),
}
self.run_import()
self.assertEqual(Player.objects.count(), first_counts["players"])
self.assertEqual(Team.objects.count(), first_counts["teams"])
self.assertEqual(PlayerSeason.objects.count(), first_counts["contexts"])
self.assertEqual(PlayerSeasonStats.objects.count(), first_counts["stats"])
self.assertEqual(
ExternalEntityMapping.objects.filter(source_name=self.SOURCE_NAME).count(),
first_counts["mappings"],
)
def test_importer_respects_external_entity_mapping(self):
self.run_import()
self.assertTrue(
ExternalEntityMapping.objects.filter(
source_name=self.SOURCE_NAME,
entity_type=ExternalEntityMapping.EntityType.COMPETITION,
external_id="lba-serie-a",
).exists()
)
self.assertTrue(
ExternalEntityMapping.objects.filter(
source_name=self.SOURCE_NAME,
entity_type=ExternalEntityMapping.EntityType.PLAYER,
external_id="6609",
).exists()
)
self.assertTrue(
ExternalEntityMapping.objects.filter(
source_name=self.SOURCE_NAME,
entity_type=ExternalEntityMapping.EntityType.TEAM,
external_id="1642",
).exists()
)
self.assertTrue(
ExternalEntityMapping.objects.filter(
source_name=self.SOURCE_NAME,
entity_type=ExternalEntityMapping.EntityType.PLAYER_SEASON,
external_id="2025:1642:6609",
).exists()
)
def test_importer_does_not_overwrite_internal_scouting_enrichment(self):
role = Role.objects.create(name="internal role lba", slug="internal-role-lba")
specialty = Specialty.objects.create(name="internal specialty lba", slug="internal-specialty-lba")
self.run_import()
player = Player.objects.get(full_name="Muhammad-Ali Abdur-Rahkman")
player.roles.add(role)
player.specialties.add(specialty)
self.run_import()
player.refresh_from_db()
self.assertTrue(player.roles.filter(pk=role.pk).exists())
self.assertTrue(player.specialties.filter(pk=specialty.pk).exists())
def test_importer_does_not_interfere_with_user_owned_data(self):
self.run_import()
user = User.objects.create_user(username="lba_user", password="pass12345")
player = Player.objects.get(full_name="Muhammad-Ali Abdur-Rahkman")
favorite = FavoritePlayer.objects.create(user=user, player=player)
note = PlayerNote.objects.create(user=user, player=player, body="LBA imported profile")
saved = SavedSearch.objects.create(user=user, name="LBA Search", params={"name": "Muhammad"})
self.run_import()
self.assertTrue(FavoritePlayer.objects.filter(pk=favorite.pk).exists())
self.assertTrue(PlayerNote.objects.filter(pk=note.pk).exists())
self.assertTrue(SavedSearch.objects.filter(pk=saved.pk).exists())
def test_imported_data_is_visible_in_search_and_detail_flows(self):
self.run_import()
list_response = self.client.get(reverse("scouting:player_list"), {"name": "Muhammad"})
self.assertEqual(list_response.status_code, 200)
self.assertContains(list_response, "Muhammad-Ali Abdur-Rahkman")
player = Player.objects.get(full_name="Muhammad-Ali Abdur-Rahkman")
detail_response = self.client.get(reverse("scouting:player_detail", args=[player.id]))
self.assertEqual(detail_response.status_code, 200)
self.assertContains(detail_response, "Muhammad-Ali Abdur-Rahkman")
self.assertContains(detail_response, "PTS 19.5")
def test_importer_fails_cleanly_for_malformed_fixture(self):
broken_fixture = Path(__file__).resolve().parent / "sample_data" / "imports" / "lba_public_broken_fixture.json"
broken_fixture.write_text("{\"categories\": {\"points\": {\"stats\": [{\"name\": \"OnlyName\"}]}}}", encoding="utf-8")
try:
with self.assertRaises(CommandError):
call_command(self.COMMAND_NAME, fixture=str(broken_fixture), season=2025)
finally:
if broken_fixture.exists():
broken_fixture.unlink()
class FavoritePlayerViewsTests(TestCase):
@classmethod
def setUpTestData(cls):