tliu93
57f2459f60
M6-T09: add Energy data API (prices/costs/summary/dsmr-latest/recompute/tibber-test)
...
- routes/api/energy.py + schemas/energy.py: GET prices (tibber points or manual
tariff, buy/sell consistent with the pricing strategies), GET costs (time
window + limit, ascending), GET costs/summary (summarize passthrough), GET
dsmr/latest, POST costs/recompute (idempotent, <=366d guard, CSRF), POST
tibber/test (three-state, token never echoed, CSRF).
- main.py registers router; OpenAPI re-exported; tests for all endpoints.
2026-06-23 23:05:45 +02:00
tliu93
c61fc2d4ba
M6-T08: add energy_cost expose provider + post-billing state publish
...
- expose.py: _energy_cost_provider yields buy/sell_price_now + import_cost_total
/export_revenue_total (total_increasing, monetary), value_getters read from
energy_cost_period; 2-element identifiers so the existing HA Discovery builder
(which indexes identifiers[1]) works. Other providers/mechanism untouched.
- main.py: energy-cost job calls publish_states after computing periods (no-op
when MQTT/Discovery off). Tests added.
Note: energy-cost sensors lack an availability heartbeat (ha_discovery mechanism
limitation, out of scope here).
2026-06-23 22:44:41 +02:00
tliu93
d35ac7d752
M6-T07: add metering billing engine + period job + summary + recompute
...
- app/services/energy_cost.py: register_at (register values at a UTC boundary),
compute_period (register deltas x active-version strategy, snapshot price +
contract_version_id, immutable unless overwrite), compute_closed_periods
(bounded lookback), recompute_range (explicit overwrite), summarize
(sum net + standing fees month/30 - heffingskorting year/365).
- Missing tibber price -> skip; missing reading -> degraded (zeroed amounts,
null version), consistently on both create and recompute paths.
- main.py: 1-minute energy-cost job (existing jobs/MQTT untouched). Tests added.
2026-06-23 22:26:56 +02:00
tliu93
ef48032adb
M6-T06: extend MqttManager with subscribe + DSMR ingest
...
- mqtt.py: subscription registry, on_message dispatch (swallows handler
errors), re-subscribe on (re)connect; publish/connect/disconnect/reconnect
unchanged.
- app/services/dsmr_ingest.py: handle_message stores the full telegram frame
(no allowlist; gas/phases/null preserved), 10s downsample by timestamp.second,
source_id idempotency (select + IntegrityError rollback). Net-thread safe.
- main.py registers the DSMR subscription only when dsmr_ingest_enabled.
2026-06-23 21:52:57 +02:00
tliu93
7e266a21eb
M6-T05: add Tibber GraphQL client + price refresh service + schedule
...
- app/integrations/tibber/client.py: fetch_price_range/fetch_current_price
(priceInfoRange QUARTER_HOURLY, startsAt->UTC, no fixed node count assumption),
TibberError/TibberAuthError; token never logged or put in exception text.
- app/services/tibber_prices.py: refresh_prices upserts by starts_at (idempotent),
no-op unless an active kind=tibber contract + token exist.
- main.py: hourly tibber-refresh job (existing jobs/MQTT untouched). Tests added.
2026-06-23 21:35:29 +02:00
tliu93
4284bd40a7
M6-T04: add EnergyContract CRUD + versions + profile validation API
...
- app/services/contracts.py: create_contract, add_version (append-only,
auto-closes prior version), activate_contract (mutex), and
active_contract_version_at (half-open [from, to)) for the billing engine.
- app/schemas/energy_contract.py + routes/api/energy_contracts.py: 6 endpoints
(GET/POST contracts, GET/PATCH contracts/{id}, POST .../versions, GET profiles).
Values validated against pricing profiles (422 on mismatch); no DELETE.
- main.py registers router; OpenAPI re-exported; tests for CRUD/version/activate.
2026-06-23 21:19:25 +02:00
tliu93
bedea196c3
M6-T03: add pricing profile framework + strategy registry
...
- app/integrations/pricing: profiles.py (pydantic ManualProfile/TibberProfile,
load_profile/list_profiles/validate_values), manual.yaml + tibber.yaml
(structure-only, no price values), strategies.py (register/get_strategy,
manual dual-tariff + tibber strategies, all Decimal).
- tibber strategy matches nearest tibber_price with starts_at <= t0.
- tests for profiles + strategies (hand-checked dual-tariff & tibber math).
2026-06-23 20:58:34 +02:00
tliu93
df54f5518b
M6-T02: add DSMR + Tibber flat config fields
...
- Settings: dsmr_ingest_enabled, dsmr_mqtt_topic, dsmr_sample_interval_s,
tibber_api_token (secret), tibber_home_id (all opt-in / off by default).
- CONFIG_FIELDS DSMR + Tibber sections; _settings_payload extended.
- .env.example examples; test_api_config coverage (sections, secret, 422).
2026-06-23 20:43:46 +02:00
tliu93
18fe18281b
M6-T01: add 5 energy tables, models, and migration
...
- app/models/energy.py: DsmrReading, EnergyContract, EnergyContractVersion,
TibberPrice, EnergyCostPeriod (per design §3.5; JSON blobs, RESTRICT FKs).
- alembic_app migration 20260623_11_energy_tables (head), env.py imports.
- app_db_adopt APP_BASELINE_REVISION bumped to new head.
- tests/test_energy_models.py + expose_catalog test fixups for new head.
2026-06-23 20:33:35 +02:00
tliu93
c64693b8e5
M6: add design doc — generic pricing layer (manual/tibber) + DSMR cost engine
2026-06-23 19:53:26 +02:00