Skip to content
2 changes: 1 addition & 1 deletion impectPy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
70 changes: 57 additions & 13 deletions impectPy/xml.py → impectPy/generate_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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]]
Expand All @@ -152,14 +182,18 @@ 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
# - if code matches legend
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:
Expand All @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion impectPy/impect.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
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
Expand Down