Skip to content

feat(schema): odds and standings models of P28 in schema v1 [FX-21] - #164

Merged
tunjayoff merged 4 commits into
mainfrom
feat/fx-21
Oct 6, 2026
Merged

tunjayoff merged 4 commits into
mainfrom
feat/fx-21

Conversation

@tunjayoff

Copy link
Copy Markdown
Owner

Behaviour change: the JSON Schema and ssc describe schemas now include the five models Odds, OddsMarket, OddsChoice, OddsLine and StandingsRow. Odds, OddsLine and StandingsRow are listed as records. The API v1 responses and the export files are unchanged; only the descriptions of some score fields in the API document change.

Item: FX-21 in docs/design/03-implementation-plan.md, section 10 (order 61b, lane contract). Design: 04-schema-v1.md section 4, "Odds and standings". Depends on P28 (#140).

What was done

Four commits. Each one passes on its own.

1. P28's models in schema v1 (bd14bf5)

  • src/schema/models.py:
    • MODELS gains Odds, OddsMarket, OddsChoice, OddsLine and StandingsRow, in that order, after LiveEvent.
    • RECORDS gains Odds, OddsLine and StandingsRow.
    • PENDING_MODELS is removed.
  • 04-schema-v1.md:
    • The five tables that docs(design): status and findings after P27 to FX-19 and the newcomer pass #162 added are now wrapped in <!-- fields:<Model> --> markers, so they are generated and test-checked blocks. REGEN_SCHEMA_DOC=1 left the tables unchanged, because they already matched the models.
    • The note "The table below is copied from the model … not yet a generated block" is removed from each of the five blocks.
    • One json example: block each is added for Odds, OddsLine and StandingsRow. The document test requires an example for every record, and validates it against the JSON Schema. The values come from the mappers applied to tests/fixtures/p28.
  • tests/golden/schema/json_schema.json regenerated:
    • five new $defs;
    • x-records gains Odds, OddsLine and StandingsRow;
    • no existing definition changed.
  • ssc describe schemas prints schema.describe(), so its data.records and data.schema change in the same way. No CLI golden contains this output, so no other golden changed.

2. Slice.key known values (755e6da)

  • The known values now include innings and the 13 keys of P28's odds and owner slices. Slice.key is an open set, so this changes the examples only.
  • New test test_slice_key_lists_every_registered_slice:
    • every key of registered_slices() must be a known value;
    • the only known values outside the registry are event, seasons and schedule;
    • every registry owner must be a known owner_kind.
  • Regenerated: the Slice.key examples in the JSON Schema golden, and the Slice table of 04-schema-v1.md.

3. Heading of section 9 (4a38394)

  • ## 9. Open questions is renamed ## 9. Decisions.
  • The test assertion that pinned the old heading now pins the new one, and also checks that the old heading is gone.

4. Text pass over the score texts (6b09d46)

  • Only descriptions change. No unit, type or name changes, so SCHEMA_VERSION stays 1. Each claim below was checked against src/status.py.
  • SetScore.home / away: per set, the value is games in tennis and padel (points in a match tie-break), points in volleyball, badminton and table tennis, and legs in darts played in sets.
  • SetScore.tiebreak: always null outside tennis and padel.
  • SetsScore.home / away / sets_won: with the formats frames, legs_won and games_won, the value is the frames, legs or games won.
  • SetsScore.sets:
    • empty for those three formats;
    • source changed to period1 to period7 (tennis: to period5), with tie-breaks only in tennis and padel.
  • SetsScore.match_tiebreak: always false outside tennis and padel.
  • PeriodsScore:
    • home and away count goals in the goal sports (ice hockey, handball, futsal, minifootball, floorball);
    • periods, regulation, overtime and final say "(goals in the goal sports)";
    • format names which sports use quarters, halves and thirds.
  • Regenerated: the JSON Schema golden, the field tables of 04-schema-v1.md, docs/api/openapi-v1.json (python -m src.web.openapi --write) and frontend/src/api/v1/schema.ts (node frontend/scripts/gen-api-types.mjs).
  • Every difference in docs/api/openapi-v1.json: 15 description strings in the components PeriodsScore, SetScore and SetsScore. No path, operation or type changed.
  • Every difference in schema.ts: the same 15 doc comments.

How it was verified

  • Full suite with STORE_SHADOW_CHECK=1, run in the foreground, with TMPDIR and --basetemp on ~/.cache/fx21-tmp:
  • The +17 are:
    • 15 parametrized cases for the five new models: the field contract and the JSON Schema rules in tests/test_schema_v1.py, and the pydantic mirror against the contract in tests/test_api_v1_resources.py;
    • two new tests: test_slice_key_lists_every_registered_slice, and test_odds_and_standings_records_follow_the_json_schema. The second maps the recorded odds_all, odds_featured and standings_total bodies and validates every Odds, OddsLine and StandingsRow record against the JSON Schema. It also checks that a wrong type and a missing field are refused, and that an unknown table value is accepted.
  • ruff check . is clean.
  • python -m src.web.openapi --check passes, and node frontend/scripts/gen-api-types.mjs --check passes.
  • REGEN_SCHEMA_DOC=1 and REGEN_SCHEMA_GOLDEN=1 produce no further change.
  • No network. The store boundary baseline and tests/snapshots/openapi-legacy.json are unchanged, and no route changed.
  • Not run: vitest (no node_modules in the worktree; apiTypes.test.ts makes the same check as --check), Windows, macOS.

Existing tests changed

  • tests/test_odds_slices.py::test_the_new_models_carry_their_contract (outside ownership). It took its models from models.PENDING_MODELS and asserted model not in models.MODELS, which is exactly what this item reverses. It now names the five models and asserts model in models.MODELS. Its field-contract checks are unchanged.
  • tests/test_schema_v1.py (owned):
    • test_version_and_ids: the expected RECORDS list gains the three records. It also checks the five models at the end of MODELS and that PENDING_MODELS is gone.
    • test_record_schema_and_describe: it used "Odds" as its example of an unknown record. It now uses "Standings" and checks that record_schema("Odds") works.
    • test_document_states_the_version_and_its_examples_are_valid: the heading assertion (step 3).

Files outside ownership

  • tests/test_odds_slices.py: see above.
  • docs/api/openapi-v1.json: a generated file (rule 2). It is regenerated because the score descriptions changed.
  • frontend/src/api/v1/schema.ts: generated from that document. The frontend test apiTypes.test.ts fails if it is stale. Earlier items regenerated it the same way (P28, FX-13, FX-19). FX-20 owns frontend/** in parallel. If it also regenerates the file, the conflict is resolved by running node frontend/scripts/gen-api-types.mjs again after the rebase. If that is not acceptable, commit 6b09d46 (the text pass) can be dropped on its own; the first three commits do not touch these files.
  • docs/design/04-schema-v1.md: beyond the generated blocks and the heading, I added the three json example: blocks and removed the five "copied from the model" notes (see Deviations).

Deviations and why

  • Three example blocks added to 04-schema-v1.md. The brief allows only the generated blocks and the heading. But test_document_states_the_version_and_its_examples_are_valid requires a json example: for every model in RECORDS, so the brief's move into RECORDS cannot pass without them. The test validates them against the JSON Schema.
  • The five "The table below is copied from the model …" notes removed. They described the state that this item ends.
  • Text pass, "frames" per set. The brief says the per-set values are "points, frames, legs or games". In the code, snooker (frames) has no set list: _COUNT_ONLY_UNITS in src/status.py, and its period1 repeats current. The SetScore text therefore names games, points and legs. The frames appear in SetsScore.home, away and sets_won.
  • PENDING_MODELS removed, not left as an empty tuple. An empty tuple would leave the parametrized test in tests/test_odds_slices.py with no cases, so it would be skipped without failing.
  • The P28 models are not exported from src/schema/__init__.py. They are reached as src.schema.models.Odds, as before. The brief did not ask for it, and the file is not owned.

Design mismatches

04-schema-v1.md prose that this item makes stale. A batch PR may not edit it, so it is left for the docs PR:

  • Lines 22–29 (the "Revised on 2026-10-06" paragraph) say the five models "are in models.PENDING_MODELS, not in MODELS and RECORDS" and that describe schemas and the golden do not show them.
  • Section 1, "Not part of version 1": "they are not in version 1's MODELS yet (FX-21)". The table "Records of version 1" has no rows for Odds, OddsLine and StandingsRow. Suggested "Given out by" values:
    • Odds: /events/{id}/odds/{key};
    • OddsLine: export dataset odds;
    • StandingsRow: /seasons/{id}/standings, export dataset standings.
  • Section 4, Slice: two paragraphs need updating.
    • "The known values of Slice.key in the field table above do not list innings yet …" and "… do not list them, nor innings; FX-21 adds them": both are now done.
    • "More keys come with P28 (odds, standings, …)": these keys exist now.
  • Section 4, "Odds and standings": the paragraph "These models are in models.PENDING_MODELS (src/schema/models.py:845) …".
  • Section 8: the bullet "Since P28 (feat(slices): odds and non-match data as selectable slices [P28] #140): the models … in models.PENDING_MODELS … outside the JSON Schema and the generated tables until FX-21".
  • Section 9: the first paragraph explains why the heading was kept as "Open questions". The heading is now "Decisions", so the paragraph can be reduced to "None are left …".
  • Section 9, decision 11 and the PeriodsScore prose still say "basketball" in places (the SP-1 note in 03 section 10). That is prose, not a generated block.
  • 03-implementation-plan.md cites tests/test_schema_v1.py:1112 for the heading assertion. It is at :1172 after this PR.
  • 02-services.md (:2416) says "The P28 models are not in it yet (models.PENDING_MODELS; FX-21)".
  • 00-platform.md (:159) and README.md of docs/design (:25, :89) mention FX-21 as still to do.

Notes for the next items

  • REN-1: src/schema/models.py no longer has PENDING_MODELS. Every model of the schema is in MODELS, and every model in MODELS must have a generated block in 04-schema-v1.md. Every model in RECORDS must also have a json example: there.
  • A new slice key in the registry (FX-16 or later) now fails test_slice_key_lists_every_registered_slice until it is added to the known values of Slice.key. This only adds an example; no version change.
  • P30 / anyone adding a record: add it to RECORDS, add the block and an example to 04-schema-v1.md, then run REGEN_SCHEMA_DOC=1 and REGEN_SCHEMA_GOLDEN=1.
  • The Odds record still has no country field (meta.country only, FX-15). That would be a schema change outside this item.

Changelog entry

Under [Unreleased], "Changed":

  • The published data schema (ssc describe schemas, schema id sofascore.data/1) now includes the odds and standings records (Odds, OddsLine, StandingsRow, with OddsMarket and OddsChoice) that API v1 and the odds and standings exports already return. The descriptions of the set and period score fields now explain what they count in each sport.

Move Odds, OddsMarket, OddsChoice, OddsLine and StandingsRow from
models.PENDING_MODELS into MODELS, and Odds, OddsLine and StandingsRow
into RECORDS. The JSON Schema and ssc describe schemas now carry them.

The five field tables of 04-schema-v1.md become generated, test-checked
blocks (the tables were already equal to the models), with a checked
example for each new record. The JSON Schema golden is regenerated: five
new $defs and three more x-records, nothing else changes.
…lues

Slice.key lists innings and the odds and owner slices of P28, which
were missing; a test now checks that every key of the slice registry is
a known value and every owner a known owner_kind. Slice.key is an open
set, so this adds examples only. The JSON Schema golden and the Slice
table of 04-schema-v1.md are regenerated.
None of its points is open since the approval of 2026-10-02. The test
that pinned the old heading now pins the new one.
The texts of SetScore, SetsScore and PeriodsScore were written for
tennis and basketball. They now say what each field counts in the other
sports of the family: points or legs per set, the frames, legs or games
won when the format has no sets, sets up to period7, tie-breaks and the
match tie-break in tennis and padel only, goals in the goal sports of
the periods family and which sports divide time how. No unit, type or
name changes, so the schema version stays 1.

Regenerated: the JSON Schema golden, the field tables of
04-schema-v1.md, docs/api/openapi-v1.json and the frontend types
generated from it (descriptions only).
@tunjayoff
tunjayoff merged commit 3968d07 into main Oct 6, 2026
8 checks passed
@tunjayoff
tunjayoff deleted the feat/fx-21 branch October 6, 2026 21:32
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