Skip to content

Addresses

An address represents a physical location. Every scheme attaches to one address, so addresses are the foundation that other records hang off. The rules are short — Address Line and Country are required, postal code and a few other fields have length limits — but addresses do more automatic work than most other entities: once you set a location, the system auto-links the address to every market boundary whose polygon contains it.

Save is only enabled once you’ve changed something.

FieldRequired whenWhat you see if missing
Address LineAlways”Address is required.”
CountryAlways”Country is required.”

The form rejects values longer than 255 characters with “Address must be 255 characters or fewer.”

A practical limit covering every postal code format the system supports.

The locality is the town/city; the form caps it at 100 characters with “Locality must be 100 characters or fewer.”

Administrative Area, Sublocality max 255 characters

Section titled “Administrative Area, Sublocality max 255 characters”

Administrative Area (state/province/county) and Sublocality (neighbourhood/district) follow the same 255-character cap as the main Address Line.

Where this lives: src/services/web/src/lib/validation/address.ts (frontend) and src/common/models/Models/Address.cs (model annotations).

Closed date appears when “Is Closed” is ticked

Section titled “Closed date appears when “Is Closed” is ticked”

Ticking Is Closed? reveals a Closed Date field. Unticking hides the field; any date entered is retained on the record.

The address has a Location (a single point in WGS84 coordinates) that you don’t type in directly — you either drop a pin on the map, or pick a Google place from the address-search dropdown which fills the location automatically. The location drives the market-boundary auto-assignment below.

The Country dropdown supports type-to-filter. Typing “un” narrows to “United Kingdom” and “United States”; typing “fra” leaves “France”. Match is case-insensitive, starts-with. Arrow keys and Enter pick the highlighted country.

The server re-runs the address-line and country requirements. Country is taken from either a numeric CountryId or a Country.CountryCode reference (the system normalises the latter to the former before saving).

After the address is saved, the system synchronises AddressMarketBoundary links by spatial intersection. For every market boundary whose polygon contains the address’s location point, a link is created or kept; for every link to a boundary the address has moved out of, the link is removed.

If the location is cleared (set to null), every existing AddressMarketBoundary link for the address is removed — the system treats “no location” as “no longer inside any boundary”.

The sync runs in the SaveChanges flow so the address and its boundary links are consistent within a single transaction.

Where this lives: src/services/api/app/app.api/Services/Model/AddressMarketBoundaryService.cs.

The save carries the RowVersion of the address you loaded. If another user has updated it since, you get a concurrency error. See Concurrent edits.

  • An AddressCreated or AddressUpdated event fires.
  • The address’s row in the AddressList query view is upserted.
  • The vector-tile cache for any market-boundary type the address now intersects (or no longer intersects) bumps its cache version so the boundary overlay on maps stays accurate. See Market Boundaries → After save.
  • An audit log entry records the change.

How the list view’s Markets column is scoped

Section titled “How the list view’s Markets column is scoped”

The AddressList query view carries a denormalised Markets column listing the market boundaries the address falls in. It is not a straight dump of every linked boundary — it is filtered by what the address is actually used for:

  • If the address has at least one scheme, the column lists only the markets whose MarketType matches at least one scheme’s BuildingType.MarketType. An address with one Office scheme that sits inside Office + Retail + Industrial boundaries will only list the Office market — the others are correct intersections but irrelevant to how the address is used.
  • If the address has no schemes yet (typical for a freshly created pin), every linked market boundary is listed. There is nothing to scope by, so the column reflects the raw spatial result of the sync.

The same applies to the per-row scheme-type sectors driving the cache-key tier for the map view; the rule is consistent across the read paths.

  • Schemes — every scheme hangs off an address.
  • Market Boundaries — what the AddressMarketBoundary links point to and how their polygons are validated.
  • Companies — companies optionally attach to a primary address.
  • Search matching — how the address search box matches (synonyms for street types, abbreviations).
  • Completeness score — postal code, locality, administrative area, country, location, and Is Verified all feed into the score.
  • Concurrent edits — RowVersion mechanics.
  • Frontend validation: src/services/web/src/lib/validation/address.ts
  • Edit form: src/services/web/src/lib/components/panels/AddressEdit.svelte
  • Model: src/common/models/Models/Address.cs
  • Command handlers: src/services/api/app/app.api/Handlers/Commands/Addresses/
  • Market-boundary sync service: src/services/api/app/app.api/Services/Model/AddressMarketBoundaryService.cs
  • Search-side mapping: src/services/api/app/app.api/Services/Search/AddressSearchService.cs
  • List-view mapper: src/common/models/Services/QueryViews/AddressListViewMapper.cs