diff --git a/impectPy/__init__.py b/impectPy/__init__.py index 7c3cd89..7bbb826 100644 --- a/impectPy/__init__.py +++ b/impectPy/__init__.py @@ -8,7 +8,7 @@ from .player_scores import getPlayerMatchScores, getPlayerIterationScores from .squad_scores import getSquadMatchScores, getSquadIterationScores from .player_profile_scores import getPlayerProfileScores -from .xml import generateXML +from .generate_xml import generateXML from .set_pieces import getSetPieces from .squad_ratings import getSquadRatings from .match_info import getFormations, getSubstitutions, getStartingPositions diff --git a/impectPy/xml.py b/impectPy/generate_xml.py similarity index 94% rename from impectPy/xml.py rename to impectPy/generate_xml.py index b2bc127..fbdc62e 100644 --- a/impectPy/xml.py +++ b/impectPy/generate_xml.py @@ -9,7 +9,7 @@ # ###### -#define allowed KPIs, labels and codes +# define allowed KPIs, labels and codes allowed_labels = [ {"order": "00 | ", "name": "eventId"}, {"order": "01 | ", "name": "matchId"}, @@ -69,11 +69,16 @@ allowed_codes = [ "playerName", - "squadName", + "team", "actionType", "action" ] +allowed_perspectives = [ + "teamName", + "teamFocus" +] + # define allowed label/code combinations combinations = { "eventId": {"playerName": True, "team": False, "action": True, "actionType": True}, @@ -125,18 +130,43 @@ def generateXML( p4Start: int, p5Start: int, codeTag: str, + squad: None, + perspective: None, labels=None, kpis=None, labelSorting: bool = True, sequencing: bool = True, buckets: bool = True ) -> ET.ElementTree: - - # handle kpis and labels defaults + # periodId check + period_start_times = { + 1: p1Start, + 2: p2Start, + 3: p3Start, + 4: p4Start, + 5: p5Start + } + existing_period_ids = events['periodId'].unique() + # Validate the start time for each period that exists in the data + for periodId in existing_period_ids: + if periodId in period_start_times: + start_time = period_start_times[periodId] + # Verify the start time is an integer and within the valid range + if not isinstance(start_time, int) or not start_time > 0: + raise ValueError( + f"Invalid start time for periodId {periodId}. " + f"A valid integer is required, but got: '{start_time}'." + ) + + # handle kpis, labels, squad and perspective defaults if labels is None: labels = [label["name"] for label in allowed_labels if combinations.get(label.get("name")).get(codeTag)] if kpis is None: kpis = [kpi["name"] for kpi in allowed_kpis] + if squad is None or perspective is None: + perspective = "teamName" + elif squad not in events["squadId"].unique(): + raise ValueError(f"Provided squad ID '{squad}' not found in event data.") # check for invalid kpis invalid_kpis = [kpi for kpi in kpis if kpi not in [kpi["name"] for kpi in allowed_kpis]] @@ -152,6 +182,9 @@ def generateXML( if not codeTag in allowed_codes: raise ValueError(f"Invalid Code: {codeTag}") + if not perspective in allowed_perspectives: + raise ValueError(f"Invalid perspective: {perspective}") + # keep only : # - if KPI in kpis # - if Label in labels @@ -159,7 +192,8 @@ def generateXML( labels_and_kpis = [] invalid_labels = [] for label in allowed_labels: - if label.get("name") in labels and label.get("name") != codeTag: # ensure code attribute is not repeated as a label + if label.get("name") in labels and label.get( + "name") != codeTag: # ensure code attribute is not repeated as a label if combinations.get(label.get("name")).get(codeTag): labels_and_kpis.append(label) else: @@ -177,7 +211,6 @@ def generateXML( if labelSorting: labels_and_kpis = sorted(labels_and_kpis, key=lambda x: x["order"]) - # compile periods start times into dict offsets = { "p1": p1Start, @@ -646,20 +679,31 @@ def generateXML( # reset index phases.reset_index(inplace=True) - # merge phase and squadName into one column to later pass into code tag - phases["teamPhase"] = phases["squadName"] + " - " + phases["phase"].str.replace("_", " ") + # Determine how to label team phases: by squadName or by role (home/away) + + if perspective == "teamName": + phases["teamPhase"] = phases["squadName"] + " - " + phases["phase"].str.replace("_", " ") + elif perspective == "teamFocus": + my_squad_id = squad + phases["teamPhase"] = np.where( + phases["squadId"] == my_squad_id, + "mySquad - " + phases["phase"].str.replace("_", " "), + "opponent - " + phases["phase"].str.replace("_", " ") + ) + else: + raise ValueError(f"Invalid value for perspective: {perspective}") # get period starts # filter for kick off events of each period kickoffs = events.copy()[ (events.actionType == "KICK_OFF") & ((events.gameTimeInSec - (events.periodId - 1) * 10000) < 10) - ].reset_index() + ].reset_index() # check for penalty shootout penalty_shootout = events.copy()[ events.periodId == 5 - ] + ] # add row for start of penalty shootout if len(penalty_shootout) > 0: @@ -786,7 +830,7 @@ def get_bucket(bucket, value, zero_value, error_value): seq_id_current = None # If the selected code attribute is "squadName", generate XML entries from the `phases` DataFrame - if codeTag == "squadName": + if codeTag == "team": for index, phase in phases.iterrows(): # Create a new XML instance for each team phase instance = ET.SubElement(instances, "instance") @@ -873,7 +917,7 @@ def get_bucket(bucket, value, zero_value, error_value): seq_id_current = seq_id_new else: # Same logic as above, but without sequencing (i.e., one clip per row) - if codeTag == "squadName": + if codeTag == "team": for index, phase in phases.iterrows(): instance = ET.SubElement(instances, "instance") event_id = ET.SubElement(instance, "ID") @@ -968,7 +1012,7 @@ def row(value, colors): # call function row(player, home_colors) - elif codeTag == "squadName": + elif codeTag == "team": # add entries for away team phases for phase in away_phases: # call function diff --git a/impectPy/impect.py b/impectPy/impect.py index d8b8334..e6b1dc6 100644 --- a/impectPy/impect.py +++ b/impectPy/impect.py @@ -9,12 +9,12 @@ from .player_scores import getPlayerMatchScoresFromHost, getPlayerIterationScoresFromHost from .squad_scores import getSquadMatchScoresFromHost, getSquadIterationScoresFromHost from .player_profile_scores import getPlayerProfileScoresFromHost -from .xml import generateXML +from .generate_xml import generateXML from .set_pieces import getSetPiecesFromHost from .squad_ratings import getSquadRatingsFromHost from .match_info import getFormationsFromHost, getSubstitutionsFromHost, getStartingPositionsFromHost import pandas as pd -from xml.etree import ElementTree as ET +from generate_xml.etree import ElementTree as ET class Impect: