Wave 9 #119.b: Phase 2 capability mapping layer (35 rules + pure-Python mapper + 30 pytest tests) - #110
Open
bernardc6 wants to merge 1 commit into
Open
Wave 9 #119.b: Phase 2 capability mapping layer (35 rules + pure-Python mapper + 30 pytest tests)#110bernardc6 wants to merge 1 commit into
bernardc6 wants to merge 1 commit into
Conversation
…on mapper + 30 pytest tests) Context: Build the canonical capability mapping layer that turns raw Home Assistant entity_ids into the canonical RoamCore capability ids declared in connections/_schema/canonical_capabilities.json (the #119 schema primitive). The mapping is declarative and the engine is a pure-Python module that any other extension can import and call. Principle served: GOLDEN.md §'Product principles' #3 (Victron-centric capability-driven tiles, not hand-configured) + §'Engineering principles' #7 (rc-entity- naming contract) + the doctrine in the slice spec ('must-not-fail + super-intuitive + critical infrastructure'). Changes: * New connections/_schema/mapping_rules.json (35 rules, ~250 LOC) — declarative rules (id, source_pattern, canonical_capability, weight, description) covering every declared canonical capability across all 6 categories (power / lighting / climate / water / position / network). Includes Victron, Renogy, generic, and self-mapping rules. * New homeassistant/custom_components/roamcore/capability_mapper.py (~440 LOC, stdlib + json only, no Home Assistant runtime imports) — exports load_mapping_rules, load_capability_schema, resolve_entity_to_capability, map_entities, apply_mapping_rules, validate_mapping_rules. Auto-recovers on unknown entity_ids (returns None + logs to unmatched list — never crashes the dashboard). Idempotent (deterministic weight-then-alphabetical tie-break). Consumes vehicle_model.load_capabilities (no rewrite). * New homeassistant/custom_components/roamcore/tests/test_capability_mapper.py (~750 LOC, 30 pytest tests) — covers the slice-spec golden test (Victron voltage → rc_power_battery_voltage at confidence ≥ 0.7), unknown-entity auto-recover, conflict resolution (highest weight wins, alphabetical tie-break), validation rejection of broken rules (bad regex, bad capability id, weight out of range, missing keys, duplicate rule ids), rc-naming compliance cross-cutting sweep, the 50-entity real-world mapping covering all 15 canonical capabilities with 0 unmapped required entries, 1000-call idempotency sweep, and input-purity (no mutation of the rules / schema docs). * New scripts/checks/capability-mapping-smoke.sh (~310 LOC, 10+ inline assertions + pytest + rule-sweep belt-and-braces guards + no-regression probe) — runs the mapper, asserts no secrets / live URLs, validates the rc-naming pattern on every rule's canonical_capability, asserts the 50-entity batch covers every declared canonical capability with 0 unmapped required entries, asserts validation rejects crafted bad rules, and probes both test_vehicle_model.py + canonical-capabilities-smoke.sh for no regression. * New docs/reference/rc-capability-mapping.md (~80 LOC, IKEA 5-step, no file paths / no PR numbers / no internal jargon) — user-facing reference explaining the 'RoamCore automatically organises whatever you plug in' behaviour in plain English. * scripts/check.sh: +9 lines to wire scripts/checks/capability-mapping-smoke.sh into the --core-only chain as a run_if_present step (additive, no other touches). Verification: * python3 -m pytest homeassistant/custom_components/roamcore/tests/ -q → 73 passed (43 existing test_vehicle_model.py + 30 new test_capability_mapper.py) * bash scripts/checks/capability-mapping-smoke.sh → 10+ inline OK lines + 30 pytest tests passed + rule-sweep belt-and-braces OK + no-regression probe OK * bash scripts/check.sh --core-only (clean main baseline) → GREEN before my changes; with my changes the new smoke step adds a green step in the chain (all existing smokes still pass, no regression). * Sample spot-check: resolve_entity_to_capability( 'sensor.victron_smartshunt_battery_voltage') -> ('rc_power_battery_voltage', 'victron_battery_voltage', 0.95) ✓ * Auto-recover: resolve_entity_to_capability('sensor.unknown_xyz') -> None ✓ * Idempotency: 1000 consecutive map_entities calls on the same input return identical output ✓ Rollback: revert the single commit (all changes additive, no other files touched). All 6 new files can be removed with rm + git rm; the single check.sh hunk is a self-contained 9-line block right after the canonical-capabilities-smoke.sh line. User-facing: When I plug in a new device, RoamCore automatically knows whether it's a battery, solar panel, water sensor, or anything else — no matter which brand it is.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Wave 9 #119 ships the canonical vehicle model schema. #119.b is the mapping layer that turns raw Home Assistant entity_ids into the canonical capability ids declared in
connections/_schema/canonical_capabilities.json. Without this layer, every consumer (dashboard, OpenClaw, Trip Wrapped) would have to know about Victron / Renogy / iOverlander / Tailscale individually. With it, they only ever seerc_*contract ids.Principle served: GOLDEN.md §'Product principles' #3 ("Victron-centric power MVP — auto-discovery + capability-driven tiles. Don't hand-configure.") + §'Engineering principles' #7 ("Naming follows
rc-entity-naming.md— Home Assistant entities use the canonical convention") + the slice-spec doctrine ("must-not-fail + super-intuitive + critical infrastructure").Changes (additive, minimal, no rewrite of unrelated files)
connections/_schema/mapping_rules.json(NEW, 253 LOC, 35 rules) — declarative rules (id,source_pattern,canonical_capability,weight,description) covering every declared canonical capability across all 6 categories: power (battery / solar / shore), lighting (interior + approach), climate (indoor temp + HVAC), water (fresh + pump), position (lat + lon), network (internet + WAN IP). Includes Victron, Renogy, generic, and self-mapping rules. Verified by the slice-spec's rc-naming compliance check + the cross-cutting sweep that every rule targets a real canonical_capability id.homeassistant/custom_components/roamcore/capability_mapper.py(NEW, 444 LOC) — pure-stdlib + json Python module that consumesvehicle_model.load_capabilities(no rewrite of vehicle_model). Exposesload_mapping_rules,load_capability_schema,resolve_entity_to_capability,map_entities,apply_mapping_rules,validate_mapping_rules. Auto-recovers on unknown entity_ids (returns None + logs to the unmatched list — never crashes the dashboard). Idempotent (deterministic weight-then-alphabetical tie-break on conflicts).homeassistant/custom_components/roamcore/tests/test_capability_mapper.py(NEW, 755 LOC, 30 pytest tests) — covers the slice-spec golden test (sensor.victron_smartshunt_battery_voltage→rc_power_battery_voltageat confidence ≥ 0.7), unknown-entity auto-recover, conflict resolution (highest weight wins, alphabetical tie-break), validation rejection of broken rules (bad regex, bad capability id, weight out of range, missing keys, duplicate rule ids), rc-naming compliance cross-cutting sweep, the 50-entity real-world mapping covering all 15 canonical capabilities with 0 unmapped required entries, 1000-call idempotency sweep, and input-purity (no mutation of rules / schema).scripts/checks/capability-mapping-smoke.sh(NEW, 311 LOC, 10+ inline assertions + pytest + rule-sweep belt-and-braces guards + no-regression probe) — wired intoscripts/check.sh --core-onlyas arun_if_presentstep. Asserts no secrets / live URLs in the rules file or mapper module, validates the rc-naming pattern on every rule's canonical_capability, asserts the 50-entity batch covers every declared canonical capability with 0 unmapped required entries, asserts validation rejects crafted bad rules, and probestest_vehicle_model.py+canonical-capabilities-smoke.shfor no regression.docs/reference/rc-capability-mapping.md(NEW, 53 LOC, IKEA 5-step, plain English) — user-facing reference explaining the "RoamCore automatically organises whatever you plug in" behaviour. No file paths, no PR numbers, no internal jargon.scripts/check.sh(+9 lines, additive) — wires the new smoke into the--core-onlychain.Verification
python3 -m pytest homeassistant/custom_components/roamcore/tests/ -q→ 73 passed (43 existingtest_vehicle_model.py+ 30 newtest_capability_mapper.py).bash scripts/checks/capability-mapping-smoke.sh→ 10+ inline OK assertions + 30 pytest tests + rule-sweep belt-and-braces + no-regression probe, all green.resolve_entity_to_capability('sensor.victron_smartshunt_battery_voltage')→('rc_power_battery_voltage', 'victron_battery_voltage', 0.95)✓resolve_entity_to_capability('sensor.unknown_xyz')→None✓map_entitiescalls on the same input return identical output ✓canonical_capabilitystarts withrc_✓canonical_capabilityexists in canonical_capabilities.json ✓Rollback
Revert the single commit (
f747bff). All changes are additive — 5 new files + a 9-linescripts/check.shblock — so the revert is self-contained and has no side effects on other slices.User-facing
Tier
tier-a (RoamCore Certified): the slice ships a real Python module (
homeassistant/custom_components/roamcore/capability_mapper.py) that other extensions can import and call, bench-tested by 30 pytest tests on real entity_ids. Per GOLDEN.md Engineering principle E1 ("Customer-facing repo — code must be honest, real, and bench-tested"), the tier-a bar is honestly met.