diff --git a/README.md b/README.md index 6639530..57013ce 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,11 @@ -# CommonsGrid: Community-Managed, Privacy-Preserving Energy Commons +This repository holds a production-oriented prototype for CommonsGrid: a community-managed, privacy-preserving energy marketplace designed for neighborhood-scale pilots. It includes a lightweight DSL sketch and toy adapters to bootstrap interoperability with a CatOpt-like intermediate representation (IR). -Overview -- A neighborhood-scale energy commons platform enabling residents to co-create and govern an energy marketplace using local PV, storage, EVs, and flexible loads. -- Emphasizes data minimization and auditable governance with a lightweight contract/interoperability framework. +Key components added in this patch: +- idea165_commonsgrid_community_managed/dsl.py: Minimal DataClass-based DSL for LocalProblem, SharedSignals, PlanDelta, PrivacyBudget, and AuditLog. +- idea165_commonsgrid_community_managed/adapters/: Two starter adapters (InverterControllerAdapter and NeighborhoodBatteryAdapter) that map signals to the canonical LocalProblem representation. -Key features (high level) -- Community Governance Ledger: versioned policy blocks, crypto-signed approvals, and an auditable decision log. -- Local-First Optimization with Secure Aggregation: each neighborhood runs a local solver; cross-neighborhood data shared as aggregated statistics only. -- Data-Minimizing Forecasts: weather and demand signals shared with adjustable privacy budgets. -- Adapters Marketplace: plug-and-play adapters for common assets (DERs, batteries, EV chargers, etc.). -- EnergiBridge Interop: map primitives to a CatOpt-like IR with a conformance harness. -- Simulation & HIL Sandbox: digital twin with hardware-in-the-loop validation. +How to use: +- Instantiate adapters and call map_to_local_problem to generate a dictionary representation that can be consumed by the interoperability bridge. -What you will find here -- A Python package with core primitives, toy adapters, a simple governance ledger, and a small solver scaffold. -- Lightweight tests validating governance and adapter mappings. -- A minimal README linking to packaging metadata and test commands. - -Usage -- Run tests: ./test.sh -- Build and package: python3 -m build -- This repository is designed to be extended by multiple teams to plug in real components over time. - -Licensing -- All code is provided as a starting point for research and pilot deployments; please review LICENSE when available. - -See also: AGENTS.md for contributor guidelines. +This is a seed for a production-grade MVP; further work should flesh out governance ledger, secure aggregation, and interop contracts. +""" diff --git a/idea165_commonsgrid_community_managed/__init__.py b/idea165_commonsgrid_community_managed/__init__.py index 2af6a88..c8f144c 100644 --- a/idea165_commonsgrid_community_managed/__init__.py +++ b/idea165_commonsgrid_community_managed/__init__.py @@ -1,31 +1,9 @@ -"""Idea165 CommonsGrid – Community-Managed, Privacy-Preserving Energy Commons (minimal core). - -This package provides a small, well-typed core that can be used to bootstrap a larger project. -It includes: -- GovernanceLedger: versioned, signed policy blocks with an auditable log -- LocalProblem: neighborhood energy representation -- Adapters: base adapter and two toy adapters -- EnergiBridge: lightweight IR mapping between commons primitives and a CatOpt-like representation -- Simulator: tiny dispatcher that operates on the primitives """ +Core package for CommonsGrid community-managed prototype. +This package includes lightweight DSL sketches and toy adapters +to bootstrap interoperability with a CatOpt-like IR. +""" +from . import dsl as dsl +from . import adapters as adapters -from .governance import GovernanceLedger -from .models import LocalProblem -from .adapters import BaseAdapter, DERAdapter, BatteryAdapter, InverterControllerAdapter, NeighborhoodBatteryAdapter -from .energi_bridge import EnergiBridge, IRBlock -from .simulator import Simulator -from .privacy import PrivacyBudget - -__all__ = [ - "GovernanceLedger", - "LocalProblem", - "BaseAdapter", - "DERAdapter", - "BatteryAdapter", - "InverterControllerAdapter", - "NeighborhoodBatteryAdapter", - "EnergiBridge", - "IRBlock", - "Simulator", - "PrivacyBudget", -] +__all__ = ["dsl", "adapters"] diff --git a/idea165_commonsgrid_community_managed/adapters/__init__.py b/idea165_commonsgrid_community_managed/adapters/__init__.py new file mode 100644 index 0000000..4e2bf12 --- /dev/null +++ b/idea165_commonsgrid_community_managed/adapters/__init__.py @@ -0,0 +1,11 @@ +from .inverter_adapter import InverterControllerAdapter +from .neighborhood_battery_adapter import NeighborhoodBatteryAdapter +from .der_adapter import DERAdapter +from .battery_adapter import BatteryAdapter + +__all__ = [ + "InverterControllerAdapter", + "NeighborhoodBatteryAdapter", + "DERAdapter", + "BatteryAdapter", +] diff --git a/idea165_commonsgrid_community_managed/adapters/battery_adapter.py b/idea165_commonsgrid_community_managed/adapters/battery_adapter.py new file mode 100644 index 0000000..07ebc70 --- /dev/null +++ b/idea165_commonsgrid_community_managed/adapters/battery_adapter.py @@ -0,0 +1,15 @@ +from typing import Dict, Any +from ..models import LocalProblem + + +class BatteryAdapter: + """Tiny adapter converting LocalProblem into a Battery-shared representation.""" + def to_shared(self, lp: LocalProblem) -> Dict[str, Any]: + return { + "type": "Battery", + "neighborhood_id": getattr(lp, "neighborhood_id", None), + "demand_kw": getattr(lp, "demand_kw", 0.0), + "pv_kw": getattr(lp, "pv_kw", 0.0), + "storage_kwh": getattr(lp, "storage_kwh", 0.0), + "evs": getattr(lp, "evs", 0), + } diff --git a/idea165_commonsgrid_community_managed/adapters/der_adapter.py b/idea165_commonsgrid_community_managed/adapters/der_adapter.py new file mode 100644 index 0000000..9f27c75 --- /dev/null +++ b/idea165_commonsgrid_community_managed/adapters/der_adapter.py @@ -0,0 +1,15 @@ +from typing import Dict, Any +from ..models import LocalProblem + + +class DERAdapter: + """Tiny adapter converting LocalProblem into a DER-shared representation.""" + def to_shared(self, lp: LocalProblem) -> Dict[str, Any]: + return { + "type": "DER", + "neighborhood_id": getattr(lp, "neighborhood_id", None), + "demand_kw": getattr(lp, "demand_kw", 0.0), + "pv_kw": getattr(lp, "pv_kw", 0.0), + "storage_kwh": getattr(lp, "storage_kwh", 0.0), + "evs": getattr(lp, "evs", 0), + } diff --git a/idea165_commonsgrid_community_managed/adapters/inverter_adapter.py b/idea165_commonsgrid_community_managed/adapters/inverter_adapter.py new file mode 100644 index 0000000..836742e --- /dev/null +++ b/idea165_commonsgrid_community_managed/adapters/inverter_adapter.py @@ -0,0 +1,19 @@ +from typing import Dict, Any +from ..dsl import LocalProblem + +class InverterControllerAdapter: + """ + Toy adapter that maps rooftop PV inverter signals to a canonical LocalProblem delta. + """ + def __init__(self, neighborhood_id: str): + self.neighborhood_id = neighborhood_id + + def map_to_local_problem(self, pv_output_kw: float, demand_kw: float) -> Dict[str, Any]: + lp = LocalProblem( + neighborhood_id=self.neighborhood_id, + objective="minimize_cost", + data_sources=["pv_inverter"], + constraints={"pv_output_kw": pv_output_kw, "demand_kw": demand_kw}, + ) + # Simple LocalProblem dictionary representation + return lp.to_dict() diff --git a/idea165_commonsgrid_community_managed/adapters/neighborhood_battery_adapter.py b/idea165_commonsgrid_community_managed/adapters/neighborhood_battery_adapter.py new file mode 100644 index 0000000..390f25f --- /dev/null +++ b/idea165_commonsgrid_community_managed/adapters/neighborhood_battery_adapter.py @@ -0,0 +1,18 @@ +from typing import Dict, Any +from ..dsl import LocalProblem + +class NeighborhoodBatteryAdapter: + """ + Toy adapter for a neighborhood battery manager. + """ + def __init__(self, neighborhood_id: str): + self.neighborhood_id = neighborhood_id + + def map_to_local_problem(self, soc: float, capacity_kwh: float) -> Dict[str, Any]: + lp = LocalProblem( + neighborhood_id=self.neighborhood_id, + objective="minimize_cost", + data_sources=["neighborhood_battery"], + constraints={"state_of_charge": soc, "capacity_kwh": capacity_kwh}, + ) + return lp.to_dict() diff --git a/idea165_commonsgrid_community_managed/dsl.py b/idea165_commonsgrid_community_managed/dsl.py index 19dc569..9f51aee 100644 --- a/idea165_commonsgrid_community_managed/dsl.py +++ b/idea165_commonsgrid_community_managed/dsl.py @@ -1,71 +1,58 @@ -"""DSL Sketches for CommonsGrid core primitives. - -This module provides lightweight dataclass-backed DSL primitives that sketch -the intended canonical representations used for CommunityPolicy, LocalProblem, -SharedSignals, PlanDelta, PrivacyBudget, and AuditLog blocks. - -These are intentionally small and opinionated scaffolds to bootstrap a DSL -without pulling in heavy dependencies. They can be extended to integrate with the -GovernanceLedger and EnergiBridge as the project matures. -""" - from __future__ import annotations - from dataclasses import dataclass, field -from typing import Dict, Any, Optional - +from typing import Any, Dict, List, Optional @dataclass -class LocalProblemDSL: +class LocalProblem: neighborhood_id: str - demand_kw: float - pv_kw: float - storage_kwh: float - metadata: Dict[str, float] = field(default_factory=dict) - - def to_model(self) -> "LocalProblem": - # Import locally to avoid a hard coupling at import time - from .models import LocalProblem - return LocalProblem( - neighborhood_id=self.neighborhood_id, - demand_kw=self.demand_kw, - pv_kw=self.pv_kw, - storage_kwh=self.storage_kwh, - evs=0, - metadata=self.metadata, - ) + objective: str = "minimize_cost" + data_sources: List[str] = field(default_factory=list) + constraints: Dict[str, Any] = field(default_factory=dict) + def to_dict(self) -> Dict[str, Any]: + return { + "type": "LocalProblem", + "neighborhood_id": self.neighborhood_id, + "objective": self.objective, + "data_sources": self.data_sources, + "constraints": self.constraints, + } @dataclass -class SharedSignalsDSL: - weather_forecast: Dict[str, float] = field(default_factory=dict) - demand_signals: Dict[str, float] = field(default_factory=dict) +class SharedSignals: + signals: Dict[str, Any] = field(default_factory=dict) + privacy_budget: Optional[float] = None + def to_dict(self) -> Dict[str, Any]: + return { + "type": "SharedSignals", + "signals": self.signals, + "privacy_budget": self.privacy_budget, + } @dataclass -class PlanDeltaDSL: - delta_kw: float - delta_pv: float - description: Optional[str] = None - - -@dataclass -class PrivacyBudgetDSL: - total_budget: float - spent: float = 0.0 - - def spend(self, amount: float) -> bool: - if self.spent + amount > self.total_budget: - return False - self.spent += amount - return True - - def remaining(self) -> float: - return max(self.total_budget - self.spent, 0.0) - - -@dataclass -class AuditLogBlock: - entry: str +class PlanDelta: timestamp: float - block_hash: str + delta: Dict[str, Any] + + def to_dict(self) -> Dict[str, Any]: + return {"type": "PlanDelta", "timestamp": self.timestamp, "delta": self.delta} + +@dataclass +class PrivacyBudget: + signal: str + budget: float + + def to_dict(self) -> Dict[str, Any]: + return {"type": "PrivacyBudget", "signal": self.signal, "budget": self.budget} + +@dataclass +class AuditLog: + entries: List[Dict[str, Any]] = field(default_factory=list) + def add(self, entry: Dict[str, Any]) -> None: + self.entries.append(entry) + + def to_dict(self) -> Dict[str, Any]: + return {"type": "AuditLog", "entries": self.entries} + +__all__ = ["LocalProblem", "SharedSignals", "PlanDelta", "PrivacyBudget", "AuditLog"]