Skip to content
Updated Jul 4, 2026

Boundary

Status: feature branch, not merged or deployed

All boundary code lives on two un-merged worktree branches (feat-boundary-service and feat-rating-engine). Mainline /root/olly has no packages/go/geo, no boundary domain types, and policy-admin migrations stop at 0013 (the PostGIS and boundary migrations 0014-0021 are branch-only). Nothing here is running on dev-2 yet. Treat this page as describing built-but-unmerged code, not a live capability.

Boundary is a spatial capability layered onto the Policy Admin service, not a standalone microservice. It stores hierarchical polygons (administrative regions, rating zones, service areas, exclusion zones) and answers point-in-polygon questions: which boundary contains this point, is this point serviceable, which boundary in a named group covers it. Tables live in the policy_admin schema so they can join against rating-factor and product-config rows. The shared packages/go/geo package exposes a typed BoundaryStore interface for in-process callers; the rating engine resolves boundaries through a BoundaryResolver seam whose cross-service HTTP fetch is not yet wired.

Field reference: column-level types and nullability live in the catalog. This page is the narrative; the schema tables below give the load-bearing shape.

What it owns

All tables are in the policy_admin schema.

TablePurpose
boundary_levelsPer-market hierarchy definitions (country, region, county, ...)
boundariesThe polygons themselves, one MULTIPOLYGON per row, typed by boundary_type
boundary_groupsNamed collections of boundaries (e.g. a rating-zone set for a market)
boundary_group_membersm:n join between groups and boundaries

rate_tables is not a Boundary table. It belongs to the rating engine (feat-rating-engine, migration 0020) and has no FK to boundaries. The boundary-to-rate link is runtime-only, resolved through source_path (see below), never a foreign key.

There is no provider_service_areas table in either branch.

The boundary hierarchy

A Boundary carries a MarketCode, an optional integer Level (nil for arbitrary/custom shapes), an optional ParentID pointing at another boundary in the same market, a Code, a Name, and a BoundaryType. Geometry is a single PostGIS GEOMETRY(MULTIPOLYGON, 4326) column. It is not mapped into the Go struct: writes go through raw SQL (ST_GeomFromGeoJSON wrapped in ST_Multi), reads come back as GeoJSON via ST_AsGeoJSON.

boundary_type is constrained by a DB CHECK to: ADMINISTRATIVE, SERVICE_AREA, RATING_ZONE, SCHEME_REGION, EXCLUSION_ZONE. Serviceability is derived from two of these: a point is serviceable if it falls inside a SERVICE_AREA and outside every EXCLUSION_ZONE in the same market.

Consumers

ConsumerUseStatus
Rating engineGeographic rating factors: $boundary:<level> and $zone_group:<code> resolutionBuilt (on feat-rating-engine)
Any in-process callerContainment, group lookup, serviceability via BoundaryStoreBuilt
Care: nearest-provider lookups-Planned, not implemented. No nearest-neighbour query exists.
Provider: service-area declaration and nearest-by-specialty search-Planned, not implemented. No provider service-area table or endpoint exists.

There is no nearest-neighbour capability anywhere in the code. The Point and NearestResult structs in packages/go/geo/types.go are dead code, referenced by nothing; no ST_Distance/ST_DWithin query exists.

Source-path grammar

The rating engine references boundary data (and ordinary document fields) through a source_path string on a RatingFactor. Resolution lives in packages/go/rating/resolve.go (resolveSourcePath), and runs at rate/query time, not config-load time. There is no separate sourcepath package and no up-front validation pass: a malformed path surfaces as a rating error when the factor is evaluated. Parsing uses prefix matching plus fmt.Sscanf.

Two boundary prefixes are recognised:

source_pathSemantics
$boundary:<int level>Resolve the boundary at that integer level containing the point. Market comes from the element/policy document, not the path.
$zone_group:<code>Resolve which boundary in group <code> contains the point.

For both, lat/lng (and, for $boundary:, the market code) are pulled from the element data or policy document via extractLocationFromDocs; market defaults to GB when absent.

All other paths are dotted namespace.sub.key lookups into the in-memory documents, with at least three segments required:

NamespaceSource map
element.data.*element data
element.coverage_terms.*coverage terms
policy.document.*policy document

Unknown namespaces or boundary levels return an error from resolveSourcePath at evaluation time.

The Go interface

Package packages/go/geo ships interfaces only; the PostGIS implementation is GORMBoundaryRepository in policy-admin.

