"""PID + DTC data model and registry, backed by a vehicle Profile. The actual PID numbers, scaling formulas, and DTC meanings live in JSON vehicle profiles under profiles/ (data, not code) so the app is vehicle- agnostic and others can contribute profiles. This module is the in-memory model + lookups; profile.py loads/saves the JSON. """ from dataclasses import dataclass, field from typing import Callable, Tuple @dataclass class Pid: key: str name: str mode: str = "22" # "01" | "22" | "atrv" | "derived" pid: str = "" # hex: "1446" (m22) or "0C" (m01) nbytes: int = 2 formula: str = "" # scaling expr in A/B/... (raw) or dep keys (derived) decode: Callable = None # built from formula by profile loader unit: str = "" group: str = "misc" # fuel | ficm | air | engine | driveline | power | misc vmin: float = 0.0 vmax: float = 100.0 confidence: str = "verified" # verified | doc | tentative round: int = None # display rounding (None=raw float, 0=int) deps: Tuple[str, ...] = () notes: str = "" @dataclass class Dtc: code: str desc: str system: str = "powertrain" no_start: bool = False causes: str = "" class PidRegistry: """In-memory PID set + presets for the active vehicle profile.""" def __init__(self, profile): self.profile = profile self._by_key = {p.key: p for p in profile.pids} self.presets = dict(profile.presets) def get(self, key): return self._by_key.get(key) def all(self): return list(self._by_key.values()) def group(self, g): return [p for p in self._by_key.values() if p.group == g] def preset(self, name): return [self._by_key[k] for k in self.presets.get(name, []) if k in self._by_key] def preset_names(self): return list(self.presets.keys()) class DtcDatabase: def __init__(self, profile): self._db = {d.code: d for d in profile.dtcs} def get(self, code): return self._db.get(code) or Dtc(code, "(unknown - look up this code)") def all(self): return list(self._db.values())