build(agent): new-agents-2#7e3bbc iteration

This commit is contained in:
agent-7e3bbc424e07835b 2026-04-23 22:13:15 +02:00
parent 02455e101c
commit 7c4187ffd1
14 changed files with 310 additions and 2 deletions

21
.gitignore vendored Normal file
View File

@ -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

25
AGENTS.md Normal file
View File

@ -0,0 +1,25 @@
# SolarSphere Offline-First MVP Agents
This repository contains a production-oriented Python MVP for SolarSphere, an offline-first geospatial visualization scaffold. The code focuses on data contracts, a pluggable data-source adapter surface, deterministic delta reconciliation, and a lightweight render summary suitable for testing and extension.
Architecture overview
- Core data primitives: Asset, TelemetryPoint, WeatherLayer, Event.
- Data sources: pluggable adapters (CSV, REST, MQTT, etc.). CSVAdapter is implemented for MVP.
- Delta reconciliation: deterministic, replay-friendly merge of local and remote deltas.
- Lightweight renderer: builds a serializable snapshot suitable for downstream visualization without a browser runtime.
- Packaging: Python package under idea78_solarsphere_offline_first with a small, exportable API.
Tech stack decisions
- Language: Python 3.9+ for rapid prototyping, strong typing, and easy packaging.
- No external GUI dependencies yet; a JSON-friendly render path is provided to support future WebGL/Canvas rendering layers.
- Tests: pytest-based unit tests ensure data models, adapters, and delta logic behave deterministically.
Testing and commands
- Run tests locally: ./test.sh
- Build package: python3 -m build
- Inspect package: pytest tests -q
Contributing rules
- Add small, well-scoped changes; prefer minimal surface area.
- Update AGENTS.md if you add new architecture or data contracts.
- Ensure tests cover new behavior.

View File

@ -1,3 +1,30 @@
# idea78-solarsphere-offline-first # SolarSphere Offline-First MVP
Source logic for Idea #78 Overview
- A Python-based MVP for offline-first geospatial visualization and telemetry replay. Focuses on simple, well-defined data contracts and deterministic delta reconciliation that can run entirely offline with optional data-sync when connectivity returns.
Whats included
- Core data primitives: Asset, TelemetryPoint, WeatherLayer, Event
- CSV-based telemetry adapter for MVP data ingestion
- Delta reconciliation utilities for deterministic merges
- Lightweight render summary producing JSON suitable for downstream tooling
- Minimal CLI and a test suite to validate core behavior
Getting started
- Install dependencies and run tests: `bash test.sh`
- Package: `python3 -m build` (also in test script)
Project structure (high level)
- src/idea78_solarsphere_offline_first/: Python package with core models, adapters, delta logic, and rendering helpers
- tests/: Unit tests for models, delta logic, and adapters
- AGENTS.md: Architecture and contribution guidance
- test.sh: Test runner that executes tests and packaging checks
Usage example (quick)
- Load a CSV telemetry file via CSVAdapter, reconcile with a local delta, and render a snapshot:
- adapter = CSVAdapter("telemetry.csv")
- telem = adapter.load_telemetry()
- snapshot = render.render_snapshot([asset], telem, weather, events)
- print(snapshot)
This repository adheres to the contribution guidelines described in AGENTS.md and is designed to be extended in sprint fashion.

16
pyproject.toml Normal file
View File

@ -0,0 +1,16 @@
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "idea78_solarsphere_offline_first"
version = "0.1.0"
description = "Offline-first geospatial visualization scaffolding for SolarSphere MVP"
readme = "README.md"
requires-python = ">=3.9"
dependencies = []
[tool.setuptools]
package-dir = {"" = "src"}
# Removed legacy setuptools.find block to ensure compatibility with current build backends

View File

@ -0,0 +1,5 @@
"""Idea78 SolarSphere Offline-First package initializer"""
__version__ = "0.1.0"
from .core import Asset, TelemetryPoint, WeatherLayer, Event # re-export core types

View File

