diff --git a/collect.py b/collect.py index c1f7664..0f015a3 100644 --- a/collect.py +++ b/collect.py @@ -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: diff --git a/server/data_model.py b/server/data_model.py index 7094d07..10f20d4 100644 --- a/server/data_model.py +++ b/server/data_model.py @@ -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