---
id: CAP-CRM-REG-006
title: Determine an address’s geographic region
feature: FEAT-CRM-REG-02
product_area: crm
module: regions
revision: 2
status: draft
product_intent: proposed
implementation_evidence: observed
supersedes:
  - CAP-CRM-CFG-016
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - >-
    services-crm:docs/migrations/crm-regions/README.md#Behaviour changes, Not changed (kept as Rhythm 1.0 did;
    accepted by Andrew 2026-10-08)
  - >-
    services-crm:docs/migrations/crm-regions/README.md#Behaviour changes (Andrew's requirement for the port:
    replace or remove stale calculated assignments)
purpose: Rhythm determines the geographic region of an address consistently wherever assignment is requested.
outcomes:
  - All placement paths share one geographic decision.
  - Uncovered addresses receive no region.
concepts:
  - CON-CRM-REG-002
  - CON-CRM-REG-003
actors:
  - ACT-CRM-005
surfaces:
  - job
  - api
preconditions: []
postconditions: []
permissions: []
rules:
  - id: BR-CAP-CRM-REG-006-01
    proved_by: [ATS-CRM-REG-002-07, ATS-CRM-REG-004-09, ATS-CRM-REG-005-08, ATS-CRM-REG-006-01, ATS-CRM-REG-006-10, ATS-CRM-REG-006-15, ATS-CRM-REG-007-06, TST-CRM-REG-058, TST-CRM-REG-065]
    statement: The first matching enabled geographic rule in policy order supplies the region.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/unit/region-matching.spec.js#region_for_address › answers the first
      rule that matches when several would
    origin: >-
      services-crm:docs/migrations/crm-regions/README.md#Behaviour changes, Not changed (kept as Rhythm 1.0
      did; accepted by Andrew 2026-10-08)
  - id: BR-CAP-CRM-REG-006-02
    proved_by: [ATS-CRM-REG-006-02, TST-CRM-REG-057, TST-CRM-REG-065]
    statement: >-
      Fallback rules are reserved until all geographic rules miss, and the last enabled fallback supplies the
      result when several exist.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/unit/region-matching.spec.js#region_for_address › tries the
      rest-of-the-world rule last wherever it sits in the order
    origin: >-
      services-crm:docs/migrations/crm-regions/README.md#Behaviour changes, Not changed (kept as Rhythm 1.0
      did; accepted by Andrew 2026-10-08)
  - id: BR-CAP-CRM-REG-006-03
    proved_by: [ATS-CRM-REG-006-03, TST-CRM-REG-057]
    statement: 'Matching ignores disabled rules, including disabled fallback rules.'
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment-rules.integration.spec.js#a
      rule staff turned off › skips the disabled rule and answers the next rule that matches
    origin: none
  - id: BR-CAP-CRM-REG-006-04
    proved_by: [ATS-CRM-REG-006-04, TST-CRM-REG-058, TST-CRM-REG-064, TST-CRM-REG-065]
    statement: >-
      Postal ranges compare text with inclusive endpoints, accepting a ZIP+4 value within a five-digit
      interval and rejecting a value beyond its end.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/unit/region-matching.spec.js#region_for_address › compares postal
      codes as strings, so a ZIP+4 code inside the range matches
    origin: >-
      services-crm:docs/migrations/crm-regions/README.md#Behaviour changes, Not changed (kept as Rhythm 1.0
      did; accepted by Andrew 2026-10-08)
  - id: BR-CAP-CRM-REG-006-05
    proved_by: [ATS-CRM-REG-006-05, TST-CRM-REG-057]
    statement: >-
      A country-restricted postal rule matches only that country, treating an omitted address country as the
      United States.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment-rules.integration.spec.js#a
      postal code rule confined to a country › refuses the same postal code in another country
    origin: none
  - id: BR-CAP-CRM-REG-006-06
    proved_by: [ATS-CRM-REG-006-06, TST-CRM-REG-064, TST-CRM-REG-065]
    statement: 'State matching uses exact stored text, including letter case.'
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/unit/region-matching.spec.js#region_for_address › matches states
      exactly, so a lower-case state is not the upper-case one
    origin: >-
      services-crm:docs/migrations/crm-regions/README.md#Behaviour changes, Not changed (kept as Rhythm 1.0
      did; accepted by Andrew 2026-10-08)
  - id: BR-CAP-CRM-REG-006-07
    proved_by: [ATS-CRM-REG-006-07, TST-CRM-REG-064, TST-CRM-REG-065]
    statement: Country matching ignores letter case and treats a missing or blank address country as the United States.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/unit/region-matching.spec.js#region_for_address › counts an address
      with no country as US
    origin: >-
      services-crm:docs/migrations/crm-regions/README.md#Behaviour changes, Not changed (kept as Rhythm 1.0
      did; accepted by Andrew 2026-10-08)
  - id: BR-CAP-CRM-REG-006-08
    proved_by: [ATS-CRM-REG-006-14, TST-CRM-REG-058]
    statement: >-
      Assignment reads addresses after save-time cleaning, allowing supported state and country names to match
      their stored codes.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#normalized
      address variants (the save-time cleaning, docs/migrations/contact-and-organization-cleaning/) › matches
      a state rule from the state written out in full
    origin: none
  - id: BR-CAP-CRM-REG-006-09
    proved_by: [ATS-CRM-REG-006-08, TST-CRM-REG-065]
    statement: An address outside all enabled geographic rules has no region when no enabled fallback exists.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/unit/region-matching.spec.js#region_for_address › answers no region
      when nothing matches and there is no rest-of-the-world rule
    origin: >-
      services-crm:docs/migrations/crm-regions/README.md#Behaviour changes (Andrew's requirement for the port:
      replace or remove stale calculated assignments)
  - id: BR-CAP-CRM-REG-006-10
    proved_by: [ATS-CRM-REG-006-09, TST-CRM-REG-065]
    statement: An address absent or empty in every recognized field has no region even when a fallback exists.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/unit/region-matching.spec.js#region_for_address › answers no region
      for no address even with a rest-of-the-world rule
    origin: >-
      services-crm:docs/migrations/crm-regions/README.md#Behaviour changes (Andrew's requirement for the port:
      replace or remove stale calculated assignments)
  - id: BR-CAP-CRM-REG-006-11
    proved_by: [ATS-CRM-REG-006-12]
    statement: >-
      Geographic criteria compare postal code, state and country, while any populated recognized address field
      can establish address presence for matching.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: >-
      services-crm:modules/regions/services/region-assignment/region-matching.ts:geographicAddressOf,
      hasAddress, matchRegionRule
    origin: none
  - id: BR-CAP-CRM-REG-006-12
    proved_by: [ATS-CRM-REG-006-13, TST-CRM-REG-007]
    statement: 'Saving accepts a postal interval with its end before its start, but that interval cannot match a value.'
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: inferred
    source: >-
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:regionAssignmentRuleProblems;
      services-crm:modules/regions/services/region-assignment/region-matching.ts:matchRegionRule
    origin: none
    note: >-
      Saving checks nonempty endpoints without comparing their order; matching compares those endpoints
      lexically, so a reversed interval contains no value.
