Skip to content
89 changes: 88 additions & 1 deletion docs/calibration-map-hierarchy.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Calibration map hierarchy

The calibration map displays one producer-authored hierarchy for every target
in Microcosm diagnostics schema 8:
in Microcosm diagnostics schemas 8 and 9:

1. provider
2. category
Expand Down Expand Up @@ -51,6 +51,74 @@ while preserving the order in which the dashboard must traverse dimensions.
}
```

## Schema 9 catalogs

Schema 8 copies every label onto every row, so one geography or one dimension
value can be labelled differently on different rows (for example "Maine" and
"ME", or "No adjusted gross income" and "No adjusted gross income and
deficit"). Schema 9 declares provider, category, geography and dimension labels
once, in a top-level `hierarchy_catalogs` object, and each row refers to them by
ID. A schema 9 file therefore cannot give one ID two labels. Every other
top-level field and every other row field is the same as in schema 8, and the
target's own label stays on the row.

```json
{
"schema_version": 9,
"hierarchy_catalogs": {
"providers": [
{"id": "obr", "label": "Office for Budget Responsibility"}
],
"categories": [
{
"id": "obr.efo_receipts",
"label": "Economic and fiscal outlook receipts",
"provider_id": "obr"
}
],
"geographies": [
{"id": "K02000001", "label": "United Kingdom", "level": "country"}
],
"dimensions": [
{
"id": "obr.efo_line",
"label": "Economic and fiscal outlook line",
"values": [
{"id": "income_tax", "label": "Income tax (gross of tax credits)"}
]
}
]
},
"targets": [
{
"name": "obr.income_tax@2025",
"target_name": "obr.income_tax",
"hierarchy": {
"category_id": "obr.efo_receipts",
"geography_id": "K02000001",
"dimensions": [{"id": "obr.efo_line", "value_id": "income_tax"}],
"target": {"id": "obr.income_tax", "label": "Income tax receipts"}
}
}
]
}
```

- A row names its category only; the provider is the category's.
- A row's `dimensions` keep producer order. Each `value_id` is looked up among
its own dimension's `values`, so two dimensions can both have a value `all`.
- Catalogs are arrays, so a repeated ID stays visible to a reader.
- A geography ID has one level as well as one label.

The dashboard expands each schema 9 row into the schema 8 row above
(`expandHierarchyCatalogs` in `@policyengine/calibration-tree`) before any
other code reads it, so the schema 8 reader, the tree builder, published trees
and every page treat both versions the same way. A schema 9 file builds the same
tree files as the equivalent schema 8 file without label variants, apart from
the build ID and the recorded source digest, which come from the file's bytes.
The Calibration summary reports `diagnostics_schema_version` 9 and the
`hierarchy` representation.

Microcosm owns the provider/category relationship. Chronicle supplies
geography, categorical dimensions, categorical value labels, and the preferred
target label when its selected fact can describe the resulting target exactly.
Expand All @@ -72,6 +140,7 @@ file. Target row structure is not used to choose a reader.
| 2–6 | Legacy name/filter reader |
| 7 | Structured source/variable/dimensions reader |
| 8 | Normalized hierarchy reader |
| 9 | Normalized hierarchy reader, after expanding `hierarchy_catalogs` |

An absent version, version 1, and unsupported future versions produce an
explicit compatibility error. A schema 7 file containing a legacy-shaped row
Expand All @@ -81,6 +150,24 @@ The schema 8 reader requires every identifier and label, verifies that the
category references the row's provider, rejects duplicate dimension IDs, and
verifies that `hierarchy.target.id` agrees with the diagnostic target ID.

Before that, a schema 9 file is refused, with a message naming the row or
catalog entry, when:

- `hierarchy_catalogs` or one of its four arrays is missing, or an entry lacks
an ID or label (or a category its `provider_id`, a geography its `level`);
- a catalog lists one ID twice, or one dimension lists one value ID twice;
- a category's `provider_id` is not in `providers`;
- a row refers to a category, geography or dimension the catalogs do not list,
or to a value its own dimension does not list (including a value listed only
under another dimension);
- a row lists one dimension twice, or is still in the schema 8 layout;
- a row's `hierarchy`, or one of its `dimensions` references, has a field
schema 9 does not define (such as an inline schema 8 `geography` or
`value_label`); or
- a row's `hierarchy.target.id` differs from its `target_name`.

IDs are compared after trimming whitespace, as the schema 8 reader trims them.

## Tree construction

The map first groups targets by provider and category, then by geography. It
Expand Down
4 changes: 3 additions & 1 deletion docs/spec-driven-countries.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ inference.

The top-level diagnostics schema selects exactly one target reader. Schemas 2–6
use the legacy reader, schema 7 uses the structured reader described above, and
schema 8 uses the normalized hierarchy reader. Missing versions, version 1, and
schemas 8 and 9 use the normalized hierarchy reader (schema 9 after its
hierarchy catalogs are expanded into schema 8 rows; see
[Calibration map hierarchy](calibration-map-hierarchy.md#schema-9-catalogs)). Missing versions, version 1, and
unsupported future versions are rejected explicitly. A row that does not match
its file's declared representation is rejected; the dashboard does not choose a
different reader from that row's shape.
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/calibration-tree/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"./chronicle-metadata": "./src/chronicle-metadata.ts",
"./countries": "./src/countries.ts",
"./geography-labels": "./src/geography-labels.ts",
"./hierarchy-catalog-reader": "./src/hierarchy-catalog-reader.ts",
"./hierarchy-target-reader": "./src/hierarchy-target-reader.ts",
"./historical-weight-summary": "./src/historical-weight-summary.ts",
"./latest-artifact": "./src/latest-artifact.ts",
Expand Down
Loading
Loading