Verification Log

This scaffold was not just written — it was run. Every endpoint below was exercised against a real PostgreSQL 16 + PostGIS 3 instance and a live uvicorn process, not unit-tested in isolation. Five real bugs were found and fixed in the process; all are documented here so the fix history isn't lost.

Environment

Core platform verification

Check Result
All Python files byte-compile Pass
FastAPI app imports, all routes register Pass (after adding email-validator)
Table creation against live DB Pass
Seed script inserts territory polygons, property polygons, premise points Pass
GET /healthz, GET /readyz Pass
GET /assets/{id}/geometry — polygon round-trips through PostGIS with exact coordinates preserved Pass
?format=esri — same polygon as Esri JSON rings Pass
POST /geo/query — mixed-geometry results (a Polygon and a Point in one FeatureCollection) Pass
GET /geo/bbox — viewport query Pass
POST /geo/query/radius — geodesic proximity search Pass (after fix)
GET /utilities/lookup — point-in-polygon match Pass (after fix)
POST / PUT / DELETE /assets Pass
Plans, rate history, usage, forecast, enrollments, exchange transactions Pass
POST /auth/token — JWT issuance Pass
Unauthenticated request Pass (401)
GET /openapi.json — valid spec Pass

Reseller multi-tenancy verification

A second pass after the tenancy work, against the same live setup:

Check Result
One customer attached to 2 resellers across 3 states, each attachment cost-keyed Pass
Cost key defaults to the reseller UUID; explicit override honored Pass
Reseller-scoped JWT sees only its own properties Pass
Reseller 1 reading Reseller 2's property directly Pass (403)
Shared customer: each reseller sees only its own attachment rows Pass
Spatial /geo/bbox respects tenant scope across a 3-state box Pass
/geo/customers/search returns customers + properties + cost keys in an area Pass
/geo/utilities/status returns per-utility status and affected property counts Pass
Attaching in a state outside the reseller's operating_states Pass (422)
Duplicate (customer, reseller, state) attachment Pass (409)
/cost-keys rollup maps each key to reseller, customers, properties, states Pass
Partial unique index rejects a second primary via direct SQL UPDATE Pass (unique violation)
Service-layer promote still succeeds through that index (demote → flush → promote) Pass (exactly 1 primary after)
Canonical schema.sql applies clean from scratch, partial index present Pass
GeoJSON and Esri features carry reseller_id / customer_id / cost_key / state Pass (after fix)

Bugs found and fixed during verification

1. Missing email-validator dependency

Pydantic's EmailStr (used on EnrollmentCreate.customer_email) requires the optional email-validator package, which was not pinned. The app failed to import at all.

Fix: requirements.txt now pins pydantic[email].

2. Raw WKT strings passed to PostGIS without a geometry cast

GET /utilities/lookup and POST /geo/query/radius built a point as an f-string (f"SRID=4326;POINT({lon} {lat})") and passed it into ST_Contains(...) / ST_DWithin(...). PostgreSQL has no ST_Contains(geometry, character varying) overload, so both endpoints returned 500.

Fix: both now build a shapely.geometry.Point and convert with from_shape(point, srid=4326) — the same pattern already used for asset writes — producing a properly-typed WKBElement bound as real geometry.

3. geoalchemy2.functions.ST_Geography doesn't exist as a callable

The radius query's geography cast raised AttributeError — GeoAlchemy2 doesn't pre-register that name as a function proxy in this version.

Fix: switched to SQLAlchemy's generic cast(Asset.geom, Geography), which compiles to the ::geography cast PostGIS expects without depending on a specific ST_* proxy.

4. Cost key silently mis-attributed across states

A property created in a state the customer had no attachment for was accepted and stamped with an unrelated state's cost key — a property in Kansas inherited the Oklahoma key. That is exactly the mis-billing the cost key exists to prevent, and it failed silently rather than loudly.

Fix: cost_key_for() no longer falls back to an arbitrary attachment. A state-specific attachment wins, then a stateless catch-all; if neither exists the request fails with 422 naming the covered states, so the caller creates the right attachment instead of getting wrong data.

5. GeoJSON/Esri output omitted tenancy attribution

asset_to_feature() and asset_to_esri_feature() were never updated when the tenancy columns were added, so every map feature came back without reseller_id, customer_id, cost_key, or state. An ArcGIS layer therefore could not style or filter by reseller, and a property could not be attributed to a cost key without a second round trip — which defeats the point of the attribution work. Caught while wiring the front-end to real exported output rather than to hand-written fixtures.

Fix: both serializers now emit the four tenancy fields alongside the existing properties.

What was not covered