go
type BoundaryStore interface {
    FindAtLevel(ctx context.Context, marketCode string, level int, lat, lng float64) (*domain.Boundary, error)
    FindInGroup(ctx context.Context, groupCode string, lat, lng float64) (*domain.Boundary, error)
    FindAll(ctx context.Context, marketCode string, lat, lng float64) ([]domain.Boundary, error)
    ContainedBy(ctx context.Context, boundaryID uuid.UUID, lat, lng float64) (bool, error)
    Ancestors(ctx context.Context, boundaryID uuid.UUID) ([]domain.Boundary, error)
    Children(ctx context.Context, boundaryID uuid.UUID) ([]domain.Boundary, error)
    IsServiceable(ctx context.Context, marketCode string, lat, lng float64) (bool, error)
}

domain.Boundary fields: ID uuid.UUID, Locator string, MarketCode string, Level *int, ParentID *uuid.UUID, Code string, Name string, BoundaryType string, Properties []byte (jsonb), EffectiveFrom time.Time, EffectiveTo *time.Time, CreatedAt time.Time. There is no GroupCode field and no WKT/polygon field on the struct (geometry stays in the DB).

API routes

All routes are served by policy-admin. Two mounting groups exist (see internal/handler/handler.go):

  • External routes are wrapped by auth.JWTMiddleware only. A valid Keycloak JWT is required; there is no role check. Any authenticated caller can create, import, or delete boundaries. A boundary-admin role gate is not implemented.
  • Internal routes under /internal have no middleware at all: no JWT, no token header. There is no X-Internal-Token check and no INTERNAL_API_TOKEN env var. These endpoints rely entirely on network-level isolation (not reachable outside the cluster/VPC), which is a documented gap, not an enforced control.

Request and response bodies are camelCase JSON.

Boundary levels

MethodPathBodyResponse
POST/boundary-levels{marketCode, level, name, parentLevel?}201 the level
GET/boundary-levels/{marketCode}-200 {levels: [...]}

Boundaries

MethodPathBody / paramsResponse
POST/boundaries{marketCode, level?, parentLocator?, code, name, boundaryType, geoJSON, properties?, effectiveFrom?}201 the boundary
POST/boundaries/import{marketCode, level?, boundaryType, featureCollection}201 {imported: <count>}
GET/boundaries/listquery: market_code (required), level?, boundary_type?200 {boundaries: [...]}
GET/boundaries/{locator}-200 the boundary; 404 if absent
GET/boundaries/{locator}/geometry-200 GeoJSON (application/geo+json)
GET/boundaries/{locator}/children-200 {boundaries: [...]}
GET/boundaries/{locator}/ancestors-200 {boundaries: [...]} (recursive walk up parent_id)
DELETE/boundaries/{locator}-204. Soft-delete: sets effective_to = CURRENT_DATE. No rate-table reference check.

Boundary groups

MethodPathBody / paramsResponse
POST/boundary-groups{code, marketCode, description?}201 the group
GET/boundary-groups/listquery: market_code (required)200 {groups: [...]}
GET/boundary-groups/{code}-200 the group; 404 if absent
PUT/boundary-groups/{code}/members{boundaryIds: [uuid...]}204. Replaces the full membership set.
GET/boundary-groups/{code}/members-200 {boundaries: [...]}

Spatial queries (JWT)

MethodPathBodyResponse
POST/spatial/contains{boundaryId, lat, lng}200 {contained: bool}
POST/spatial/lookup{marketCode, lat, lng, level?, groupCode?}If level set: the boundary at that level. Else if groupCode set: the boundary in that group. Else: {boundaries: [...]} for all containing boundaries.
POST/spatial/serviceability{marketCode, lat, lng}200 {serviceable: bool}

/spatial/lookup does not detect ambiguity or return 422: if a group is not partition-clean, the underlying query simply LIMIT 1s and returns the first match. There is no /spatial/nearest endpoint, no POST /boundaries/{locator}/groups, and no PUT /boundaries/{locator}/geometry.

Internal (service-to-service, unauthenticated)

MethodPathQuery paramsResponse
GET/internal/spatial/boundary-at-levelmarket_code, level, lat, lng200 the boundary; 404 if none
GET/internal/spatial/boundary-in-groupgroup_code, lat, lng200 the boundary; 404 if none

Database schema

Schema policy_admin. Migrations 0014-0018 (boundary tables + PostGIS) on feat-boundary-service; 0020 (rate_tables) on feat-rating-engine.

boundary_levels (0015)

ColumnTypeNotes
idUUID PK
market_codetext not null
levelint not null0=country, 1=region, ... (convention)
nametext not nulldisplay
parent_levelintnullable
created_attimestamptzNOW()

