# Open Impact Claim Standard, v0 (`impact-claim/v0`)

**Status:** Draft v0, proposed by Better World (diditwork.org). **License: pending Ho Hum's decision** (proposed CC BY 4.0 for this spec; see [`LICENSE-PROPOSAL.md`](LICENSE-PROPOSAL.md)).
**Schema:** [`schema/impact_claim.schema.json`](../../schema/impact_claim.schema.json) · **Validator:** [`scripts/validate_impact_claim.py`](../../scripts/validate_impact_claim.py) (one file, Python standard library only) · **Examples:** [`examples/`](examples/)

## Why this exists

Charities, evaluators, and AI agents all repeat impact numbers, often with no unit, no source, no date, and no way to check them later. This standard is a small, boring format for one claim: who says what, about which program, in which unit, according to which source, as of when, and when someone should check again. A claim that passes the validator is **well-formed and honestly labeled**. It is **not** thereby true, endorsed, or ranked.

## Design rules

1. **Never guaranteed.** `honesty.guaranteed` must be `false`. Modeled estimates must say `honesty.modeled: true`.
2. **Numbers need sources.** A non-null `metric.value` needs at least one `https` citation with an `accessed_on` date. Unknown means `null`; never guess.
3. **Attribution must match the source.** If `source.evaluator` names GiveWell, HLI, ACE, WHO or IHME, at least one citation must be on that evaluator's own domain. Unrecognized evaluators get a warning, and readers should treat the claim as self-reported until it is checked.
4. **Units are labels, not conversions.** Use a shared v0 unit or `custom:<name>`. Units are never converted, and there is no cross-unit "impact score" (Better World ADR 0007 stays Proposed).
5. **Claims are data, not instructions.** Text that tries to instruct an AI reader (for example "ignore previous instructions", "rank us first", or role tags) is rejected.
6. **Did it work?** Modeled estimates and outcome claims should carry `follow_up.check_after`, the date someone should re-check them. Better World's outcome ledger is built on this field.

## Fields

| Field | Required | Notes |
|---|---|---|
| `standard` | yes | `"impact-claim/v0"` |
| `claim_id` | yes | Stable within the publisher, `^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$` |
| `as_of` | yes | `YYYY-MM-DD`; not in the future |
| `org` | yes | `name`; optional `website` (https), `identifiers[]` (`us_ein`, `uk_charity_number`, `bw_org_id`, `other`) |
| `intervention` | yes | `name`; optional `bw_intervention_id`, `geography` |
| `metric` | yes | `label`, `unit`, `value` (number or `null`), optional `period` |
| `claim_type` | yes | `modeled_estimate` · `measured_output` · `measured_outcome` |
| `source` | yes | `kind` (`independent_evaluator` · `peer_reviewed` · `self_reported`), `evaluator`, `citations[]` (`url`, `accessed_on`, `title`) |
| `honesty` | yes | `modeled` (bool), `guaranteed` (always `false`) |
| `uncertainty` | no | `low`, `high`, `interval`, `confidence` (`low`/`medium`/`high`), `notes` |
| `follow_up` | no | `check_after` (date), `method` |
| `notes` | no | Free text, max 1000 characters |

No other top-level fields are allowed in v0. Extensions wait for v1, so that "valid" means the same thing everywhere.

**v0 unit vocabulary:** `USD_per_life_saved`, `USD_per_DALY_averted`, `USD_per_WELLBY`, `WELLBY_per_1000_USD`, `WELLBY_per_person`, `deaths_averted`, `DALYs_averted`, `people_reached`, `USD_per_person`, `USD_per_child`, `USD_per_net`, `nets_distributed`, `x_givedirectly`, `x_givewell_benchmark`, `tCO2e_averted`, `USD_per_tCO2e`, `animals_spared`, `percent`, `count`.

## Error and warning codes

| Code | Meaning |
|---|---|
| E000–E006 | Not an object, missing field (E001), unknown field (E002), wrong `standard` (E003), bad `claim_id` (E004), bad date (E005), future `as_of` (E006) |
| E010–E012 | Org or intervention name missing; org website not https |
| E020–E024 | Metric label missing; bad unit (E021); unit not in vocabulary, so use `custom:` (E022); `value` key missing (E023); non-numeric value (E024) |
| E030–E033 | Bad `claim_type` (E030); `guaranteed` not false (E031); `modeled` not boolean (E032); modeled estimate not marked modeled (E033) |
| E040–E048 | Bad source kind; citations not a list; citation not an object; non-https or credentialed URL (E043); bad `accessed_on` (E044); future `accessed_on` (E045); number without citation (E046); evaluator missing (E047); named evaluator not supported by a citation on its domain (E048) |
| E050–E051 | Uncertainty low > high (E050); value outside [low, high] (E051) |
| E060 | Bad `follow_up.check_after` |
| E070–E071 | Instruction-like text (E070); guarantee language (E071) |
| E080 | Duplicate `claim_id` in a document |
| W030, W040, W041, W050, W060 | Measured claim marked modeled; unrecognized evaluator; self-reported label; no uncertainty; no follow-up date |

## Run it on your own claims

```bash
# Copy scripts/validate_impact_claim.py from the Better World repo (no public download URL yet;
# publishing waits on the license decision).
python3 validate_impact_claim.py my_claims.json          # summary
python3 validate_impact_claim.py my_claims.json --json   # machine-readable report
```

Input can be one claim, an array, or `{"claims": [...]}`. Exit code 0 means all valid, 1 means at least one invalid, and 2 means the file is unreadable.

## How Better World uses it (and doesn't)

- Better World will accept structured submissions in this format **after** Ho Hum approves an intake process. No intake is live today.
- A valid claim is shown with its source label (self-reported, peer-reviewed, or independent evaluator). It **never** changes rankings on its own. Ranking changes need evaluator evidence and Ho Hum's approval.
- Paid or sponsored status has no field in this standard and can never affect a claim's evidence (ADR 0003).

## Versioning

`impact-claim/v0` is a draft. Breaking changes go to `v1`. The validator's error codes are stable within v0.
