Skip to content

CELINE REC Registry

API for modelling Renewable Energy Communities (RECs). Manages communities, members, assets, delivery points, and grid topology. Provides self-service endpoints for participants and administrative endpoints for managers, with import/export of community bundles in YAML format.

Features

  • Multi-community support with v0.7 schema (each area one GSE primary substation)
  • Self-service user API (profile, membership, assets, delivery points)
  • Admin API for community management, cross-community lookup, and batch operations
  • Runtime member management — create, update, deactivate members and their delivery points and assets, one at a time
  • YAML-based import/export with full replace semantics, guarded so a restore cannot silently delete a live community
  • In-process OPA policy evaluation for authorization
  • CLI (celine-rec-registry) for import, export, listing, and lookup operations, and read-only reports of stored rows the write checks would now refuse
  • Paginated responses with cursor-based navigation

Two ways a community changes

A community is seeded from a YAML bundle and changes through the member API. Both paths build the same rows, so an export taken after weeks of runtime changes re-imports to the same state.

The distinction matters because import is replacement: it deletes the community with every member and asset, then recreates it from the file. That was safe when the file was the only source of members. Now that members arrive at runtime, restoring a stale export is the likeliest way to lose them — so overwriting an existing community requires force, and is refused without it.

Quick Start

uv sync

export DATABASE_URL="postgresql+asyncpg://postgres:securepassword123@host.docker.internal:15432/celine_rec_registry"
# Development defaults (this password, the local Keycloak, AUTH_ENABLED/POLICIES_ENABLED=false)
# are accepted only with CELINE_ENV=dev; unset is hardened and refuses them at startup.
# `task run` sets CELINE_ENV=dev for you. See docs/development.md.

uv run alembic upgrade head
# or: task db:migrate

task run
# runs on port 8004

API Overview

Path prefix Description
GET /user Self-service: profile, membership, community, assets, delivery points
GET /admin/communities List/detail communities, members, assets, delivery points, meters
GET /admin/lookup/* Cross-community lookups by user ID, sensor ID, or delivery point
POST /admin/communities/{key}/members Create a member; sub-resources for its delivery points and assets
PUT\|DELETE /admin/communities/{key}/members/{member}/assets/meter-{sensor_id} Attach or detach a meter; one active holder per sensor id (409 sensor_held)
PATCH /admin/communities/{key}/members/{member}/profile Correct a member's role and area, nothing else (members.profile.write)
PUT /admin/communities/{key}/members/{member}/name\|role\|area Set one field of a member, granted per field (members.name.write, members.role.write, members.area.write)
GET /admin/communities/{key}/delivery-points/duplicates This community's PODs another active member also holds; holders elsewhere only counted (read)
PUT\|DELETE /admin/communities/{key}/members/{member}/delivery-points/{id} Add or remove a delivery point (members.delivery_points.write); ?replaces={old} corrects one and relinks its meters in one write; one active holder per POD (409 delivery_point_held); a point a meter names is not deleted (409 delivery_point_linked)
PATCH /admin/communities/{key} Update community metadata (areas have their own route)
PUT\|DELETE /admin/communities/{key}/areas/{area} Add, replace or remove one area; an area is one GSE primary substation (422 invalid_area_boundary) under an area key (422 invalid_area_key)
POST /admin/communities/{key}/areas/{area}/rename Move an area to a new key with its members, in one write (community.write)
PUT\|DELETE /admin/communities/{key}/topology/{node} Add, replace or remove one topology node; a node an area lists or another node names as parent is not deleted (409 topology_node_in_use)
POST /admin/import Import community from JSON bundle (destructive)
POST /admin/import/yaml Import communities from YAML multidocument (destructive)
GET /admin/export Export communities as YAML
GET /health, GET /version Service health and version

CLI

celine-rec-registry import --file recs/rec-example.yaml            # refuses an existing community
celine-rec-registry import --file recs/rec-example.yaml --dry-run  # see what it would replace
celine-rec-registry import --file recs/rec-example.yaml --force    # accept the replacement
celine-rec-registry export --community example_rec
celine-rec-registry list
celine-rec-registry tree --community example_rec
celine-rec-registry lookup-user --user-id <id>
celine-rec-registry lookup-sensor --sensor-id <id>
celine-rec-registry duplicate-sensors                              # read-only; exits 1 if a sensor has two active holders
celine-rec-registry duplicate-delivery-points                      # read-only; exits 1 if a POD has two active holders
celine-rec-registry out-of-set-values                              # read-only; exits 1 if a role, status or area is out of set
celine-rec-registry invalid-area-boundaries                        # read-only; exits 1 if a stored area breaks the one-substation or area-key rule

A refusal a caller acts on answers {"detail": "<sentence>", "code": "<code>"} — see refusal codes.

Documentation

Document Description
Requirements What the service must do — 79 requirements, every one named by a test, none planned
Decisions Why a technical choice was made
Data Model Community, Member, Asset schema; JSONB fields; relationships
API Reference All endpoint groups, query params, responses
Import & Export Bundle format, replace semantics, the force guard, CLI usage, before deploying 1.6.0
AGENTS.md Operational setup: invariants, authorization model, the two write paths
Development Setup, configuration, migrations, project layout

License

Apache 2.0 — Copyright © 2025 Spindox Labs