Asteria Fleet Data Quality Hub — reconciliation & certification
Every task in this family is the same pipeline over a different collection and
output contract. Recognize it, run the pipeline, then bend the output to the
exact answer_template.json in front of you. Do not invent numbers: every value
must be derived from the live hub as of the scoped cutoff.
0. Inputs you are always given
payloads/case_scope.json — the only source of scope: collection id, the
business cutoff / as-of / period, focus items (clusters, assets, events,
people, charges, transactions), anchored control cases with pinned evidence
rows, ranking limits and tie-breaks, canonical units / base currency,
status thresholds and a status→action map.
payloads/answer_template.json — the output contract (usually JSON Schema,
sometimes a field_contract document). It is authoritative for keys, enums,
array cardinality (minItems/maxItems), ordering, rounding, and
additionalProperties:false. Read it last-mile and satisfy it literally.
environment_access.md — the only thing that grants network access: the
base URL (*_BASE_URL=...) and an AUTHORIZATION: Bearer <token> header,
plus the list of allowed endpoints. Send the auth header on every request.
Never hardcode a token, base URL, count, or code mapping from a previous task —
they change per task. Read them fresh at runtime.
1. Connect and discover (never assume the API shape)
Use scripts/asteria_client.py (stdlib-only) to read environment_access.md
and make authenticated calls, or replicate its logic. Before pulling data:
GET /api/catalog/collections — confirm the scoped collection_id exists
and learn its domain/metadata.
GET /api/catalog/schema — learn the real field names and types for
rows, snapshots, and reference tables. Do not guess field names from this
skill; the schema is ground truth.
GET /api/source-snapshots — snapshot metadata: ids, status
(e.g. CERTIFIED / PROVISIONAL / STALE), timestamps, coverage.
- Inspect one page of the domain endpoint and one
POST /api/query response to
learn the pagination envelope and the query request/response shape, then
adapt get_all() / query() accordingly. Collections span multiple
pages — page to exhaustion; never reason from a truncated first page.
Endpoints seen in this family (subset appears per task):
/api/catalog/collections, /api/catalog/schema, /api/contacts,
/api/transactions/fuel, /api/transactions/freight,
/api/maintenance/events, /api/reference/aliases, /api/reference/conversions,
/api/reference/fx, /api/source-snapshots, POST /api/query.
2. The reconciliation pipeline (shared spine)
Run these stages in order; every task uses a subset, described in its prompt.
A. Scope & pull. Restrict to the scoped collection_id. Pull all raw rows
across snapshots (paginate). raw_row_count = in-scope raw source rows
(including cross-snapshot duplicates).
B. Cutoff filter. Keep only rows whose business/effective date is <= the
scoped cutoff / as-of / within the period. Use the business-date evidence the
prompt names, not ingestion time.
C. Authoritative snapshot & de-duplication. Rows overlap across snapshots.
Choose the authoritative snapshot from snapshot metadata (prefer certified/
current status and the latest as-of within cutoff — verify the rule from the
schema/metadata, don't assume). Collapse raw rows into logical entities
(logical transaction / charge / event / person / contact) by their stable
business key. For each duplicate group the retained occurrence comes from the
authoritative/retained snapshot. Report duplicate_raw_count = raw − logical
and, where required, per-group snapshot_ids (sorted) + retained_snapshot_id.
D. Normalize. Depending on domain:
- Text/contacts: canonical name (Unicode-preserving), email = trimmed NFKC
lowercase, phone = digits only (kept as a string), canonical city,
canonical region/depot value.
- Fuel/freight: resolve descriptions/aliases to a recognized canonical
category/class via
/api/reference/aliases; convert quantities to the
canonical unit via /api/reference/conversions; convert money to the base
currency via /api/reference/fx using the applicable business-date rate.
- Maintenance: convert odometer/labor to declared units; reconstruct per-asset
history ordered by reliable timestamp.
E. Classify: valid vs mismatch vs quarantine. Quarantine = a row that cannot
enter normalized totals. Reason families observed:
unrecognized — no recognized category/alias match.
ambiguous — matches more than one category/alias.
invalid_quantity / invalid_weight / invalid_distance — non-positive or
out-of-range physical measure.
no usable contact channel — no usable email or phone (contacts/roster).
- maintenance rejects — missing/unparsable timestamp, invalid odometer range,
negative or extreme labor.
A mismatch (expected category/class ≠ recognized category/class) is still a
valid record: it counts in normalized totals and is flagged separately.
Quarantined rows are excluded from all normalized totals; valid mismatches are
included. An "exception" is a distinct logical record that is a valid mismatch
or is quarantined.
F. Canonical resolution & survivors. Group logical entities that are the same
real-world entity (identity resolution). Pick a survivor / master_id (a real
public row id) and build canonical field values. When sources disagree per
field, apply field-level source precedence (discover the precedence order
from schema/reference — e.g. CRM vs Compliance Master vs Partner Portal, or
HR Directory vs Dispatch vs Identity Registry) and record which source won each
field. Entities with genuinely conflicting identity evidence on a watchlist are
contested → not auto-merged (CONTESTED_NO_AUTOMERGE). member_row_ids is
the deduplicated, lexicographically-sorted set of contributing public row ids.
G. Rollups, rankings, readiness.
- Rollups (region/depot, fuel_type, service_class): one row per represented
value from the contract's enum, canonical-entity/charge counts, sorted
ascending by the key.
- Rankings (merchant / carrier / asset): sort by the primary metric descending
then the declared tie-breaks (usually id ascending); truncate to the scope's
limit; assign
rank from 1.
- Readiness (contacts/roster): an entity is readiness-eligible only when it
is active AND retains at least one usable email or phone. A channel is
ready only when consent is granted. Partition eligible entities into
mutually exclusive buckets (
both / email_only / phone_only / not_ready)
that sum to the eligible count. "Dispatchable" = active + usable channel +
consent granted; blocked reasons (consent / no-contact / inactive) partition
the depot total.
H. Control codes (opaque; expansions withheld on purpose). See §3.
I. Certification / status. Compute the gate metric — usually
quarantine_rate = quarantined_rows / canonical_entities, rounded to 4 dp — and
apply the scope's thresholds: at-or-below pass_max → PASS; else at-or-below
pass_with_exceptions_max → PASS_WITH_EXCEPTIONS; else HOLD. Honor any
hard gate the scope names (e.g. any odometer regression forces HOLD /
BLOCK_AND_REMEDIATE) — a hard gate overrides the rate. Map status → action with
the scope's status_action_map (typically PASS→RELEASE,
PASS_WITH_EXCEPTIONS→REVIEW_EXCEPTIONS, HOLD→BLOCK_AND_REMEDIATE).
J. Assemble & self-check. Build the object to the contract, then run §4.
3. Assigning the opaque control codes
Codes like IC-25/40/70/90, OR-15/35/60/80, FP-20/55/75, RB-17/42/83,
SB-24/61/79, LD-14/31/53/72/88, MS-12/47/86, HR-19/33/74 are labels for
categorical reconciliation outcomes within a family (identity, outreach/
readiness, field-provenance, reference-basis, source-basis, ledger-disposition,
maintenance-source, history-route). Their plain-language expansions are
deliberately not provided — you must infer the mapping from the data, not
memorize it. Method:
- Count the outcomes. The enum size = number of distinct outcomes to
distinguish (e.g. FP/RB/SB/MS/HR have 3; IC/OR have 4; LD has 5). Derive that
many categorical outcomes from your reconciliation for that family.
- Use built-in calibration. When the contract pairs a code family with
named categories, that pairing reveals the family's semantics — e.g. a
readiness_partition object that asks for an OR code per
both/email_only/phone_only/not_ready bucket tells you OR codes label
readiness states; solve the 1:1 assignment from that. Anchored control cases
(pinned evidence rows in the scope) exist precisely to let you tie a known
reconciliation outcome to its code.
- Cross-check with the hub. Look for a signal in the records, reference
tables, or
/api/query results that separates the outcomes; assign codes so
the observable ordering/partition is consistent.
- Be internally consistent. The same outcome always maps to the same code,
everywhere in the answer. Do not assume the numeric suffix implies severity or
order unless the evidence shows it.
4. Output contract compliance (do this before returning)
The contract — not this skill — is authoritative. Verify each:
- Shape: every required key present; no extra keys (
additionalProperties: false / additional_top_level_keys_allowed:false). Objects/arrays nested
exactly as specified.
- Cardinality: arrays with
minItems/maxItems (or a length) have exactly
that many items — one row per requested focus item / rollup enum value / scope
id, no more, no fewer.
- Ordering: apply each list's stated rule (usually lexicographic ascending by
id; rankings by metric desc then tie-break). Set membership is deduplicated.
- Enums: every enum-typed value is one of the allowed literals (statuses,
actions, source systems, categories, control codes).
- Rounding / precision: money/volume/weight/distance to 2 dp; rates to 4 dp;
honor
multipleOf. Counts are exact integers. Phone stays a string of
digits.
- Ids: use only stable public ids present in the data or supplied in the
scope; match id
patterns.
- Partitions add up: mutually-exclusive count groups sum to their stated
total (readiness buckets, depot dispositions).
- Consistency: e.g.
dispatchable_person_count == length of
dispatchable_master_ids; duplicate_raw_count == raw − logical;
quarantine_rate == quarantined / canonical (rounded).
- Return one JSON object only — no Markdown, no commentary, no code fences.
5. Cross-cutting reminders
- Discover, don't assume: field names, pagination, snapshot-selection rule, and
precedence order all come from the live catalog/schema/metadata.
- Reconcile as of the cutoff; ignore rows and rate versions outside it.
- Quarantined rows never enter normalized totals; valid mismatches always do.
- Recompute derived counts from your own reconciled set — never copy a count the
API reports for a different scope.
- Keep every derivation deterministic so the same inputs always yield the same
answer.
See references/task-family-map.md for how each observed task variant maps onto
this pipeline, and scripts/asteria_client.py for the authenticated client.
1---2name: asteria-fleet-data-quality-hub3description: Solve Asteria Fleet Data Quality Hub reconciliation-and-certification tasks. Use whenever a task points at the "Fleet Data Quality Hub" / a <TASK_ENV_BASE_URL> with endpoints like /api/catalog, /api/source-snapshots, /api/contacts, /api/transactions/{fuel,freight}, /api/maintenance/events, /api/reference/{aliases,conversions,fx}, and /api/query, and asks for one JSON object (matching payloads/answer_template.json) reconciling overlapping source records as of a business cutoff and returning quality counts, canonical/ survivor decisions, rollups/rankings, opaque control codes, and a certification/status decision. Covers contact-master certification, contact- readiness rosters, fuel/freight normalization audits, and maintenance-log integrity — all variants of the same pipeline.4---56# Asteria Fleet Data Quality Hub — reconciliation & certification78Every task in this family is the **same pipeline over a different collection and9output contract**. Recognize it, run the pipeline, then bend the output to the10exact `answer_template.json` in front of you. Do not invent numbers: every value11must be derived from the live hub as of the scoped cutoff.1213## 0. Inputs you are always given1415- `payloads/case_scope.json` — the *only* source of scope: collection id, the16 business cutoff / as-of / period, focus items (clusters, assets, events,17 people, charges, transactions), anchored control cases with pinned evidence18 rows, ranking limits and tie-breaks, canonical units / base currency,19 status thresholds and a status→action map.20- `payloads/answer_template.json` — the output contract (usually JSON Schema,21 sometimes a `field_contract` document). It is authoritative for keys, enums,22 array cardinality (`minItems`/`maxItems`), ordering, rounding, and23 `additionalProperties:false`. **Read it last-mile and satisfy it literally.**24- `environment_access.md` — the **only** thing that grants network access: the25 base URL (`*_BASE_URL=...`) and an `AUTHORIZATION: Bearer <token>` header,26 plus the list of allowed endpoints. Send the auth header on every request.2728Never hardcode a token, base URL, count, or code mapping from a previous task —29they change per task. Read them fresh at runtime.3031## 1. Connect and discover (never assume the API shape)3233Use `scripts/asteria_client.py` (stdlib-only) to read `environment_access.md`34and make authenticated calls, or replicate its logic. Before pulling data:35361. `GET /api/catalog/collections` — confirm the scoped `collection_id` exists37 and learn its domain/metadata.382. `GET /api/catalog/schema` — learn the **real field names and types** for39 rows, snapshots, and reference tables. Do not guess field names from this40 skill; the schema is ground truth.413. `GET /api/source-snapshots` — snapshot metadata: ids, status42 (e.g. CERTIFIED / PROVISIONAL / STALE), timestamps, coverage.434. Inspect one page of the domain endpoint and one `POST /api/query` response to44 learn the pagination envelope and the query request/response shape, then45 adapt `get_all()` / `query()` accordingly. Collections span **multiple46 pages** — page to exhaustion; never reason from a truncated first page.4748Endpoints seen in this family (subset appears per task):49`/api/catalog/collections`, `/api/catalog/schema`, `/api/contacts`,50`/api/transactions/fuel`, `/api/transactions/freight`,51`/api/maintenance/events`, `/api/reference/aliases`, `/api/reference/conversions`,52`/api/reference/fx`, `/api/source-snapshots`, `POST /api/query`.5354## 2. The reconciliation pipeline (shared spine)5556Run these stages in order; every task uses a subset, described in its prompt.5758**A. Scope & pull.** Restrict to the scoped `collection_id`. Pull *all* raw rows59across snapshots (paginate). `raw_row_count` = in-scope raw source rows60(including cross-snapshot duplicates).6162**B. Cutoff filter.** Keep only rows whose business/effective date is `<=` the63scoped cutoff / as-of / within the period. Use the business-date evidence the64prompt names, not ingestion time.6566**C. Authoritative snapshot & de-duplication.** Rows overlap across snapshots.67Choose the authoritative snapshot from snapshot metadata (prefer certified/68current status and the latest as-of within cutoff — verify the rule from the69schema/metadata, don't assume). Collapse raw rows into **logical entities**70(logical transaction / charge / event / person / contact) by their stable71business key. For each duplicate group the **retained** occurrence comes from the72authoritative/retained snapshot. Report `duplicate_raw_count = raw − logical`73and, where required, per-group `snapshot_ids` (sorted) + `retained_snapshot_id`.7475**D. Normalize.** Depending on domain:76- Text/contacts: canonical name (Unicode-preserving), email = trimmed NFKC77 **lowercase**, phone = **digits only** (kept as a string), canonical city,78 canonical region/depot value.79- Fuel/freight: resolve descriptions/aliases to a **recognized canonical80 category/class** via `/api/reference/aliases`; convert quantities to the81 canonical unit via `/api/reference/conversions`; convert money to the base82 currency via `/api/reference/fx` using the applicable business-date rate.83- Maintenance: convert odometer/labor to declared units; reconstruct per-asset84 history ordered by reliable timestamp.8586**E. Classify: valid vs mismatch vs quarantine.** Quarantine = a row that cannot87enter normalized totals. Reason families observed:88- `unrecognized` — no recognized category/alias match.89- `ambiguous` — matches more than one category/alias.90- `invalid_quantity` / `invalid_weight` / `invalid_distance` — non-positive or91 out-of-range physical measure.92- `no usable contact channel` — no usable email or phone (contacts/roster).93- maintenance rejects — missing/unparsable timestamp, invalid odometer range,94 negative or extreme labor.95A **mismatch** (expected category/class ≠ recognized category/class) is still a96**valid** record: it counts in normalized totals and is flagged separately.97**Quarantined rows are excluded from all normalized totals; valid mismatches are98included.** An "exception" is a distinct logical record that is a valid mismatch99*or* is quarantined.100101**F. Canonical resolution & survivors.** Group logical entities that are the same102real-world entity (identity resolution). Pick a survivor / `master_id` (a real103public row id) and build canonical field values. When sources disagree per104field, apply **field-level source precedence** (discover the precedence order105from schema/reference — e.g. CRM vs Compliance Master vs Partner Portal, or106HR Directory vs Dispatch vs Identity Registry) and record which source won each107field. Entities with genuinely conflicting identity evidence on a watchlist are108**contested → not auto-merged** (`CONTESTED_NO_AUTOMERGE`). `member_row_ids` is109the deduplicated, lexicographically-sorted set of contributing public row ids.110111**G. Rollups, rankings, readiness.**112- Rollups (region/depot, fuel_type, service_class): one row per represented113 value from the contract's enum, canonical-entity/charge counts, sorted114 ascending by the key.115- Rankings (merchant / carrier / asset): sort by the primary metric **descending**116 then the declared tie-breaks (usually id ascending); truncate to the scope's117 limit; assign `rank` from 1.118- Readiness (contacts/roster): an entity is **readiness-eligible** only when it119 is **active AND retains at least one usable email or phone**. A channel is120 **ready only when consent is granted**. Partition eligible entities into121 mutually exclusive buckets (`both` / `email_only` / `phone_only` / `not_ready`)122 that sum to the eligible count. "Dispatchable" = active + usable channel +123 consent granted; blocked reasons (consent / no-contact / inactive) partition124 the depot total.125126**H. Control codes (opaque; expansions withheld on purpose).** See §3.127128**I. Certification / status.** Compute the gate metric — usually129`quarantine_rate = quarantined_rows / canonical_entities`, rounded to 4 dp — and130apply the scope's thresholds: at-or-below `pass_max` → `PASS`; else at-or-below131`pass_with_exceptions_max` → `PASS_WITH_EXCEPTIONS`; else `HOLD`. Honor any132**hard gate** the scope names (e.g. any odometer regression forces HOLD /133BLOCK_AND_REMEDIATE) — a hard gate overrides the rate. Map status → action with134the scope's `status_action_map` (typically PASS→RELEASE,135PASS_WITH_EXCEPTIONS→REVIEW_EXCEPTIONS, HOLD→BLOCK_AND_REMEDIATE).136137**J. Assemble & self-check.** Build the object to the contract, then run §4.138139## 3. Assigning the opaque control codes140141Codes like `IC-25/40/70/90`, `OR-15/35/60/80`, `FP-20/55/75`, `RB-17/42/83`,142`SB-24/61/79`, `LD-14/31/53/72/88`, `MS-12/47/86`, `HR-19/33/74` are **labels for143categorical reconciliation outcomes** within a family (identity, outreach/144readiness, field-provenance, reference-basis, source-basis, ledger-disposition,145maintenance-source, history-route). Their plain-language expansions are146**deliberately not provided** — you must infer the mapping from the data, not147memorize it. Method:1481491. **Count the outcomes.** The enum size = number of distinct outcomes to150 distinguish (e.g. FP/RB/SB/MS/HR have 3; IC/OR have 4; LD has 5). Derive that151 many categorical outcomes from your reconciliation for that family.1522. **Use built-in calibration.** When the contract pairs a code family with153 *named* categories, that pairing reveals the family's semantics — e.g. a154 `readiness_partition` object that asks for an OR code per155 `both/email_only/phone_only/not_ready` bucket tells you OR codes label156 readiness states; solve the 1:1 assignment from that. Anchored control cases157 (pinned evidence rows in the scope) exist precisely to let you tie a known158 reconciliation outcome to its code.1593. **Cross-check with the hub.** Look for a signal in the records, reference160 tables, or `/api/query` results that separates the outcomes; assign codes so161 the observable ordering/partition is consistent.1624. **Be internally consistent.** The same outcome always maps to the same code,163 everywhere in the answer. Do not assume the numeric suffix implies severity or164 order unless the evidence shows it.165166## 4. Output contract compliance (do this before returning)167168The contract — not this skill — is authoritative. Verify each:169170- **Shape:** every required key present; no extra keys (`additionalProperties:171 false` / `additional_top_level_keys_allowed:false`). Objects/arrays nested172 exactly as specified.173- **Cardinality:** arrays with `minItems`/`maxItems` (or a `length`) have exactly174 that many items — one row per requested focus item / rollup enum value / scope175 id, no more, no fewer.176- **Ordering:** apply each list's stated rule (usually lexicographic ascending by177 id; rankings by metric desc then tie-break). Set membership is deduplicated.178- **Enums:** every enum-typed value is one of the allowed literals (statuses,179 actions, source systems, categories, control codes).180- **Rounding / precision:** money/volume/weight/distance to 2 dp; rates to 4 dp;181 honor `multipleOf`. Counts are exact integers. Phone stays a **string** of182 digits.183- **Ids:** use only stable public ids present in the data or supplied in the184 scope; match id `pattern`s.185- **Partitions add up:** mutually-exclusive count groups sum to their stated186 total (readiness buckets, depot dispositions).187- **Consistency:** e.g. `dispatchable_person_count` == length of188 `dispatchable_master_ids`; `duplicate_raw_count` == raw − logical;189 quarantine_rate == quarantined / canonical (rounded).190- **Return one JSON object only** — no Markdown, no commentary, no code fences.191192## 5. Cross-cutting reminders193194- Discover, don't assume: field names, pagination, snapshot-selection rule, and195 precedence order all come from the live catalog/schema/metadata.196- Reconcile *as of the cutoff*; ignore rows and rate versions outside it.197- Quarantined rows never enter normalized totals; valid mismatches always do.198- Recompute derived counts from your own reconciled set — never copy a count the199 API reports for a different scope.200- Keep every derivation deterministic so the same inputs always yield the same201 answer.202203See `references/task-family-map.md` for how each observed task variant maps onto204this pipeline, and `scripts/asteria_client.py` for the authenticated client.