scenarios:
  - id: SC-CAP-CRM-REG-006-01
    given: Boston postal coverage precedes Massachusetts coverage and a fallback.
    when: Matching evaluates a Boston postal address.
    then: The postal rule supplies Boston’s region.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#incomplete
      addresses and the matcher’s own comparisons › answers the first rule in order when two match (postal
      code before state)
  - id: SC-CAP-CRM-REG-006-02
    given: The policy also contains a fallback region.
    when: Matching evaluates an address in Paris.
    then: The fallback supplies the region after geographic rules miss.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment-rules.integration.spec.js#the
      rest of the world rule › gives an address no other rule matched the rest of the world region
  - id: SC-CAP-CRM-REG-006-03
    given: The policy has no fallback.
    when: Matching evaluates an uncovered Ontario address.
    then: The address receives no region.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/unit/region-matching.spec.js#region_for_address › answers no region
      when nothing matches and there is no rest-of-the-world rule
edge_cases:
  - id: SC-CAP-CRM-REG-006-04
    given: A state criterion uses lower-case letters.
    when: Matching evaluates the corresponding uppercase stored state.
    then: Exact comparison does not match the criterion.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/unit/region-matching.spec.js#region_for_address › matches states
      exactly, so a lower-case state is not the upper-case one
  - id: SC-CAP-CRM-REG-006-05
    given: An address contains a city but no country.
    when: Matching evaluates it against Canada-only coverage.
    then: The blank country is treated as the United States and Canada does not match.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#incomplete
      addresses and the matcher’s own comparisons › gives an address with only a city no region (a blank
      country counts as US, which no rule names)
constraints: []
depends_on:
  - CAP-CRM-REG-004
related:
  - CAP-CRM-REG-002
  - CAP-CRM-REG-004
  - CAP-CRM-REG-007
  - CAP-CRM-REG-008
  - CAP-CRM-REG-011
journeys:
  - JNY-CRM-REG-001
open_questions:
  - OQ-CRM-REG-002
evidence:
  - >-
    services-crm: Examined the pinned regional implementation, its declared constraints and the supporting
    records.
  - 'react-console: Examined the pinned regional configuration surfaces and their available test evidence.'
  - 'services-crm:docs/migrations/crm-regions/README.md'
  - 'services-crm:modules/regions/tests/unit/region-matching.spec.js'
  - >-
    services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment-rules.integration.spec.js
  - >-
    services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js
  - 'services-crm:modules/regions/services/region-assignment/region-matching.ts'
  - 'services-crm:modules/regions/services/persistence/region-assignment-settings.ts'
controls: [TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-IAM-001, TC-IAM-005, TC-IAM-006, TC-LOG-005, TC-PI-001, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003]
exercised_by: [FAS-CRM-REG-02-01, FAS-CRM-REG-02-02]
suites: [TST-CRM-REG-007, TST-CRM-REG-057, TST-CRM-REG-058, TST-CRM-REG-064, TST-CRM-REG-065]
---

# Determine an address’s geographic region

## Purpose

Rhythm determines the geographic region of an address consistently wherever assignment is requested.

## How It Works

Rhythm evaluates enabled geographic rules in their saved order. Postal criteria compare text ranges, state criteria use exact stored values and country criteria ignore letter case. A missing country counts as the United States. Address presence is checked before geographic comparison, including recognized fields outside the three geographic criteria. A populated address outside geographic coverage can use the last enabled fallback. An absent address cannot use that fallback. Save-time address cleaning supplies normalized values to assignment, while preview does not perform the same record save. A reversed postal interval is accepted but cannot match. Product should decide whether saving that interval ought to fail.

## Relationships

- Build the geographic assignment policy maintains the geographic policy that other regional workflows evaluate.
- Retire regions while preserving their references handles retirement and reference-sensitive removal of regional definitions.
- Check an address against the saved policy lets staff inspect the saved policy’s result before applying it widely.
- Keep placements aligned with address changes coordinates placement after committed address changes.
- Apply the current policy across existing records applies policy changes to existing records.
