Files
nickol-knx-mcp/tools/corpus_baseline.json
Nikolay MiroshnichenkoandClaude Opus 5 a3f436fc74 generate_ha: one climate entity per physical device, fail closed when devices cannot be told apart
Found on a real 1312-GA house: a room with floor heating, a convector and an AC unit became
ONE climate (convector setpoint, AC controller mode, AC fan speed as the valve). Room 1.09
matched 2.09 and a central "09." GA, "Kids room 1" took "Kids room 2 temperature", and valves
came from light brightness and blind position statuses. The zone was the room alone: device
words were stripped as qualifiers, "А/С" vanishes in the tokenizer, the floor digit of "1.09"
is dropped.

Now a climate is assembled per room AND device type (floor, wall, radiator/convector, fan
coil, AC; RU/EN/DE words, removed from the zone as whole words):
- room code "N.NN" only at the start of a name, and it must match on both sides;
- standalone numbers of the anchor must be in a device member; a shared room sensor's
  numbers must be among the anchor's; digits in dotted codes and values do not count;
- an untyped GA is shared room data: any role if the room has one device, otherwise only
  the current temperature, which several devices may reuse; with a room code the room's
  untyped sensor qualifies even without the device's extra words;
- a word that sets another device of the room apart keeps its GAs away;
- control roles must be unambiguous: more than one candidate -> review climate_ambiguous;
- two mode GAs that cannot be told apart -> review climate_duplicate_anchor, not dropped;
- only 20.102 / 20.105 anchor; valve needs a valve word; AC never gets a valve;
- stable address order, so results never depend on parse order.

Two gates. Own audit on seven real projects, every changed entity inspected: no control GA
used by two climates, no address lost; house 16 -> 33 climates (one per device), manual
review 33 -> 12; flat 7 -> 9; HDL 3 -> 5; demo 6 -> 13; villa 23 -> 21 with the 3 it cannot
disambiguate sent to review instead of guessed. LLM council (three models + devil's
advocate, sanitised packet): accept with hardening; its P0s (same-type devices, silent
first pick) and P1s (mid-name codes, dotted digits, non-102/105 anchors, generic valve
words) are all in this commit, with tests E and F covering its failure catalogue.
Out of scope, recorded: identically named rooms of different flats without room codes, and
lexical identity in general -> device-channel identity ("lever 0").

Also works around a mypy 2.3.1 parse error on a comment right after a compound if.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:20:49 +02:00

422 lines
10 KiB
JSON

