Files
MeterVault/README.md
T
schmidt.florian 1282acf82c SDD §8 panels (PV/oil/meter-detail) + fix reference-data-in-Docker
Complete the SDD §8 dashboard views that were deferred at the M5 boundary,
and fix a shipping bug that left the Docker demo empty.

Bug: "Load reference data" created meters/tank/tariffs but imported zero
readings in Docker. Root cause: sampledata/ was excluded by .dockerignore and
never copied into the build stage, so the App csproj's linked Content glob
resolved to nothing at publish time; ReferenceDataImporter then silently
skipped the missing CSVs after already writing its marker meter, leaving the
DB permanently "loaded" but empty.
  - .dockerignore: stop excluding sampledata/
  - Dockerfile: COPY sampledata/ into the build stage
  - ReferenceDataImporter: fail-fast (validate CSVs exist before the marker
    meter) and throw instead of silently skipping a missing file
  - Program.cs + MeterVaultOptions: opt-in MeterVault__SeedReferenceData
    (compose METERVAULT_SEED=true) for a one-command populated demo

New SDD §8 panels (read models in Infrastructure/Dashboard, Blazor pages):
  - §8.4 Solar/PV (/solar): generation from GenerationCounter meters;
    self-consumption / autarky % / self-consumption % / savings derived from
    meters tagged total_load & grid_import via Meter.Meta role config
    (MeterRoles/MeterMeta) — nothing hardcoded by name.
  - §8.5 Oil/consumable (/consumables): tank level (cm→L calibrated), fill
    gauge, deliveries log, burner runtime, effective L/h (fixed/empirical),
    forecast-to-empty, tariff cost, monthly series.
  - §8.6 Meter detail (/meters/{id}): raw readings, normalized consumption,
    source status, tariff timeline, events, measured-vs-estimated markers.
  - Reusable SeriesChart component; nav links; Meters list rows link to detail.

Tests: MeterMetaTests (Core, +10); DashboardRenderTests extended to assert the
three panel services compute real figures and the new routes render (108 total,
all green). Live-verified in Docker: seed imports 302 readings / 347 consumption
rows; panels render (generation 16,481 kWh, oil 3,967 L) cross-checking the DB.

Claude-Session: https://claude.ai/code/session_01Lz2RqAsnQhetqWNoCDfexK
2026-07-14 09:52:11 +02:00

90 lines
4.5 KiB
Markdown

# MeterVault
A self-hosted, local-first **energy & utility metering platform**. MeterVault pulls meter data
from Home Assistant, Tasmota and raw MQTT on a schedule, stores every reading timestamped and
immutable, normalizes it into consumption, and turns it into cost dashboards. Energy types
(electricity, water, heating oil, gas, district heat, …) and meters are **user-defined — nothing
is hardcoded**.
Successor to a hand-maintained *Energiebilanz* spreadsheet. See [`docs/SDD.md`](docs/SDD.md) for the
full design.
## Features
- **Automatic ingestion** from MQTT/Tasmota (persistent subscriptions) and Home Assistant (REST
poll or push), plus manual entry, a REST push API, and CSV import.
- **Immutable raw readings** on a TimescaleDB hypertable; a normalized, append-only **consumption**
layer on top — reproducible, auditable.
- **Seven measurement modes** (cumulative/generation registers, burner runtime, tank/consumable,
direct delta, instant rate, virtual). Handles meter swaps, counter resets, tank dip-sticks with
calibration, and **virtual meters** defined by an expression (PV self-consumption, savings, net).
- **Tariff engine** with time-ranged price history (unit/base/feed-in), scoped global / per type /
per meter; **cost categories** decoupled from energy types; meterless manual costs.
- **Continuous aggregates** (daily/monthly/yearly, local timezone) so dashboards never scan raw.
- **Dashboard**: cost KPIs with period-over-period deltas, "what costs most", a "what cost more/
less" difference view, trends, a **PV/Solar panel** (generation, self-consumption, autarky %,
savings), an **oil/consumable panel** (tank gauge, deliveries, burner runtime, effective L/h,
forecast-to-empty) and a **per-meter detail view** (raw readings, consumption, sources, tariff
timeline, events), one-click reference-data load, CSV dry-run.
- **REST API + OpenAPI/Swagger**, API-key auth, reverse-proxy trust (Authelia/Traefik).
- **JSON config export/import** for portability; Docker Compose + multi-arch image.
## Quick start (Docker)
```bash
docker compose -f deploy/docker-compose.yml up -d
# open http://localhost:8080 → Import → "Load reference data" for a populated demo
# ...or start pre-populated: METERVAULT_SEED=true docker compose -f deploy/docker-compose.yml up -d
# API docs at http://localhost:8080/swagger
```
Configuration is via environment variables (`Section__Key` double-underscore mapping), e.g.:
| Variable | Purpose |
|----------|---------|
| `ConnectionStrings__Default` | PostgreSQL/Timescale connection string |
| `MeterVault__TimeZone` | Local timezone for buckets/display (default `Europe/Berlin`) |
| `MeterVault__ApiKeys__0` | An API key accepted on the `X-Api-Key` header |
| `MeterVault__AllowAnonymousApi` | `true` to open the REST API without a key (trusted LAN only) |
| `MeterVault__ReverseProxyTrust` | `true` to honour `X-Forwarded-User` behind an auth proxy |
| `MeterVault__EnableLiveIngestion` | `false` to disable the MQTT/HA workers |
| `MeterVault__SeedReferenceData` | `true` to load the bundled demo dataset on first start (idempotent) |
The REST API is **closed by default**: with no `ApiKeys` configured and `AllowAnonymousApi` off, it
returns 401. Set at least one API key (or open it explicitly for a trusted network).
Secrets (broker/HA tokens) are **never** stored in the database — endpoint configs hold the *name*
of an environment variable, resolved at runtime.
## Pushing readings (Home Assistant)
```bash
curl -X POST http://localhost:8080/api/v1/readings \
-H "X-Api-Key: $METERVAULT_API_KEY" -H "Content-Type: application/json" \
-d '[{"meterId": 1, "time": "2026-01-01T12:00:00Z", "value": 47200}]'
```
See [`docs/wiring.md`](docs/wiring.md) for wiring up Tasmota, MQTT and Home Assistant.
## Development
```bash
dotnet build
dotnet test # integration tests spin a TimescaleDB via Testcontainers (needs Docker)
dotnet test tests/Core.Tests # fast unit tests, no Docker
dotnet run --project src/App
```
Architecture, project layout and conventions live in [`CLAUDE.md`](CLAUDE.md).
## Releasing
Edit the [`VERSION`](VERSION) file on `master`; Gitea Actions tags `vX.Y.Z` and builds/pushes a
multi-arch image to the Gitea container registry (`.gitea/workflows/`). Locally:
`pwsh deploy/build-and-push.ps1 -Registry git.finalfactory.de -Image finalfactory/metervault -Push`.
Requires a Docker-capable `act_runner`; the image build itself is self-contained.
## License
Not yet chosen (see SDD §14). Add a `LICENSE` before the first public tag.