# Injuries

> Injury availability and history, what it covers, and how to read it honestly. Absence only means healthy inside a covered window.

Statshawk serves player injury availability and, for MLB, real injured-list history.
It arrives as one new endpoint plus three additive fields on responses you already use.
Everything here is additive: a healthy player serializes exactly as before, so nothing
breaks until you choose to read the new data.

The one rule that governs the whole feature: &#x2A;*injury data is only as honest as its
coverage window.** Read [Reading coverage honestly](#reading-coverage-honestly) before you
treat an absent field as a healthy player, because outside a covered window an absent field
means we do not know, not that the player is fine.

## What's covered
We track injury data for five leagues today. MLB is the only sport with real multi-season
history; the rest carry current status from the ESPN injury report.

| Sport               | Current status                                                               | Historical intervals                                                 |
| ------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| MLB                 | Yes, two sources (ESPN report + official MLB transactions), refreshed hourly | Yes, true injured-list stints back to 2024-01-01                     |
| NFL, NBA, NHL, WNBA | Yes, ESPN report, refreshed hourly                                           | Report transitions only, accruing since 2026-08-28 (no deep history) |
| Everything else     | None                                                                         | None                                                                 |

> **Do not imply coverage we do not have**
>
> Soccer, NCAA, KBO, and every other competition have no injury feed. The `injury` field is
>   absent for those leagues because there is nothing to report, not because the player is
>   healthy. Do not document, surface, or promise injury coverage outside the five leagues above.

## Reading coverage honestly
Every source reports a coverage window, `covered_from` and `covered_through`. Inside that
window the source is actively tracking the player; outside it, the source was not yet
watching. This window is the difference between "healthy" and "unknown," so it comes first.

* **Inside a covered window,** an absent `injury` field means the player is healthy. The
  field is omitted, never `null`.
* **Outside a covered window,** absence means *unknown*. ESPN history begins 2026-08-28 and
  MLB transactions go back to 2024-01-01, so a player with no ESPN interval before that date
  is not "healthy then," just untracked then.
* A known-healthy person returns `200` with coverage blocks and empty event and interval
  lists. A `404` means the person id is unknown, never that the player is healthy.

> **Sources are never merged**
>
> An ESPN "listed" and an MLB "activated" describe the same player from two vantage points, and
>   we keep them apart. Render each source as its own timeline, or pick one, but never splice an
>   ESPN report transition into an MLB transaction history. When both sources report a current
>   status for the same MLB player, `mlb_stats` wins the summary slot: the official transaction
>   beats the news blurb.

## Where injury data shows up
### Current status on person detail
`GET /v1/persons/{id}` gains an optional `injury` object. List, search, and overview
responses are unchanged.

```json
"injury": {
  "status": "60-Day-IL",
  "detail": "New York Yankees placed RF Aaron Judge on the 60-day injured list. Right rib stress fracture.",
  "injury_type": "Ribs",
  "return_date": null,
  "source": "mlb_stats",
  "source_reported_at": "2026-07-18T00:00:00Z",
  "source_observed_at": "2026-08-29T13:06:00Z"
}
```

`injury_type` ("Ribs", "Hamstring") and `return_date` come from ESPN-sourced rows only, and
`return_date` is an estimate: treat it as soft. See the
[Persons API reference](/docs/api/persons) for the full endpoint.

### Current status on team rosters
`GET /v1/teams/{id}/roster` carries the same optional `injury` object on each entry, with the
same shape and the same `mlb_stats`-wins precedence. Healthy entries are byte-identical to
before.

> **compare-teams rosters do not carry injury**
>
> The roster arrays inside `GET /v1/analysis/compare-teams` deliberately omit injury and use a
>   separate schema, `CompareTeamsRosterEntryView`. If you need per-player injury on a roster, use
>   the team roster endpoint above.

### Stale-form flags on prop analysis
This is the motivating feature. `GET /v1/analysis/player-prop` gains an optional `injury`
object and an `injury_window_flags` object. The flags are annotation only: averages, hit
rates, windows, and denominators are computed exactly as before. They exist so a UI can
*explain* a window, never to alter it.

```json
"injury_window_flags": {
  "games_before_latest_interval": 59,
  "latest_interval_days": 42,
  "window_spans_interval": false,
  "interval": { "start": "2026-07-18", "end": null }
}
```

| Field                          | Meaning                                                                                                            |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
| `games_before_latest_interval` | Sampled games whose kickoff precedes the stint. The stale-form signal.                                             |
| `latest_interval_days`         | The stint's span, counted through today while it is still open.                                                    |
| `window_spans_interval`        | `true` when the sampled games bracket the stint, a gap a raw game log hides.                                       |
| `interval`                     | The stint driving the flags, picked for relevance to the sampled window, with source precedence only as tie-break. |

Together they let a card say "59 of the last 59 sampled games predate a 42-day injured-list
stint (out since Jul 18)" instead of letting a stale game log read as a slump.

### Full history
`GET /v1/persons/{id}/injuries` returns the complete history, one block per source, with
derived injured-list intervals. Sources stay separate.

```json
{
  "person": "per_06fhk9szx1rz7besydrene8qj0",
  "sources": [
    {
      "source": "mlb_stats",
      "intervals": [
        { "start": "2025-07-26", "end": "2025-08-05",
          "start_kind": "reported", "close_kind": "activated", "status": "10-Day-IL" },
        { "start": "2026-07-18", "end": null,
          "start_kind": "reported", "close_kind": "open", "status": "60-Day-IL" }
      ],
      "covered_from": "2024-01-01T00:00:00Z",
      "covered_through": "2026-08-29T13:06:00Z"
    }
  ]
}
```

Each interval's `start_kind` and `close_kind` tell you how much to trust its edges:

* `start_kind` says how the interval opened. `reported` is a dated transaction; `listed` is
  the first time the player was observed on the source report; `unknown` is left-censored (the
  player was already injured when tracking began, so the start date is a bound, not the onset);
  and `tolerant_open` means a change or transfer event arrived with no previously open interval,
  so the start boundary is inferred and weaker.
* `close_kind: "activated"` is an official MLB activation. `close_kind: "unlisted"` only means
  the player left the ESPN report, which is weaker than "recovered." `close_kind: "open"`
  (with `end: null`) means the stint is ongoing.

Events sit in a separate `events` array on each source block, as siblings of the intervals,
not nested inside them. Each carries the receipts: `detail`, `return_date`, `changed_fields`
(which facts moved, for example an estimated-return slip), `source_event_id` (the MLB
transaction id), and both `observed_at` (when we ingested the event) and `effective_date` (when
an MLB transaction actually took effect, which differs from `observed_at`). `unattached_events`
is a source-wide count of events not attached to any person; a nonzero value qualifies how
confidently you can read an empty history for a single player.

## Vocabulary
* **`status`** is the source's own wording, not an enum we invented, so render it verbatim.
  ESPN ships values like `Out`, `Questionable`, `Day-To-Day`, `Injured Reserve`, `60-Day-IL`,
  and `Active`; MLB transactions use `7-Day-IL`, `10-Day-IL`, `15-Day-IL`, and `60-Day-IL`.
  Do not hardcode an exhaustive list.
* **`Active` is meaningful, not an error.** It means the player is on the injury report but
  playing (probable, or just returned). Keep those rows; do not filter them out.
* **`source`** is `espn` or `mlb_stats`.
* **Event `kind`** values are `snapshot_baseline`, `snapshot_listed`, `snapshot_changed`, and
  `snapshot_unlisted` for ESPN report transitions, and `il_placed`, `il_transferred`, and
  `il_activated` for MLB transactions.

## Freshness
Both lanes refresh hourly, but read the injury-specific timestamps, not `meta.fetched_at`,
which records when the response was assembled, not when the injury data changed. For current
status, `source_observed_at` is when the source last observed the player. For history, each
source's `covered_through` is how current its coverage runs, and an absent `covered_from` means
the source has never synced, so everything is unknown for it. See
[Data freshness](/docs/getting-started/freshness) for the full
freshness contract, and the [REST API reference](/docs/api/persons) or the
[data model](/docs/reference/data-model) for the generated schemas
(`PersonDetailView`, `PersonInjuryHistory`, `SourceInjuryHistory`, `InjuryStatusView`,
`InjuryEventView`, `DerivedInterval`, and `InjuryWindowFlags`).