@ -0,0 +1,55 @@
"""Core data primitives for SolarSphere offline-first MVP."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List, Dict
import json
@dataclass
class Asset:
site_id: str
location: Dict[str, float] # e.g., {"lat": 12.34, "lon": 56.78}
layers: List[str] = field(default_factory=list)
version: int = 1
def to_json(self) -> str:
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
@dataclass
class TelemetryPoint:
site_id: str
ts: int # epoch milliseconds
lat: float
lon: float
metric: str
value: float
quality: str = "good"
def to_json(self) -> str:
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
@dataclass
class WeatherLayer:
region: str
ts: int
param: str
value: float
source: str
def to_json(self) -> str:
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
@dataclass
class Event:
ts: int
site_id: str
type: str
description: str
severity: str
def to_json(self) -> str:
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)

View File

@ -0,0 +1,40 @@
"""CSV-based adapter for ingesting telemetry data (MVP)."""
from __future__ import annotations
import csv
from typing import List
from .core import TelemetryPoint
class CSVAdapter:
"""Simple CSV data adapter for TelemetryPoint ingest.
Expected CSV header:
site_id,ts,lat,lon,metric,value,quality
"""
def __init__(self, path: str):
self.path = path
def load_assets(self) -> List:
# MVP: not implemented for tests; return empty list
return []
def load_telemetry(self) -> List[TelemetryPoint]:
pts: List[TelemetryPoint] = []
with open(self.path, newline="") as f:
reader = csv.DictReader(f)
for row in reader:
pts.append(
TelemetryPoint(
site_id=row["site_id"],
ts=int(row["ts"]),
lat=float(row["lat"]),
lon=float(row["lon"]),
metric=row["metric"],
value=float(row["value"]),
quality=row.get("quality", "good"),
)
)
return pts

View File

@ -0,0 +1,35 @@
"""Deterministic, replay-friendly delta reconciliation helpers."""
from __future__ import annotations
from typing import List, Dict, Tuple
from .core import TelemetryPoint
def _key(pt: TelemetryPoint) -> Tuple[str, int, str]:
# Unique key for a telemetry point by site, timestamp, and metric
return (pt.site_id, pt.ts, pt.metric)
def reconcile_deltas(local: List[TelemetryPoint], remote: List[TelemetryPoint]) -> List[TelemetryPoint]:
"""Deterministically merge local and remote telemetry points.
Remote data takes precedence when keys collide. The function returns a new
list containing the merged view. Order is not guaranteed but deterministic
with respect to the input keys.
"""
merged: Dict[Tuple[str, int, str], TelemetryPoint] = {}
for p in local:
merged[_key(p)] = p
for p in remote:
merged[_key(p)] = p
return list(merged.values())
class TelemetryDelta:
def __init__(self, delta: List[TelemetryPoint], ts: int, anchor: str, signature: str):
self.delta = delta
self.ts = ts
self.anchor = anchor
self.signature = signature

View File

@ -0,0 +1,18 @@
"""Lightweight render summary for offline-first MVP.
This is intentionally data-centric (JSON-friendly) to support downstream
visualization layers without a browser runtime in this MVP.
"""
from __future__ import annotations
from typing import List
from .core import Asset, TelemetryPoint, WeatherLayer, Event
def render_snapshot(assets: List[Asset], telemetry: List[TelemetryPoint], weather: List[WeatherLayer], events: List[Event]) -> dict:
return {
"assets": [a.__dict__ for a in assets],
"telemetry_count": len(telemetry),
"weather_count": len(weather),
"events": len(events),
}

11
test.sh Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
# Basic test suite and packaging verification for SolarSphere MVP
echo "Running tests..."
pytest -q
echo "Building package..."
python3 -m build
echo "All tests passed and package built."

7
tests/conftest.py Normal file
View File

@ -0,0 +1,7 @@
import os
import sys
# Ensure the local src package is importable by pytest
SRC_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))
if SRC_PATH not in sys.path:
sys.path.insert(0, SRC_PATH)

20
tests/test_csv_adapter.py Normal file
View File

@ -0,0 +1,20 @@
import tempfile
import os
from idea78_solarsphere_offline_first.csv_adapter import CSVAdapter
from idea78_solarsphere_offline_first.core import TelemetryPoint
def test_csv_adapter_loads_telemetry_points():
with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".csv") as tf:
tf.write("site_id,ts,lat,lon,metric,value,quality\n")
tf.write("siteA,1000,1.0,2.0,temperature,25.0,good\n")
tf.flush()
path = tf.name
adapter = CSVAdapter(path)
pts = adapter.load_telemetry()
assert isinstance(pts, list) and len(pts) == 1
p = pts[0]
assert isinstance(p, TelemetryPoint)
assert p.site_id == "siteA" and p.metric == "temperature" and p.value == 25.0
os.unlink(path)

10
tests/test_delta.py Normal file
View File

@ -0,0 +1,10 @@
from idea78_solarsphere_offline_first.core import TelemetryPoint
from idea78_solarsphere_offline_first.delta import reconcile_deltas
def test_reconcile_deltas_basic_override():
local = [TelemetryPoint(site_id="S1", ts=1000, lat=0.0, lon=0.0, metric="temp", value=20.0)]
remote = [TelemetryPoint(site_id="S1", ts=1000, lat=0.0, lon=0.0, metric="temp", value=22.5)]
merged = reconcile_deltas(local, remote)
assert len(merged) == 1
assert merged[0].value == 22.5

18
tests/test_models.py Normal file
View File

@ -0,0 +1,18 @@
from idea78_solarsphere_offline_first.core import Asset, TelemetryPoint, WeatherLayer, Event
def test_asset_dataclass():
a = Asset(site_id="site-01", location={"lat": 12.34, "lon": 56.78}, layers=["terrain", "overlay"], version=1)
assert a.site_id == "site-01"
assert isinstance(a.layers, list) and a.layers[0] == "terrain"
def test_telemetry_point_dataclass():
t = TelemetryPoint(site_id="site-01", ts=1234567890, lat=12.3, lon=45.6, metric="temp", value=22.5)
assert t.metric == "temp" and t.value == 22.5
def test_weather_event_dataclass():
w = WeatherLayer(region="region-1", ts=1000, param="wind", value=5.4, source="sim")
e = Event(ts=1000, site_id="site-01", type="maintenance", description="routine check", severity="low")
assert w.param == "wind" and e.severity == "low"