diff --git a/.changeset/geo-metro-registry.md b/.changeset/geo-metro-registry.md new file mode 100644 index 0000000000..899f6be960 --- /dev/null +++ b/.changeset/geo-metro-registry.md @@ -0,0 +1,4 @@ +--- +--- + +Empty changeset: Adds the AAO geo metro reference registry — a JSON schema for the registry entry shape, an initial verified `nielsen_dma` seed (~27 markets), and reference documentation covering agent usage for name-to-code resolution and code-to-name display. diff --git a/docs.json b/docs.json index c5e6162830..d6632cec67 100644 --- a/docs.json +++ b/docs.json @@ -1787,6 +1787,7 @@ ] }, "docs/reference/gmsf-reference", + "docs/reference/geo-metro-registry", "docs/reference/media-channel-taxonomy", "docs/measurement/taxonomy", "docs/reference/roadmap", diff --git a/docs/reference/geo-metro-registry.mdx b/docs/reference/geo-metro-registry.mdx new file mode 100644 index 0000000000..01c76cafac --- /dev/null +++ b/docs/reference/geo-metro-registry.mdx @@ -0,0 +1,143 @@ +--- +title: Geo Metro Registry +description: "AAO-maintained reference tables mapping numeric geo metro codes to canonical names and aliases for use in AdCP targeting." +"og:title": "AdCP Geo Metro Registry" +--- + +# Geo Metro Registry + +AdCP's `geo_metros` targeting field accepts system-qualified numeric codes: + +```json +{ + "geo_metros": [ + { "system": "nielsen_dma", "values": ["501", "532"] } + ] +} +``` + +Buyers think in market names, not codes. Agents that guess codes from training data produce hallucinations — "Albany" matches both DMA 525 (Albany, GA) and DMA 532 (Albany-Schenectady-Troy, NY), and resolving to the wrong one silently misdirects spend. The AAO geo metro registry provides authoritative code-to-name tables so agents can resolve names correctly and display human-readable labels for stored codes. + +## Registry Structure + +One registry file per system. Registry data files are available in the AdCP repository under `static/schemas/source/registries/`. + + +A `GET /api/reference/geo-metros/{system}` endpoint is planned to serve registry data over HTTP. Until it ships, consume registry files directly from the repository or bundle them in your SDK. + + +Each registry file conforms to the [geo-metro-registry schema](https://adcontextprotocol.org/schemas/v3/registries/geo-metro-registry.json) and includes: + +| Field | Description | +|-------|-------------| +| `system` | The metro system identifier (matches `metro-system.json` enum) | +| `version` | Semver — bump on every published change | +| `digest` | sha256 of the `entries` array (sorted keys, hex-encoded) for cache validation | +| `last_updated` | ISO date of the last published change | +| `entries[]` | All code-name pairs for this system | + +Each entry: + +| Field | Description | +|-------|-------------| +| `code` | Opaque numeric string — the value used in `geo_metros.values[]` | +| `name` | Official canonical market name | +| `aliases[]` | Common variants: abbreviations, historical names, city-only forms | +| `region` | ISO 3166-2 region code (informational, where applicable) | +| `country` | ISO 3166-1 alpha-2 country code (required for non-US systems) | +| `deprecated` | `true` if the system authority has discontinued this market | +| `deprecated_reason` | Human-readable explanation of deprecation | + +## Supported Systems + +| System | Status | Coverage | Source | +|--------|--------|----------|--------| +| `nielsen_dma` | Available | ~27 verified US markets (initial seed; expanding to ~210) | Nielsen DMA regions | +| `uk_itl1` | Planned | 12 UK ITL Level 1 regions | ONS ITL reference | +| `uk_itl2` | Planned | ~41 UK ITL Level 2 regions | ONS ITL reference | +| `eurostat_nuts2` | Planned | ~240 EU NUTS Level 2 regions | Eurostat NUTS reference | + +`custom` system values are provider-scoped and are not covered by the AAO registry. + +## Agent Usage + +### Name → code (write direction) + +When a buyer provides a market name, resolve it before setting `geo_metros`: + +1. Load the registry for the relevant system. +2. Match the buyer's input against `name` and `aliases[]` (case-insensitive, trimmed). +3. If multiple entries match, surface all matches and ask the buyer to confirm. For example, "Albany" matches both DMA 525 (Albany, GA) and DMA 532 (Albany-Schenectady-Troy, NY) — these are distinct markets in different states. +4. Use the confirmed `code` in the `values[]` array. + +**Never infer a code from training data.** DMA codes are a fixed, non-sequential numbering scheme — market size rank does not correspond to code. An agent guessing codes silently misdirects spend. + +### Code → name (read direction) + +When displaying campaign targeting to a user, resolve stored codes to names: + +1. Load the registry for the system. +2. Look up each code in `entries[]` by `code`. +3. Display `name`. If a code is not found, display the raw code — it may be a deprecated market or one not yet in the registry seed. + +### Caching + +Cache registry data by `version`. Use the `digest` field (sha256 of `entries` with sorted keys, hex-encoded) to detect stale caches without comparing entries entry-by-entry. A 24-hour TTL is appropriate — registries change infrequently. + +## Example: Nielsen DMA Lookup + +**Abbreviated `nielsen_dma` data file:** +```json +{ + "system": "nielsen_dma", + "version": "1.0.0", + "entries": [ + { + "code": "501", + "name": "New York, NY", + "aliases": ["NYC", "New York City", "New York"] + }, + { + "code": "525", + "name": "Albany, GA", + "aliases": ["Albany"] + }, + { + "code": "532", + "name": "Albany-Schenectady-Troy, NY", + "aliases": ["Albany", "Albany NY", "Schenectady", "Troy NY", "Capital Region"] + }, + { + "code": "602", + "name": "Chicago, IL", + "aliases": ["Chicago"] + }, + { + "code": "803", + "name": "Los Angeles, CA", + "aliases": ["LA", "Los Angeles", "L.A."] + } + ] +} +``` + +A buyer saying "target Albany" triggers an ambiguity — both DMA 525 and DMA 532 carry "Albany" in their aliases. The agent surfaces both and asks which market the buyer intends before writing `geo_metros`. + +## Governance + +Registry entries are additive. Once published, a code is never removed — it may be marked `deprecated: true` with a `deprecated_reason` if the system authority discontinues a market. Name changes follow the source system's official release; the previous name moves to `aliases[]`. + +The `digest` field is sha256 of the `entries` array serialized with sorted keys. It is computed at publish time and changes whenever any entry changes; clients use it for cache invalidation. + +Corrections and additions land via PR against [adcontextprotocol/adcp](https://github.com/adcontextprotocol/adcp) with a reference to the source system's authoritative publication. + +## Long-term Direction + +The geo metro registry is the first of a broader class of shared reference registries that AAO maintains on behalf of the AdCP ecosystem — covering reference data that is: + +- Standardized or semi-standardized across many sellers +- Needed for natural-language resolution by agents +- Stable enough to version +- Painful and duplicative for every provider to ship independently + +The `nielsen_dma` file ships as a verified seed (~27 markets). The full ~210 US DMA list and the ITL and NUTS registries for UK and EU markets are tracked as follow-up contributions. Property identity registries (mobile apps, CTV apps, domains) are tracked separately under the property registry design. diff --git a/static/schemas/source/registries/geo-metro-nielsen_dma.json b/static/schemas/source/registries/geo-metro-nielsen_dma.json new file mode 100644 index 0000000000..ddc09db8f6 --- /dev/null +++ b/static/schemas/source/registries/geo-metro-nielsen_dma.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/registries/geo-metro-nielsen_dma.json", + "title": "Nielsen DMA Geo Metro Registry", + "description": "AAO-maintained registry of Nielsen Designated Market Area (DMA) codes and canonical market names for use in AdCP geo_metros targeting. Initial verified seed — expanding to full ~210 US markets.", + "system": "nielsen_dma", + "version": "1.0.0", + "last_updated": "2026-06-18", + "digest": "2e2fb3ca2059e0a6d173b036dbf11caa310e219e547e769c73b48b7be2dd3334", + "source_url": "https://www.nielsen.com/dma-regions/", + "entries": [ + { "code": "501", "name": "New York, NY", "aliases": ["NYC", "New York City", "New York"] }, + { "code": "504", "name": "Philadelphia, PA", "aliases": ["Philadelphia", "Philly"] }, + { "code": "505", "name": "Detroit, MI", "aliases": ["Detroit"] }, + { "code": "506", "name": "Boston, MA (Manchester)", "aliases": ["Boston", "Manchester NH"] }, + { "code": "508", "name": "Pittsburgh, PA", "aliases": ["Pittsburgh"] }, + { "code": "510", "name": "Cleveland-Akron (Canton), OH", "aliases": ["Cleveland", "Akron", "Canton"] }, + { "code": "511", "name": "Washington, DC (Hagerstown)", "aliases": ["Washington DC", "DC", "Washington", "Hagerstown"] }, + { "code": "524", "name": "Atlanta, GA", "aliases": ["Atlanta"] }, + { "code": "525", "name": "Albany, GA", "aliases": ["Albany"] }, + { "code": "531", "name": "Tri-Cities, TN/VA", "aliases": ["Tri-Cities", "Johnson City", "Kingsport", "Bristol"] }, + { "code": "532", "name": "Albany-Schenectady-Troy, NY", "aliases": ["Albany", "Albany NY", "Schenectady", "Troy NY", "Capital Region"] }, + { "code": "543", "name": "Springfield-Holyoke, MA", "aliases": ["Springfield MA", "Holyoke"] }, + { "code": "602", "name": "Chicago, IL", "aliases": ["Chicago"] }, + { "code": "610", "name": "Rockford, IL", "aliases": ["Rockford"] }, + { "code": "618", "name": "Houston, TX", "aliases": ["Houston"] }, + { "code": "623", "name": "Dallas-Ft. Worth, TX", "aliases": ["Dallas", "Fort Worth", "DFW"] }, + { "code": "628", "name": "Monroe, LA-El Dorado, AR", "aliases": ["Monroe LA", "El Dorado AR"] }, + { "code": "634", "name": "Amarillo, TX", "aliases": ["Amarillo"] }, + { "code": "709", "name": "Tyler-Longview (Lufkin & Nacogdoches), TX", "aliases": ["Tyler TX", "Longview TX", "Lufkin", "Nacogdoches"] }, + { "code": "722", "name": "Lincoln-Hastings-Kearney, NE", "aliases": ["Lincoln NE", "Hastings NE", "Kearney NE"] }, + { "code": "734", "name": "Jonesboro, AR", "aliases": ["Jonesboro"] }, + { "code": "743", "name": "Anchorage, AK", "aliases": ["Anchorage"] }, + { "code": "753", "name": "Phoenix (Prescott), AZ", "aliases": ["Phoenix", "Prescott AZ"] }, + { "code": "798", "name": "Glendive, MT", "aliases": ["Glendive"] }, + { "code": "803", "name": "Los Angeles, CA", "aliases": ["LA", "Los Angeles", "L.A."] }, + { "code": "807", "name": "San Francisco-Oakland-San Jose, CA", "aliases": ["San Francisco", "SF", "Bay Area", "Oakland", "San Jose"] }, + { "code": "819", "name": "Seattle-Tacoma, WA", "aliases": ["Seattle", "Tacoma"] } + ] +} diff --git a/static/schemas/source/registries/geo-metro-registry.json b/static/schemas/source/registries/geo-metro-registry.json new file mode 100644 index 0000000000..637ed56663 --- /dev/null +++ b/static/schemas/source/registries/geo-metro-registry.json @@ -0,0 +1,78 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/registries/geo-metro-registry.json", + "title": "Geo Metro Code Registry", + "description": "AAO-maintained registry mapping numeric geo metro codes to canonical names and aliases for each supported metro classification system.\n\n**Purpose.** AdCP's `geo_metros` targeting field accepts system-qualified numeric codes (e.g. `{ \"system\": \"nielsen_dma\", \"values\": [\"532\"] }`). Human buyers know markets by name, not code. This registry provides the authoritative code → name mapping and alias table so agents can resolve natural-language market names to the correct codes without hallucination, and display human-readable labels for stored codes.\n\n**Systems covered.** One registry file per system, named `geo-metro-{system}.json` (e.g. `geo-metro-nielsen_dma.json`). Each file conforms to this schema. The set of valid system identifiers is defined by `/schemas/enums/metro-system.json`.\n\n**Agent usage (normative).**\n1. *Name → code (write direction):* When a buyer provides a market name (e.g. \"Albany\" or \"Albany, NY\"), the agent MUST fetch the registry for the relevant system and match against `name` and `aliases[]` before setting `geo_metros`. The agent MUST NOT guess or infer a code from training data.\n2. *Code → name (read direction):* When displaying campaign targeting to a user, the agent SHOULD resolve stored codes to names via the registry and present `name`, not raw codes.\n3. *Ambiguous input:* When an input matches multiple entries (e.g. \"Albany\" matches Albany, GA (DMA 525) and Albany-Schenectady-Troy, NY (DMA 532)), the agent MUST surface all matches and ask the buyer to confirm.\n\n**Caching.** Clients SHOULD cache registry files by `version`. The `digest` field (sha256 of the `entries` array serialised with sorted keys, hex-encoded) lets clients detect changes without re-fetching. Registries are updated infrequently; a TTL of 24 hours is appropriate.\n\n**Governance.** Entries are additive. Once published, a code entry is never removed (it may be marked `deprecated: true` with a `deprecated_reason` if a market is discontinued or renamed). Name changes follow the source system's official release; the previous name moves to `aliases[]` and the official current name becomes `name`. Additions and corrections land via PR with a reference to the source system's authoritative publication.", + "type": "object", + "required": ["system", "version", "last_updated", "digest", "entries"], + "properties": { + "system": { + "$ref": "/schemas/enums/metro-system.json", + "description": "The metro classification system these entries belong to. Matches the `system` field used in AdCP `geo_metros` targeting." + }, + "version": { + "type": "string", + "description": "Semver of this registry file. Bumped on every published change." + }, + "last_updated": { + "type": "string", + "format": "date", + "description": "ISO date of the last published change." + }, + "digest": { + "type": "string", + "description": "sha256 of the `entries` array serialised with sorted keys, hex-encoded. Clients use this to detect stale caches without comparing entry-by-entry." + }, + "source_url": { + "type": "string", + "format": "uri", + "description": "URL of the authoritative source publication for this registry (e.g. Nielsen DMA documentation, Eurostat NUTS reference)." + }, + "entries": { + "type": "array", + "description": "All known code-name pairs for this system. Ordered by code ascending.", + "items": { + "$ref": "#/definitions/GeoMetroEntry" + } + } + }, + "definitions": { + "GeoMetroEntry": { + "type": "object", + "required": ["code", "name"], + "properties": { + "code": { + "type": "string", + "description": "The numeric string code used in AdCP `geo_metros.values[]`. Must be treated as an opaque string identifier — do not cast to integer." + }, + "name": { + "type": "string", + "description": "Official canonical name for this market as published by the system authority." + }, + "aliases": { + "type": "array", + "items": { "type": "string" }, + "description": "Alternative names buyers commonly use to refer to this market, including historical names, abbreviations, and city-only variants. Used for fuzzy name resolution." + }, + "region": { + "type": "string", + "description": "ISO 3166-2 region code for the primary region this market falls within (where applicable). Informational only." + }, + "country": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country code. Required for non-US systems." + }, + "deprecated": { + "type": "boolean", + "default": false, + "description": "When true, this market has been discontinued by the system authority. The code is retained for backward-compatibility with stored campaigns but SHOULD NOT be offered in new targeting." + }, + "deprecated_reason": { + "type": "string", + "description": "Human-readable explanation of why this entry is deprecated (e.g. 'Market discontinued in Nielsen 2024 DMA refresh')." + } + }, + "additionalProperties": false + } + } +}