Unique: (market_code, level). There is no separate locator on levels.

boundaries (0016)

ColumnTypeNotes
idUUID PK
locatortext not null uniqueBND-...
market_codetext not null
levelintnullable
parent_idUUIDself-FK to boundaries.id
codetext not null
nametext not null
boundary_typetext not nullCHECK: ADMINISTRATIVE / SERVICE_AREA / RATING_ZONE / SCHEME_REGION / EXCLUSION_ZONE
geometryGEOMETRY(MULTIPOLYGON, 4326)the shape; not mapped to Go
propertiesjsonb not null default {}
effective_fromdate not null default CURRENT_DATE
effective_todateNULL = active
created_attimestamptzNOW()

Indexes: GIST on geometry; btree on (market_code, level), parent_id, code, boundary_type. There are no lat, lng, or centroid columns and no centroid trigger on this table (see Party location below).

boundary_groups and boundary_group_members (0017)

boundary_groups: {id UUID PK, locator text unique, code text unique, market_code text, description text, created_at}. No partition flag.

boundary_group_members: {boundary_group_id UUID, boundary_id UUID}, PK (boundary_group_id, boundary_id), both FKs ON DELETE CASCADE.

rate_tables (rating engine, 0020)

Not owned by Boundary. Columns: {id UUID, locator text, product_version_id UUID FK→product_versions, rating_factor_id UUID FK→rating_factors, element_type text, key text, range_min numeric(14,4), range_max numeric(14,4), value numeric(14,4), value_type text CHECK('BASE_RATE','MULTIPLIER'), effective_from, effective_to, created_at}. No boundary_locator, no multiplier, no FK to boundaries.

Party location trigger

The only centroid-style trigger is on parties, not boundaries (migration 0018). The parties table gains lat/lng (DOUBLE PRECISION) plus a location GEOGRAPHY(POINT, 4326) column. A BEFORE INSERT OR UPDATE OF lat, lng trigger (sync_party_location) keeps location in sync via ST_SetSRID(ST_MakePoint(lng, lat), 4326), with a GIST index on location. This is how a member's point gets stored for later containment checks.

Seeding

Bulk-load boundaries with the import endpoint, which takes a GeoJSON FeatureCollection; each feature becomes one boundaries row (using its properties.code / properties.name, falling back to generated values). The handler iterates features in one request, but it is not wrapped in a single transaction: a mid-list failure returns the count imported so far.

bash
curl -X POST https://api.dev.hiolly.com/boundaries/import \
  -H "Authorization: Bearer ${JWT}" \
  -H "Content-Type: application/json" \
  -d '{"marketCode":"GB","level":2,"boundaryType":"ADMINISTRATIVE","featureCollection":{...}}'
# → {"imported": 348}

Dependencies

DependencyPurposeFailure mode
Postgres policy_admin with postgis extensionSpatial storage and all ST_* queriesHard fail
Keycloak (JWT validation)External route authHard fail for external routes; internal routes are unauthenticated
OTel collectorTracing (every service method opens a span)Degraded: traces lost, queries continue

Invariants

  • Boundaries are soft-versioned via effective_from / effective_to; delete sets effective_to = CURRENT_DATE rather than removing the row. Spatial lookups filter on effective_from <= CURRENT_DATE AND (effective_to IS NULL OR effective_to > CURRENT_DATE).
  • The GIST index on geometry is load-bearing: without it every ST_Contains is a full-table scan.
  • IsServiceable = inside some SERVICE_AREA AND inside no EXCLUSION_ZONE, both filtered to the same market and to currently-effective rows.
  • Group membership is replace-the-set: PUT /boundary-groups/{code}/members deletes existing members and re-inserts in one transaction.
  • Ancestors walks parent_id recursively and excludes the starting boundary; Children returns direct parent_id matches only.

Caveats

  • Not merged. See the banner. Mainline has none of this code.
  • No role-based authorization. External mutations need only a valid JWT, not a boundary-admin role. Internal endpoints have no auth at all and depend on network isolation.
  • No partition enforcement and no ambiguity errors. /spatial/lookup against a group with overlapping members returns one arbitrary match (LIMIT 1), not a 422. Partition-cleanliness is the operator's responsibility, not the database's.
  • No nearest-neighbour. Any "nearest provider / nearest by specialty" framing is unbuilt; the Point/NearestResult types are dead code.
  • Import is not atomic. A failed feature aborts the loop and leaves earlier features committed.
  • Not a general-purpose GIS. No geocoding (use an external geocoder in the caller), no routing or driving distance.

Olly Health Insurance Platform