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