Skip to content

About

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

Β 

History

151 Commits

Folders and files

Repository files navigation

Commonwatt

An equity-constrained demand-response planner for the grid β€” an AI proposes, a human approves, and no neighborhood gets asked twice.

πŸ”— Live demo: commonwatt.vercel.app Β· Track: Climate Β· Built end-to-end with Kiro

On a peak heat day the grid strains, and utilities have two bad options: fire up a fossil peaker plant, or shed load on neighborhoods β€” and that burden has historically fallen hardest on low-income and environmental-justice communities. Commonwatt makes demand response fair and auditable: it asks buildings to voluntarily reduce load, leans on the ones that can most afford it, spares the ones a reduction would hurt most, pays participants, and leaves every decision to a human operator.


Table of contents


What it does

A utility operator opens a map of Greater Boston built from the public BERDO building-emissions dataset (~2,200 real buildings), sets a target (e.g. reduce 20 MW during the 5–7 PM peak), and clicks Draft proposal. A planner agent assembles a portfolio of buildings to ask; a critic agent checks it against hard equity and safety rules; if the first plan over-burdens protected communities, the planner self-corrects and only the corrected plan becomes the proposal. The operator reviews a fully traceable reasoning log and approves, rejects, or edits β€” nothing is sent without that human decision.

On approval, each selected building's manager is contacted in-app. They can reply yes / no / counter-offer, ask questions of an assistant that answers only from their building's real data, and at any point ask for a human β€” handing the conversation to the operator.

Why it's different

  • Equity is a hard constraint, not a dashboard metric. The dispatch optimizer refuses any plan whose environmental-justice load share exceeds the configured cap; when the target is unreachable under the cap, it returns an explicit "unreachable" trade-off instead of quietly violating it.
  • The model never invents a number. Every rate, emissions factor, and building value comes from a deterministic, property-tested tool layer with a citation β€” the LLM orchestrates, it doesn't fabricate.
  • Human-in-the-loop by construction. The agent proposes; the operator decides. The system is physically unable to contact a building or export an event without operator approval.

Demo accounts

Sign in at /signin (passwords are demo-only):

Role Email Password Lands on
Utility operator utility@demo demo-utility-pass Operator workspace β€” map, planner, approvals, facility conversations
Building manager building1@demo demo-building-pass 125 High Street β€” today's request: accept, offer less, or decline
Building manager building2@demo demo-building-pass "
Building manager building3@demo demo-building-pass "

Demo flow (about 2 minutes):

  1. As utility@demo, set the reduction you need and click Find buildings. Review where the burden lands, then Approve & send requests.
  2. As building1@demo, review the request (earnings, what it takes, why you) and accept, offer less, or decline. Offers above the ask or the building's capacity are rejected.
  3. As utility@demo, see the replies under Responses and confirmation and click Confirm event. Run Find buildings again: 125 High Street now rests for one event.

Cold-start note: the backend runs on a free Render instance that sleeps after idle, so the first request after a quiet period may take ~30–60s to wake.

Built with Kiro

