Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/forecast-learning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"ftw": minor
---

Correct weather interval timing, panel direction and forecast energy. Train household and PV models only on fresh, complete measurements. Keep grid limits separate from household demand and let PV learn its scale without a battery-based guess.

Use the local Energyplan PV and load models as the planner's first forecast source, with no required panel geometry. Keep the previous forecast as a shadow and use it when the new model lacks a valid prediction or its worker fails. Save issued forecasts, per-signal sources and model state for causal comparison, report uncertainty by horizon, and add a read-only forecast evaluation command.
6 changes: 6 additions & 0 deletions .github/workflows/native-solver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ on:
paths:
- 'optimizer/native/**'
- 'go/internal/mpc/**'
- 'go/internal/energyforecast/**'
- 'go/cmd/ftw/forecast*.go'
- 'go/cmd/ftw/main.go'
- 'Makefile'
- '.github/workflows/native-solver.yml'
push:
branches: [master]
paths:
- 'optimizer/native/**'
- 'go/internal/mpc/**'
- 'go/internal/energyforecast/**'
- 'go/cmd/ftw/forecast*.go'
- 'go/cmd/ftw/main.go'
- 'Makefile'
- '.github/workflows/native-solver.yml'

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -273,5 +273,5 @@ native-solver-check:

native-solver-test: native-solver-check
@binary="$$(python3 optimizer/native/verify.py --host-binary)"; \
if [ -n "$$binary" ]; then cd go && FTW_NATIVE_SOLVER="$$binary" go test -count=1 ./internal/mpc -run '^TestNative'; \
if [ -n "$$binary" ]; then cd go && FTW_NATIVE_SOLVER="$$binary" FTW_FORECAST_WORKER="$$binary" go test -count=1 ./internal/mpc ./internal/energyforecast ./cmd/ftw -run 'Native|RustForecastHost'; \
else echo "Native execution tests skipped: no bundled worker for this host"; fi
30 changes: 27 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
FTW is a local-first home energy management system. Its architecture has
three explicit modules: **core**, **drivers**, and **optimizer**. Core is the
safety boundary. Drivers translate hardware protocols. The optimizer proposes
plans. A failure or upgrade outside core must never stop local measurement or
make dispatch unsafe.
plans and supplies primary forecasts. A failure or upgrade outside core must
never stop local measurement or make dispatch unsafe.

## Module boundaries

| Module | Source | Runtime | Responsibility |
|---|---|---|---|
| Core | [`go/cmd/ftw`](../go/cmd/ftw), [`go/internal`](../go/internal), [`web`](../web) | One Go binary | Configuration, telemetry, state, API/UI, safety, control and fallback planning |
| Drivers | Editable source in [`srcfl/device-drivers`](https://github.com/srcfl/device-drivers); bundled recovery in `drivers/*.lua`; host in [`go/internal/drivers`](../go/internal/drivers) | One sandboxed Lua VM per configured device | Vendor protocol, sign conversion and device commands |
| Optimizer | [`optimizer`](../optimizer), contract in [`go/internal/mpc`](../go/internal/mpc) | Compiled Energyplan worker | Solve the long-horizon mathematical plan |
| Optimizer | [`optimizer`](../optimizer), contracts in [`go/internal/mpc`](../go/internal/mpc) and [`go/internal/energyforecast`](../go/internal/energyforecast) | Compiled Energyplan worker | Solve the long-horizon plan and supply primary PV and household-load forecasts |

Core can run without the optimizer. Hardware cannot be accessed without a
driver, but one failed driver is isolated from the others. Optional
Expand Down Expand Up @@ -111,6 +111,30 @@ stay in the private Energyplan repository. It updates with the Core image.
The optimizer never reads hardware or issues commands, so its deployment and
dependency churn do not enlarge the safety-critical runtime.

The same worker also supplies the primary PV and household-load forecast through
a separate versioned contract. At the start of each replan, Core freezes the
legacy forecast, weather, occupancy and saved model state. It calls the forecast
worker once under a deadline, outside control and dispatch locks. Core accepts
PV and load independently for each covered interval. If either signal is
missing, late, partial or invalid, Core retains the matching legacy value. The
resulting `champion` can therefore contain Energyplan PV with legacy load, or
the reverse. `legacy_shadow` keeps both legacy signals from the same frozen
capture for a fair later comparison.

Complete qualified 15-minute observations update the local models outside
dispatch. SQLite stores the latest Energyplan state under
`forecast/energyplan_state_v1`; an update becomes visible only after its full
state has been saved, and startup restores that saved state. The learning
revision binds state to forecast inputs and stable hardware identities. A
binding or input change starts fresh learning, while a compatible program
upgrade can reuse the state. Issued forecasts use a stricter revision that also
includes the Core build, worker bytes and pipeline policy.

Core keeps issued forecasts, frozen inputs, model-state references and qualified
truth in a bounded local archive. The read-only `ftw-forecast-evaluate` source
command defaults to matched `champion` versus `legacy_shadow` results from the
same issue. It does not treat missing or censored truth as evidence.

## Versioning a module contract

Drivers release independently. Energyplan ships with Core, but Core still
Expand Down
Loading