Skip to content

Admin Boundaries

An admin boundary is a polygon that defines an administrative area — a country, region, county, city, or district. They sit alongside Market Boundaries but classify by political / administrative geography rather than by market sector.

Every address gets auto-linked to the admin boundaries it sits inside. Editing a boundary’s polygon re-runs the spatial sync against every affected address — addresses that have moved into the boundary gain a link, those that have moved out lose theirs.

Admin boundaries are managed by Admins only — non-admin users can see them on list views (one combined “Admin Boundaries” column per list) but can’t edit the boundaries themselves.

FieldRequired whenWhat you see if missing
CodeAlways”Code is required.”
NameAlways”Name is required.”
TypeAlwaysA boundary must reference an existing Admin Boundary Type (Country, Region, County, etc.).
TierAlwaysA boundary must reference a Tier Type that determines which list column it shows in.

The polygon is optional at save time — you can create a boundary with metadata only and add the polygon later. Boundaries without a polygon are skipped during the address auto-sync (they don’t contribute to any address’s boundary set until the shape is filled in).

The form rejects codes longer than 50 characters. The code must be unique across all admin boundaries (the DB enforces a unique index).

The form rejects names longer than 255 characters.

Optional free text.

Where this lives: src/services/web/src/lib/validation/admin-boundary.ts.

The polygon is run through the same validation pipeline as Market Boundaries. The rules are:

  • Must be a single polygon (no MultiPolygon).
  • At least 4 points (a closed ring needs 4 distinct coordinates with the first and last matching).
  • Non-zero area.
  • Every coordinate within WGS84 range (longitude −180 to 180, latitude −90 to 90).
  • Topologically valid (no self-intersections; rings closed properly).
  • At most 10,000 vertices. The editor surfaces an informational message above 2,500 vertices, a stronger warning above 5,000, and disables Save above 10,000.

The shared validation service handles both entity types — failure messages reference AdminBoundary.Polygon rather than MarketBoundary.Polygon for clarity in error responses.

Where this lives: src/common/services/Validation/GeometryValidationService.cs (validity checks) and src/services/api/app/app.api/Handlers/Commands/AdminBoundaries/AdminBoundaryGeometryValidator.cs (the prefix shim).

Every admin boundary belongs to a Tier Type that says where it sits in the administrative hierarchy. The seeded defaults are:

TierNameTypical examples
1CountryUK, US, France
2RegionEngland, California, Île-de-France
3CountyGreater London, Cook County, Hauts-de-Seine
4CityLondon, Chicago, Paris
5DistrictCity of London, The Loop, 1er Arrondissement

Tiers are seeded by the DACPAC but admins can add more via the Tier Types admin area. New tiers show up automatically — the list pages render a single denormalised “Admin Boundaries” string column (the boundary names, ordered by tier, comma-separated), so adding a tier doesn’t require a frontend change.

Code, Name, Type, Tier all re-checked server-side.

The server runs the polygon through the validation service. If a polygon is supplied, every rule above is enforced.

The save carries the boundary’s Version integer. If another admin has updated the boundary since you loaded it, you get a concurrency error — reload and re-apply your changes. (Unlike most other entities, admin boundaries use an int Version rather than a RowVersion byte array — same effect, less standard wire shape; flagged for future hardening.)

The geometry-changed event fires after every Create / Patch / Replace / Delete that touched the polygon. For each affected address (those inside the old polygon, the new polygon, or both), the spatial-intersection sync re-evaluates which admin boundaries the address sits inside and updates AddressAdminBoundary accordingly. The sync picks the leaf-most boundary per tier — if an address sits inside both “England” (tier 2) and “Greater Manchester” (a more specific tier 2 sub-area), only the more specific one is linked.

The same sync also runs whenever an address’s own Location changes (on Create / Patch / Replace of the address). Clearing an address’s Location removes every AddressAdminBoundary link for that address.

Where this lives: src/services/api/app/app.api/Services/Model/AddressAdminBoundaryService.cs + src/services/api/app/app.api/Handlers/Events/AdminBoundaries/AdminBoundaryGeometryChangedEventHandler.cs.

  • One “Admin Boundaries” column on the Address, Scheme, Company, Occupier Event, and Investment Event list pages — names ordered by tier (Country → Region → County → City → District) and comma-separated, mirroring the existing Markets column on Addresses.
  • An admin area at /admin-boundaries for managing boundaries (full polygon editor coming in a subsequent release; in the meantime, manage via the OData / write API).
  • Admin areas for the underlying type and tier dictionaries at /admin-boundary-types and /tier-types.
  • Market Boundaries — same shape, different classification axis (market sector vs administrative geography).
  • Addresses — the spatial-sync rule runs on every address write.
  • Concurrent edits — admin boundaries use int Version for concurrency rather than RowVersion, but the user-visible behaviour matches.
  • Permissions and roles — boundary edits are admin-gated.
  • SQL schema: src/data/app/app/Tables/AdminBoundary.sql, AddressAdminBoundary.sql, AdminBoundaryType.sql
  • Seed defaults: src/data/app/Scripts/SeedAdminBoundaryTypes.sql, SeedTierTypes.sql
  • EF model: src/common/models/Models/AdminBoundary.cs
  • Controllers: src/services/api/app/app.api/Controllers/AdminBoundariesODataController.cs, AdminBoundariesWriteController.cs
  • Command handlers: src/services/api/app/app.api/Handlers/Commands/AdminBoundaries/
  • Sync service: src/services/api/app/app.api/Services/Model/AddressAdminBoundaryService.cs
  • Frontend types + config: src/services/web/src/lib/types/common.ts, src/services/web/src/lib/odata/configs/admin-boundaries.svelte.ts
  • List-view mappers: any *ListViewMapper.cs that calls AdminBoundaryTierMapper.FromAddress(...)