build(agent): new-agents#a6e6ec iteration
This commit is contained in:
parent
0b6e7311ba
commit
dd13bf419b
|
|
@ -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,43 @@
|
|||
# AGENTS.md
|
||||
|
||||
Architecture, testing, and contribution guide for OpenEnergySphere EnergiBridge MVP.
|
||||
|
||||
Overview
|
||||
- A production-ready Python-based MVP that enables cross-domain energy planning with privacy-first primitives.
|
||||
- Core concepts (OpenEnergySphere analogies): Objects (LocalProblems), Morphisms (SharedVariables), PlanDelta, and a registry of Graph-of-Contracts. Adapters implement plug-and-play data/model interfaces.
|
||||
- EnergiBridge maps OpenEnergySphere primitives to canonical IR concepts and enables cross-domain adapters with offline-first capabilities.
|
||||
|
||||
Tech Stack (current MVP)
|
||||
- Language: Python 3.10+
|
||||
- Web API: FastAPI
|
||||
- Data Modeling: Pydantic
|
||||
- Storage: In-memory registry (extensible to SQLite/PostgreSQL)
|
||||
- Packaging: pyproject.toml with setuptools build backend
|
||||
- Testing: pytest
|
||||
|
||||
Project Structure (high level)
|
||||
- energysphere/ - Python package with models, registry, sdk, adapters, and server.
|
||||
- energysphere/server/ - FastAPI app exposing contract/adapter endpoints.
|
||||
- energysphere/adapters/ - Starter adapters (substation_meter, der_aggregator).
|
||||
- tests/ - Pytest unit tests for registry and adapters.
|
||||
- test.sh - Test runner including packaging build step.
|
||||
- AGENTS.md - This document.
|
||||
|
||||
How to run locally
|
||||
- Install dependencies (in a virtualenv):
|
||||
- python -m pip install --upgrade pip setuptools wheel
|
||||
- pip install -r requirements.txt (if you add one later) or rely on pyproject tooling
|
||||
- Run tests:
|
||||
- bash test.sh
|
||||
- Run the API (in dev):
|
||||
- uvicorn energysphere.server.main:app --reload --port 8000
|
||||
|
||||
Development workflow
|
||||
- Implement minimal MVP features in small, testable units.
|
||||
- Add tests for all new functionality and ensure test.sh passes before merging.
|
||||
- Extend registry with persistent storage and implement authentication and governance ledger in future iterations.
|
||||
|
||||
Contribution rules
|
||||
- All changes should pass tests (pytest) and packaging build (python -m build).
|
||||
- Follow existing naming conventions and keep public APIs stable where possible.
|
||||
- Document any breaking changes in the changelog and update AGENTS.md accordingly.
|
||||
18
README.md
18
README.md
|
|
@ -1,3 +1,17 @@
|
|||
# openenergysphere-federated-contract-driv
|
||||
# OpenEnergySphere EnergiBridge (EnergiBridge MVP)
|
||||
|
||||
A novel, open-source platform that enables utilities, communities, and device vendors to collaboratively plan and optimize cross-domain energy systems (electricity, heating, water pumping) while preserving data privacy. It uses a graph-contract regis
|
||||
This repository implements a production-ready skeleton for OpenEnergySphere's federated, contract-driven cross-domain energy planning platform. The MVP focuses on the EnergiBridge interoperability layer, mapping OpenEnergySphere primitives to a canonical IR and enabling plug-and-play adapters across DERs, meters, pumps, and buildings while preserving offline-first operation and governance.
|
||||
|
||||
Architecture highlights
|
||||
- Core primitives: LocalProblem, SharedVariables, DualVariables, PlanDelta, PrivacyBudget, AuditLog
|
||||
- Graph-of-Contracts registry for adapters and data schemas with per-message metadata
|
||||
- Adapters: starter implementations (substation_meter, der_aggregator)
|
||||
- EnergiBridge: mapping functions between OpenEnergySphere primitives and a CatOpt-like IR
|
||||
- Server: FastAPI app exposing endpoints to register contracts and adapters
|
||||
- Storage: in-memory by default, with potential for SQLite/PostgreSQL persistence
|
||||
|
||||
Getting started
|
||||
- Install dependencies and run tests via test.sh
|
||||
- Run API locally: uvicorn energysphere.server.main:app --reload
|
||||
|
||||
This README intentionally provides a compact architectural overview and a concrete MVP workflow to accelerate collaboration and iteration.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=61.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "openenergysphere-energibridge"
|
||||
version = "0.1.0"
|
||||
description = "EnergiBridge MVP: OpenEnergySphere federation core"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
dependencies = [
|
||||
"fastapi>=0.90.0",
|
||||
"uvicorn[standard]>=0.18.0",
|
||||
"pydantic>=1.10.2",
|
||||
"pytest>=7.0.0"
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = { "" = "src" }
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
""" Energysphere core package minimal init. """
|
||||
__version__ = "0.1.0"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
from .substation_meter import SubstationMeterAdapter
|
||||
from .der_aggregator import DERAggregatorAdapter
|
||||
|
||||
__all__ = ["SubstationMeterAdapter", "DERAggregatorAdapter"]
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from energysphere.models import LocalProblem
|
||||
from energysphere.registry import GraphContractRegistry
|
||||
|
||||
|
||||
class DERAggregatorAdapter:
|
||||
adapter_id = "openenergysphere.adapter.der_aggregator"
|
||||
|
||||
def __init__(self, registry: GraphContractRegistry) -> None:
|
||||
self._registry = registry
|
||||
|
||||
def collect_local_problem(self) -> LocalProblem:
|
||||
lp = LocalProblem(
|
||||
site_id="site-der-agg-001",
|
||||
objective="min-carbon",
|
||||
parameters={"der_count": 5, "time_horizon": 24},
|
||||
)
|
||||
self._registry.register_local_problem("contract-der-aggregator-001", lp)
|
||||
return lp
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
from energysphere.models import LocalProblem
|
||||
from energysphere.registry import GraphContractRegistry
|
||||
from energysphere.models import LocalProblem
|
||||
|
||||
|
||||
class SubstationMeterAdapter:
|
||||
"""Starter adapter: simulates a substation meter feeding a LocalProblem."""
|
||||
|
||||
adapter_id = "openenergysphere.adapter.substation_meter"
|
||||
|
||||
def __init__(self, registry: GraphContractRegistry) -> None:
|
||||
self._registry = registry
|
||||
|
||||
def collect_local_problem(self) -> LocalProblem:
|
||||
# Minimal sample LocalProblem; in a real system this would read from meters.
|
||||
lp = LocalProblem(
|
||||
site_id="site-meter-001",
|
||||
objective="min-cost",
|
||||
parameters={"load": 1.0, "time_horizon": 24},
|
||||
)
|
||||
# register a reference contract for this adapter's generated problem
|
||||
self._registry.register_local_problem("contract-substation-meter-001", lp)
|
||||
return lp
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class LocalProblem(BaseModel):
|
||||
site_id: str
|
||||
objective: str
|
||||
parameters: Dict[str, Any]
|
||||
|
||||
|
||||
class SharedVariables(BaseModel):
|
||||
name: str
|
||||
value: Any
|
||||
version: int
|
||||
privacy_bound: Optional[float] = None
|
||||
|
||||
|
||||
class DualVariables(BaseModel):
|
||||
primal: float
|
||||
dual: float
|
||||
|
||||
|
||||
class PlanDelta(BaseModel):
|
||||
delta_id: str
|
||||
changes: Dict[str, Any]
|
||||
timestamp: float
|
||||
|
||||
|
||||
class PrivacyBudget(BaseModel):
|
||||
budget: float
|
||||
used: float
|
||||
|
||||
|
||||
class AuditLog(BaseModel):
|
||||
entry_id: str
|
||||
action: str
|
||||
timestamp: float
|
||||
actor: str
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from .models import LocalProblem
|
||||
|
||||
|
||||
class GraphContractRegistry:
|
||||
"""In-memory registry for Graph-of-Contracts.
|
||||
|
||||
This is a minimal skeleton intended for MVP testing and as a foundation for
|
||||
a persistent registry backed by SQLite/PostgreSQL in the future.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._contracts: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
def register_contract(self, contract_id: str, contract_spec: Dict[str, Any]) -> None:
|
||||
self._contracts[contract_id] = contract_spec
|
||||
|
||||
def list_contracts(self) -> List[Dict[str, Any]]:
|
||||
return list(self._contracts.values())
|
||||
|
||||
def get_contract(self, contract_id: str) -> Dict[str, Any] | None:
|
||||
return self._contracts.get(contract_id)
|
||||
|
||||
# Lightweight helper to store a LocalProblem as a contract reference
|
||||
def register_local_problem(self, contract_id: str, lp: LocalProblem) -> None:
|
||||
self.register_contract(contract_id, lp.dict())
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
"""EnergiBridge mapping helpers (skeleton).
|
||||
|
||||
Provides lightweight translation between OpenEnergySphere primitives and a
|
||||
canonical IR (CatOpt-like) used by adapters.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
from .models import LocalProblem
|
||||
|
||||
|
||||
class EnergiBridge:
|
||||
@staticmethod
|
||||
def map_local_problem_to_object(local_problem: LocalProblem) -> Dict[str, Any]:
|
||||
"""Map a LocalProblem into a canonical IR object representation."""
|
||||
return {
|
||||
"type": "LocalProblem",
|
||||
"site_id": local_problem.site_id,
|
||||
"objective": local_problem.objective,
|
||||
"parameters": local_problem.parameters,
|
||||
"version": 1,
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
from energysphere.registry import GraphContractRegistry
|
||||
from energysphere.models import LocalProblem
|
||||
|
||||
app = FastAPI(title="EnergiBridge MVP API")
|
||||
|
||||
# Simple in-memory stores
|
||||
registry = GraphContractRegistry()
|
||||
adapters_registry: dict[str, dict[str, str]] = {}
|
||||
|
||||
|
||||
class LocalProblemDTO(BaseModel):
|
||||
site_id: str
|
||||
objective: str
|
||||
parameters: dict[str, object]
|
||||
|
||||
|
||||
@app.post("/contracts/register")
|
||||
def register_contract(lp: LocalProblemDTO):
|
||||
problem = LocalProblem(**lp.dict())
|
||||
contract_id = f"contract-{problem.site_id}"
|
||||
registry.register_local_problem(contract_id, problem)
|
||||
return {"contract_id": contract_id, "problem": problem.dict()}
|
||||
|
||||
|
||||
@app.get("/contracts")
|
||||
def list_contracts():
|
||||
return registry.list_contracts()
|
||||
|
||||
|
||||
class AdapterDTO(BaseModel):
|
||||
adapter_id: str
|
||||
description: str | None = None
|
||||
|
||||
|
||||
@app.post("/adapters/register")
|
||||
def register_adapter(dto: AdapterDTO):
|
||||
adapters_registry[dto.adapter_id] = {"description": dto.description or ""}
|
||||
return {"registered": True, "adapter_id": dto.adapter_id}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
export PYTHONPATH=./src
|
||||
echo "Installing dependencies..."
|
||||
pip install --upgrade pip setuptools wheel >/dev/null 2>&1 || true
|
||||
pip install "pydantic>=1.10.2" "fastapi>=0.90.0" "uvicorn[standard]>=0.18.0" "pytest>=7.0.0" >/dev/null 2>&1 || true
|
||||
echo "Running tests..."
|
||||
pytest -q
|
||||
echo "Running packaging build..."
|
||||
python3 -m build
|
||||
echo "All tests and build succeeded."
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
from energysphere.adapters.substation_meter import SubstationMeterAdapter
|
||||
from energysphere.registry import GraphContractRegistry
|
||||
from energysphere.models import LocalProblem
|
||||
|
||||
|
||||
def test_substation_meter_adapter_generates_local_problem():
|
||||
registry = GraphContractRegistry()
|
||||
adapter = SubstationMeterAdapter(registry)
|
||||
lp = adapter.collect_local_problem()
|
||||
assert isinstance(lp, LocalProblem)
|
||||
# The adapter should have registered the problem into the registry
|
||||
contracts = registry.list_contracts()
|
||||
assert any(c.get("site_id") == lp.site_id for c in contracts)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
from energysphere.registry import GraphContractRegistry
|
||||
from energysphere.models import LocalProblem
|
||||
|
||||
|
||||
def test_registry_register_and_list():
|
||||
reg = GraphContractRegistry()
|
||||
lp = LocalProblem(site_id="site-1", objective="min-cost", parameters={"load": 1.0})
|
||||
reg.register_local_problem("contract-site-1", lp)
|
||||
contracts = reg.list_contracts()
|
||||
assert len(contracts) == 1
|
||||
assert contracts[0]["site_id"] == "site-1"
|
||||
Loading…
Reference in New Issue