mirror of
https://github.com/lynchaos/ashvale-station.git
synced 2026-09-12 20:52:23 +00:00
1. POST /api/recompute re-derives every compensated column from the untouched raw values, removing the step a calibration otherwise leaves through the history. Possible because temp_raw, cpu_temp and hum are never overwritten. Idempotent by construction and tested per row: 0 of 6051 rows change on a second run. 6069 rows in 0.25 s here, so a few seconds on the Pi. 2. Calibration now emits a 'discontinuity' event alongside the calibration log, so downstream views can find the boundary without parsing prose. 3. Vendored Tailwind, Chart.js, hammer, the zoom plugin, KaTeX with its 20 woff2 faces, and both Google fonts into ashvale/static, served by the station. 1.4 MB. Verified with every non-localhost request aborted in the browser: zero external requests, equations still render, fonts still load. The dashboard no longer needs internet. 4. 54 pytest cases over the pure numerics: physics closed forms and round trips, both compensator inverse properties, the Kalman covariance invariants and NIS consistency, the RLS trace cap under a deliberately unexcited regressor, conformal coverage, and the Zambretti ordering. Wired into CI after the seed step so the recompute cases have history. Writing them caught my own sign error on the conformal update: a hit raises alpha and narrows the band, which reads backwards until you follow it through. 5. Stats for Nerds gains the condition number of each head's covariance, a standardised innovation histogram per Kalman filter from a bounded 600 sample ring buffer, and a reliability strip of realised against nominal coverage. All arithmetic on data already in memory. 6. OutdoorProbe reads a DS18B20 over the kernel 1-Wire driver, no new dependency. Polled on its own slower cadence because the sensor blocks for up to 750 ms during conversion, which would eat a third of the 2 s sample budget. Rejects the 85000 power-on sentinel and out-of-range values, and reports age so a dead probe cannot masquerade as fresh.
77 lines
2.5 KiB
YAML
77 lines
2.5 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
python-version: ["3.9", "3.11", "3.12"]
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Set up Python ${{ matrix.python-version }}
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: ${{ matrix.python-version }}
|
|
cache: pip
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
pip install -r requirements.txt
|
|
pip install httpx pytest
|
|
|
|
- name: Lint
|
|
run: |
|
|
pip install ruff
|
|
ruff check .
|
|
|
|
- name: Byte-compile every module
|
|
run: python -m compileall -q ashvale scripts run.py
|
|
|
|
- name: Import check
|
|
run: python -c "import ashvale.api; print('imports ok')"
|
|
|
|
- name: Seed synthetic history
|
|
run: python scripts/simulate.py --days 10 --wipe
|
|
|
|
# Unit tests over the pure numerics: physics closed forms, the compensator
|
|
# inverse properties, the Kalman covariance invariants and the RLS trace
|
|
# cap. Run after seeding so the recompute tests have history rather than skipping.
|
|
- name: Unit tests
|
|
run: python -m pytest tests/ -q
|
|
|
|
|
|
# The backtest is the real test: it exercises features, the RLS heads,
|
|
# climatology and conformal calibration end to end, and fails loudly if
|
|
# any of them stop producing finite numbers.
|
|
- name: Walk-forward backtest
|
|
run: python scripts/evaluate.py --train-frac 0.6 --hours 300
|
|
|
|
- name: Exercise the API surface
|
|
run: |
|
|
python - <<'PY'
|
|
from fastapi.testclient import TestClient
|
|
import ashvale.api as api
|
|
with TestClient(api.app) as c:
|
|
for ep in ["/api/telemetry", "/api/status", "/api/forecast",
|
|
"/api/outlook", "/api/precipitation", "/api/anomaly",
|
|
"/api/models", "/api/scorecard", "/api/records",
|
|
"/api/storage", "/api/methods",
|
|
"/api/history/range?hours=24",
|
|
"/api/history/daily?days=5", "/"]:
|
|
r = c.get(ep)
|
|
assert r.status_code == 200, (ep, r.status_code, r.text[:200])
|
|
assert c.post("/api/train").json()["trained"] is True
|
|
assert len(c.get("/api/export.csv?hours=6").text.splitlines()) > 1
|
|
print("api surface ok")
|
|
PY
|