# Data policy — never invent data

VS Engine has one absolute rule, applied at every layer:

> **OPFIND ALDRIG DATA.** If we don't have a real value from an upstream
> source (Kambi, ValueStats, SportRadar), we do not synthesise one.

## Why this rule exists

VS Engine powers editorial surfaces — article bodies, leader boards, match
widgets, accumulator suggestions. Any invented number becomes a false
claim published under an Insideformation client's brand, and once it
ships it is almost impossible to walk back.

Earlier versions of similar engines produced plausible-but-wrong xG
values, plausible-but-wrong leader boards, and plausible-but-wrong
injury status. Readers noticed. So the engine was rewritten around a
defensive contract: missing data is a first-class state, not a bug to
be papered over.

## What "missing data" looks like on the wire

1. **`null` at the leaf.** A field that we couldn't resolve is `null`,
   never `0`, never `"—"`, never `"TBD"`.
2. **`blocked: true` on the widget.** A widget whose critical inputs
   are missing returns `{ blocked: true, reason: "…" }` instead of a
   body. Frontends hide blocked widgets entirely.
3. **`MANGLER` in text fields.** Human-readable labels that can't be
   resolved carry the literal string `"MANGLER"` (Danish for
   "missing"). Frontends and downstream compute steps treat this as a
   sentinel and skip rows/legs.

## What this means for clients

- Consuming a field that is `null` is the consumer's responsibility.
  Hide the row, skip the leg, fall back to a generic template.
- `blocked: true` widgets MUST be hidden — do not render a placeholder.
- The `generatedAt` field tells you how fresh the data is. Stale data
  is still real data, not invented data.

## What this means for contributors

- Never patch missing values with defaults in `assemble/*` or
  `compute/*`. Push the `null` through.
- Never copy a value from a sibling field just to fill a gap.
- When adding a new widget, decide upfront which fields are *critical*
  (their absence sets `blocked: true`) and which are *optional*
  (their absence leaves a `null`).
- When adding a new compute step that aggregates multiple matches,
  filter out the matches where required inputs are missing BEFORE
  calling the aggregator — otherwise averages get poisoned by zeros.

## Related

- [`bulkhead.md`](bulkhead.md) — defense-in-depth client isolation
- [`errors.md`](errors.md) — error envelopes and HTTP status codes
