From b051fc81aeecde73415f465f8ffd7a271c0c4143 Mon Sep 17 00:00:00 2001 From: agent-23e5c897f40fd19e Date: Wed, 15 Apr 2026 22:18:54 +0200 Subject: [PATCH] build(agent): molt-y#23e5c8 iteration --- README.md | 24 +++---- gridverse/__init__.py | 16 +++-- gridverse/adapter_marketplace/__init__.py | 15 ++++ .../building_heating_adapter.py | 16 +++++ .../der_controller_adapter.py | 19 +++++ gridverse/core_contracts.py | 71 +++++++++++++++++++ gridverse/registry.py | 53 +++++++++++--- pyproject.toml | 22 ++---- test.sh | 7 +- tests/test_contracts.py | 37 ++++++++++ tests/test_registry.py | 22 +++--- 11 files changed, 243 insertions(+), 59 deletions(-) create mode 100644 gridverse/adapter_marketplace/__init__.py create mode 100644 gridverse/adapter_marketplace/building_heating_adapter.py create mode 100644 gridverse/adapter_marketplace/der_controller_adapter.py create mode 100644 gridverse/core_contracts.py create mode 100644 tests/test_contracts.py diff --git a/README.md b/README.md index 195b13d..6c38bd3 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,15 @@ -# GridVerse MVP Scaffold +# GridVerse MVP -This repository contains a minimal MVP scaffold for a cross-domain energy optimization platform inspired by the GridVerse vision. It provides a canonical graph-contract registry, a marketplace skeleton for adapters, a lightweight solver, and delta-sync primitives to enable offline/partitioned operation. +A minimal, testable MVP scaffold for a cross-domain energy optimization platform with: +- Local problems (Objects), shared signals (Morphisms), and adapters (Functors) +- A small graph-contract registry to version contracts and schemas +- Starter adapters for DER controllers and building heating, with a toy ADMM-lite solver placeholder +- Lightweight delta-sync primitives and governance-ish metadata scaffolding -What's included -- In-memory ContractRegistry to version and validate contracts (Objects, Morphisms, PlanDelta, etc.). -- Starter adapters (DERAdapter, HeatingAdapter) implementing a simple adapt() interface to demonstrate the Edge-to-Canonical translation path. -- Tiny ADMM-like solver (gridverse.solver.admm_solve) suitable for wiring into a larger distributed stack. -- Delta-sync primitive (gridverse.delta_sync.reconcile) for merging divergent states. -- Tests validating basic wiring and interfaces (tests/test_basic.py). +This repository is intended as a stepping stone toward a CatOpt-style interoperability bridge and a broader MVP as described in the project brief. -How to run -- Install dependencies via pyproject.toml (requires setuptools, wheel). +Usage - Run tests: bash test.sh -- Build package: python -m build +- Package: python -m build -This MVP is intentionally small but designed to be extended into a full Graph-of-Contracts and Adapter Marketplace MVP over subsequent iterations. - -License: MIT (example) +Note: This is intentionally minimal. It is designed to be extended with real adapters, TLS transport, and a fuller solver in subsequent iterations. diff --git a/gridverse/__init__.py b/gridverse/__init__.py index e871980..a53d517 100644 --- a/gridverse/__init__.py +++ b/gridverse/__init__.py @@ -1,9 +1,13 @@ -"""GridVerse MVP package root (minimal stubs for tests). -""" +"""GridVerse MVP namespace initialization.""" + +from .core_contracts import LocalProblem, SharedVariables, PlanDelta, ConstraintSet, DeviceInfo +from .registry import GraphContractRegistry __all__ = [ - "registry", - "adapter_marketplace", - "solver", - "delta_sync", + "LocalProblem", + "SharedVariables", + "PlanDelta", + "ConstraintSet", + "DeviceInfo", + "GraphContractRegistry", ] diff --git a/gridverse/adapter_marketplace/__init__.py b/gridverse/adapter_marketplace/__init__.py new file mode 100644 index 0000000..293ff6e --- /dev/null +++ b/gridverse/adapter_marketplace/__init__.py @@ -0,0 +1,15 @@ +class DERAdapter: + def adapt(self, lp: dict) -> dict: + # Minimal translation: wrap input as adapted payload + return {"adapted": lp} + + def contract(self) -> dict: + return {"name": "DERAdapter", "version": "0.1.0"} + + +class HeatingAdapter: + def adapt(self, lp: dict) -> dict: + return {"adapted": lp} + + def contract(self) -> dict: + return {"name": "HeatingAdapter", "version": "0.1.0"} diff --git a/gridverse/adapter_marketplace/building_heating_adapter.py b/gridverse/adapter_marketplace/building_heating_adapter.py new file mode 100644 index 0000000..8146275 --- /dev/null +++ b/gridverse/adapter_marketplace/building_heating_adapter.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from gridverse.core_contracts import LocalProblem, SharedVariables + + +class BuildingHeatingAdapter: + """Starter building heating controller adapter (toy implementation).""" + + name = "BuildingHeater" + version = "0.1" + + def adapt(self, problem: LocalProblem) -> SharedVariables: + # Toy: emit a simple slack variable for heating demand + current_temp = problem.variables.get("indoor_temp", 20) + signals = {"heating_delta": max(0.0, 22.0 - current_temp)} + return SharedVariables(signals=signals, version=1) diff --git a/gridverse/adapter_marketplace/der_controller_adapter.py b/gridverse/adapter_marketplace/der_controller_adapter.py new file mode 100644 index 0000000..bc17413 --- /dev/null +++ b/gridverse/adapter_marketplace/der_controller_adapter.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from gridverse.core_contracts import LocalProblem, SharedVariables + + +class DERControllerAdapter: + """Starter DER controller adapter (toy implementation). + + Translates a LocalProblem into a SharedVariables payload as a placeholder + for a real device-specific adapter. + """ + + name = "DERController" + version = "0.1" + + def adapt(self, problem: LocalProblem) -> SharedVariables: + # Toy: echo back a single shared signal representing available power margin + signals = {"available_power": max(0.0, 1.0 - len(problem.variables))} + return SharedVariables(signals=signals, version=1) diff --git a/gridverse/core_contracts.py b/gridverse/core_contracts.py new file mode 100644 index 0000000..c870623 --- /dev/null +++ b/gridverse/core_contracts.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from dataclasses import dataclass, asdict +from typing import Any, Dict, Optional + + +@dataclass +class LocalProblem: + site_id: str + description: str + variables: Dict[str, Any] + + def to_dict(self) -> Dict[str, Any]: + return asdict(self) + + @staticmethod + def from_dict(d: Dict[str, Any]) -> "LocalProblem": + return LocalProblem(site_id=d["site_id"], description=d["description"], variables=d.get("variables", {})) + + +@dataclass +class SharedVariables: + signals: Dict[str, Any] + version: int = 0 + + def to_dict(self) -> Dict[str, Any]: + return asdict(self) + + @staticmethod + def from_dict(d: Dict[str, Any]) -> "SharedVariables": + return SharedVariables(signals=d.get("signals", {}), version=d.get("version", 0)) + + +@dataclass +class PlanDelta: + delta_id: str + changes: Dict[str, Any] + timestamp: float + + def to_dict(self) -> Dict[str, Any]: + return asdict(self) + + @staticmethod + def from_dict(d: Dict[str, Any]) -> "PlanDelta": + return PlanDelta(delta_id=d["delta_id"], changes=d.get("changes", {}), timestamp=d.get("timestamp", 0.0)) + + +@dataclass +class ConstraintSet: + constraints: Dict[str, Any] + + def to_dict(self) -> Dict[str, Any]: + return asdict(self) + + @staticmethod + def from_dict(d: Dict[str, Any]) -> "ConstraintSet": + return ConstraintSet(constraints=d.get("constraints", {})) + + +@dataclass +class DeviceInfo: + device_id: str + device_type: str + capabilities: Dict[str, Any] + + def to_dict(self) -> Dict[str, Any]: + return asdict(self) + + @staticmethod + def from_dict(d: Dict[str, Any]) -> "DeviceInfo": + return DeviceInfo(device_id=d["device_id"], device_type=d["device_type"], capabilities=d.get("capabilities", {})) diff --git a/gridverse/registry.py b/gridverse/registry.py index a0c8dee..d24b572 100644 --- a/gridverse/registry.py +++ b/gridverse/registry.py @@ -1,11 +1,46 @@ -class ContractRegistry: - def __init__(self): - # store contracts as {(name, version): contract_dict} - self._store = {} +from __future__ import annotations - def register_contract(self, name: str, version: str, contract: dict): - key = (name, version) - self._store[key] = contract +from typing import Any, Dict, Tuple - def get_contract(self, name: str, version: str): - return self._store.get((name, version)) +from .core_contracts import LocalProblem, SharedVariables, PlanDelta, ConstraintSet, DeviceInfo + + +class GraphContractRegistry: + """Lightweight in-memory registry for contracts and adapters. + + This is a minimal scaffold to support MVP development of a graph-contract + oriented interoperability bridge. It stores basic contract schemas and + adapter interface descriptors, plus a simple conformance test harness. + """ + + def __init__(self) -> None: + self._contracts: Dict[str, Dict[str, Any]] = {} + self._adapters: Dict[str, Dict[str, Any]] = {} + + # Contracts + def register_contract(self, name: str, version: str, schema: Dict[str, Any]) -> None: + self._contracts.setdefault(name, {})[version] = schema + + def get_contract(self, name: str, version: str) -> Dict[str, Any]: + return self._contracts.get(name, {}).get(version, {}) + + # Adapters + def register_adapter(self, name: str, version: str, iface: Dict[str, Any]) -> None: + self._adapters.setdefault(name, {})[version] = iface + + def get_adapter(self, name: str, version: str) -> Dict[str, Any]: + return self._adapters.get(name, {}).get(version, {}) + + # Conformance harness (stub) + def conformance_test(self, adapter_iface: Dict[str, Any], contract_schema: Dict[str, Any]) -> bool: + # Minimal check: ensure required keys exist in both sides + required_adapter_keys = {"name", "version", "interface"} + required_contract_keys = {"name", "version", "schema"} + has_adapter = required_adapter_keys.issubset(set(adapter_iface.keys())) + has_contract = required_contract_keys.issubset(set(contract_schema.keys())) + return bool(has_adapter and has_contract) + +# Backwards-compatibility alias for older code/tests +# Some clients import ContractRegistry from gridverse.registry. +# Expose a stable name that maps to the new GraphContractRegistry implementation. +ContractRegistry = GraphContractRegistry diff --git a/pyproject.toml b/pyproject.toml index 6116489..672e3c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,22 +1,12 @@ [build-system] -requires = ["setuptools", "wheel"] +requires = ["setuptools>=42","wheel"] build-backend = "setuptools.build_meta" [project] -name = "gridverse-core" +name = "gridverse-mvp" version = "0.1.0" -description = "Minimal MVP scaffold for GridVerse cross-domain optimization" +description = "GridVerse MVP: cross-domain energy optimization core with graph-contract registry and adapter marketplace" +readme = "README.md" requires-python = ">=3.8" -dependencies = [ - "numpy>=1.26.0", - "pytest>=8.0.0", -] - -[tool.setuptools.packages.find] -where = ["."] -exclude = ["tests"] - -[project.urls] -homepage = "https://example.com/gridverse" - - +license = {text = "MIT"} +dependencies = ["numpy"] diff --git a/test.sh b/test.sh index 45132f6..0585a21 100644 --- a/test.sh +++ b/test.sh @@ -1,8 +1,7 @@ #!/usr/bin/env bash set -euo pipefail - -echo "Running GridVerse MVP tests..." +echo "Running tests..." pytest -q -echo "Running packaging check..." +echo "Building package..." python -m build -echo "All tests and build completed successfully." +echo "All tests passed and package built successfully." diff --git a/tests/test_contracts.py b/tests/test_contracts.py new file mode 100644 index 0000000..5b98916 --- /dev/null +++ b/tests/test_contracts.py @@ -0,0 +1,37 @@ +from gridverse.core_contracts import LocalProblem, SharedVariables, PlanDelta, ConstraintSet, DeviceInfo + + +def test_local_problem_roundtrip(): + lp = LocalProblem(site_id="site-1", description="test", variables={"key": "value"}) + d = lp.to_dict() + assert d["site_id"] == "site-1" + lp2 = LocalProblem.from_dict(d) + assert lp2.site_id == lp.site_id + + +def test_shared_variables_roundtrip(): + sv = SharedVariables(signals={"p": 1.0}, version=2) + d = sv.to_dict() + sv2 = SharedVariables.from_dict(d) + assert sv2.signals == sv.signals and sv2.version == sv.version + + +def test_plan_delta_roundtrip(): + pd = PlanDelta(delta_id="d1", changes={"a": 1}, timestamp=1.23) + d = pd.to_dict() + pd2 = PlanDelta.from_dict(d) + assert pd2.delta_id == pd.delta_id and pd2.timestamp == pd.timestamp + + +def test_constraint_set_roundtrip(): + cs = ConstraintSet(constraints={"mesh": True}) + d = cs.to_dict() + cs2 = ConstraintSet.from_dict(d) + assert cs2.constraints["mesh"] is True + + +def test_device_info_roundtrip(): + di = DeviceInfo(device_id="dev-1", device_type="inverter", capabilities={"cap": 10}) + d = di.to_dict() + di2 = DeviceInfo.from_dict(d) + assert di2.device_id == di.device_id and di2.device_type == di.device_type diff --git a/tests/test_registry.py b/tests/test_registry.py index fb4e803..f29881f 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -1,12 +1,14 @@ -from gridverse_open_low_code_platform_for_cro.registry import ContractRegistry +import math +from gridverse.registry import GraphContractRegistry +from gridverse.core_contracts import LocalProblem, SharedVariables -def test_registry_register_and_get(): - reg = ContractRegistry() - contract = { - "name": "LocalProblemContract", - "version": "0.1.0", - "schema": {"type": "object", "properties": {"id": {"type": "string"}}}, - } - reg.register("local_problem", contract) - assert reg.get("local_problem")["name"] == "LocalProblemContract" +def test_registry_basic_conformance(): + reg = GraphContractRegistry() + reg.register_contract("LocalProblem", "0.1", {"type": "object"}) + reg.register_adapter("DERController", "0.1", {"name": "DERController", "interface": {}}) + + adapter_iface = {"name": "DERController", "version": "0.1", "interface": {}} + contract_schema = {"name": "LocalProblem", "version": "0.1", "schema": {"type": "object"}} + + assert reg.conformance_test(adapter_iface, contract_schema) is True