diff --git a/README.md b/README.md index cc05372..582be0c 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,9 @@ How to run tests and build: This is an MVP. Future work includes governance ledger, secure aggregation, and 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. diff --git a/src/energiamesh/__init__.py b/src/energiamesh/__init__.py index b19a36c..f2bea06 100644 --- a/src/energiamesh/__init__.py +++ b/src/energiamesh/__init__.py @@ -5,6 +5,7 @@ contract-driven federation across devices. """ from .core import LocalProblem, SharedVariables, PlanDelta, DualVariables, AuditLog +from .bridge import EnergiBridge from .registry import GraphOfContractsRegistry __all__ = [ @@ -14,4 +15,5 @@ __all__ = [ "DualVariables", "AuditLog", "GraphOfContractsRegistry", + "EnergiBridge", ] diff --git a/src/energiamesh/bridge.py b/src/energiamesh/bridge.py new file mode 100644 index 0000000..3e8331f --- /dev/null +++ b/src/energiamesh/bridge.py @@ -0,0 +1,49 @@ +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: + """Canonical bridge translating EnergiaMesh primitives into CatOpt-like dicts.""" + + def __init__(self, version: str = "0.1") -> None: + self.version = version + self._bridge = CatOptBridge() + + def to_catopt(self, obj: Any) -> Dict[str, Any]: + """Translate a single EnergiaMesh object into a CatOpt-like dict. + + Supported types: LocalProblem, SharedVariables, PlanDelta, + DualVariables, AuditLog. + """ + # 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"] diff --git a/tests/test_bridge.py b/tests/test_bridge.py new file mode 100644 index 0000000..5065f18 --- /dev/null +++ b/tests/test_bridge.py @@ -0,0 +1,30 @@ +from energiamesh.bridge import EnergiBridge +from energiamesh.core import LocalProblem, SharedVariables, PlanDelta, DualVariables, AuditLog + + +def test_energi_bridge_local_problem_translation(): + lb = LocalProblem(site_id="site-1", objective="minimize_cost") + bridge = EnergiBridge() + data = bridge.to_catopt(lb) + assert data["type"] == "LocalProblem" + assert data["site_id"] == "site-1" + assert data["version"] == bridge.version + + +def test_energi_bridge_shared_variables_translation(): + sv = SharedVariables(signals={"forecast": {"temp": 20}}) + bridge = EnergiBridge() + data = bridge.to_catopt(sv) + assert data["type"] == "SharedVariables" + assert "signals" in data + + +def test_energi_bridge_batch_translation(): + bridge = EnergiBridge() + lb = LocalProblem(site_id="site-2", objective="dispatch") + dv = DualVariables(multipliers={"lambda": 1.0}) + ad = AuditLog() + results = bridge.translate_batch([lb, dv, ad]) + assert isinstance(results, list) + assert len(results) == 3 + assert results[0]["type"] == "LocalProblem"