{
"projects": {
"demo": {
"categories": {
"diagnostics": 5,
"energy": 4,
"hvac": 78,
"lighting": 91,
"scene": 4,
"sensor": 30,
"shutter": 24,
"unknown": 2
},
"devices": 0,
"file_digest": "77273cacf3f9",
"findings_by_code": {
"central_macro_no_status": 2,
"duplicate_name": 1,
"inconsistent_dpt": 1,
"missing_dpt": 1,
"missing_status_address": 2,
"missing_value_status": 1,
"scene_no_status": 4,
"subdpt_suspect": 1
},
"findings_by_severity": {
"error": 1,
"info": 6,
"warning": 6
},
"group_addresses": 238,
"ha_entities": {
"binary_sensor": 13,
"climate": 13,
"cover": 6,
"light": 25,
"sensor": 25,
"switch": 6
},
"ha_review_items": 55,
"suggestion_hints": {
"channels": 0,
"diagnostics_skipped": 0,
"duplicates_skipped": 0,
"fallback": 0,
"pseudo_channels": 0,
"review": 0,
"sensors": 0,
"skipped_fb_covered": 0,
"structural": 0,
"subunits": 0,
"unwired_flagged": 0
},
"suggestions_by_platform": {}
},
"hdl-ets6": {
"categories": {
"diagnostics": 5,
"hvac": 206,
"lighting": 425,
"sensor": 34,
"shutter": 135,
"unknown": 54
},
"devices": 53,
"file_digest": "a360d74c65ef",
"findings_by_code": {
"duplicate_name": 156,
"inconsistent_dpt": 15,
"line_without_coupler": 1,
"missing_dpt": 107,
"missing_status_address": 92,
"missing_value_status": 132,
"name_too_short": 1,
"relative_only_dimming": 20,
"reserve_without_dpt": 81,
"status_pairing_summary": 1
},
"findings_by_severity": {
"error": 107,
"info": 83,
"warning": 416
},
"group_addresses": 859,
"ha_entities": {
"binary_sensor": 66,
"climate": 5,
"cover": 1,
"expose": 1,
"light": 180,
"sensor": 51,
"switch": 42
},
"ha_review_items": 402,
"suggestion_hints": {
"channels": 229,
"diagnostics_skipped": 2,
"duplicates_skipped": 4,
"fallback": 0,
"pseudo_channels": 151,
"review": 41,
"sensors": 37,
"skipped_fb_covered": 0,
"structural": 80,
"subunits": 0,
"unwired_flagged": 29
},
"suggestions_by_platform": {
"binary_sensor": 14,
"light": 79,
"sensor": 23,
"switch": 1
}
},
"mixed-small": {
"categories": {
"hvac": 58,
"lighting": 95,
"sensor": 2,
"unknown": 12
},
"devices": 14,
"file_digest": "bfaf0f366105",
"findings_by_code": {
"central_macro_no_status": 5,
"line_without_coupler": 1,
"missing_dpt": 21,
"missing_status_address": 4,
"missing_value_status": 1,
"status_pairing_summary": 1
},
"findings_by_severity": {
"error": 21,
"info": 7,
"warning": 5
},
"group_addresses": 167,
"ha_entities": {
"expose": 1,
"light": 29,
"sensor": 19,
"switch": 3
},
"ha_review_items": 68,
"suggestion_hints": {
"channels": 52,
"diagnostics_skipped": 0,
"duplicates_skipped": 0,
"fallback": 0,
"pseudo_channels": 23,
"review": 7,
"sensors": 23,
"skipped_fb_covered": 0,
"structural": 7,
"subunits": 0,
"unwired_flagged": 1
},
"suggestions_by_platform": {
"binary_sensor": 8,
"climate": 6,
"light": 1,
"sensor": 15
}
},
"shutters-medium": {
"categories": {
"diagnostics": 9,
"hvac": 162,
"lighting": 93,
"sensor": 9,
"shutter": 36,
"unknown": 40
},
"devices": 34,
"file_digest": "8ed1f5a53c9c",
"findings_by_code": {
"central_macro_no_status": 2,
"dpt_mismatch_co": 1,
"duplicate_name": 55,
"inconsistent_dpt": 2,
"missing_dpt": 42,
"missing_status_address": 64,
"missing_value_status": 1,
"relative_only_dimming": 4,
"status_pairing_summary": 1
},
"findings_by_severity": {
"error": 42,
"info": 3,
"warning": 127
},
"group_addresses": 349,
"ha_entities": {
"binary_sensor": 5,
"cover": 8,
"expose": 1,
"light": 10,
"sensor": 87,
"switch": 48
},
"ha_review_items": 202,
"suggestion_hints": {
"channels": 135,
"diagnostics_skipped": 8,
"duplicates_skipped": 4,
"fallback": 0,
"pseudo_channels": 106,
"review": 61,
"sensors": 38,
"skipped_fb_covered": 0,
"structural": 65,
"subunits": 0,
"unwired_flagged": 2
},
"suggestions_by_platform": {
"binary_sensor": 13,
"climate": 2,
"cover": 4,
"light": 59,
"sensor": 25
}
},
"zennio-flat": {
"categories": {
"diagnostics": 47,
"energy": 39,
"hvac": 201,
"lighting": 179,
"scene": 4,
"sensor": 70,
"shutter": 49,
"unknown": 96
},
"devices": 41,
"file_digest": "d980991b0f9e",
"findings_by_code": {
"central_macro_no_status": 1,
"dpt_mismatch_co": 11,
"dpt_on_logic_object": 1,
"duplicate_name": 6,
"inconsistent_dpt": 2,
"line_without_coupler": 1,
"missing_dpt": 6,
"missing_status_address": 11,
"missing_value_status": 8,
"reserve_without_dpt": 23,
"safety_input_no_status": 22,
"status_pairing_summary": 1,
"subdpt_suspect": 2
},
"findings_by_severity": {
"error": 6,
"info": 49,
"warning": 40
},
"group_addresses": 685,
"ha_entities": {
"binary_sensor": 49,
"climate": 9,
"cover": 7,
"expose": 1,
"light": 75,
"sensor": 106,
"switch": 79
},
"ha_review_items": 287,
"suggestion_hints": {
"channels": 179,
"diagnostics_skipped": 21,
"duplicates_skipped": 4,
"fallback": 0,
"pseudo_channels": 6,
"review": 57,
"sensors": 70,
"skipped_fb_covered": 0,
"structural": 79,
"subunits": 71,
"unwired_flagged": 0
},
"suggestions_by_platform": {
"binary_sensor": 35,
"climate": 13,
"cover": 4,
"light": 56,
"sensor": 35,
"switch": 6
}
},
"zennio-house-1312": {
"categories": {
"diagnostics": 47,
"energy": 14,
"hvac": 388,
"lighting": 509,
"scene": 1,
"sensor": 123,
"shutter": 91,
"unknown": 139
},
"devices": 88,
"file_digest": "25d93b4dfb30",
"findings_by_code": {
"central_macro_no_status": 1,
"dpt_mismatch_co": 4,
"duplicate_name": 25,
"inconsistent_dpt": 3,
"line_without_coupler": 1,
"missing_dpt": 9,
"missing_status_address": 35,
"missing_value_status": 1,
"reserve_without_dpt": 25,
"safety_input_no_status": 32,
"status_pairing_summary": 1,
"subdpt_suspect": 2
},
"findings_by_severity": {
"error": 9,
"info": 60,
"warning": 70
},
"group_addresses": 1312,
"ha_entities": {
"binary_sensor": 88,
"climate": 33,
"cover": 11,
"expose": 1,
"light": 232,
"sensor": 119,
"switch": 95
},
"ha_review_items": 559,
"suggestion_hints": {
"channels": 305,
"diagnostics_skipped": 40,
"duplicates_skipped": 15,
"fallback": 0,
"pseudo_channels": 3,
"review": 118,
"sensors": 102,
"skipped_fb_covered": 0,
"structural": 178,
"subunits": 201,
"unwired_flagged": 0
},
"suggestions_by_platform": {
"binary_sensor": 82,
"climate": 35,
"cover": 9,
"light": 105,
"sensor": 20,
"switch": 29
}
},
"zennio-large": {
"categories": {
"diagnostics": 150,
"energy": 1,
"hvac": 802,
"lighting": 1748,
"scene": 2,
"sensor": 225,
"shutter": 357,
"unknown": 361
},
"devices": 275,
"file_digest": "d305fb55d52e",
"findings_by_code": {
"central_macro_no_status": 26,
"dpt_mismatch_co": 2,
"dpt_on_logic_object": 3,
"duplicate_name": 25,
"line_without_coupler": 1,
"missing_dpt": 32,
"missing_status_address": 58,
"missing_value_status": 38,
"reserve_without_dpt": 169,
"safety_input_no_status": 47,
"scene_no_status": 2,
"status_pairing_summary": 1,
"topology_segment_limit": 1
},
"findings_by_severity": {
"error": 32,
"info": 250,
"warning": 123
},
"group_addresses": 3646,
"ha_entities": {
"binary_sensor": 89,
"climate": 21,
"cover": 40,
"expose": 1,
"light": 412,
"sensor": 350,
"switch": 319
},
"ha_review_items": 1577,
"suggestion_hints": {
"channels": 983,
"diagnostics_skipped": 53,
"duplicates_skipped": 157,
"fallback": 0,
"pseudo_channels": 328,
"review": 220,
"sensors": 367,
"skipped_fb_covered": 0,
"structural": 513,
"subunits": 309,
"unwired_flagged": 20
},
"suggestions_by_platform": {
"binary_sensor": 254,
"climate": 28,
"cover": 92,
"light": 356,
"sensor": 113,
"switch": 37
}
}
}
}