Addresses
Overview
Section titled “Overview”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.
Required fields
Section titled “Required fields”| Field | Required when | What you see if missing |
|---|---|---|
| Address Line | Always | ”Address is required.” |
| Country | Always | ”Country is required.” |
Field-level rules
Section titled “Field-level rules”Address Line max 255 characters
Section titled “Address Line max 255 characters”The form rejects values longer than 255 characters with “Address must be 255 characters or fewer.”
Postal Code max 20 characters
Section titled “Postal Code max 20 characters”A practical limit covering every postal code format the system supports.
Locality max 100 characters
Section titled “Locality max 100 characters”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).
Cross-field rules
Section titled “Cross-field rules”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.
Conditional UI
Section titled “Conditional UI”Location is set via the map or the picker
Section titled “Location is set via the map or the picker”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.
Country picker filters as you type
Section titled “Country picker filters as you type”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.
On save
Section titled “On save”Required-field check
Section titled “Required-field check”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).
Market-boundary auto-sync
Section titled “Market-boundary auto-sync”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.
Concurrency check
Section titled “Concurrency check”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.
After save
Section titled “After save”- 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
MarketTypematches at least one scheme’sBuildingType.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.
Related rules
Section titled “Related rules”- 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.
Where this lives
Section titled “Where this lives”- 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