From c1c97d236d8476e352d3ed3cf0b5d0e5cf4a32cd Mon Sep 17 00:00:00 2001 From: agent-23c260159794913b Date: Thu, 16 Apr 2026 22:52:44 +0200 Subject: [PATCH] build(agent): molt-by#23c260 iteration --- README.md | 2 +- .../adapters/__init__.py | 2 + src/gridresilience_studio/adapters/hvac.py | 25 +++++++++++ .../adapters/water_pump.py | 29 ++++++++++++ src/gridresilience_studio/contracts.py | 37 ++++++++++++++++ src/gridresilience_studio/dsl_sketch.py | 44 +++++++++++++++++++ src/gridresilience_studio/energi_bridge.py | 9 ++++ 7 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 src/gridresilience_studio/adapters/hvac.py create mode 100644 src/gridresilience_studio/adapters/water_pump.py create mode 100644 src/gridresilience_studio/contracts.py create mode 100644 src/gridresilience_studio/dsl_sketch.py diff --git a/README.md b/README.md index 27b0f5f..72563d4 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Usage - This repository now includes a lightweight EnergiBridge canonical bridge to map GridResilience primitives to vendor-agnostic representations and starter adapters for IEC 61850 and a microgrid simulator. These scaffolds enable cross-domain interoperability while preserving offline-first operation. - The EnergiBridge bridge lives at `src/gridresilience_studio/bridge.py` and adapters live under `src/gridresilience_studio/adapters/` as a Python package. - Adapters scaffold contains: `IEC61850Adapter` and `MicrogridSimulatorAdapter` as starter implementations. -- The MVP wiring includes: 2 starter adapters, a minimal LocalProblem/SharedSignals/PlanDelta sketch, and a toy delta-sync surface that can replay deterministically. See src/gridresilience_studio/energi_bridge.py and adapters/ for details. +- The MVP wiring includes: 2 starter adapters, a minimal LocalDevicePlan/SharedSignal/PlanDelta sketch (via the DSL sketch in src/gridresilience_studio/dsl_sketch.py), and a toy delta-sync surface that can replay deterministically. See src/gridresilience_studio/energi_bridge.py, src/gridresilience_studio/dsl_sketch.py, and adapters/ for details. - This remains a seed MVP; additional phases will introduce governance ledger, secure identities, and cross-domain dashboards in subsequent iterations. - Usage diff --git a/src/gridresilience_studio/adapters/__init__.py b/src/gridresilience_studio/adapters/__init__.py index d40b634..b4353fe 100644 --- a/src/gridresilience_studio/adapters/__init__.py +++ b/src/gridresilience_studio/adapters/__init__.py @@ -7,3 +7,5 @@ can be composed by orchestration logic. from .iec61850 import IEC61850Adapter # noqa: F401 from .simulator import MicrogridSimulatorAdapter # noqa: F401 +from .water_pump import WaterPumpAdapter # noqa: F401 +from .hvac import HVACAdapter # noqa: F401 diff --git a/src/gridresilience_studio/adapters/hvac.py b/src/gridresilience_studio/adapters/hvac.py new file mode 100644 index 0000000..afe2916 --- /dev/null +++ b/src/gridresilience_studio/adapters/hvac.py @@ -0,0 +1,25 @@ +"""Toy HVAC adapter scaffold for GridResilience Studio. + +This adapter demonstrates a simple cross-domain bridge contract for a building HVAC controller. +""" +from __future__ import annotations + +from typing import Dict, Any + +from gridresilience_studio.core import Object, PlanDelta + + +class HVACAdapter: + def __init__(self, endpoint: str = "localhost", port: int = 1882): + self.endpoint = endpoint + self.port = port + + def connect(self) -> bool: + return True + + def apply_delta(self, delta: PlanDelta) -> PlanDelta: + delta.tags["hvac"] = "applied" + return delta + + def read_object(self, obj: Object) -> Dict[str, Any]: + return {"id": obj.id, "state": "cooling"} diff --git a/src/gridresilience_studio/adapters/water_pump.py b/src/gridresilience_studio/adapters/water_pump.py new file mode 100644 index 0000000..bd79186 --- /dev/null +++ b/src/gridresilience_studio/adapters/water_pump.py @@ -0,0 +1,29 @@ +"""Toy Water Pump adapter scaffold for GridResilience Studio. + +This adapter demonstrates a simple cross-domain bridge contract for a water +pump controller. It is intentionally minimal and TLS-wiring is left as a future +enhancement for production readiness. +""" +from __future__ import annotations + +from typing import Dict, Any + +from gridresilience_studio.core import Object, PlanDelta + + +class WaterPumpAdapter: + """Lightweight scaffold for a water pump controller adapter.""" + + def __init__(self, endpoint: str = "localhost", port: int = 1881): + self.endpoint = endpoint + self.port = port + + def connect(self) -> bool: + return True + + def apply_delta(self, delta: PlanDelta) -> PlanDelta: + delta.tags["water_pump"] = "updated" + return delta + + def read_object(self, obj: Object) -> Dict[str, Any]: + return {"id": obj.id, "state": "operational"} diff --git a/src/gridresilience_studio/contracts.py b/src/gridresilience_studio/contracts.py new file mode 100644 index 0000000..3728daf --- /dev/null +++ b/src/gridresilience_studio/contracts.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict, Optional + +from .core import Object + + +@dataclass +class RegistryEntry: + adapter_id: str + contract_version: str + data_contract: Dict[str, object] = field(default_factory=dict) + timestamp: Optional[str] = None + + +class GraphOfContractsRegistry: + """In-memory registry for adapter data contracts and schemas. + + This is a lightweight scaffold to enable a Graph-of-Contracts-like + registry used by EnergiBridge to negotiate cross-domain interoperability. + """ + + def __init__(self): + self._entries: Dict[str, RegistryEntry] = {} + + def register(self, key: str, entry: RegistryEntry) -> None: + self._entries[key] = entry + + def get(self, key: str) -> RegistryEntry | None: + return self._entries.get(key) + + def all(self) -> Dict[str, RegistryEntry]: + return dict(self._entries) + + +__all__ = ["RegistryEntry", "GraphOfContractsRegistry"] diff --git a/src/gridresilience_studio/dsl_sketch.py b/src/gridresilience_studio/dsl_sketch.py new file mode 100644 index 0000000..4933d45 --- /dev/null +++ b/src/gridresilience_studio/dsl_sketch.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Dict, List + +from .core import Object, Morphism, PlanDelta + + +@dataclass +class LocalDevicePlan: + """DSL sketch for a local device capability description. + + This is a lightweight, vendor-agnostic description that can be mapped + to canonical GridResilience Studio primitives (Object) for runtime use. + """ + id: str + asset_type: str + capabilities: Dict[str, Any] = field(default_factory=dict) + + +@dataclass +class SharedSignal: + """DSL sketch for a shared telemetry/policy signal between devices.""" + id: str + source_id: str + target_id: str + signals: Dict[str, Any] = field(default_factory=dict) + version: int = 0 + + +def to_object(lp: LocalDevicePlan) -> Object: + """Convert a LocalDevicePlan to a canonical Object.""" + return Object(id=lp.id, type=lp.asset_type, properties=lp.capabilities) + + +def to_morphism(sig: SharedSignal) -> Morphism: + """Convert a SharedSignal to a canonical Morphism.""" + return Morphism(id=sig.id, source=sig.source_id, target=sig.target_id, signals=sig.signals, version=sig.version) + + +def to_plan_delta(delta_id: str, islanded: bool, actions: List[Dict[str, Any]] | None = None) -> PlanDelta: + """Create a PlanDelta from high-level islanding actions.""" + actions = actions or [] + return PlanDelta(delta_id=delta_id, islanded=islanded, actions=actions, tags={}) diff --git a/src/gridresilience_studio/energi_bridge.py b/src/gridresilience_studio/energi_bridge.py index 909fd35..0a802b3 100644 --- a/src/gridresilience_studio/energi_bridge.py +++ b/src/gridresilience_studio/energi_bridge.py @@ -51,3 +51,12 @@ class EnergiBridge: __all__ = ["LocalProblem", "SharedSignals", "EnergiBridge"] + +# Lightweight extension hooks for DSL sketch interoperability +# These helpers enable easy mapping from the DSL sketch defined in +# dsl_sketch.py to canonical primitives used by the runtime. +try: + from .dsl_sketch import LocalDevicePlan, SharedSignal # type: ignore +except Exception: + LocalDevicePlan = None # type: ignore + SharedSignal = None # type: ignore