spforge 0.8.26__py3-none-any.whl → 0.8.27__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of spforge might be problematic. Click here for more details.

@@ -2,8 +2,10 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import copy
5
- import math
5
+ import json
6
6
  import logging
7
+ import math
8
+ from collections.abc import Mapping
7
9
  from typing import Any, Literal
8
10
 
9
11
  import narwhals.stable.v2 as nw
@@ -912,10 +914,16 @@ class PlayerRatingGenerator(RatingGenerator):
912
914
  if cn.league and cn.league in df.columns:
913
915
  player_stat_cols.append(cn.league)
914
916
 
915
- if cn.team_players_playing_time and cn.team_players_playing_time in df.columns:
917
+ if (
918
+ cn.team_players_playing_time
919
+ and cn.team_players_playing_time in df.columns
920
+ ):
916
921
  player_stat_cols.append(cn.team_players_playing_time)
917
922
 
918
- if cn.opponent_players_playing_time and cn.opponent_players_playing_time in df.columns:
923
+ if (
924
+ cn.opponent_players_playing_time
925
+ and cn.opponent_players_playing_time in df.columns
926
+ ):
919
927
  player_stat_cols.append(cn.opponent_players_playing_time)
920
928
 
921
929
  df = df.with_columns(pl.struct(player_stat_cols).alias(PLAYER_STATS))
@@ -948,6 +956,40 @@ class PlayerRatingGenerator(RatingGenerator):
948
956
  match_df = self._add_day_number(match_df, cn.start_date, "__day_number")
949
957
  return match_df
950
958
 
959
+ def _get_players_playing_time(
960
+ self, source: Mapping[str, Any], column_name: str | None
961
+ ) -> dict[str, float] | None:
962
+ if not column_name:
963
+ return None
964
+ return self._normalize_players_playing_time(source.get(column_name))
965
+
966
+ @staticmethod
967
+ def _normalize_players_playing_time(raw_value: Any) -> dict[str, float] | None:
968
+ if raw_value is None:
969
+ return None
970
+
971
+ if isinstance(raw_value, str):
972
+ raw_text = raw_value
973
+ raw_value = raw_value.strip()
974
+ if not raw_value:
975
+ return None
976
+ try:
977
+ raw_value = json.loads(raw_value)
978
+ except json.JSONDecodeError as exc:
979
+ raise ValueError(
980
+ f"unable to parse playing time JSON {raw_text!r}: {exc}"
981
+ ) from exc
982
+
983
+ if isinstance(raw_value, Mapping):
984
+ normalized: dict[str, float] = {}
985
+ for key, value in raw_value.items():
986
+ if value is None:
987
+ continue
988
+ normalized[str(key)] = float(value)
989
+ return normalized or None
990
+
991
+ return None
992
+
951
993
  def _create_pre_match_players_collection(
952
994
  self, r: dict, stats_col: str, day_number: int, team_id: str
953
995
  ) -> PreMatchPlayersCollection:
@@ -994,17 +1036,12 @@ class PlayerRatingGenerator(RatingGenerator):
994
1036
  else None
995
1037
  )
996
1038
 
997
- team_playing_time = None
998
- opponent_playing_time = None
999
- if cn.team_players_playing_time:
1000
- raw_value = team_player.get(cn.team_players_playing_time)
1001
- if raw_value is not None:
1002
- team_playing_time = raw_value
1003
-
1004
- if cn.opponent_players_playing_time:
1005
- raw_value = team_player.get(cn.opponent_players_playing_time)
1006
- if raw_value is not None:
1007
- opponent_playing_time = raw_value
1039
+ team_playing_time = self._get_players_playing_time(
1040
+ team_player, cn.team_players_playing_time
1041
+ )
1042
+ opponent_playing_time = self._get_players_playing_time(
1043
+ team_player, cn.opponent_players_playing_time
1044
+ )
1008
1045
 
1009
1046
  mp = MatchPerformance(
1010
1047
  performance_value=perf_val,
@@ -1245,16 +1282,12 @@ class PlayerRatingGenerator(RatingGenerator):
1245
1282
  ppw = pw
1246
1283
  proj_w.append(float(ppw))
1247
1284
 
1248
- team_playing_time = None
1249
- opponent_playing_time = None
1250
- if cn.team_players_playing_time:
1251
- raw_value = tp.get(cn.team_players_playing_time)
1252
- if raw_value is not None:
1253
- team_playing_time = raw_value
1254
- if cn.opponent_players_playing_time:
1255
- raw_value = tp.get(cn.opponent_players_playing_time)
1256
- if raw_value is not None:
1257
- opponent_playing_time = raw_value
1285
+ team_playing_time = self._get_players_playing_time(
1286
+ tp, cn.team_players_playing_time
1287
+ )
1288
+ opponent_playing_time = self._get_players_playing_time(
1289
+ tp, cn.opponent_players_playing_time
1290
+ )
1258
1291
 
1259
1292
  mp = MatchPerformance(
1260
1293
  performance_value=get_perf_value(tp),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spforge
3
- Version: 0.8.26
3
+ Version: 0.8.27
4
4
  Summary: A flexible framework for generating features, ratings, and building machine learning or other models for training and inference on sports data.
5
5
  Author-email: Mathias Holmstrøm <mathiasholmstom@gmail.com>
6
6
  License: See LICENSE file
@@ -51,7 +51,7 @@ spforge/performance_transformers/_performance_manager.py,sha256=WmjmlMEnq7y75MiI
51
51
  spforge/performance_transformers/_performances_transformers.py,sha256=0lxuWjAfWBRXRgQsNJHjw3P-nlTtHBu4_bOVdoy7hq4,15536
52
52
  spforge/ratings/__init__.py,sha256=OZVH2Lo6END3n1X8qi4QcyAPlThIwAYwVKCiIuOQSQU,576
53
53
  spforge/ratings/_base.py,sha256=ne4BRrYFPqMirdFPVnyDN44wjFQwOQgWoUXu_59xgWE,14687
54
- spforge/ratings/_player_rating.py,sha256=zltf4utwzKQxkTA8DAPZ4LWRDlwGxoiKFaiPIo4sdNw,60323
54
+ spforge/ratings/_player_rating.py,sha256=TDw0LM-sLn27fprUhOW5csaDqAhzagoVm8SPKipZZmg,61106
55
55
  spforge/ratings/_team_rating.py,sha256=3m90-R2zW0k5EHwjw-83Hacz91fGmxW1LQ8ZUGHlgt4,24970
56
56
  spforge/ratings/enums.py,sha256=s7z_RcZS6Nlgfa_6tasO8_IABZJwywexe7sep9DJBgo,1739
57
57
  spforge/ratings/league_identifier.py,sha256=_KDUKOwoNU6RNFKE5jju4eYFGVNGBdJsv5mhNvMakfc,6019
@@ -71,7 +71,7 @@ spforge/transformers/_other_transformer.py,sha256=w2a7Wnki3vJe4GAkSa4kealw0GILIo
71
71
  spforge/transformers/_predictor.py,sha256=2sE6gfVrilXzPVcBurSrtqHw33v2ljygQcEYXt9LhZc,3119
72
72
  spforge/transformers/_simple_transformer.py,sha256=zGUFNQYMeoDSa2CoQejQNiNmKCBN5amWTvyOchiUHj0,5660
73
73
  spforge/transformers/_team_ratio_predictor.py,sha256=g8_bR53Yyv0iNCtol1O9bgJSeZcIco_AfbQuUxQJkeY,6884
74
- spforge-0.8.26.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
74
+ spforge-0.8.27.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
75
75
  tests/test_autopipeline.py,sha256=7cNAn-nmGolfyfk3THh9IKcHZfRA-pLYC_xAyMg-No4,26863
76
76
  tests/test_autopipeline_context.py,sha256=IuRUY4IA6uMObvbl2pXSaXO2_tl3qX6wEbTZY0dkTMI,1240
77
77
  tests/test_feature_generator_pipeline.py,sha256=CK0zVL8PfTncy3RmG9i-YpgwjOIV7yJhV7Q44tbetI8,19020
@@ -94,7 +94,7 @@ tests/hyperparameter_tuning/test_estimator_tuner.py,sha256=iewME41d6LR2aQ0OtohGF
94
94
  tests/hyperparameter_tuning/test_rating_tuner.py,sha256=usjC2ioO_yWRjjNAlRTyMVYheOrCi0kKocmHQHdTmpM,18699
95
95
  tests/performance_transformers/test_performance_manager.py,sha256=gjuuV_hb27kCo_kUecPKG3Cbot2Gqis1W3kw2A4ovS4,10690
96
96
  tests/performance_transformers/test_performances_transformers.py,sha256=A-tGiCx7kXrj1cVj03Bc7prOeZ1_Ryz8YFx9uj3eK6w,11064
97
- tests/ratings/test_player_rating_generator.py,sha256=Z66LN1-YdUHrS6dszWZf4HeENRyH8oEtu4Nlsh1MpMI,82442
97
+ tests/ratings/test_player_rating_generator.py,sha256=tpU83Orw1nlus29a0s9vc1pghL-f2rs642viW_6wFgk,83633
98
98
  tests/ratings/test_player_rating_no_mutation.py,sha256=GzO3Hl__5K68DS3uRLefwnbcTJOvBM7cZqww4M21UZM,8493
99
99
  tests/ratings/test_ratings_property.py,sha256=ckyfGILXa4tfQvsgyXEzBDNr2DUmHwFRV13N60w66iE,6561
100
100
  tests/ratings/test_team_rating_generator.py,sha256=SqQcfckNmJJc99feCdnmkNYDape-p69e92Dp8Vzpu2w,101156
@@ -107,7 +107,7 @@ tests/transformers/test_other_transformer.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
107
107
  tests/transformers/test_predictor_transformer.py,sha256=N1aBYLjN3ldpYZLwjih_gTFYSMitrZu-PNK78W6RHaQ,6877
108
108
  tests/transformers/test_simple_transformer.py,sha256=wWR0qjLb_uS4HXrJgGdiqugOY1X7kwd1_OPS02IT2b8,4676
109
109
  tests/transformers/test_team_ratio_predictor.py,sha256=fOUP_JvNJi-3kom3ZOs1EdG0I6Z8hpLpYKNHu1eWtOw,8562
110
- spforge-0.8.26.dist-info/METADATA,sha256=zywZZIfNsJ6DhREXxcqGD14itLC2woDadHSANqD61Ek,20048
111
- spforge-0.8.26.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
112
- spforge-0.8.26.dist-info/top_level.txt,sha256=6UW2M5a7WKOeaAi900qQmRKNj5-HZzE8-eUD9Y9LTq0,23
113
- spforge-0.8.26.dist-info/RECORD,,
110
+ spforge-0.8.27.dist-info/METADATA,sha256=Bl0sOhG9rDGmQpTThxwNPlAKyXeR6dCxWlGmbHH0LN0,20048
111
+ spforge-0.8.27.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
112
+ spforge-0.8.27.dist-info/top_level.txt,sha256=6UW2M5a7WKOeaAi900qQmRKNj5-HZzE8-eUD9Y9LTq0,23
113
+ spforge-0.8.27.dist-info/RECORD,,
@@ -4,7 +4,7 @@ import polars as pl
4
4
  import pytest
5
5
 
6
6
  from spforge import ColumnNames
7
- from spforge.data_structures import RatingState
7
+ from spforge.data_structures import PlayerRating, RatingState
8
8
  from spforge.ratings import PlayerRatingGenerator, RatingKnownFeatures, RatingUnknownFeatures
9
9
 
10
10
 
@@ -78,6 +78,39 @@ def test_fit_transform_updates_internal_state(base_cn, sample_df):
78
78
  assert "P1" in gen._player_off_ratings
79
79
  assert "P1" in gen._player_def_ratings
80
80
 
81
+
82
+ def test_pre_match_collection_parses_playing_time_json(base_cn):
83
+ """JSON strings in the team/opponent playing time columns should become dicts."""
84
+ from dataclasses import replace
85
+
86
+ cn = replace(
87
+ base_cn,
88
+ team_players_playing_time="team_pt",
89
+ opponent_players_playing_time="opp_pt",
90
+ )
91
+ gen = PlayerRatingGenerator(performance_column="perf", column_names=cn)
92
+ gen._player_off_ratings["P1"] = PlayerRating(id="P1", rating_value=100.0)
93
+ gen._player_def_ratings["P1"] = PlayerRating(id="P1", rating_value=100.0)
94
+
95
+ stats_entry = {
96
+ cn.player_id: "P1",
97
+ "perf": 0.75,
98
+ cn.participation_weight: 1.0,
99
+ cn.team_players_playing_time: '{"P1": 30}',
100
+ cn.opponent_players_playing_time: '{"P3": 25}',
101
+ }
102
+
103
+ collection = gen._create_pre_match_players_collection(
104
+ r={"__PLAYER_STATS": [stats_entry]},
105
+ stats_col="__PLAYER_STATS",
106
+ day_number=1,
107
+ team_id="T1",
108
+ )
109
+
110
+ match_perf = collection.pre_match_player_ratings[0].match_performance
111
+ assert match_perf.team_players_playing_time == {"P1": 30.0}
112
+ assert match_perf.opponent_players_playing_time == {"P3": 25.0}
113
+
81
114
  assert gen._player_off_ratings["P1"].rating_value > 0
82
115
 
83
116