A small production-shaped API adapter in front of the legacy Urja Meter Ops portal. It gives downstream clients typed, stable JSON without requiring them to understand the portal's form login, session cookie, abbreviated fields, HTML detail page, or string-encoded numeric values.
GET /api/v1/meters— paginated meter search (qmatches meter ID or serial number).GET /api/v1/meters/{meter_id}— normalized nameplate, network breadcrumb, and coordinates.GET /api/v1/meters/{meter_id}/consumption— normalized half-hourly energy readings.GET /health— liveness endpoint./docs— interactive Swagger UI;/openapi.json— generated OpenAPI 3 schema.
The protocol investigation is recorded in PROTOCOL.md. The reflection is in REFLECTION.md.
Requires Python 3.10+.
py -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
Copy-Item .env.example .env
# edit .env and set PORTAL_PASSWORD
uvicorn app.main:app --reloadThe service is then available at http://127.0.0.1:8000. Never commit .env; credentials are intentionally not hard-coded.
curl "http://127.0.0.1:8000/api/v1/meters?q=SE33962&page=1"
curl "http://127.0.0.1:8000/api/v1/meters/J100000"
curl "http://127.0.0.1:8000/api/v1/meters/J100000/consumption"Example list response shape:
{
"data": [{
"meter_id": "J100000",
"serial_number": "SE33962",
"make": "HPL",
"phase_type": "single",
"installation_status": "Decommissioned",
"transformer_code": "DT-001"
}],
"meta": {"page": 1, "page_size": 20, "total": 403, "has_next": true}
}HTTP consumer
-> FastAPI routes / Pydantic DTOs / request IDs
-> UrjaPortalClient (anti-corruption adapter)
-> httpx cookie-aware session + BeautifulSoup HTML parser
-> Urja Meter Ops portal
The route layer owns HTTP semantics and validation. The client owns upstream authentication, session renewal, endpoint calls, and normalization. Pydantic models form the contract boundary. This keeps portal-specific behavior isolated so a future replacement of the portal does not leak into consumers.
- FastAPI + async httpx: provides typed validation and generated OpenAPI while allowing concurrent web-server work. The upstream calls are still deliberately simple and bounded.
- One process-wide client: appropriate for this take-home service and reuses the connection pool and cookie. A multi-tenant deployment would use a credential-scoped client pool instead.
- Pass-through freshness: data is fetched on request; no cache can silently serve stale operational data. A short TTL cache would be a sensible next step after measuring upstream load.
- Stable envelope: all successful resources use
data; list calls includemeta. Upstream failures are translated to controlled errors and never expose portal HTML or credentials. - Conservative scope: hierarchy is parsed as part of meter detail, but a separate hierarchy index, frontend, persistence, rate limiting, and distributed session store are intentionally not included in this 4–6 hour solution.
pytest -qUnit tests cover portal field normalization, HTML breadcrumb parsing, and required configuration. The adapter boundary is designed for HTTP mocking with respx; integration testing against the supplied portal should be run manually with real environment credentials and must not be part of CI.
- Add service authentication (for example OAuth2 or mTLS), rate limiting, audit logging, and secrets-manager integration before exposing this API beyond a trusted network.
- Add contract tests with recorded, sanitized fixtures and a scheduled upstream compatibility check.
- Add retries with bounded exponential backoff only for safe/idempotent transient failures, plus metrics/traces for upstream latency and auth renewal.
- Confirm the portal timezone and represent timestamps as timezone-aware values.
- Add a local index or materialized snapshot for cross-meter filtering and hierarchy queries if request volume outgrows the portal.
- Add a separate frontend only after product requirements clarify the operational workflow.
- PROTOCOL.md — observed portal behavior.
- REFLECTION.md — engineering reflection.
openapi.json— exported contract for this service.