Skip to content

feat: add optional team_side + market_segment to odds/EV/closing models (#76, #689) - #13

Merged
Mlaz-code merged 1 commit into
mainfrom
feat/team-side-market-segment-76
Jun 3, 2026
Merged

feat: add optional team_side + market_segment to odds/EV/closing models (#76, #689)#13
Mlaz-code merged 1 commit into
mainfrom
feat/team-side-market-segment-76

Conversation

@Mlaz-code

Copy link
Copy Markdown
Contributor

Adds the structured team_side + market_segment axes the SharpAPI now emits (sharp-api-go #76 Phase C / #689) to the response models.

  • OddsLine, EVOpportunity, ClosingOddsLine each gain optional team_side ("home"|"away"|"draw") and market_segment ("full_game", "1st_half", ...).
  • Additive, default None; existing code unaffected.
  • The API keeps emitting the compound selection_type for back-compat (Go re-synthesizes it), so this is purely additive on the consumer side.

Smoke-tested model instantiation with the new fields. Bump 0.3.3 → 0.3.4.

Type: feat

…ls (#76, #689)

OddsLine, EVOpportunity, ClosingOddsLine each gain optional team_side
("home"|"away"|"draw") + market_segment ("full_game","1st_half",...). Surfaces
the structured axes the API now emits (sharp-api-go #76 Phase C / #689).
Additive, default None. Bump 0.4.0 → 0.4.1.

Type: feat

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Mlaz-code
Mlaz-code force-pushed the feat/team-side-market-segment-76 branch from 362b102 to 3a33100 Compare June 3, 2026 02:41
@Mlaz-code
Mlaz-code merged commit 31ba1c1 into main Jun 3, 2026
4 checks passed
@Mlaz-code
Mlaz-code deleted the feat/team-side-market-segment-76 branch June 3, 2026 02:45
Mlaz-code added a commit that referenced this pull request Jul 28, 2026
…ality_tier (#20)

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 change** — `side.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

---------

Co-authored-by: root <root@api-dev.hs.chocopancake.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant