Skip to content

Simplify float stat conversion using Pydantic coercion #340

Closed

Description

@Mattsface

Summary

PR #241 adds a custom BeforeValidator / OptionalFloat type to convert MLB Stats API string values such as ".287" into Python floats.

Pydantic already performs this coercion for fields typed as float or Optional[float] (float | None), so the custom conversion logic is unnecessary for normal numeric strings.

Related PR: #241

Proposed change

Replace uses of the custom OptionalFloat type with standard Pydantic annotations, for example:

avg: Optional[float] = None
ops: Optional[float] = None
slg: Optional[float] = None

Pydantic will convert values such as:

".287" -> 0.287
"1.024" -> 1.024

without a custom validator.

MLB sentinel values

Before removing the validator entirely, verify whether the MLB Stats API can return non-numeric sentinel values for these fields, such as:

.---
-
""

If those values occur, normalization should handle only those MLB-specific sentinel values and allow Pydantic to perform the normal string-to-float coercion.

We should avoid silently converting every invalid value to None, since that can hide unexpected API data or model errors.

Acceptance criteria

  • Verify representative MLB responses for numeric float fields and identify any sentinel/non-numeric values.
  • Replace OptionalFloat with Optional[float] / float | None where standard Pydantic coercion is sufficient.
  • Remove the generic float_or_none / BeforeValidator conversion if it is no longer needed.
  • If MLB sentinel values require normalization, handle only the known sentinel values explicitly.
  • Add or update tests covering numeric strings, None, and any known MLB sentinel values.
  • Ensure the full test suite passes.

Activity

  1. Mattsface commented on Sep 3, 2026

    @Mattsface
    MemberAuthor

    Fields to update from PR #241

    The following string-backed stat fields are candidates to replace with standard Optional[float] / float | None, allowing Pydantic to handle numeric-string coercion directly.

    SimpleCatchingSplit

    • avg
    • obp
    • slg
    • ops
    • caught_stealing_percentage
    • stolen_base_percentage
    • strikeout_walk_ratio

    SimpleFieldingSplit

    • caught_stealing_percentage
    • stolen_base_percentage
    • fielding
    • range_factor_per_game
    • range_factor_per_9_inn
    • catcher_era

    AdvancedHittingSplit

    • babip
    • pitches_per_plate_appearance
    • walks_per_plate_appearance
    • strikeouts_per_plate_appearance
    • home_runs_per_plate_appearance
    • walks_per_strikeout
    • iso

    SimpleHittingSplit

    • avg
    • obp
    • slg
    • ops
    • caught_stealing_percentage
    • stolen_base_percentage
    • babip
    • groundouts_to_airouts
    • at_bats_per_home_run

    SimplePitchingSplit

    • avg
    • obp
    • slg
    • ops
    • caught_stealing_percentage
    • stolen_base_percentage
    • era
    • whip
    • strike_percentage
    • groundouts_to_airouts
    • win_percentage
    • pitches_per_inning
    • strikeout_walk_ratio
    • strikeouts_per_9_inn
    • walks_per_9_inn
    • hits_per_9_inn
    • runs_scored_per_9
    • home_runs_per_9

    AdvancedPitchingSplit

    • winning_percentage
    • runs_scored_per_9
    • babip
    • obp
    • slg
    • ops
    • strikeouts_per_9
    • base_on_balls_per_9
    • home_runs_per_9
    • hits_per_9
    • strikeouts_to_walks
    • strikeouts_minus_walks_percentage
    • gidp_percentage
    • batters_faced_per_game
    • whiff_percentage
    • strike_percentage
    • pitches_per_inning
    • pitches_per_plate_appearance
    • walks_per_plate_appearance
    • strikeouts_per_plate_appearance
    • home_runs_per_plate_appearance
    • walks_per_strikeout
    • iso
    • flyball_percentage

    ExpectedStatistics

    • avg
    • slg
    • woba
    • wobacon

    The baseball-notation innings fields are intentionally excluded from this conversion list:

    • SimpleFieldingSplit.innings
    • SimplePitchingSplit.innings_pitched
    • AdvancedPitchingSplit.innings_pitched_per_game

    They should remain outside the scope of this issue because values such as "6.2" represent 6 2/3 innings rather than the mathematical decimal 6.2.

  2. added a commit that references this issue on Sep 7, 2026
    113c880
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions