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