Gauge redline zones + C/F units toggle + cleaner dials

Gauges:
- Optional per-metric warning zones (warn_hi/redline_hi/warn_lo/redline_lo) in
  the profile schema; gauges draw colored redline/warn bands and color the
  needle + readout by zone. Default neutral when unset (no false redline).
- Removed the value progress-arc fill (it dominated the dial / looked wrong) ->
  clean tach face: bezel, ticks, numeric scale, needle, redline band, readout.
- Auto-derivation rejected: bad direction/threshold vary per metric, so zones
  are config (with a sensible neutral default).

Units:
- New Units menu: Temperature C / F. Converts gauges, graph, table, and PID
  browser (values, scale, zones, unit labels) at display time; data stays C.

Ford 6.0 profile: zones for ICP (red<500), FICM (red<40/amber40-48/green48+),
ECT/EOT (high redline), RPM (redline 3800), boost, battery; tightened FICM
(38-52) and battery (9-15) ranges so redline bands land sensibly.

Docs: profiles/PROFILE_SPEC.md -- canonical, AI-agent-ready profile spec
(schema, formula language, zones, confidence, rules); README points to it.

Validated headless: zones parse/classify, F conversion (112C->233.6F, zones
converted), gauges render; obdcore + diagnostics tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs
This commit is contained in:
2026-06-30 15:52:49 -04:00
parent deef305c63
commit d893ff383a
8 changed files with 326 additions and 32 deletions
+5
View File
@@ -80,6 +80,8 @@ def _pid_from_dict(d):
vmin=d.get("vmin", 0.0), vmax=d.get("vmax", 100.0),
confidence=d.get("confidence", "verified"), round=d.get("round"),
deps=tuple(d.get("deps", ())), notes=d.get("notes", ""),
warn_hi=d.get("warn_hi"), redline_hi=d.get("redline_hi"),
warn_lo=d.get("warn_lo"), redline_lo=d.get("redline_lo"),
)
@@ -110,6 +112,9 @@ def _pid_to_dict(p):
"confidence": p.confidence})
if p.round is not None:
d["round"] = p.round
for z in ("warn_hi", "redline_hi", "warn_lo", "redline_lo"):
if getattr(p, z) is not None:
d[z] = getattr(p, z)
if p.notes:
d["notes"] = p.notes
return d