Commonwatt was built spec-first with Kiro, across the whole dev lifecycle:

  • Spec-driven development β€” requirements, design, and tasks were committed to .kiro/specs/ before implementation. Every feature traces to an acceptance criterion; working from the task list let the team build auth, map, equity dispatch, and chat/handoff in parallel without collisions.
  • Steering as guardrails β€” .kiro/steering/{safety,provenance,stack}.md are rules the agent never crossed: every user-facing number carries a source_id, no message may say "cut" / "shut off" / "curtail" / "disconnect" (it's a voluntary, paid program), and generated code stays on the chosen stack.
  • MCP (Model Context Protocol) β€” the heart of the app is the Commonwatt MCP server: 11 deterministic, property-tested tools (FastMCP). The same server Kiro called during development is what the running planner/critic call at runtime β€” so results are reproducible, explainable, and auditable, with no model calls hidden inside the tools.
  • Agent hooks β€” ran the property-based test suite on every save, so a broken equity invariant was caught the instant it appeared rather than in the demo.
  • Property-based testing β€” spec invariants became Hypothesis properties (see below).
  • Skills β€” the BERDO + Census data-wrangling workflow was crystallized into a reusable Kiro skill for fast re-seeding.
  • Decisions log β€” .kiro/DECISIONS.md records design trade-offs (e.g. dropping SMS in favor of in-app chat with human handoff).

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   HTTP (cookie auth)   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  React 18 SPA (web/)  β”‚ ─────────────────────▢ β”‚  FastAPI  (app/)     β”‚
β”‚  MapLibre Β· TanStack  β”‚ ◀───────────────────── β”‚  routes: portfolio,  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      JSON (sourced)    β”‚  auth, chat, push    β”‚
                                                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                   planner / critic loop    β”‚ calls
                                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                   β”‚ agent/  (Claude via Amazon Bedrock)    β”‚
                                   β”‚ planner β†’ critic β†’ self-correct        β”‚
                                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                            β”‚ tool calls (MCP)
                                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                   β”‚ server/  Commonwatt MCP server         β”‚
                                   β”‚ 11 deterministic, property-tested toolsβ”‚
                                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                            β”‚ reads
                                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                   β”‚ fixtures/berdo.json  + Census ACS      β”‚
                                   β”‚ (committed public data; runs offline)  β”‚
                                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       state: SQLAlchemy 2.0 ORM + Alembic migrations Β· SQLite (dev) / Postgres (prod)
  • server/ β€” the MCP server: 11 pure tools (plan_event, rank_candidates, check_constraints, estimate_savings, score_socioeconomic, building_info, …). No model calls inside; every number carries a source_id.
  • agent/ β€” the planner/critic loop and the building-chat agent (Claude via Amazon Bedrock), restricted to the tool-grounded MCP surface.
  • app/ β€” the FastAPI application: cookie-session auth (bcrypt), portfolio/map/savings routes, the planner route, and the in-app chat/handoff routes. SQLAlchemy 2.0 models in db/, Alembic migrations in db/migrations/.
  • web/ β€” the React + MapLibre SPA (operator workspace and building-manager view).
  • fixtures/berdo.json β€” real Boston BERDO buildings, pulled once and committed so the app runs offline.
  • .kiro/ β€” specs, steering, hooks, MCP registration, decisions log.

Stack

Layer Tech
Frontend React 18, TypeScript (strict), Vite, React Router, TanStack Query, MapLibre GL
API FastAPI (Python 3.12), Pydantic, Uvicorn
Auth bcrypt password hashing, HTTP-only cw_session cookie (SameSite=None; Secure cross-site)
Agents Claude via Amazon Bedrock β€” planner, critic, and building-chat, tool-grounded
Tools Commonwatt MCP server (FastMCP) β€” 11 deterministic tools
Persistence SQLAlchemy 2.0 ORM + Alembic; SQLite (dev) / PostgreSQL (prod)
Data Committed public fixtures: BERDO buildings, Census ACS income, Census geocodes
Testing pytest + Hypothesis (property-based) + Vitest; 61 backend tests
Deploy Render (FastAPI + managed Postgres), Vercel (SPA)
Built with Kiro β€” spec-driven dev, MCP, steering, agent hooks, property-based testing, skills

The equity engine

The planner ranks candidate buildings by a socioeconomic priority score (ask those who can most afford a reduction first β€” higher-income areas, large commercial/institutional buildings) and assembles a portfolio to hit the MW target. The critic then validates it against hard rules:

  1. EJ cap β€” environmental-justice load share must not exceed the configured cap (default 35%).
  2. Cooldown β€” no building called in the immediately preceding event.
  3. Protected facilities β€” schools in session, cooling centers, and nursing homes are excluded when the heat threshold (95Β°F) is exceeded.
  4. Reachability β€” the portfolio must meet the target, or the system returns an explicit "unreachable under cap" trade-off (achievable MW vs. cap) and stops.

Both the rejected and accepted plan versions are retained in the trace, so the operator can see exactly how and why the agent self-corrected.

In-app chat with human handoff

Each approved event opens one conversation per building, keyed to (event, building):

  • Auto mode (default): the building manager's questions are answered by a Bedrock agent grounded strictly in that building's real data β€” it cannot invent numbers. To the manager, it speaks as the utility (one unified voice).
  • Human handoff: the manager can request a person at any time (request-human); the thread flips to human mode, the operator is notified, picks up from the operator console, and the agent falls silent.
  • Safety filter: on every message, in both modes, banned load-shedding language (cut, disconnect, …) is rejected β€” the program is voluntary and paid, and the language reflects that.

Routes: GET /inbox/{id}, POST /inbox/{id}/reply, POST /inbox/{id}/request-human, POST /inbox/{id}/operator-message, POST /inbox/{id}/close.

Provenance & safety invariants

  • Every user-facing number carries a source_id; the frontend refuses to render a value without one.
  • Program rates, capacity prices, LMPs, and emissions factors live one-per-constant with a single citation each in server/constants.py β€” never inlined.
  • Savings figures are labeled estimated with retrievable assumptions; simulated data (enrollment, meter reads, replies) is explicitly marked simulated in both code and UI.

Setup & run

Requires Python 3.12+ and Node 20+ (for the SPA).

make setup                       # venv + deps + migrations + seed demo data

The map and data endpoints work with no API key. For the LLM features (planner loop, building chat) you need Amazon Bedrock access:

cp .env.example .env             # set AWS_BEARER_TOKEN_BEDROCK and AWS_REGION
make check-bedrock               # prints PASS when key + region + model work

Run the demo (watch the equity self-correction in the trace):

make demo                        # plans a 20 MW event under a 35% EJ cap:
                                 # v1 over-calls EJ zips at 38.7% β†’ critic rejects
                                 # β†’ planner shifts load β†’ v2 approved

Serve the API the SPA talks to, and run the frontend:

make serve                       # FastAPI on http://127.0.0.1:8000
cd web && npm install && npm run dev   # Vite dev server

API

Read-only unless noted. Every numeric field carries a source_id.

Method Path Returns
GET /health status + building count
GET /zips per-zip aggregates for the choropleth
GET /zips/{zip}/buildings building markers for a zip drill-in
GET /portfolio/buildings all buildings (map + conversations list)
GET /portfolio/buildings/{id} one building's full profile
GET /savings?shed_mw=&hours= three savings counters (estimated, sourced)
GET /recruitment?top_n= high-load unenrolled EJ buildings, ranked
POST /event/draft run the planner/critic loop (needs Bedrock)
POST /event/approve approve an event; opens a thread + contacts each building
GET /inbox/{id} the building's conversation + active-event ask
POST /inbox/{id}/reply building manager reply (auto-mode agent answers)
POST /inbox/{id}/request-human flip the thread to human mode
POST /inbox/{id}/operator-message operator takes over (operator-only)
POST /auth/login Β· /auth/logout Β· GET /auth/me cookie-session auth

Tests

make test        # 61 backend tests: property-based + unit + integration

The suite runs without Bedrock (LLM endpoints are mocked). Property-based tests (Hypothesis) encode the spec invariants directly:

  • EJ load share never exceeds the configured cap.
  • A portfolio either meets the target or returns an explicit "unreachable" β€” never a silent violation.
  • Savings are monotonic in MW shed and zero at zero.
  • Backfill never re-adds a building that already declined.

Data sources

  • Buildings: Boston BERDO 2021 building-emissions dataset (resource 43e4abc9-1fae-40d9-a02e-ff950bd0e732, data.boston.gov β€” public, keyless). ~2,200 buildings after de-duplicating multi-record campuses.
  • Socioeconomic weighting: US Census ACS median household income by zip (table B19013).
  • Geocoding: US Census Geocoder for building lat/long.
  • Constants: program rates (Mass Save ConnectedSolutions), capacity prices/LMPs (ISO-NE), emissions factors (EPA eGRID) β€” one citation per constant in server/constants.py.

Team

Sanchali Banerjee Β· Sophie Cheung Β· Nicole Zheng

About

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages