Skip to content

fix(models): flatten MiddleSide to the wire; alias confidence; add quality_tier - #20

Merged
Mlaz-code merged 2 commits into
mainfrom
fix/18-middles-wire-drift
Jul 28, 2026
Merged

fix(models): flatten MiddleSide to the wire; alias confidence; add quality_tier#20
Mlaz-code merged 2 commits into
mainfrom
fix/18-middles-wire-drift

Conversation

@Mlaz-code

Copy link
Copy Markdown
Contributor

Fixes #18.

The two defects, reproduced against live payloads

MiddleOpportunity.model_validate(<live row>)
  -> 2 validation errors
     side1.odds  Field required
     side2.odds  Field required

EVOpportunity.model_validate(<live row>)
  -> parsed, but: wire confidence=80  ->  model.confidence_score=None
                  wire quality_tier='A'  ->  model has no such attribute

client.middles() could not parse any successful response. confidence_score read None on every row.

One correction to the issue

#18 says the wire sends sportsbook rather than book. It doesn't — live sides send book, which the model already accepted. The only field that broke parsing was the nested odds. Changing book on that report would have introduced a bug rather than fixed one.

Live side shape, all 12 fields present on 4/4 sampled sides:

book  selection  line  odds_american  odds_decimal  odds_probability
fair_probability  stake_percent  odds_age_seconds
external_event_id  market_id  selection_id

Choices worth flagging

confidence_score is aliased, not renamed. AliasChoices("confidence", "confidence_score") matches the file's existing idiom (fair_probability/true_probability, sharp_book/devig_book) and keeps the public attribute name, so nobody's code breaks.

MiddleSide is a hard shape changeside.odds.american becomes side.odds_american. Normally breaking; here it cannot be, because the model never parsed a response, so no working consumer exists to break.

Bonus defect found while checking the release path

pyproject.toml = 0.4.1, __init__.py = 0.4.0. #13 bumped one and missed the other, and 0.4.1 is already on PyPI — so the published package reports sharpapi.__version__ == "0.4.0". PyPI is immutable, so that stays wrong for 0.4.1 forever; the test here only stops the next one. Synced to 0.4.1 (matching what is published), which is a correction, not a release bump.

Tests — 9 new, all against captured live payloads

tests/test_wire_contract.py + tests/fixtures/{middles,ev}_live.json. Hand-written dicts could never have caught this: they'd be written from the same wrong belief as the model.

Two notes on what is deliberately not asserted, both discovered by writing the check and watching it misfire:

  • "every declared field is populated by some payload" — dropped. A 2-row fixture cannot tell "the SDK declares a field the wire never sends" from "this sample lacked a conditional field." It flagged sharp_odds_american, is_suspended and the *_ref objects, all legitimately conditional.
  • importlib.metadata.version() for the version check — replaced with a direct read of pyproject.toml. Dist metadata is only as fresh as the last pip install; the editable install on the dev box reports 0.2.0, so that test would have passed in CI and failed locally for a reason unrelated to the bug.

Run locally with PYTHONPATH=src — the editable install resolves to the canonical checkout, not a worktree.

Release

Not bumping the release version — this repo lands those as separate chore(release): commits, and PyPI needs a gh release create to publish. This fix should land before the docs audit's Wave 1, since the docs mirror these model shapes and the SDK is the source they follow.

Type: fix

root and others added 2 commits July 27, 2026 12:32
…ality_tier

MiddleSide declared a required `odds: OddsValue`. The wire sends odds flat, so
client.middles() raised ValidationError on every response carrying a side.

EVOpportunity.confidence_score had no alias for the wire's `confidence`, so it
silently read None on every row. Added quality_tier (shipped 2026-07-14).

Also syncs __version__ 0.4.0 -> 0.4.1; #13 bumped pyproject and missed it, so
published 0.4.1 reports __version__ == 0.4.0.

Adds tests/test_wire_contract.py pinning all of it against captured live payloads.

Fixes #18

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The workflow is named "Tests" and has never run one — it installed ruff and
pyright, linted and type-checked tests/, and stopped there. tests/test_base_url.py
has existed since 0.3.x without ever executing in CI.

#18 asks for a fixture round-trip 'so model drift fails CI'. That only works if
the suite runs. Also fixes the pyright failure this PR surfaced: 'import pytest
could not be resolved', because the test extra was never installed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Mlaz-code
Mlaz-code merged commit 4d854b6 into main Jul 28, 2026
4 checks passed
@Mlaz-code
Mlaz-code deleted the fix/18-middles-wire-drift branch July 28, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Models drifted from wire: MiddleSide rejects live /opportunities/middles payloads; EVOpportunity.confidence_score never populates

1 participant