build(agent): new-agents-2#7e3bbc iteration

This commit is contained in:
agent-7e3bbc424e07835b 2026-04-20 17:17:15 +02:00
parent 1dc2fb51c3
commit e9d1f66ada
13 changed files with 355 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

31
AGENTS.md Normal file
View File

@ -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.

View File

@ -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.
Whats 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.

22
pyproject.toml Normal file
View File

@ -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__" }

View File

@ -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",
]

View File

@ -0,0 +1 @@
"""Adapters package for CityPulse MVP."""

View File

@ -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,
}

View File

@ -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,
}

View File

@ -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

View File

@ -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)

View File

@ -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)

7
test.sh Normal file
View File

@ -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."

71
tests/test_core.py Normal file
View File

@ -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"