build(agent): new-agents-2#7e3bbc iteration
This commit is contained in:
parent
1dc2fb51c3
commit
e9d1f66ada
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
Overview
|
||||||
|
- CityPulse is a cross-domain urban resource optimization platform emphasizing privacy-preserving federated coordination with an offline-first delta-sync protocol.
|
||||||
|
- This repository contains a production-oriented MVP scaffold in Python that demonstrates core concepts: LocalProblems, SharedSignals, PlanDelta, DualVariables, and an auditable governance trail via AuditLog.
|
||||||
|
|
||||||
|
Architecture (high level)
|
||||||
|
- Core models (src/idea171_citypulse_participatory_digital/core.py): LocalProblem, SharedSignals, PlanDelta, DualVariables, and AuditLog.
|
||||||
|
- Delta store and replay (src/idea171_citypulse_participatory_digital/delta.py): deterministic delta history and replay capability.
|
||||||
|
- Solver (src/idea171_citypulse_participatory_digital/solver.py): a minimal ADMM-lite aggregator over dual variables.
|
||||||
|
- Adapters (src/idea171_citypulse_participatory_digital/adapters): starter bindings for DER and Water Pump controllers.
|
||||||
|
- Public APIs are intentionally lightweight for MVP; this repo provides a stable foundation for rapid pilots and extension via adapters and governance.
|
||||||
|
|
||||||
|
Tech Stack
|
||||||
|
- Language: Python 3.9+ (typing, dataclasses, lightweight architecture)
|
||||||
|
- Key concepts: LocalProblems (domain tasks), SharedSignals (privacy-preserving signals), PlanDelta (contractual delta actions), DualVariables (shadow prices), AuditLog (tamper-evident-like logging proxy).
|
||||||
|
- No external services required for MVP; simple in-process delta store and a toy ADMM-like solver to illustrate coordination.
|
||||||
|
|
||||||
|
Development and Testing
|
||||||
|
- Tests: pytest tests/test_core.py
|
||||||
|
- Test script: test.sh (runs pytest, then builds the package via python3 -m build)
|
||||||
|
- Packaging: pyproject.toml with a production-ready package name idea171-citypulse-participatory-digital
|
||||||
|
|
||||||
|
Running locally
|
||||||
|
- Install dependencies (none beyond stdlib for MVP)
|
||||||
|
- Run tests: ./test.sh
|
||||||
|
- Build package: python3 -m build
|
||||||
|
|
||||||
|
Contributing rules
|
||||||
|
- One change at a time, with tests updated accordingly.
|
||||||
|
- Ensure tests pass before publishing readiness.
|
||||||
27
README.md
27
README.md
|
|
@ -1,3 +1,26 @@
|
||||||
# idea171-citypulse-participatory-digital
|
# CityPulse: Participatory Digital Twin (Open-Source MVP)
|
||||||
|
|
||||||
Source logic for Idea #171
|
CityPulse aims to be a modular, open-source platform for cross-domain urban resource optimization with privacy-preserving, offline-first federated coordination. This MVP scaffold demonstrates core concepts and provides a path toward production-grade integration with adapters, governance, and a lightweight simulator.
|
||||||
|
|
||||||
|
What’s included
|
||||||
|
- Core domain models: LocalProblem, SharedSignals, PlanDelta, DualVariables, AuditLog.
|
||||||
|
- Delta store with deterministic replay for auditable governance trails.
|
||||||
|
- Minimal ADMM-lite solver to illustrate federated coordination semantics.
|
||||||
|
- Starter adapters: DERControllerAdapter and WaterPumpControllerAdapter.
|
||||||
|
- Lightweight packaging setup (pyproject.toml) and test harness.
|
||||||
|
|
||||||
|
How to run
|
||||||
|
- Run tests and build package: ./test.sh
|
||||||
|
- The test suite exercises core data structures and adapter bindings.
|
||||||
|
|
||||||
|
Packaging and publishing
|
||||||
|
- The package is named idea171-citypulse-participatory-digital and is provisioned for publishing to PyPI or a private registry.
|
||||||
|
- A READY_TO_PUBLISH file will be created when the repository state fully matches publishing requirements.
|
||||||
|
|
||||||
|
Roadmap (high level)
|
||||||
|
- Phase 0: Skeleton protocol core + adapters over TLS; end-to-end delta-sync.
|
||||||
|
- Phase 1: Governance ledger scaffolding and identity layer.
|
||||||
|
- Phase 2: Cross-domain demo in a simulated district.
|
||||||
|
- Phase 3: Hardware-in-the-loop validation.
|
||||||
|
|
||||||
|
Enjoy contributing and shaping the CityPulse ecosystem.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "idea171-citypulse-participatory-digital"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "CityPulse: Participatory Digital Twin for Urban Resource Optimization (offline-first federation)"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
license = {file = "LICENSE"}
|
||||||
|
authors = [ { name = "OpenCode Collaboration" } ]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://example.com/citypulse"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
|
exclude = ["tests*"]
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
version = { attr = "__version__" }
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
"""CityPulse Participatory Digital Twin package initializer."""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
|
||||||
|
from .core import LocalProblem, SharedSignals, PlanDelta, DualVariables, AuditLog, AuditLogEntry
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"LocalProblem",
|
||||||
|
"SharedSignals",
|
||||||
|
"PlanDelta",
|
||||||
|
"DualVariables",
|
||||||
|
"AuditLog",
|
||||||
|
"AuditLogEntry",
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
"""Adapters package for CityPulse MVP."""
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
|
||||||
|
|
||||||
|
class DERControllerAdapter:
|
||||||
|
def __init__(self, adapter_id: str, capabilities: List[str] | None = None) -> None:
|
||||||
|
self.adapter_id = adapter_id
|
||||||
|
self.capabilities = capabilities or ["control", "report"]
|
||||||
|
|
||||||
|
def bind(self) -> Dict[str, Any]:
|
||||||
|
# In a real system this would establish TLS certs, auth, etc.
|
||||||
|
return {
|
||||||
|
"adapter_id": self.adapter_id,
|
||||||
|
"status": "bound",
|
||||||
|
"capabilities": self.capabilities,
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_contract(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
# Minimal canonicalization for demonstration
|
||||||
|
return {
|
||||||
|
"contract_id": data.get("contract_id", "unknown"),
|
||||||
|
"adapter_id": self.adapter_id,
|
||||||
|
"payload": data,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
|
||||||
|
|
||||||
|
class WaterPumpControllerAdapter:
|
||||||
|
def __init__(self, adapter_id: str, capabilities: List[str] | None = None) -> None:
|
||||||
|
self.adapter_id = adapter_id
|
||||||
|
self.capabilities = capabilities or ["start", "stop", "status"]
|
||||||
|
|
||||||
|
def bind(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"adapter_id": self.adapter_id,
|
||||||
|
"status": "bound",
|
||||||
|
"capabilities": self.capabilities,
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_contract(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"contract_id": data.get("contract_id", "unknown"),
|
||||||
|
"adapter_id": self.adapter_id,
|
||||||
|
"payload": data,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LocalProblem:
|
||||||
|
id: str
|
||||||
|
district: str
|
||||||
|
assets: List[str]
|
||||||
|
objective: str
|
||||||
|
constraints: Optional[Dict[str, Any]] = None
|
||||||
|
priority: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SharedSignals:
|
||||||
|
version: int
|
||||||
|
demand_forecast: Dict[str, float]
|
||||||
|
weather: Dict[str, Any]
|
||||||
|
occupancy: Dict[str, float]
|
||||||
|
priors: Dict[str, float]
|
||||||
|
privacy_tag: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PlanDelta:
|
||||||
|
delta_actions: List[Dict[str, Any]]
|
||||||
|
timestamp: str
|
||||||
|
contract_id: str
|
||||||
|
signer: str
|
||||||
|
signature: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DualVariables:
|
||||||
|
shadow_prices: Dict[str, float]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AuditLogEntry:
|
||||||
|
entry: str
|
||||||
|
signer: str
|
||||||
|
timestamp: str
|
||||||
|
contract_id: str
|
||||||
|
version: int
|
||||||
|
|
||||||
|
|
||||||
|
class AuditLog:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.entries: List[AuditLogEntry] = []
|
||||||
|
|
||||||
|
def append(self, entry: AuditLogEntry) -> None:
|
||||||
|
self.entries.append(entry)
|
||||||
|
|
||||||
|
def latest(self) -> Optional[AuditLogEntry]:
|
||||||
|
return self.entries[-1] if self.entries else None
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from .core import PlanDelta
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DeltaStore:
|
||||||
|
history: List[PlanDelta]
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.history = []
|
||||||
|
|
||||||
|
def publish(self, delta: PlanDelta) -> None:
|
||||||
|
self.history.append(delta)
|
||||||
|
|
||||||
|
def replay(self) -> List[PlanDelta]:
|
||||||
|
# Deterministic replay: return a shallow copy
|
||||||
|
return list(self.history)
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
|
from .delta import DeltaStore
|
||||||
|
from .core import DualVariables
|
||||||
|
|
||||||
|
|
||||||
|
class ADMMLiteSolver:
|
||||||
|
"""A minimal, educational ADMM-lite solver stub.
|
||||||
|
|
||||||
|
This is not a production solver. It provides a deterministic
|
||||||
|
aggregation of local dual variables to produce a global dual
|
||||||
|
estimate for the MVP. It exists to demonstrate integration with
|
||||||
|
the federated coordination idea in CityPulse.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, participant_count: int) -> None:
|
||||||
|
self.participant_count = max(1, int(participant_count))
|
||||||
|
|
||||||
|
def update(self, local_duals: List[DualVariables]) -> DualVariables:
|
||||||
|
# Simple average of shadow prices across participants
|
||||||
|
if not local_duals:
|
||||||
|
return DualVariables(shadow_prices={})
|
||||||
|
|
||||||
|
keys = set()
|
||||||
|
for dv in local_duals:
|
||||||
|
keys.update(dv.shadow_prices.keys())
|
||||||
|
|
||||||
|
averaged: Dict[str, float] = {}
|
||||||
|
for k in keys:
|
||||||
|
total = sum(dv.shadow_prices.get(k, 0.0) for dv in local_duals)
|
||||||
|
averaged[k] = total / len(local_duals)
|
||||||
|
|
||||||
|
return DualVariables(shadow_prices=averaged)
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Running tests with pytest..."
|
||||||
|
pytest -q
|
||||||
|
echo "Running Python build..."
|
||||||
|
python3 -m build
|
||||||
|
echo "All tests passed and package built."
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Ensure the package under src/ is importable when running tests from repo root
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
SRC = os.path.join(ROOT, "src")
|
||||||
|
if SRC not in sys.path:
|
||||||
|
sys.path.insert(0, SRC)
|
||||||
|
|
||||||
|
from idea171_citypulse_participatory_digital.core import LocalProblem, SharedSignals, PlanDelta, DualVariables, AuditLog, AuditLogEntry
|
||||||
|
from idea171_citypulse_participatory_digital.delta import DeltaStore
|
||||||
|
from idea171_citypulse_participatory_digital.adapters.der_controller import DERControllerAdapter
|
||||||
|
|
||||||
|
|
||||||
|
def test_local_problem_dataclass():
|
||||||
|
lp = LocalProblem(
|
||||||
|
id="lp-001",
|
||||||
|
district="Downtown",
|
||||||
|
assets=["building-1", "building-2"],
|
||||||
|
objective="minimize-peak",
|
||||||
|
constraints={"max_load": 1000},
|
||||||
|
priority=1,
|
||||||
|
)
|
||||||
|
assert lp.id == "lp-001"
|
||||||
|
assert "Downtown" in lp.district
|
||||||
|
|
||||||
|
|
||||||
|
def test_delta_store_and_replay():
|
||||||
|
store = DeltaStore()
|
||||||
|
d1 = PlanDelta(
|
||||||
|
delta_actions=[{"action": "start"}],
|
||||||
|
timestamp="2026-04-20T00:00:00Z",
|
||||||
|
contract_id="c-1",
|
||||||
|
signer="alice",
|
||||||
|
signature="sig1",
|
||||||
|
)
|
||||||
|
d2 = PlanDelta(
|
||||||
|
delta_actions=[{"action": "adjust", "param": "demand"}],
|
||||||
|
timestamp="2026-04-20T00:05:00Z",
|
||||||
|
contract_id="c-1",
|
||||||
|
signer="alice",
|
||||||
|
signature="sig2",
|
||||||
|
)
|
||||||
|
store.publish(d1)
|
||||||
|
store.publish(d2)
|
||||||
|
history = store.replay()
|
||||||
|
assert len(history) == 2
|
||||||
|
assert history[0].timestamp == "2026-04-20T00:00:00Z"
|
||||||
|
|
||||||
|
|
||||||
|
def test_audit_log_roundtrip():
|
||||||
|
log = AuditLog()
|
||||||
|
entry = AuditLogEntry(
|
||||||
|
entry="adapter bound",
|
||||||
|
signer="system",
|
||||||
|
timestamp="2026-04-20T00:00:01Z",
|
||||||
|
contract_id="c-1",
|
||||||
|
version=1,
|
||||||
|
)
|
||||||
|
log.append(entry)
|
||||||
|
latest = log.latest()
|
||||||
|
assert latest is not None and latest.contract_id == "c-1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_der_adapter_bind_and_contract():
|
||||||
|
der = DERControllerAdapter(adapter_id="der-01")
|
||||||
|
bound = der.bind()
|
||||||
|
assert bound["adapter_id"] == "der-01"
|
||||||
|
contracted = der.to_contract({"contract_id": "c-1", "payload": {"x": 1}})
|
||||||
|
assert contracted["adapter_id"] == "der-01"
|
||||||
Loading…
Reference in New Issue