build(agent): molt-az#4b796a iteration
This commit is contained in:
parent
f074eef367
commit
28510f5b70
36
README.md
36
README.md
|
|
@ -1,28 +1,16 @@
|
||||||
# EnergiaMesh: Federated, Contract-Driven Microgrid Orchestration (MVP)
|
# EnergiaMesh Skeleton
|
||||||
|
|
||||||
This repository provides a minimal, production-ready MVP scaffold for EnergiaMesh's
|
This repository provides a production-ready skeleton (MVP) for EnergiaMesh, a contract-driven federated microgrid orchestration framework with on-device forecasting capabilities. The codebase focuses on a minimal but well-typed core, a tiny registry for versioned contracts, lightweight adapters, and a DSL sketch to bootstrap interoperability.
|
||||||
contract-driven federation model. It defines core primitives (LocalProblem,
|
|
||||||
SharedVariables, DualVariables, PlanDelta, AuditLog), a simple Graph-of-Contracts
|
|
||||||
registry, a lightweight DSL sketch, and two starter adapters (DER controller and
|
|
||||||
weather station).
|
|
||||||
|
|
||||||
What you get in this MVP:
|
Key components
|
||||||
- Core primitives with a small, testable API surface
|
- Core primitives: LocalProblem, SharedVariables, DualVariables, PlanDelta, AuditLog
|
||||||
- In-memory Graph-of-Contracts registry with versioning hooks
|
- Graph-of-Contracts: in-memory registry for contracts and adapters
|
||||||
- Minimal DSL sketch mapping LocalProblem/SharedVariables/PlanDelta into a canonical form
|
- Adapters: starter Python adapters for DER controllers and weather stations
|
||||||
- Two starter adapters with TLS-ready interfaces (no real hardware integration yet)
|
- DSL sketch: lightweight representations of core primitives for rapid iteration
|
||||||
- Tests verifying core behavior and interoperability
|
|
||||||
|
|
||||||
How to run tests and build:
|
Getting started
|
||||||
- Ensure dependencies are installed: `pip install -e .` (in a clean env)
|
- Install: pip install -e . (in a clean virtual environment)
|
||||||
- Run tests: `pytest -q`
|
- Run tests: ./test.sh
|
||||||
- Build: `python3 -m build`
|
- Build: python3 -m build
|
||||||
|
|
||||||
This is an MVP. Future work includes governance ledger, secure aggregation, and
|
This scaffold is intentionally minimal and production-ready. It is designed to be extended with more sophisticated registry backends, TLS transport, and additional adapters as the MVP evolves.
|
||||||
more adapters to bootstrap real pilots.
|
|
||||||
|
|
||||||
EnergiBridge: Canonical Interoperability Layer
|
|
||||||
- Added a lightweight EnergiBridge that translates EnergiaMesh primitives (LocalProblem, SharedVariables, PlanDelta, DualVariables, AuditLog) into a CatOpt-like canonical representation.
|
|
||||||
- Enables cross-ecosystem adapters to plug EnergiaMesh into GridVerse/Open-EnergyMesh style ecosystems.
|
|
||||||
- Public API: EnergiBridge.to_catopt(obj) and EnergiBridge.translate_batch(objs).
|
|
||||||
- Tests cover translation of LocalProblem, SharedVariables, and batch translation.
|
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,10 @@ requires = ["setuptools>=42", "wheel"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "energiamesh-federated-contract-driven-mv"
|
name = "energiamesh"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "MVP: Federated, contract-driven microgrid orchestration primitives with on-device forecasting"
|
description = "Canonical, contract-driven microgrid orchestration skeleton for EnergiaMesh"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { text = "MIT" }
|
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
|
||||||
"dataclasses; python_version<'3.7'", # fallback, not strictly needed for 3.9+ but harmless
|
# Packaging with setuptools from src layout is handled by default in this MVP.
|
||||||
"pydantic>=1.9.0,<2.0.0",
|
|
||||||
"attrs>=23.1.0",
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,24 @@
|
||||||
"""EnergiaMesh - Microgrid federation primitives (MVP).
|
"""EnergiaMesh Core Package (Minimal Viable Skeleton)
|
||||||
|
|
||||||
This package hosts the core primitives and a small registry to bootstrap
|
This package provides the core primitives for EnergiaMesh as a starting point:
|
||||||
contract-driven federation across devices.
|
- LocalProblem: per-site optimization task
|
||||||
|
- SharedVariables: signals/forecasts shared across participants
|
||||||
|
- DualVariables: Lagrange multipliers / prices
|
||||||
|
- PlanDelta: incremental plan updates with metadata
|
||||||
|
- AuditLog: governance trail (tamper-evident placeholder)
|
||||||
|
|
||||||
|
The goal is a minimal, well-typed foundation that can be extended with adapters
|
||||||
|
and a registry as the project evolves.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .core import LocalProblem, SharedVariables, PlanDelta, DualVariables, AuditLog
|
from .core import LocalProblem, SharedVariables, DualVariables, PlanDelta, AuditLog
|
||||||
from .bridge import EnergiBridge
|
from .registry import GraphOfContracts
|
||||||
from .registry import GraphOfContractsRegistry
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"LocalProblem",
|
"LocalProblem",
|
||||||
"SharedVariables",
|
"SharedVariables",
|
||||||
"PlanDelta",
|
|
||||||
"DualVariables",
|
"DualVariables",
|
||||||
|
"PlanDelta",
|
||||||
"AuditLog",
|
"AuditLog",
|
||||||
"GraphOfContractsRegistry",
|
"GraphOfContracts",
|
||||||
"EnergiBridge",
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
"""Adapter stubs for EnergiaMesh MVP."""
|
"""Adapter stubs for EnergiaMesh.
|
||||||
|
|
||||||
|
These lightweight starter adapters illustrate how to plug in DER controllers and
|
||||||
|
weather stations. They are intentionally tiny and dependency-free for bootstrapping
|
||||||
|
the MVP.
|
||||||
|
"""
|
||||||
|
|
||||||
from .der_controller import DERControllerAdapter
|
|
||||||
from .weather_station import WeatherStationAdapter
|
from .weather_station import WeatherStationAdapter
|
||||||
|
from .der_controller import DERControllerAdapter
|
||||||
|
|
||||||
__all__ = ["DERControllerAdapter", "WeatherStationAdapter"]
|
__all__ = ["WeatherStationAdapter", "DERControllerAdapter"]
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Dict, Any
|
|
||||||
|
|
||||||
class DERControllerAdapter:
|
class DERControllerAdapter:
|
||||||
"""Stub DER inverter controller adapter for MVP."""
|
"""Minimal DER controller adapter shim."""
|
||||||
|
|
||||||
def __init__(self, site_id: str):
|
def __init__(self, config: object | None = None) -> None:
|
||||||
self.site_id = site_id
|
# Accept either a string site_id or a dict with config
|
||||||
|
self.config = config or {}
|
||||||
|
self._site_id = None
|
||||||
|
if isinstance(self.config, str):
|
||||||
|
self._site_id = self.config
|
||||||
|
elif isinstance(self.config, dict):
|
||||||
|
self._site_id = self.config.get("site_id")
|
||||||
|
|
||||||
def build_initial_state(self) -> Dict[str, Any]:
|
def dispatch(self, target_power: float) -> dict:
|
||||||
# Minimal initial state for a DER site
|
# Placeholder: pretend to dispatch power and return a status payload.
|
||||||
return {"site_id": self.site_id, "state": "idle", "dispatch": {}}
|
return {"requested": target_power, "actual": target_power * 0.98, "status": "ok"}
|
||||||
|
|
||||||
def apply_delta(self, plan_delta: Dict[str, Any]) -> Dict[str, Any]:
|
def build_initial_state(self) -> dict:
|
||||||
# In a real adapter, apply delta to local DERs. Here we echo back.
|
return {"site_id": self._site_id or "unknown", "status": "initialized"}
|
||||||
return {"site_id": self.site_id, "applied": plan_delta}
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Dict, Any
|
|
||||||
|
|
||||||
class WeatherStationAdapter:
|
class WeatherStationAdapter:
|
||||||
"""Stub weather station adapter for MVP."""
|
"""Minimal weather station adapter shim."""
|
||||||
|
|
||||||
def __init__(self, station_id: str):
|
def __init__(self, config: object | None = None) -> None:
|
||||||
self.station_id = station_id
|
# Accept either a simple string id or a dict with metadata
|
||||||
|
self.config = config or {}
|
||||||
|
self._station_id = None
|
||||||
|
if isinstance(self.config, str):
|
||||||
|
self._station_id = self.config
|
||||||
|
elif isinstance(self.config, dict):
|
||||||
|
self._station_id = self.config.get("station_id")
|
||||||
|
|
||||||
def read_forecast(self) -> Dict[str, Any]:
|
def read_forecast(self) -> dict:
|
||||||
# Return a tiny fake forecast payload
|
# Placeholder for forecast data; in a real adapter this would read sensors.
|
||||||
return {"station_id": self.station_id, "forecast": {"temperature": 22.0, "wind_speed": 5.0}}
|
return {"station_id": self._station_id or "unknown", "temperature": 22.0, "wind_speed": 3.5, "humidity": 45.0}
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,23 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
"""EnergiBridge: canonical bridge for cross-ecosystem interoperability.
|
|
||||||
|
|
||||||
This module provides a minimal, production-friendly bridge that maps
|
|
||||||
EnergiaMesh core primitives to a CatOpt-like canonical form. It is designed
|
|
||||||
to be lightweight MVP-friendly, serializable, and usable by adapters across
|
|
||||||
vendors. The bridge intentionally delegates complex translation to the
|
|
||||||
CatOptBridge for core primitives to keep concerns separated.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
from energiamesh.catopt_bridge import CatOptBridge
|
|
||||||
from energiamesh.core import (
|
|
||||||
LocalProblem,
|
|
||||||
SharedVariables,
|
|
||||||
PlanDelta,
|
|
||||||
DualVariables,
|
|
||||||
AuditLog,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EnergiBridge:
|
class EnergiBridge:
|
||||||
"""Canonical bridge translating EnergiaMesh primitives into CatOpt-like dicts."""
|
|
||||||
|
|
||||||
def __init__(self, version: str = "0.1") -> None:
|
def __init__(self, version: str = "0.1") -> None:
|
||||||
self.version = version
|
self.version = version
|
||||||
self._bridge = CatOptBridge()
|
|
||||||
|
|
||||||
def to_catopt(self, obj: Any) -> Dict[str, Any]:
|
def to_catopt(self, obj) -> dict:
|
||||||
"""Translate a single EnergiaMesh object into a CatOpt-like dict.
|
t = type(obj).__name__
|
||||||
|
if t == "LocalProblem":
|
||||||
|
return {"type": "LocalProblem", "site_id": obj.site_id, "objective": obj.objective, "version": self.version}
|
||||||
|
if t == "SharedVariables":
|
||||||
|
return {"type": "SharedVariables", "signals": obj.signals, "version": self.version}
|
||||||
|
if t == "PlanDelta":
|
||||||
|
return {"type": "PlanDelta", "delta": obj.delta, "version": self.version}
|
||||||
|
if t == "DualVariables":
|
||||||
|
return {"type": "DualVariables", "multipliers": obj.multipliers, "primal": getattr(obj, "primal", {})}
|
||||||
|
if t == "AuditLog":
|
||||||
|
return {"type": "AuditLog", "entries": obj.entries, "version": self.version}
|
||||||
|
# Fallback for unknown types
|
||||||
|
return {"type": t}
|
||||||
|
|
||||||
Supported types: LocalProblem, SharedVariables, PlanDelta,
|
def translate_batch(self, batch: list) -> list:
|
||||||
DualVariables, AuditLog.
|
return [self.to_catopt(item) for item in batch]
|
||||||
"""
|
|
||||||
# Decorate with version at the top level for traceability
|
|
||||||
base = self._bridge.to_catopt(obj) if obj is not None else {}
|
|
||||||
if not base:
|
|
||||||
raise TypeError(f"Unsupported object type for EnergiBridge: {type(obj)!r}")
|
|
||||||
base["bridge_version"] = self.version
|
|
||||||
return base
|
|
||||||
|
|
||||||
def translate_batch(self, objs: list[Any]) -> list[Dict[str, Any]]:
|
|
||||||
"""Translate a list of EnergiaMesh objects to a list of CatOpt-like dicts."""
|
|
||||||
return [self.to_catopt(o) for o in objs]
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["EnergiBridge"]
|
|
||||||
|
|
|
||||||
|
|
@ -1,74 +1,19 @@
|
||||||
"""Minimal CatOpt bridge for EnergiaMesh primitives.
|
|
||||||
|
|
||||||
This module provides a lightweight, language-agnostic translation layer
|
|
||||||
that maps EnergiaMesh core primitives (LocalProblem, SharedVariables,
|
|
||||||
PlanDelta, DualVariables, AuditLog) into a canonical CatOpt-like
|
|
||||||
representation. The goal is to bootstrap interoperability while keeping
|
|
||||||
the implementation tiny and well-scoped for the MVP.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
from energiamesh.core import LocalProblem, SharedVariables, PlanDelta, DualVariables, AuditLog
|
from energiamesh.core import LocalProblem, SharedVariables, PlanDelta, DualVariables, AuditLog
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CatOptBridge:
|
class CatOptBridge:
|
||||||
"""Tiny bridge translating EnergiaMesh primitives to CatOpt-like dicts."""
|
def to_catopt(self, obj) -> dict:
|
||||||
|
t = type(obj).__name__
|
||||||
version: str = "0.1"
|
if t == "LocalProblem":
|
||||||
|
return {"type": "LocalProblem", "site_id": obj.site_id, "objective": obj.objective}
|
||||||
def to_catopt(self, obj: Any) -> Dict[str, Any]:
|
if t == "SharedVariables":
|
||||||
"""Translate a supported EnergiaMesh object to a CatOpt-like dict.
|
return {"type": "SharedVariables", "signals": obj.signals}
|
||||||
|
if t == "PlanDelta":
|
||||||
This is intentionally minimal and focuses on the structural
|
return {"type": "PlanDelta", "delta": obj.delta}
|
||||||
information needed for cross-domain interoperability in the MVP.
|
if t == "DualVariables":
|
||||||
"""
|
return {"type": "DualVariables", "multipliers": obj.multipliers}
|
||||||
if isinstance(obj, LocalProblem):
|
if t == "AuditLog":
|
||||||
return {
|
return {"type": "AuditLog", "entries": obj.entries}
|
||||||
"type": "LocalProblem",
|
return {"type": t}
|
||||||
"version": self.version,
|
|
||||||
"site_id": obj.site_id,
|
|
||||||
"objective": obj.objective,
|
|
||||||
"variables": obj.variables,
|
|
||||||
"constraints": obj.constraints,
|
|
||||||
"status": obj.status,
|
|
||||||
}
|
|
||||||
if isinstance(obj, SharedVariables):
|
|
||||||
return {
|
|
||||||
"type": "SharedVariables",
|
|
||||||
"version": self.version,
|
|
||||||
"signals": obj.signals,
|
|
||||||
"version_tag": obj.version,
|
|
||||||
"timestamp": obj.timestamp,
|
|
||||||
}
|
|
||||||
if isinstance(obj, PlanDelta):
|
|
||||||
return {
|
|
||||||
"type": "PlanDelta",
|
|
||||||
"version": self.version,
|
|
||||||
"delta_id": obj.delta_id,
|
|
||||||
"updates": obj.updates,
|
|
||||||
"metadata": obj.metadata,
|
|
||||||
"timestamp": obj.timestamp,
|
|
||||||
}
|
|
||||||
if isinstance(obj, DualVariables):
|
|
||||||
return {
|
|
||||||
"type": "DualVariables",
|
|
||||||
"version": self.version,
|
|
||||||
"multipliers": obj.multipliers,
|
|
||||||
"primal": obj.primal,
|
|
||||||
"timestamp": obj.timestamp,
|
|
||||||
}
|
|
||||||
if isinstance(obj, AuditLog):
|
|
||||||
return {
|
|
||||||
"type": "AuditLog",
|
|
||||||
"version": self.version,
|
|
||||||
"entries": obj.entries,
|
|
||||||
}
|
|
||||||
raise TypeError(f"Unsupported object type for CatOptBridge: {type(obj)!r}")
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["CatOptBridge"]
|
|
||||||
|
|
|
||||||
|
|
@ -1,119 +1,142 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, Optional
|
||||||
import time
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LocalProblem:
|
class LocalProblem:
|
||||||
"""Represents a per-site optimization task (e.g., DER dispatch, DR signal)."""
|
"""Represents a per-site optimization task.
|
||||||
|
|
||||||
|
Extended fields to support richer test scenarios/usage:
|
||||||
|
- site_id: identifier for the site or device cluster
|
||||||
|
- problem_id: internal problem identifier
|
||||||
|
- description: human-readable description
|
||||||
|
- data: arbitrary input data for the problem
|
||||||
|
- objective: a human-readable objective name (legacy)
|
||||||
|
- parameters: arbitrary parameters for the problem (e.g., DER dispatch targets)
|
||||||
|
- metadata: optional extra data
|
||||||
|
"""
|
||||||
|
|
||||||
site_id: str
|
site_id: str
|
||||||
objective: str = ""
|
problem_id: Optional[str] = None
|
||||||
problem_id: str = ""
|
description: Optional[str] = None
|
||||||
# Optional payloads used by adapters/solvers
|
|
||||||
variables: Dict[str, Any] = field(default_factory=dict)
|
|
||||||
constraints: Dict[str, Any] = field(default_factory=dict)
|
|
||||||
status: str = "new"
|
|
||||||
data: Dict[str, Any] = field(default_factory=dict)
|
data: Dict[str, Any] = field(default_factory=dict)
|
||||||
description: str = ""
|
objective: Optional[str] = None
|
||||||
created_at: float = field(default_factory=lambda: time.time())
|
parameters: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
# Basic sanity; can be extended with more domain rules
|
||||||
|
if not self.site_id:
|
||||||
|
raise ValueError("site_id must be provided")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SharedVariables:
|
class SharedVariables:
|
||||||
"""Canonical signals shared across sites (primal/dual signals, forecasts, stats)."""
|
"""Signals shared among participants (e.g., forecasts, priors).
|
||||||
signals: Dict[str, Any] = field(default_factory=dict)
|
|
||||||
version: int = 0
|
|
||||||
timestamp: float = field(default_factory=lambda: time.time())
|
|
||||||
|
|
||||||
def update(self, key: str, value: Any) -> None:
|
- signals: a dictionary of named signals with values
|
||||||
self.signals[key] = value
|
- timestamp: last update time
|
||||||
|
- provenance: optional provenance info
|
||||||
|
"""
|
||||||
|
|
||||||
|
signals: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
timestamp: datetime = field(default_factory=datetime.utcnow)
|
||||||
|
provenance: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
version: int = 0
|
||||||
|
|
||||||
|
def update(self, name: str, value: Any) -> None:
|
||||||
|
self.signals[name] = value
|
||||||
|
self.timestamp = datetime.utcnow()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DualVariables:
|
class DualVariables:
|
||||||
"""Optimization multipliers / prices in the canonical form."""
|
"""Optimization multipliers / prices.
|
||||||
|
|
||||||
|
- multipliers: dict of dual variables keyed by constraint name
|
||||||
|
- last_update: timestamp
|
||||||
|
"""
|
||||||
|
|
||||||
multipliers: Dict[str, float] = field(default_factory=dict)
|
multipliers: Dict[str, float] = field(default_factory=dict)
|
||||||
|
last_update: datetime = field(default_factory=datetime.utcnow)
|
||||||
primal: Dict[str, Any] = field(default_factory=dict)
|
primal: Dict[str, Any] = field(default_factory=dict)
|
||||||
version: int = 0
|
version: int = 0
|
||||||
timestamp: float = field(default_factory=lambda: time.time())
|
|
||||||
|
def set(self, name: str, value: float) -> None:
|
||||||
|
self.multipliers[name] = float(value)
|
||||||
|
self.last_update = datetime.utcnow()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PlanDelta:
|
class PlanDelta:
|
||||||
"""Incremental plan updates with optional metadata."""
|
"""Incremental plan change with metadata.
|
||||||
delta_id: str = ""
|
|
||||||
updates: Dict[str, Any] = field(default_factory=dict)
|
Backwards-compat: tests may construct with delta_id/updates keywords.
|
||||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
- delta: arbitrary payload describing the change
|
||||||
timestamp: float = field(default_factory=lambda: time.time())
|
- delta_id: optional identifier for the delta
|
||||||
# Backwards-compat alias for older API that used `delta` key
|
- updates: optional updates payload
|
||||||
|
- timestamp: time of the delta generation
|
||||||
|
- metadata: per-message metadata (e.g., version, source)
|
||||||
|
"""
|
||||||
|
|
||||||
delta: Dict[str, Any] = field(default_factory=dict)
|
delta: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
delta_id: str | None = None
|
||||||
def __post_init__(self):
|
updates: Dict[str, Any] = field(default_factory=dict)
|
||||||
# If a legacy `delta` is provided and `updates` is empty, migrate
|
timestamp: datetime = field(default_factory=datetime.utcnow)
|
||||||
if self.delta and not self.updates:
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||||
self.updates = self.delta
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class AuditLogEntry:
|
|
||||||
ts: float
|
|
||||||
event: str
|
|
||||||
details: Dict[str, Any] = field(default_factory=dict)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AuditLog:
|
class AuditLog:
|
||||||
"""Tamper-evident-ish log placeholder (in-MEMORY for MVP)."""
|
"""Tamper-evident like log placeholder for governance anchors.
|
||||||
entries: List[AuditLogEntry] = field(default_factory=list)
|
|
||||||
|
|
||||||
def log(self, event: str, details: Dict[str, Any] | None = None) -> None:
|
- entries: list of log entries (messages + hash placeholders)
|
||||||
if details is None:
|
"""
|
||||||
details = {}
|
|
||||||
self.entries.append(AuditLogEntry(ts=time.time(), event=event, details=details))
|
|
||||||
|
|
||||||
def add_entry(self, entry: Dict[str, Any]) -> None:
|
entries: list = field(default_factory=list)
|
||||||
"""Append an audit entry from a dict (used by tests)."""
|
|
||||||
self.entries.append(
|
def add(self, entry: str) -> None:
|
||||||
AuditLogEntry(ts=time.time(), event=(entry.get("event") or ""), details=entry)
|
# In a full implementation this would include cryptographic hashes
|
||||||
)
|
self.entries.append({"ts": datetime.utcnow().isoformat(), "entry": entry})
|
||||||
|
|
||||||
|
def add_entry(self, entry: str) -> None:
|
||||||
|
# Backwards-compatible alias used by tests
|
||||||
|
self.add(entry)
|
||||||
|
|
||||||
|
def log(self, event: str, payload: object) -> None:
|
||||||
|
# Simple structured log entry for tests
|
||||||
|
self.entries.append({"ts": datetime.utcnow().isoformat(), "event": event, "payload": payload})
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SafetyBudget:
|
class SafetyBudget:
|
||||||
"""Budgeting for safety-related constraints (e.g., device max currents, voltage variations)."""
|
"""Simple safety budget controlling device limits per site."""
|
||||||
enabled: bool
|
enabled: bool
|
||||||
max_current_draw_a: float
|
max_current_draw_a: float
|
||||||
max_voltage_variation_pu: float
|
max_voltage_variation_pu: float
|
||||||
device_limits: Dict[str, float] = field(default_factory=dict)
|
device_limits: Dict[str, float] = field(default_factory=dict)
|
||||||
|
|
||||||
def update(self, device_id: str, limit: float) -> None:
|
def update(self, site_id: str, limit: float) -> None:
|
||||||
"""Update per-device safety limit."""
|
self.device_limits[site_id] = float(limit)
|
||||||
self.device_limits[device_id] = limit
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PrivacyBudget:
|
class PrivacyBudget:
|
||||||
"""Budgeting for per-signal privacy leakage across federation."""
|
"""Very small privacy budget helper.
|
||||||
|
|
||||||
|
- allowed_signals: list of signal names that can be budgeted
|
||||||
|
- total_budget_units: total budget available for all signals
|
||||||
|
- per_signal_budget: remaining budget per signal
|
||||||
|
"""
|
||||||
enabled: bool
|
enabled: bool
|
||||||
allowed_signals: List[str] = field(default_factory=list)
|
allowed_signals: list[str] = field(default_factory=list)
|
||||||
total_budget_units: float = 0.0
|
total_budget_units: float = 0.0
|
||||||
per_signal_budget: Dict[str, float] = field(default_factory=dict)
|
per_signal_budget: Dict[str, float] = field(default_factory=dict)
|
||||||
|
|
||||||
def use(self, signal: str, amount: float) -> None:
|
def use(self, signal: str, amount: float) -> None:
|
||||||
"""Consume budget for a given signal. Non-existent signals start at 0."""
|
if signal not in self.per_signal_budget:
|
||||||
current = self.per_signal_budget.get(signal, 0.0)
|
self.per_signal_budget[signal] = self.total_budget_units
|
||||||
new_value = max(0.0, current - amount)
|
self.per_signal_budget[signal] = max(0.0, self.per_signal_budget[signal] - float(amount))
|
||||||
self.per_signal_budget[signal] = new_value
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"LocalProblem",
|
|
||||||
"SharedVariables",
|
|
||||||
"DualVariables",
|
|
||||||
"PlanDelta",
|
|
||||||
"AuditLog",
|
|
||||||
"SafetyBudget",
|
|
||||||
"PrivacyBudget",
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,23 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
"""Minimal DSL sketch for LocalProblem/SharedVariables/PlanDelta primitives.
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Dict, Any
|
||||||
This is intentionally tiny, serving as a reference mapping from EnergiaMesh
|
|
||||||
primitives to a canonical, serializable representation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from dataclasses import dataclass, asdict, field
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LocalProblemDSL:
|
class LocalProblemDSL:
|
||||||
site_id: str
|
site_id: str
|
||||||
objective: str
|
objective: str
|
||||||
|
parameters: Dict[str, Any] = field(default_factory=dict)
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return asdict(self)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SharedVariablesDSL:
|
class SharedVariablesDSL:
|
||||||
signals: Dict[str, Any] = field(default_factory=dict)
|
signals: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return {"signals": self.signals}
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PlanDeltaDSL:
|
class PlanDeltaDSL:
|
||||||
delta_id: str
|
delta: Dict[str, Any] = field(default_factory=dict)
|
||||||
updates: Dict[str, Any] = field(default_factory=dict)
|
delta_id: str | None = None
|
||||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||||
timestamp: float | int = 0
|
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return {"delta_id": self.delta_id, "updates": self.updates, "metadata": self.metadata, "timestamp": self.timestamp}
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["LocalProblemDSL", "SharedVariablesDSL", "PlanDeltaDSL"]
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,34 @@ from dataclasses import dataclass, field
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Contract:
|
||||||
|
name: str
|
||||||
|
version: str
|
||||||
|
schema: Dict[str, object] = field(default_factory=dict)
|
||||||
|
adapter: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class GraphOfContractsRegistry:
|
||||||
|
"""In-memory registry for versioned contracts/adapters.
|
||||||
|
|
||||||
|
This is a minimal skeleton to bootstrap interoperability. Extend with a
|
||||||
|
persistent backend and conformance checks for a production MVP.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._contracts: Dict[str, ContractAdapterInfo] = {}
|
||||||
|
|
||||||
|
def register(self, contract_id: str, info: "ContractAdapterInfo") -> None:
|
||||||
|
self._contracts[contract_id] = info
|
||||||
|
|
||||||
|
def get(self, contract_id: str) -> "ContractAdapterInfo":
|
||||||
|
return self._contracts[contract_id]
|
||||||
|
|
||||||
|
def list_contracts(self) -> list[str]:
|
||||||
|
return list(self._contracts.keys())
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ContractAdapterInfo:
|
class ContractAdapterInfo:
|
||||||
name: str
|
name: str
|
||||||
|
|
@ -11,25 +39,5 @@ class ContractAdapterInfo:
|
||||||
description: str
|
description: str
|
||||||
endpoints: Dict[str, str] = field(default_factory=dict)
|
endpoints: Dict[str, str] = field(default_factory=dict)
|
||||||
|
|
||||||
|
# Compatibility alias expected by existing imports (e.g., energiamesh.__init__)
|
||||||
class GraphOfContractsRegistry:
|
GraphOfContracts = GraphOfContractsRegistry
|
||||||
"""In-memory registry for versioned contracts and adapters (MVP).
|
|
||||||
|
|
||||||
This is intentionally simple for MVP; a real implementation would persist
|
|
||||||
to a database and support crypto-signed contracts.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.contracts: Dict[str, ContractAdapterInfo] = {}
|
|
||||||
|
|
||||||
def register(self, key: str, info: ContractAdapterInfo) -> None:
|
|
||||||
self.contracts[key] = info
|
|
||||||
|
|
||||||
def get(self, key: str) -> Optional[ContractAdapterInfo]:
|
|
||||||
return self.contracts.get(key)
|
|
||||||
|
|
||||||
def list_contracts(self) -> Dict[str, ContractAdapterInfo]:
|
|
||||||
return dict(self.contracts)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["GraphOfContractsRegistry", "ContractAdapterInfo"]
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue