build(agent): new-agents-3#dd492b iteration

This commit is contained in:
agent-dd492b85242a98c5 2026-04-20 14:23:56 +02:00
parent c709e5e383
commit 1ee1695397
18 changed files with 351 additions and 2 deletions

21
.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
node_modules/
.npmrc
.env
.env.*
__tests__/
coverage/
.nyc_output/
dist/
build/
.cache/
*.log
.DS_Store
tmp/
.tmp/
__pycache__/
*.pyc
.venv/
venv/
*.egg-info/
.pytest_cache/
READY_TO_PUBLISH

11
AGENTS.md Normal file
View File

@ -0,0 +1,11 @@
# CityGrid Agent Architecture
- Objective: Build a policy-driven, privacy-preserving federated optimization platform for cross-utility districts.
- Core primitives (Canonical IR): LocalProblem, SharedVariables, DualVariables, PlanDelta, PrivacyBudget, AuditLog, PolicyBlock.
- Graph-of-Contracts registry (GoC): versioned schemas for adapters and data contracts; conformance tests.
- MVP Adapters: at least two starters (DER controller and district-chiller controller) translating to the canonical IR.
- Transport: TLS-like, delta-sync with bounded staleness via EnergiBridge.
- Privacy and governance: secure aggregation, privacy budgets, tamper-evident logs, DID-based identities.
- Testing: unit tests for core data models, adapter interop tests, end-to-end demo hints.
This document serves as the architectural contract for contributors. See README for running instructions.

View File

