build(agent): new-agents-3#dd492b iteration
This commit is contained in:
parent
7cfae65f0c
commit
3eb87a9f94
|
|
@ -9,6 +9,12 @@ Offline-first cross-domain orchestration for disaster-resilient grids.
|
||||||
- Simulation and hardware-in-the-loop testing with KPI dashboards.
|
- Simulation and hardware-in-the-loop testing with KPI dashboards.
|
||||||
- Governance ledger and event sourcing for auditability.
|
- Governance ledger and event sourcing for auditability.
|
||||||
|
|
||||||
|
### EnergiBridge Enhancements
|
||||||
|
- Introduced EnergiBridge extensions to map GridResilience primitives (LocalProblem/LocalDevicePlans, SharedSignals, PlanDelta) to a vendor-agnostic, cross-domain representation and back.
|
||||||
|
- Added deterministic delta reconciliation (reconcile_deltas) to merge islanding/load-shedding updates across partitions while preserving cryptographic tags and metadata.
|
||||||
|
- Added tests validating object/morphism mapping and delta reconciliation to ensure offline-first consistency.
|
||||||
|
- Kept the surface lightweight and dependency-free for rapid integration with existing adapters.
|
||||||
|
|
||||||
Project structure and packaging
|
Project structure and packaging
|
||||||
- Python-based core with adapters under src/gridresilience_studio/adapters.
|
- Python-based core with adapters under src/gridresilience_studio/adapters.
|
||||||
- Core primitives live in src/gridresilience_studio/core.py.
|
- Core primitives live in src/gridresilience_studio/core.py.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ tests fast and focused on integration surface.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from .core import Object, Morphism, PlanDelta
|
from .core import Object, Morphism, PlanDelta
|
||||||
|
|
||||||
|
|
@ -49,6 +49,74 @@ class EnergiBridge:
|
||||||
"""Identity mapping for PlanDelta (pass-through in this sketch)."""
|
"""Identity mapping for PlanDelta (pass-through in this sketch)."""
|
||||||
return delta
|
return delta
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
# Cross-domain canonical representations (GoC-style)
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def to_goc_object(obj: Object) -> Dict[str, Any]:
|
||||||
|
"""Translate a GridResilience Object into a simple GoC payload."""
|
||||||
|
return {
|
||||||
|
"type": "Object",
|
||||||
|
"id": obj.id,
|
||||||
|
"class": obj.type,
|
||||||
|
"properties": obj.properties,
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_goc_morphism(morph: Morphism) -> Dict[str, Any]:
|
||||||
|
"""Translate a GridResilience Morphism into a simple GoC payload."""
|
||||||
|
return {
|
||||||
|
"type": "Morphism",
|
||||||
|
"id": morph.id,
|
||||||
|
"source": morph.source,
|
||||||
|
"target": morph.target,
|
||||||
|
"signals": morph.signals,
|
||||||
|
"version": morph.version,
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def reconcile_deltas(local_deltas: List[PlanDelta], remote_deltas: List[PlanDelta]) -> List[PlanDelta]:
|
||||||
|
"""Deterministically reconcile two sets of PlanDelta objects.
|
||||||
|
|
||||||
|
- Delta IDs are used as the canonical key.
|
||||||
|
- Actions from both sides are merged (without duplicating identical actions).
|
||||||
|
- islanded flag is OR'ed; tags are merged shallowly.
|
||||||
|
- Returns a new list of PlanDelta instances representing the merged view.
|
||||||
|
"""
|
||||||
|
merged: dict[str, PlanDelta] = {}
|
||||||
|
|
||||||
|
# seed with local deltas
|
||||||
|
for d in local_deltas:
|
||||||
|
merged[d.delta_id] = PlanDelta(
|
||||||
|
delta_id=d.delta_id,
|
||||||
|
islanded=d.islanded,
|
||||||
|
actions=list(d.actions),
|
||||||
|
tags=dict(d.tags),
|
||||||
|
)
|
||||||
|
|
||||||
|
# merge remote deltas
|
||||||
|
for d in remote_deltas:
|
||||||
|
if d.delta_id in merged:
|
||||||
|
base = merged[d.delta_id]
|
||||||
|
# merge actions without duplicates
|
||||||
|
existing = list(base.actions)
|
||||||
|
for act in d.actions:
|
||||||
|
if act not in existing:
|
||||||
|
existing.append(act)
|
||||||
|
base.actions = existing
|
||||||
|
base.islanded = base.islanded or d.islanded
|
||||||
|
# shallow merge of tags
|
||||||
|
base.tags = {**base.tags, **d.tags}
|
||||||
|
else:
|
||||||
|
merged[d.delta_id] = PlanDelta(
|
||||||
|
delta_id=d.delta_id,
|
||||||
|
islanded=d.islanded,
|
||||||
|
actions=list(d.actions),
|
||||||
|
tags=dict(d.tags),
|
||||||
|
)
|
||||||
|
|
||||||
|
return list(merged.values())
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["LocalProblem", "SharedSignals", "EnergiBridge"]
|
__all__ = ["LocalProblem", "SharedSignals", "EnergiBridge"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from gridresilience_studio.energi_bridge import EnergiBridge, LocalProblem, SharedSignals
|
||||||
|
from gridresilience_studio.core import Object, Morphism, PlanDelta
|
||||||
|
|
||||||
|
|
||||||
|
def test_goC_object_mapping():
|
||||||
|
lp = LocalProblem(id="LP1", description="Test LocalProblem", resources={"foo": "bar"})
|
||||||
|
obj = EnergiBridge.map_object(lp)
|
||||||
|
assert isinstance(obj, Object)
|
||||||
|
goc = EnergiBridge.to_goc_object(obj)
|
||||||
|
assert goc["type"] == "Object"
|
||||||
|
assert goc["id"] == "LP1"
|
||||||
|
assert goc["class"] == "LocalProblem" or goc["class"] == obj.type
|
||||||
|
assert isinstance(goc["properties"], dict)
|
||||||
|
|
||||||
|
|
||||||
|
def test_goC_morphism_mapping():
|
||||||
|
sig = SharedSignals(id="SIG1", source="LP1", target="LP2", signals={"voltage": 1.0})
|
||||||
|
morph = EnergiBridge.map_signals(sig)
|
||||||
|
assert isinstance(morph, Morphism)
|
||||||
|
gocm = EnergiBridge.to_goc_morphism(morph)
|
||||||
|
assert gocm["type"] == "Morphism"
|
||||||
|
assert gocm["id"] == "SIG1"
|
||||||
|
assert gocm["source"] == "LP1"
|
||||||
|
assert gocm["signals"] == {"voltage": 1.0}
|
||||||
|
|
||||||
|
|
||||||
|
def test_delta_reconciliation():
|
||||||
|
d_local = PlanDelta(delta_id="D1", islanded=False, actions=[{"type": "noop"}], tags={})
|
||||||
|
d_remote = PlanDelta(delta_id="D1", islanded=True, actions=[{"type": "toggle"}], tags={"t": "v1"})
|
||||||
|
merged = EnergiBridge.reconcile_deltas([d_local], [d_remote])
|
||||||
|
assert len(merged) == 1
|
||||||
|
merged_delta = merged[0]
|
||||||
|
assert merged_delta.delta_id == "D1"
|
||||||
|
# both actions should be merged without duplicates
|
||||||
|
actions = merged_delta.actions
|
||||||
|
assert any(a.get("type") == "noop" for a in actions)
|
||||||
|
assert any(a.get("type") == "toggle" for a in actions)
|
||||||
|
# islanded should be True due to OR
|
||||||
|
assert merged_delta.islanded is True
|
||||||
Loading…
Reference in New Issue