PulsePlay coverage
Every requirement in soccer_data_requirements.md probed against 3 live fixtures / 2874 updates (17882805, 17932445, 17952170).
Market settleability (A–E)
Ship goal / corner / card / shot-on-target windows. Reset nuance (own-goal/penalty split) partial; stale-void heuristic until heartbeat confirmed.
LOCK is the blocker — no corner/penalty/free-kick *_taken marker. Penalty & corner-outcome via time-window lock (documented risk); FK location gating impossible.
Strict Seq ordering ✅. Curate the taxonomy to verbs that exist (exclude foul + standalone offside). Confirmed adds ~1–15s latency.
Corners ✅ and throw-ins ✅ (count events in segment). Fouls ✗ — no foul event.
Self-integrate the Possession stream (~6s cadence). Basis (time-in-control?) + final-vs-interim unconfirmed → flag for review upstream.
Universal spine (§3 — required on every message)
| Data point | Feed | Note / evidence | |
|---|---|---|---|
| ✅ | match_id (stable, unique) | FixtureId | FixtureId on every message.live example17882805 |
| 🟡 | match_status incl. suspended/abandoned/postponed medium | StatusId (+ suspend action) | StatusId observed: 1=NS, 2=H1, 3=HT, 4=H2, 5=FT. Only NS/H1/HT/H2/FT live; suspended/abandoned/postponed documented (15/18/19) but unobserved; `suspend` action present.live example[ 1, 2, 3, 4, 5 ] |
| ✅ | match_clock (second res) + announced added time | Clock{Running,Seconds} + additional_time.Data.Minutes | Clock.Seconds = elapsed match seconds; additional_time carries announced minutes.live example{
"clock": {
"Running": false,
"Seconds": 0
}
} |
| ✅ | timestamp_utc (millisecond, every message) | Ts | 13-digit epoch ms on every message (944 distinct sub-second remainders → genuine ms precision).live example1780325524893 |
| ✅ | sequence_id (monotonic, per match) | Seq | Strictly increasing per fixture but GAPPY (~4 missing — non-fixture frames, not loss; dedup on Seq, can't count-detect drops).live example{
"sampleFixture": 17882805,
"range": [
0,
954
]
} |
| 🟡 | event_id (unique, dedup) medium | Id | Id is a LOGICAL-event id reused across provisional/confirmed/amend (99 ids span ≥2 Seq) — NOT unique per message. Dedup on Seq; use Id for lifecycle correlation.live example{
"reusedIds": 99
} |
| 🟡 | heartbeat (fixed, known interval) high | SSE event: heartbeat / standby / Ts-gap | No top-level heartbeat field; SSE stream emits `event: heartbeat` (cadence unconfirmed). Infer staleness from Ts inter-arrival + disconnected/standby/suspend. Confirm cadence on a tier-12 live match.live example{
"standbyFrames": 15,
"disconnectedFrames": 4
} |
| ✅ | confirmation_status (provisional / confirmed) | Confirmed | Per-event Confirmed bool transitions false→true; settle only on true.live example{
"provisional": {
"FixtureId": 17882805,
"GameState": "scheduled",
"StartTime": 1780600200000,
"IsTeam": true,
"FixtureGroupId": 10116166,
"CompetitionId": 430,
"CountryId": 466,
"SportId": 1,
"Participant1IsHome": true,
"Participant2Id": 2236,
"Participant1Id": 1999,
"CoverageSecondaryData": true,
"CoverageType": "TV/Stream",
"Action": "kickoff",
"Id": 27,
"Ts": 1780600225527,
"ConnectionId": 534,
"Seq": 17,
"StatusId": 2,
"Type": "Soccer",
"Confirmed": false,
"Clock": {
"Running": true,
"Seconds": 0
},
"Kickoff": {
"Team": 1
},
"Possession": 1,
"PossessionType": "SafePossession"
},
"confirmed": {
"FixtureId": 17882805,
"GameState": "scheduled",
"StartTime": 1780600200000,
"IsTeam": true,
"FixtureGroupId": 10116166,
"CompetitionId": 430,
"CountryId": 466,
"SportId": 1,
"Participant1IsHome": true,
"Participant2Id": 2236,
"Participant1Id": 1999,
"CoverageSecondaryData": true,
"CoverageType": "TV/Stream",
"Action": "connected",
"Id": 2,
"Ts": 1780597443946,
"ConnectionId": 534,
"Seq": 2,
"Confirmed": true
}
} |
| 🟡 | revision_flag / retraction high | action_amend{New,Previous} + action_discarded | action_amend carries a full field diff; action_discarded self-describes nothing → requires an Id-cache to know what was withdrawn. (VAR-disallowed goal surfaces via var_end:Overturned, NOT action_discarded — handle both retraction paths.)live example{
"amend": {
"Action": "injury",
"New": {
"Clock": {
"Running": true,
"Seconds": 1013
},
"Outcome": "OnPitch",
"PlayerId": 10248162
},
"Previous": {
"Clock": {
"Running": true,
"Seconds": 1013
},
"Outcome": "OffPitch",
"PlayerId": 10248162
}
},
"discardCount": 7
} |
| ✅ | review_state (VAR: none/in_review/confirmed/overturned) | var → var_end.Data.Outcome | var (in_review) → var_end.Outcome ∈ {Stands,Overturned}. Both Stands+Overturned observed → confirmed/overturned distinguishable.live example{
"varTypes": [
"Goal",
"Penalty"
],
"varEndOutcomes": [
"Stands",
"Overturned"
]
} |
| ✅ | score (home / away) | Score tree + Participant1IsHome | Period-bucketed Score tree (H1/HT/H2/Total → Goals/YellowCards/RedCards/Corners); resolve home via Participant1IsHome.live example{} |
| ✅ | team_ref (stable home/away) | Participant (1|2) + Participant1IsHome | Participant 1/2 on events; Participant1IsHome maps to home/away, constant for the match.live example{
"participant1IsHome": true,
"p1": 1999,
"p2": 2236
} |
Events (§5 / §7)
| Data point | Feed | Note / evidence | |
|---|---|---|---|
| 🟡 | Goal + type (open_play / penalty / own_goal) medium | goal.Data.GoalType (+ penalty_outcome for pens) | goal.GoalType observed: {Shot} (only "Shot" seen → no open_play/own_goal split; penalty goals arrive as a separate penalty_outcome).live example{
"goalTypes": [
"Shot"
]
} |
| ✅ | Corner awarded + team | corner action | corner action (+Participant; Score.Corners increments). No corner_taken.live example{
"FixtureId": 17882805,
"GameState": "scheduled",
"StartTime": 1780600200000,
"IsTeam": true,
"FixtureGroupId": 10116166,
"CompetitionId": 430,
"CountryId": 466,
"SportId": 1,
"Participant1IsHome": true,
"Participant2Id": 2236,
"Participant1Id": 1999,
"CoverageSecondaryData": true,
"CoverageType": "TV/Stream",
"Action": "corner",
"Id": 261,
"Ts": 1780601826883,
"ConnectionId": 534,
"Seq": 272,
"StatusId": 2,
"Type": "Soccer",
"Confirmed": false,
"Clock": {
"Running": true,
"Seconds": 1602
},
"Score": {},
"Participant": 2,
"Possession": 2,
"PossessionType": "DangerPossession",
"Parti1State": {},
"Parti2State": {}
} |
| 🔴 | corner_taken marker (LOCK signal) blocker | — (none) | No corner_taken action — next event = resumption of play. Lock must use a time-window. |
| ✅ | Card + type (yellow / second_yellow / red) | yellow_card; red_card.Data.Type | yellow_card present; red_card.Type ∈ {SecondYellow} → second-yellow vs straight-red distinguishable.live example{
"redTypes": [
"SecondYellow"
]
} |
| ✅ | Shot + classification (on_target / off_target / blocked) | shot.Data.Outcome | shot.Outcome ∈ {Blocked,OnTarget,OffTarget} (Woodwork is a bonus 4th value).live example{
"outcomes": [
"Blocked",
"OnTarget",
"OffTarget"
]
} |
| ✅ | throw_in + team | throw_in action | throw_in + Participant + ThrowInType danger class.live example{
"FixtureId": 17882805,
"GameState": "scheduled",
"StartTime": 1780600200000,
"IsTeam": true,
"FixtureGroupId": 10116166,
"CompetitionId": 430,
"CountryId": 466,
"SportId": 1,
"Participant1IsHome": true,
"Participant2Id": 2236,
"Participant1Id": 1999,
"CoverageSecondaryData": true,
"CoverageType": "TV/Stream",
"Action": "throw_in",
"Id": 227,
"Ts": 1780601517789,
"ConnectionId": 534,
"Seq": 234,
"StatusId": 2,
"Type": "Soccer",
"Confirmed": false,
"Clock": {
"Running": true,
"Seconds": 1293
},
"Data": {},
"Participant": 1,
"Possession": 2,
"PossessionType": "AttackPossession"
} |
| 🔴 | foul + team (Archetype D) blocker | — (none) | No `foul` action and no foul leaf in Score. Over/under-fouls NOT settleable. Proxy = free_kick minus FreeKickType:Offside (disputable). |
Archetype B — set-piece outcome
| Data point | Feed | Note / evidence | |
|---|---|---|---|
| ✅ | penalty_awarded (team) | penalty action | penalty action (announce+confirm).live example{
"FixtureId": 17932445,
"GameState": "scheduled",
"StartTime": 1780512300000,
"IsTeam": true,
"FixtureGroupId": 10116166,
"CompetitionId": 430,
"CountryId": 466,
"SportId": 1,
"Participant1IsHome": true,
"Participant2Id": 2638,
"Participant1Id": 2790,
"CoverageSecondaryData": true,
"CoverageType": "TV/Stream",
"Action": "penalty",
"Id": 646,
"Ts": 1780518419261,
"ConnectionId": 546,
"Seq": 723,
"StatusId": 4,
"Type": "Soccer",
"Confirmed": false,
"Clock": {
"Running": true,
"Seconds": 4531
},
"Participant": 2
} |
| 🔴 | penalty_taken / run-up marker (LOCK) blocker | — (none) | Stream goes award→outcome; no taken/run-up frame. Lock via time-window. |
| 🟡 | penalty_outcome (goal/saved/off_target/woodwork) low | penalty_outcome.Data.Outcome | penalty_outcome.Outcome ∈ {Scored} (only "Scored" seen on tier-1; saved/off/woodwork unconfirmed).live example{
"outcomes": [
"Scored"
]
} |
| 🔴 | retake_ordered low | — (none) | No retake action; inferable only via surrounding var. |
| 🟡 | free_kick_awarded + location (x,y/zone) + danger high | free_kick.Data.FreeKickType (danger class only) | FreeKickType danger class ∈ {Safe,Attack,Danger,Offside} — NO x/y/zone coordinates, AND arrives on Confirmed:true (post-kick) → cannot gate market OPENING at award. Location-gated FK market cannot open precisely.live example{
"freeKickTypes": [
"Safe",
"Attack",
"Danger",
"Offside"
]
} |
| 🔴 | free_kick_taken marker (LOCK) high | — (none) | No taken marker. |
| 🔴 | Phase / set-piece linkage ID (corner outcome §6.2) blocker | — (none) | No PhaseId/SetPieceId/CornerId/ParentId on any frame (ConnectionId=feed session, Id=per-event, Possession flips with play). Corner-outcome must be bounded by a fixed time window from the award. |
Archetype C — next-event
| Data point | Feed | Note / evidence | |
|---|---|---|---|
| 🟡 | Event taxonomy coverage (next-event markets) medium | Action verbs | Most taxonomy verbs present and strictly ordered by Seq. MISSING as standalone: foul, offside (offside only via free_kick FreeKickType:Offside). Curate the next-event taxonomy to verbs that exist; note Confirmed adds ~1-15s latency.live example{
"presentSample": [
"goal",
"shot",
"corner",
"throw_in",
"free_kick",
"card_yellow",
"substitution",
"goal_kick",
"penalty"
]
} |
Archetype E — possession
| Data point | Feed | Note / evidence | |
|---|---|---|---|
| 🟡 | Possession time series (cumulative sec, time-in-control) high | Possession/PossessionType event stream (no cumulative key) | No cumulative seconds-in-possession key; integrate the Possession/PossessionType stream yourself (~127.1s median cadence, viable in a 5-min window). Basis (time-in-control vs danger heuristic) and final-vs-interim semantics UNCONFIRMED — escalate to TxOdds.live example{
"possessionFrames": 2220,
"types": [
"SafePossession",
"AttackPossession",
"DangerPossession",
"HighDangerPossession"
]
} |
