Add procedural field post-processing to snapshots

server/data_model.py:
- apply_procedural(record) — stub function; add computed fields to the
  proc dict inside and they appear under record['procedural'] in every
  record stored and broadcast.
- _get(data, *keys) — safe nested dict accessor for use inside
  apply_procedural.

collect.py:
- New snapshots: apply_procedural() called after collect_snapshot(),
  before appending to the store and broadcasting.
- Preloaded 24-h history: guarded apply (skip if 'procedural' already
  present) applied after load_last_24h_records().
- Current-day store loaded from disk: same guarded apply after
  load_store(), so legacy records sent to clients are enriched on the fly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 17:35:10 +02:00
co-authored by Claude Sonnet 4.6
parent e6c9f7d687
commit 2756c6a49b
2 changed files with 44 additions and 1 deletions
+8 -1
View File
@@ -53,7 +53,7 @@ from datetime import datetime, timezone
from pathlib import Path
from server import log_config, network, we_connect
from server.data_model import ALL_DOMAINS, collect_snapshot
from server.data_model import ALL_DOMAINS, collect_snapshot, apply_procedural
from server.storage_helpers import auto_out_path, load_last_24h_records, load_store, save_store
from utils.jay_diff import jay_diff_full
@@ -95,11 +95,17 @@ def run(args: argparse.Namespace) -> None:
push_clients, push_lock, push_store_ref = network.start_push_server(args.host, args.port)
push_store_ref["diff_mode"] = args.diff
push_store_ref["preloaded"] = load_last_24h_records(logs_dir, vin)
for _r in push_store_ref["preloaded"]:
if "procedural" not in _r:
apply_procedural(_r)
if push_store_ref["preloaded"]:
log.info("Pre-loaded %d record(s) from the last 24 h", len(push_store_ref["preloaded"]))
current_day = datetime.now(timezone.utc).date()
store = load_store(out, vin, args.interval)
for _r in store["records"]:
if "procedural" not in _r:
apply_procedural(_r)
push_store_ref["current"] = store
last_broadcast: dict = {}
@@ -127,6 +133,7 @@ def run(args: argparse.Namespace) -> None:
try:
wc.update()
snapshot = collect_snapshot(vehicle, domains)
apply_procedural(snapshot)
store["records"].append(snapshot)
save_store(out, store, args.max_records)
if args.diff and last_broadcast:
+36
View File
@@ -243,3 +243,39 @@ def collect_snapshot(vehicle, domains: list[str]) -> dict:
if data is not None:
snapshot[domain] = data
return snapshot
# ── procedural fields ─────────────────────────────────────────────────────────
def _get(data: dict, *keys):
"""Navigate nested dicts safely; returns None if any key is missing."""
for k in keys:
if not isinstance(data, dict):
return None
data = data.get(k)
return data
def apply_procedural(record: dict) -> dict:
"""Compute derived fields and store them under record['procedural'].
Called for every new snapshot before storage and broadcast, and for
historical records that lack the 'procedural' key before they are sent
to clients. Add computed fields to the proc dict below.
"""
proc: dict = {}
# ── add computed fields here ──────────────────────────────────────────
# Use _get(record, "domain", "statusObject", "field", "value") to
# safely read any nested value. Always use {"value": ..., "unit": ...}
# format so the GUI picks up the field automatically.
#
# Example — usable energy estimated from SOC and battery capacity:
# soc = _get(record, "charging", "batteryStatus", "currentSOC", "value")
# if soc is not None:
# proc["energy_stored"] = {"value": round(soc / 100 * 77.0, 1), "unit": "kWh"}
# ─────────────────────────────────────────────────────────────────────
if proc:
record["procedural"] = proc
return record