Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions kloppy/infra/serializers/event/statsperform/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,11 @@ def _parse_pass(
# Look for a ball receipt event based on the result and sequence of events
ball_receipt_event = None
if result == PassResult.COMPLETE and next_event is not None:
if (
next_event.contestant_id == team.team_id
and next_event.type_id in BALL_OWNING_EVENTS
next_event_same_team = next_event.contestant_id == team.team_id
next_event_ball_owning_event = next_event.type_id in BALL_OWNING_EVENTS
next_event_duel_event = next_event.type_id == EVENT_TYPE_AERIAL
if next_event_same_team and (
next_event_ball_owning_event or next_event_duel_event
):
ball_receipt_event = next_event
elif (
Expand Down
42 changes: 42 additions & 0 deletions kloppy/tests/prs/pr_500/pr_500_ma1.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<match>
<matchInfo id="71pif9hi2vwzp6q0xzilyxst0" coverageLevel="15" date="2021-09-11Z" time="18:30:00Z" localDate="2021-09-11" localTime="20:30:00" week="6" attendanceInfoId="2" attendanceInfo="Limited Audience" numberOfPeriods="2" periodLength="45" lastUpdated="2024-03-14T21:27:20Z">
<description>Hamburger SV vs Sandhausen</description>
<sport id="289u5typ3vp4ifwh5thalohmq">Soccer</sport>
<ruleset id="79plas4983031idr6vw83nuel">Men</ruleset>
<competition id="722fdbecxzcq9788l6jqclzlw" name="2. Bundesliga" knownName="German Bundesliga Zwei" competitionCode="2.B" competitionFormat="Domestic league">
<country id="36min0qztu8eydwvpv8t1is0m">Germany</country>
</competition>
<tournamentCalendar id="93wcwtf25153hracvqva88aac" startDate="2021-07-23Z" endDate="2022-05-15Z">2021/2022</tournamentCalendar>
<stage id="9443wbz12vfz0f7ewmg8xhes4" formatId="e2q01r9o9jwiq1fls93d1sslx" startDate="2021-07-23Z" endDate="2022-05-15Z">Regular Season</stage>
<contestants>
<contestant id="75xi6hloabmnjn2kzgj1g8h1s" name="Hamburger SV" shortName="Hamburg" officialName="Hamburger SV" code="HSV" position="home">
<country id="36min0qztu8eydwvpv8t1is0m">Germany</country>
</contestant>
<contestant id="884uzyf1wosc7ykji6e18gifp" name="Sandhausen" shortName="Sandhausen" officialName="SV Sandhausen" code="SVS" position="away">
<country id="36min0qztu8eydwvpv8t1is0m">Germany</country>
</contestant>
</contestants>
<venue id="8gaqzo8y471bfkkc3tq7ra7o" neutral="no" longName="Volksparkstadion">Volksparkstadion</venue>
</matchInfo>
<liveData>
<matchDetails periodId="14" matchStatus="Played" winner="home" matchLengthMin="97" matchLengthSec="41">
<periods>
<period id="1" start="2021-09-11T18:30:06Z" end="2021-09-11T19:15:08Z" lengthMin="45" lengthSec="2"/>
<period id="2" start="2021-09-11T19:34:13Z" end="2021-09-11T20:26:52Z" lengthMin="52" lengthSec="39"/>
</periods>
<scores>
<ht home="0" away="0"/>
<ft home="2" away="1"/>
<total home="2" away="1"/>
</scores>
</matchDetails>
<lineUp contestantId="75xi6hloabmnjn2kzgj1g8h1s" formationUsed="433">
<player playerId="2nrmndj0uq3f46c2cb1fbf85" firstName="Moritz" lastName="Heyer" shortFirstName="Moritz" shortLastName="Heyer" matchName="M. Heyer" shirtNumber="3" position="Midfielder" positionSide="Left/Centre" formationPlace="8"/>
<player playerId="aksjicf4keobpav3tuujngell" firstName="Manuel Paul" lastName="Wintzheimer" shortFirstName="Manuel" shortLastName="Wintzheimer" matchName="M. Wintzheimer" shirtNumber="19" position="Striker" positionSide="Centre/Right" formationPlace="10"/>
</lineUp>
<lineUp contestantId="884uzyf1wosc7ykji6e18gifp" formationUsed="451">
<player playerId="test_player_away" firstName="Test" lastName="Player" shortFirstName="Test" shortLastName="Player" matchName="T. Player" shirtNumber="99" position="Midfielder" positionSide="Centre" formationPlace="10"/>
</lineUp>
</liveData>
</match>
54 changes: 54 additions & 0 deletions kloppy/tests/prs/pr_500/pr_500_ma3.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<matchEvents>
<matchInfo id="71pif9hi2vwzp6q0xzilyxst0" coverageLevel="15" date="2021-09-11Z" time="18:30:00Z" localDate="2021-09-11" localTime="20:30:00" week="6" attendanceInfoId="2" attendanceInfo="Limited Audience" numberOfPeriods="2" periodLength="45" lastUpdated="2024-03-14T21:27:20Z">
<description>Hamburger SV vs Sandhausen</description>
<sport id="289u5typ3vp4ifwh5thalohmq">Soccer</sport>
<ruleset id="79plas4983031idr6vw83nuel">Men</ruleset>
<competition id="722fdbecxzcq9788l6jqclzlw" name="2. Bundesliga" knownName="German Bundesliga Zwei" competitionCode="2.B" competitionFormat="Domestic league">
<country id="36min0qztu8eydwvpv8t1is0m">Germany</country>
</competition>
<tournamentCalendar id="93wcwtf25153hracvqva88aac" startDate="2021-07-23Z" endDate="2022-05-15Z">2021/2022</tournamentCalendar>
<stage id="9443wbz12vfz0f7ewmg8xhes4" formatId="e2q01r9o9jwiq1fls93d1sslx" startDate="2021-07-23Z" endDate="2022-05-15Z">Regular Season</stage>
<contestants>
<contestant id="75xi6hloabmnjn2kzgj1g8h1s" name="Hamburger SV" shortName="Hamburg" officialName="Hamburger SV" code="HSV" position="home">
<country id="36min0qztu8eydwvpv8t1is0m">Germany</country>
</contestant>
<contestant id="884uzyf1wosc7ykji6e18gifp" name="Sandhausen" shortName="Sandhausen" officialName="SV Sandhausen" code="SVS" position="away">
<country id="36min0qztu8eydwvpv8t1is0m">Germany</country>
</contestant>
</contestants>
<venue id="8gaqzo8y471bfkkc3tq7ra7o" neutral="no" longName="Volksparkstadion">Volksparkstadion</venue>
</matchInfo>
<liveData>
<matchDetails periodId="14" matchStatus="Played" winner="home" matchLengthMin="97" matchLengthSec="41">
<periods>
<period id="1" start="2021-09-11T18:30:06Z" end="2021-09-11T19:15:08Z" lengthMin="45" lengthSec="2"/>
<period id="2" start="2021-09-11T19:34:13Z" end="2021-09-11T20:26:52Z" lengthMin="52" lengthSec="39"/>
</periods>
<scores>
<ht home="0" away="0"/>
<ft home="2" away="1"/>
<total home="2" away="1"/>
</scores>
</matchDetails>
<events>
<event id="2328592303" eventId="35" typeId="1" periodId="1" timeMin="2" timeSec="34" contestantId="75xi6hloabmnjn2kzgj1g8h1s" playerId="2nrmndj0uq3f46c2cb1fbf85" playerName="M. Heyer" outcome="1" keyPass="1" x="85.7" y="97.1" timeStamp="2021-09-11T18:32:40.959Z" lastModified="2021-09-16T08:33:50Z">
<qualifier id="3088136087" qualifierId="155"/>
<qualifier id="3088136085" qualifierId="2"/>
<qualifier id="3088136089" qualifierId="56" value="Center"/>
<qualifier id="3088136091" qualifierId="140" value="92.0"/>
<qualifier id="3088141151" qualifierId="154"/>
<qualifier id="3088136097" qualifierId="213" value="4.9"/>
<qualifier id="3093691325" qualifierId="223"/>
<qualifier id="3098050259" qualifierId="178"/>
<qualifier id="3088141149" qualifierId="210" value="15"/>
<qualifier id="3093691327" qualifierId="20"/>
</event>
<event id="2328592321" eventId="36" typeId="44" periodId="1" timeMin="2" timeSec="36" contestantId="75xi6hloabmnjn2kzgj1g8h1s" playerId="aksjicf4keobpav3tuujngell" playerName="M. Wintzheimer" outcome="1" x="91.4" y="36.6" timeStamp="2021-09-11T18:32:43.177Z" lastModified="2021-09-16T08:33:49Z">
<qualifier id="3088136155" qualifierId="56" value="Center"/>
<qualifier id="3088136157" qualifierId="286"/>
<qualifier id="3088136211" qualifierId="233" value="19"/>
</event>
</events>
</liveData>
</matchEvents>
87 changes: 87 additions & 0 deletions kloppy/tests/prs/pr_500/test_pr_500.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""
Test for PR 500: Fix for Stats Perform pass receiver detection.

This test verifies that event ID '2328592303' correctly identifies
receiver player ID 'aksjicf4keobpav3tuujngell'.
"""

from pathlib import Path

import pytest

from kloppy import statsperform
from kloppy.domain import EventDataset


@pytest.fixture(scope="module")
def pr_500_metadata_xml() -> Path:
return Path(__file__).parent / "pr_500_ma1.xml"


@pytest.fixture(scope="module")
def pr_500_event_data_xml() -> Path:
return Path(__file__).parent / "pr_500_ma3.xml"


@pytest.fixture(scope="module")
def pr_500_event_dataset(
pr_500_metadata_xml: Path, pr_500_event_data_xml: Path
) -> EventDataset:
return statsperform.load_event(
ma1_data=pr_500_metadata_xml,
ma3_data=pr_500_event_data_xml,
coordinates="opta",
)


class TestPR500PassReceiverFix:
"""Test for PR 500: Stats Perform pass receiver detection fix."""

def test_pass_receiver_identification(
self, pr_500_event_dataset: EventDataset
):
"""Test that event ID '2328592303' correctly identifies receiver player ID 'aksjicf4keobpav3tuujngell'."""

# Get the specific pass event
pass_event = pr_500_event_dataset.get_event_by_id("2328592303")

# Verify it's a pass event
assert (
pass_event is not None
), "Event 2328592303 should exist in dataset"
assert (
pass_event.event_type.value == "PASS"
), f"Event should be a pass, got {pass_event.event_type.value}"

# Verify the passer
assert (
pass_event.player.player_id == "2nrmndj0uq3f46c2cb1fbf85"
), "Incorrect passer player ID"
assert (
pass_event.player.full_name == "M. Heyer"
), "Incorrect passer name"

# Verify the receiver - this is the main test for the fix
assert (
pass_event.receiver_player is not None
), "Pass should have a receiver player"
assert (
pass_event.receiver_player.player_id == "aksjicf4keobpav3tuujngell"
), f"Expected receiver player ID 'aksjicf4keobpav3tuujngell', got '{pass_event.receiver_player.player_id}'"
assert (
pass_event.receiver_player.full_name == "M. Wintzheimer"
), "Incorrect receiver name"

# Verify the receive timestamp is set
assert (
pass_event.receive_timestamp is not None
), "Pass should have a receive timestamp"

# Verify the next event is the ball reception by the correct player
next_event = pass_event.next_record
assert (
next_event is not None
), "There should be a next event after the pass"
assert (
next_event.player.player_id == "aksjicf4keobpav3tuujngell"
), "Next event should be by the receiver player"
Loading