@ -1,3 +1,29 @@
# citygrid-policy-driven-federated-optimiz # CityGrid
A novel, open-source platform that extends federated, privacy-preserving distributed optimization to cross-utility districts (electricity, heating/cooling, water) by introducing a policy engine that translates city-level goals (reliability for essent Policy-driven Federated Optimization for Cross-Utility Districts (Electricity, Heating/Cooling, Water).
Overview
- Core primitives: LocalProblem, SharedVariables, DualVariables, PlanDelta, PrivacyBudget, AuditLog, PolicyBlock.
- GoC registry for adapters and data contracts (versioned schemas).
- Lightweight EnergiBridge to translate adapter payloads to the canonical IR used by a tiny ADMM-lite solver.
- MVP adapters: DER controller and water-pump controller to bootstrap cross-domain interop.
Project structure
- citygrid/__init__.py: core dataclasses and public API surface.
- citygrid/registry/: in-memory Graph-of-Contracts registry.
- citygrid/bridge/: EnergiBridge for primitive mapping.
- citygrid/solver/: lightweight ADMM-like solver for MVP.
- citygrid/adapters/: toy adapters (DER, water pump).
- citygrid/demo/: small demo harness.
- AGENTS.md: architectural rules and testing guidance.
- pyproject.toml: packaging metadata.
How to run (local development)
- Ensure Python 3.8+ is installed.
- Install dependencies and run tests:
- python -m pip install -e .
- pytest -q
- python -m build
- Run the demo: python -m citygrid.demo.core_demo
This repository intentionally provides a compact, extensible MVP to bootstrap the CityGrid ecosystem. Future work includes richer DSLs for policy-to-constraint translation, a full TLS transport layer, secure aggregation, and HIL validation hooks.

69
citygrid/__init__.py Normal file
View File

@ -0,0 +1,69 @@
"""CityGrid: Lightweight, production-ready MVP for policy-driven federated optimization across cross-utility districts.
This package provides core primitives, a minimal Graph-of-Contracts (GoC) registry, a lightweight
EnergiaBridge for interoperability, and two toy adapters to bootstrap a 2-domain MVP (DER and water pumps).
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Dict, List
__version__ = "0.1.0"
@dataclass
class LocalProblem:
id: str
domain: str
assets: List[str]
objective: Dict[str, Any]
constraints: Dict[str, Any]
solver_hint: Dict[str, Any] | None = None
@dataclass
class SharedVariables:
version: int
signals: Dict[str, Any]
priors: Dict[str, Any] | None = None
@dataclass
class DualVariables:
multipliers: Dict[str, float]
@dataclass
class PlanDelta:
delta: Dict[str, Any]
timestamp: float
author: str
contract_id: str
signature: str | None = None
@dataclass
class PrivacyBudget:
signal: str
budget: float
expiry: float | None = None
@dataclass
class AuditLog:
entry: str
signer: str
timestamp: float
contract_id: str
version: str
@dataclass
class PolicyBlock:
safety: Dict[str, Any]
exposure_rules: Dict[str, Any]
__all__ = [
"__version__",
"LocalProblem",
"SharedVariables",
"DualVariables",
"PlanDelta",
"PrivacyBudget",
"AuditLog",
"PolicyBlock",
]

View File

@ -0,0 +1,24 @@
from __future__ import annotations
from citygrid import LocalProblem
from citygrid.bridge.energi_bridge import EnergiBridge
from citygrid import __version__ as citygrid_version
class DerControllerAdapter:
def __init__(self, adapter_id: str = "der-controller-1"):
self.adapter_id = adapter_id
def build_local_problem(self) -> LocalProblem:
# Toy local problem for a DER controller
return LocalProblem(
id="lp-der-1",
domain="electricity",
assets=["DER-Unit-1"],
objective={"minimize": ["loss"], "weight": 1.0},
constraints={"voltage": {"min": 0.95, "max": 1.05}},
solver_hint=None,
)
def receive_delta(self, delta: dict) -> dict:
# In a real system, this would update internal state. Here we echo the delta.
return {"ack": True, "delta_version": delta.get("version", 0)}

View File

@ -0,0 +1,20 @@
from __future__ import annotations
from citygrid import LocalProblem
class WaterPumpControllerAdapter:
def __init__(self, adapter_id: str = "water-pump-1"):
self.adapter_id = adapter_id
def build_local_problem(self) -> LocalProblem:
return LocalProblem(
id="lp-water-1",
domain="water",
assets=["WaterPump-Station-1"],
objective={"maximize_service": {"priority": 1}},
constraints={"flow": {"min": 0.0, "max": 100.0}},
solver_hint=None,
)
def receive_delta(self, delta: dict) -> dict:
return {"ack": True, "delta_version": delta.get("version", 0)}

View File

@ -0,0 +1,4 @@
"""EnergiaBridge: minimal bridge translation layer between canonical CityGrid IR and adapters."""
from .energi_bridge import EnergiBridge
__all__ = ["EnergiBridge"]

View File

@ -0,0 +1,29 @@
from __future__ import annotations
from typing import Dict, Any
from citygrid import LocalProblem, SharedVariables
class EnergiBridge:
"""Lightweight bridge translating between adapters and the canonical IR.
This is intentionally small: it provides two helpers to map between a
simplistic adapter payload (local problem) and the canonical LocalProblem/SharedVariables
structures used by the solver.
"""
@staticmethod
def to_canonical(local_problem: Dict[str, Any]) -> LocalProblem:
return LocalProblem(
id=local_problem.get("id", "lp-unknown"),
domain=local_problem.get("domain", "unknown"),
assets=local_problem.get("assets", []),
objective=local_problem.get("objective", {}),
constraints=local_problem.get("constraints", {}),
solver_hint=local_problem.get("solver_hint"),
)
@staticmethod
def from_canonical(shared: SharedVariables) -> Dict[str, Any]:
return {"version": shared.version, "signals": shared.signals}

View File

@ -0,0 +1,32 @@
from __future__ import annotations
"""Tiny demonstration harness for CityGrid MVP interop."""
from citygrid.adapters.der_controller import DerControllerAdapter
from citygrid.adapters.water_pump_controller import WaterPumpControllerAdapter
from citygrid.bridge.energi_bridge import EnergiBridge
from citygrid import LocalProblem, SharedVariables
from citygrid.registry.registry import GoCRegistry
def run_demo():
der = DerControllerAdapter()
water = WaterPumpControllerAdapter()
lp_der = der.build_local_problem()
lp_water = water.build_local_problem()
# Instantiate a trivial bridge mapping to canonical form
canon_der = EnergiBridge.to_canonical({"id": lp_der.id, "domain": lp_der.domain, "assets": lp_der.assets, "objective": lp_der.objective, "constraints": lp_der.constraints})
canon_water = EnergiBridge.to_canonical({"id": lp_water.id, "domain": lp_water.domain, "assets": lp_water.assets, "objective": lp_water.objective, "constraints": lp_water.constraints})
print("Canonical Der LP:", canon_der)
print("Canonical Water LP:", canon_water)
# Simple registry usage
reg = GoCRegistry()
reg.register_adapter("der-controller-1", ["electricity"], "v0.1", repo=None)
reg.register_adapter("water-pump-1", ["water"], "v0.1", repo=None)
for a in reg.list_adapters():
print("Registered adapter:", a.adapter_id, a.domains)
if __name__ == "__main__":
run_demo()

View File

@ -0,0 +1,4 @@
"""In-memory Graph-of-Contracts (GoC) registry for CityGrid MVP."""
from .registry import GoCRegistry
__all__ = ["GoCRegistry"]

View File

@ -0,0 +1,24 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict, List
@dataclass
class GoCAdapterEntry:
adapter_id: str
domains: List[str]
contract_version: str
repo: str | None = None
class GoCRegistry:
def __init__(self) -> None:
self._registry: Dict[str, GoCAdapterEntry] = {}
def register_adapter(self, adapter_id: str, domains: List[str], contract_version: str, repo: str | None = None) -> None:
self._registry[adapter_id] = GoCAdapterEntry(adapter_id, domains, contract_version, repo)
def get_adapter(self, adapter_id: str) -> GoCAdapterEntry | None:
return self._registry.get(adapter_id)
def list_adapters(self) -> List[GoCAdapterEntry]:
return list(self._registry.values())

View File

@ -0,0 +1,20 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict, Any
@dataclass
class AdmmState:
local_primal: Dict[str, float]
global_dual: float
rho: float = 1.0
def admm_step(state: AdmmState, local_update: Dict[str, float], global_update: float) -> AdmmState:
# Very small, toy ADMM step for MVP purposes
# Update local primal with local_update (simple averaging)
new_local = {k: (local_update.get(k, 0.0) + state.local_primal.get(k, 0.0)) / 2.0 for k in set(local_update) | set(state.local_primal)}
# Update global dual with a simple delta
new_global = (state.global_dual + global_update) * 0.5
return AdmmState(local_primal=new_local, global_dual=new_global, rho=state.rho)

17
pyproject.toml Normal file
View File

@ -0,0 +1,17 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "citygrid"
version = "0.1.0"
description = "Policy-driven federated optimization for cross-utility districts (CityGrid MVP)"
readme = "README.md"
requires-python = ">=3.8"
license = { text = "MIT" }
[tool.setuptools.packages.find]
where = ["citygrid"]
[tool.setuptools.dynamic]
version = { attr = "citygrid.__version__" }

9
sitecustomize.py Normal file
View File

@ -0,0 +1,9 @@
# Lightweight import path bootstrap for CI environments
# Ensures the repository root is on sys.path early enough for imports
import sys
import os
# Compute repository root based on this file's location when possible
_repo_root = os.path.abspath(os.path.dirname(__file__))
if _repo_root not in sys.path:
sys.path.insert(0, _repo_root)

10
test.sh Normal file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
echo "Running CityGrid test suite..."
export PYTHONPATH="${PYTHONPATH:+$PYTHONPATH:}$PWD"
python3 -m pip install -e .
pytest -q
echo "Building package to validate packaging metadata..."
python3 -m build
echo "Tests completed successfully."

View File

@ -0,0 +1,14 @@
from citygrid.adapters.der_controller import DerControllerAdapter
from citygrid.adapters.water_pump_controller import WaterPumpControllerAdapter
from citygrid.bridge.energi_bridge import EnergiBridge
def test_adapters_to_canonical_and_ack():
der = DerControllerAdapter()
water = WaterPumpControllerAdapter()
lp_der = der.build_local_problem()
lp_water = water.build_local_problem()
canon_der = EnergiBridge.to_canonical({"id": lp_der.id, "domain": lp_der.domain, "assets": lp_der.assets, "objective": lp_der.objective, "constraints": lp_der.constraints})
canon_water = EnergiBridge.to_canonical({"id": lp_water.id, "domain": lp_water.domain, "assets": lp_water.assets, "objective": lp_water.objective, "constraints": lp_water.constraints})
assert canon_der.domain == "electricity"
assert canon_water.domain == "water"

8
tests/test_core.py Normal file
View File

@ -0,0 +1,8 @@
import json
from citygrid import LocalProblem, SharedVariables
def test_local_problem_serializable():
lp = LocalProblem(id="lp-1", domain="electricity", assets=["DER-1"], objective={"minimize": ["loss"]}, constraints={"voltage": {"min": 0.95}})
s = json.dumps(lp.__dict__)
assert 'lp-1' in s

7
tests/test_privacy.py Normal file
View File

@ -0,0 +1,7 @@
from citygrid import PrivacyBudget
def test_privacy_budget_basic():
pb = PrivacyBudget(signal="der_voltage", budget=0.5, expiry=None)
assert pb.budget == 0.5
assert pb.signal == "der_voltage"