From cb89a6119694990b697e55d71d6114e76684b2e1 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Fri, 30 Jan 2026 17:28:40 -0600 Subject: [PATCH] Fix PostgreSQL upsert column names and CSV null handling - Fix upsert_many() to use column_name for EXCLUDED references (ForeignKeyField columns end in _id, e.g., batter -> batter_id) - Add null checks in batting/pitching CSV output for player, team, game fields to prevent 'NoneType' not subscriptable errors Co-Authored-By: Claude Opus 4.5 --- app/db_helpers.py | 15 +++++++------ app/routers_v2/stratplays.py | 42 ++++++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/app/db_helpers.py b/app/db_helpers.py index 666dc52..9bf6577 100644 --- a/app/db_helpers.py +++ b/app/db_helpers.py @@ -100,12 +100,15 @@ def upsert_many( # Build conflict target - get actual field objects conflict_target = [getattr(model, f) for f in conflict_fields] - # Build update dict - update_dict = { - getattr(model, f): EXCLUDED[f] - for f in update_fields - if hasattr(model, f) - } + # Build update dict - use column_name for EXCLUDED reference + # (ForeignKeyField column names end in _id, e.g., batter -> batter_id) + update_dict = {} + for f in update_fields: + if hasattr(model, f): + field_obj = getattr(model, f) + # Get the actual column name from the field + col_name = field_obj.column_name + update_dict[field_obj] = EXCLUDED[col_name] if update_dict: model.insert_many(batch).on_conflict( diff --git a/app/routers_v2/stratplays.py b/app/routers_v2/stratplays.py index fd81782..67e11ca 100644 --- a/app/routers_v2/stratplays.py +++ b/app/routers_v2/stratplays.py @@ -782,17 +782,24 @@ async def get_batting_totals( ) for x in return_vals: - x["player_id"] = x["player"]["player_id"] - x["player_name"] = x["player"]["p_name"] - x["player_cardset"] = x["player"]["cardset"]["name"] - x["team_id"] = x["team"]["id"] - x["team_abbrev"] = x["team"]["abbrev"] - if "id" in x["game"]: + x["player_id"] = x["player"]["player_id"] if x["player"] else None + x["player_name"] = x["player"]["p_name"] if x["player"] else None + x["player_cardset"] = ( + x["player"]["cardset"]["name"] + if x["player"] and x["player"].get("cardset") + else None + ) + x["team_id"] = x["team"]["id"] if x["team"] else None + x["team_abbrev"] = x["team"]["abbrev"] if x["team"] else None + if x.get("game") and isinstance(x["game"], dict) and "id" in x["game"]: x["game_id"] = x["game"]["id"] if "game_type" in x["game"]: x["game_type"] = x["game"]["game_type"] del x["game"] - del x["player"], x["team"] + if x.get("player"): + del x["player"] + if x.get("team"): + del x["team"] output = pd.DataFrame(return_vals) first = ["player_id", "player_name", "player_cardset", "team_id", "team_abbrev"] @@ -1174,17 +1181,24 @@ async def get_pitching_totals( ) for x in return_vals: - x["player_id"] = x["player"]["player_id"] - x["player_name"] = x["player"]["p_name"] - x["player_cardset"] = x["player"]["cardset"]["name"] - x["team_id"] = x["team"]["id"] - x["team_abbrev"] = x["team"]["abbrev"] - if "id" in x["game"]: + x["player_id"] = x["player"]["player_id"] if x["player"] else None + x["player_name"] = x["player"]["p_name"] if x["player"] else None + x["player_cardset"] = ( + x["player"]["cardset"]["name"] + if x["player"] and x["player"].get("cardset") + else None + ) + x["team_id"] = x["team"]["id"] if x["team"] else None + x["team_abbrev"] = x["team"]["abbrev"] if x["team"] else None + if x.get("game") and isinstance(x["game"], dict) and "id" in x["game"]: x["game_id"] = x["game"]["id"] if "game_type" in x["game"]: x["game_type"] = x["game"]["game_type"] del x["game"] - del x["player"], x["team"] + if x.get("player"): + del x["player"] + if x.get("team"): + del x["team"] output = pd.DataFrame(return_vals) first = ["player_id", "player_name", "player_cardset", "team_id", "team_abbrev"]