build(agent): molt-az#4b796a iteration
This commit is contained in:
parent
492b4a8e9a
commit
a5a0706dbf
|
|
@ -0,0 +1,21 @@
|
||||||
|
node_modules/
|
||||||
|
.npmrc
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
__tests__/
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.cache/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
tmp/
|
||||||
|
.tmp/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
*.egg-info/
|
||||||
|
.pytest_cache/
|
||||||
|
READY_TO_PUBLISH
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
# SunHub Agent Overview
|
||||||
|
|
||||||
|
Architecture
|
||||||
|
- SunHub is a modular, city-scale solar siting and DER coordination platform.
|
||||||
|
- Core abstractions:
|
||||||
|
- Object: a neighborhood-level optimization target (Object in a city may map to a block or district).
|
||||||
|
- SharedSignal: aggregated signals used for cross-neighborhood coordination (e.g., forecasted irradiance, storage state).
|
||||||
|
- PlanDelta: incremental actions to advance planning (delta updates to per-neighborhood plans).
|
||||||
|
- GraphOfContracts: registry for adapters to external systems (GIS, weather, DER controllers).
|
||||||
|
- Lightweight federation: simplified ADMM-lite loop coordinating neighborhood plans while preserving locality.
|
||||||
|
|
||||||
|
Tech Stack (in this repo)
|
||||||
|
- Python 3.10+ with setuptools-based packaging (pyproject.toml).
|
||||||
|
- Tests powered by pytest.
|
||||||
|
- Lightweight in-memory data models for MVP; pluggable adapters via GraphOfContracts.
|
||||||
|
|
||||||
|
Testing and Commands
|
||||||
|
- Run tests: `bash test.sh` in repo root. This also builds the package (`python3 -m build`).
|
||||||
|
- Local development: import sunhub.core modules and run minimal scenarios via `pytest` tests.
|
||||||
|
|
||||||
|
Contribution Rules
|
||||||
|
- Use the AGENTS.md as a jump-off for new agents; implement features in small, well-scoped patches.
|
||||||
|
- All changes must pass tests before PRs; avoid breaking existing contracts in the in-repo API.
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- This document will be updated as the project evolves and new agents are introduced.
|
||||||
27
README.md
27
README.md
|
|
@ -1,3 +1,26 @@
|
||||||
# sunhub-open-city-scale-solar-siting-and-
|
# SunHub Open City-Scale Solar Siting and Dispatch Planner
|
||||||
|
|
||||||
A novel, open-source software platform to optimize solar siting, rooftop solar deployment, community solar integration, and distributed storage across a city. SunHub uses a canonical, CatOpt-inspired data model to map local neighborhood optimization
|
SunHub is an open-source platform for optimizing solar siting, rooftop solar deployment, community solar integration, and distributed storage across a city. This repository contains a minimal MVP scaffold designed to be extended into a full production-grade system.
|
||||||
|
|
||||||
|
Project goals
|
||||||
|
- Provide a canonical data model: Object (neighborhood target), SharedSignal (aggregated signals), PlanDelta (incremental actions).
|
||||||
|
- Offer a pluggable GraphOfContracts for adapters to GIS, weather, DER controllers, and energy storage units.
|
||||||
|
- Implement a lightweight, offline-first, deterministic delta-sync protocol for resilience.
|
||||||
|
- Provide an extensible SDK with sample adapters.
|
||||||
|
|
||||||
|
Structure
|
||||||
|
- src/sunhub/: Core Python package with data models and orchestration skeleton.
|
||||||
|
- tests/: Unit tests for the MVP components.
|
||||||
|
- AGENTS.md: Architecture and contribution guidelines for future agents.
|
||||||
|
- pyproject.toml: Packaging metadata (Python) for a production-ready package.
|
||||||
|
- test.sh: Execute tests and packaging as part of CI checks.
|
||||||
|
- READY_TO_PUBLISH: Marker file created when the repository is ready to publish.
|
||||||
|
|
||||||
|
Getting started
|
||||||
|
- Install Python 3.10+: see pyproject.toml for build requirements.
|
||||||
|
- Run tests: `bash test.sh`.
|
||||||
|
- Run quick demos by importing sunhub.core in Python and constructing a SunHub instance.
|
||||||
|
|
||||||
|
This repository follows a pragmatic, minimal MVP approach. Future iterations will expand the data model, add real adapters, and integrate with a simulated HIL sandbox.
|
||||||
|
|
||||||
|
Note: This README is hooked into packaging metadata once the project is fully wired for publishing.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "sunhub_open_city_scale_solar_siting_and"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Open city-scale solar siting and DER coordination MVP"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://example.com/sunhub"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
package-dir = {"" = "src"}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .core import Object, SharedSignal, PlanDelta, GraphOfContracts, SunHub
|
||||||
|
|
||||||
|
__all__ = ["Object", "SharedSignal", "PlanDelta", "GraphOfContracts", "SunHub"]
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Object:
|
||||||
|
id: str
|
||||||
|
neighborhood: str
|
||||||
|
data: Dict[str, Any]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create(neighborhood: str, data: Dict[str, Any]) -> "Object":
|
||||||
|
return Object(id=str(uuid.uuid4()), neighborhood=neighborhood, data=data)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SharedSignal:
|
||||||
|
id: str
|
||||||
|
signal_type: str
|
||||||
|
value: Any
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create(signal_type: str, value: Any) -> "SharedSignal":
|
||||||
|
return SharedSignal(id=str(uuid.uuid4()), signal_type=signal_type, value=value)
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {"id": self.id, "signal_type": self.signal_type, "value": self.value}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PlanDelta:
|
||||||
|
id: str
|
||||||
|
neighborhood: str
|
||||||
|
actions: List[Dict[str, Any]] = field(default_factory=list)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create(neighborhood: str, actions: List[Dict[str, Any]]) -> "PlanDelta":
|
||||||
|
return PlanDelta(id=str(uuid.uuid4()), neighborhood=neighborhood, actions=actions)
|
||||||
|
|
||||||
|
def apply(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
# Minimal deterministic application: apply key-value updates from actions
|
||||||
|
new_state = dict(state)
|
||||||
|
for act in self.actions:
|
||||||
|
key = act.get("key")
|
||||||
|
value = act.get("value")
|
||||||
|
if key is not None:
|
||||||
|
new_state[key] = value
|
||||||
|
return new_state
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GraphOfContracts:
|
||||||
|
registry: Dict[str, Dict[str, Any]] = field(default_factory=dict)
|
||||||
|
|
||||||
|
def register(self, contract_name: str, adapter: Dict[str, Any]) -> None:
|
||||||
|
self.registry[contract_name] = adapter
|
||||||
|
|
||||||
|
def get(self, contract_name: str) -> Dict[str, Any]:
|
||||||
|
return self.registry.get(contract_name, {})
|
||||||
|
|
||||||
|
|
||||||
|
class SunHub:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.objects: Dict[str, Object] = {}
|
||||||
|
self.shared_signals: Dict[str, SharedSignal] = {}
|
||||||
|
self.plan_deltas: List[PlanDelta] = []
|
||||||
|
self.contracts = GraphOfContracts()
|
||||||
|
|
||||||
|
# Object lifecycle
|
||||||
|
def add_object(self, neighborhood: str, data: Dict[str, Any]) -> Object:
|
||||||
|
obj = Object.create(neighborhood, data)
|
||||||
|
self.objects[obj.id] = obj
|
||||||
|
return obj
|
||||||
|
|
||||||
|
# SharedSignal lifecycle
|
||||||
|
def add_signal(self, signal_type: str, value: Any) -> SharedSignal:
|
||||||
|
sig = SharedSignal.create(signal_type, value)
|
||||||
|
self.shared_signals[sig.id] = sig
|
||||||
|
return sig
|
||||||
|
|
||||||
|
# PlanDelta lifecycle
|
||||||
|
def add_plan_delta(self, neighborhood: str, actions: List[Dict[str, Any]]) -> PlanDelta:
|
||||||
|
delta = PlanDelta.create(neighborhood, actions)
|
||||||
|
self.plan_deltas.append(delta)
|
||||||
|
return delta
|
||||||
|
|
||||||
|
# Simple aggregator applying all deltas
|
||||||
|
def apply_all(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
cur = dict(state)
|
||||||
|
for delta in self.plan_deltas:
|
||||||
|
cur = delta.apply(cur)
|
||||||
|
return cur
|
||||||
|
|
||||||
|
# Registry access
|
||||||
|
def register_adapter(self, name: str, adapter: Dict[str, Any]) -> None:
|
||||||
|
self.contracts.register(name, adapter)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Test script for SunHub MVP
|
||||||
|
# 1) Run pytest tests
|
||||||
|
# 2) Build the Python package to ensure packaging metadata and directory structure compile
|
||||||
|
|
||||||
|
echo "Running test suite..."
|
||||||
|
pytest -q || { echo "Tests failed"; exit 1; }
|
||||||
|
|
||||||
|
echo "Building package (python3 -m build)..."
|
||||||
|
python3 -m build || { echo "Build failed"; exit 1; }
|
||||||
|
|
||||||
|
echo "All tests passed and package built successfully."
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import sys, os
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from sunhub import SunHub
|
||||||
|
from sunhub import Object, SharedSignal, PlanDelta
|
||||||
|
|
||||||
|
|
||||||
|
def test_object_creation_and_storage():
|
||||||
|
hub = SunHub()
|
||||||
|
o = hub.add_object("Downtown", {"pv_capacity_kw": 120})
|
||||||
|
assert o.id in hub.objects
|
||||||
|
assert hub.objects[o.id].neighborhood == "Downtown"
|
||||||
|
|
||||||
|
|
||||||
|
def test_signal_creation():
|
||||||
|
hub = SunHub()
|
||||||
|
s = hub.add_signal("irradiance_forecast", {"hour": 12, "value": 800})
|
||||||
|
assert s.id in hub.shared_signals
|
||||||
|
assert hub.shared_signals[s.id].signal_type == "irradiance_forecast"
|
||||||
|
|
||||||
|
|
||||||
|
def test_plan_delta_application():
|
||||||
|
hub = SunHub()
|
||||||
|
state = {"solar_production_kw": 0}
|
||||||
|
delta = hub.add_plan_delta("Downtown", [{"key": "solar_production_kw", "value": 45}])
|
||||||
|
new_state = delta.apply(state)
|
||||||
|
assert new_state["solar_production_kw"] == 45
|
||||||
|
|
||||||
|
|
||||||
|
def test_offline_delta_sync_roundtrip():
|
||||||
|
hub = SunHub()
|
||||||
|
# offline delta updates
|
||||||
|
hub.add_plan_delta("Downtown", [{"key": "solar_production_kw", "value": 30}])
|
||||||
|
hub.add_plan_delta("Downtown", [{"key": "storage_state", "value": "charging"}])
|
||||||
|
final = hub.apply_all({})
|
||||||
|
assert final["solar_production_kw"] == 30
|
||||||
|
assert final["storage_state"] == "charging"
|
||||||
|
|
||||||
|
|
||||||
|
def test_contract_registry():
|
||||||
|
hub = SunHub()
|
||||||
|
hub.register_adapter("gis", {"endpoint": "http://localhost/gis"})
|
||||||
|
reg = hub.contracts.get("gis")
|
||||||
|
assert reg["endpoint"] == "http://localhost/gis"
|
||||||
Loading…
Reference in New Issue