Commit Graph
47 Commits
Author SHA1 Message Date
Nikolay Miroshnichenko aea59066ba suggest.py: structure-first entity suggestions prototype (HA SuggestionProvider #2 contract)
- channel -> object flags (write=sink, transmit=source, dual ignored) -> DPT pattern
  (cover, climate, light/switch, sensors) -> vendor object texts -> names as tie-break
- FB-covered channels skipped (also in fallback); shared status GAs excluded;
  duplicate writers deduped; pseudo-channels for channel-less devices; unwired flagged
- tests/test_suggest.py: FB-provider nameless fixture reproduced from structure (CI)
- tools/eval_suggest.py: agreement vs name/Function engine on real projects
- CHANGELOG (Unreleased/Added, experimental)
2026-09-10 12:18:49 +02:00
Nikolay Miroshnichenko a32b1970f3 check_policy: example profile seeded from the loaded project's own main groups (issue #13)
- example_policy_yaml(project): mains written with inferred domain, range name and
  category mix; no-majority mains commented-out with their mix; defaults only as a
  labelled comment; reserve.expect_range follows the project; empty/2-level -> {}.
- taxonomy_seed() is the single source for _infer_taxonomy and the example.
- server.check_policy(write_example_to) passes the loaded project, reports seeded_from.
- tests/test_policy.py: cases 4-6 (no leaked mains, round-trip, mixed main, quotes, no project); added to CI.
- README/README.ru/CHANGELOG.
2026-09-09 20:03:32 +02:00
Nikolay MiroshnichenkoandClaude Fable 5.1 05a0658a3a ux: gentle real-project feedback nudge where people actually run the tool
Cloners install and run the tool but rarely reread the README, so the existing
'call for testers' never reaches them. Two low-noise touchpoints instead:
- project_report: a one-line footer linking the real-project test-report issue
  template (the report is the artefact a human reads after a real run);
- load_project: a short 'feedback' field on the result — the first call every
  new user makes.
Anonymised addresses explicitly welcome. No logic change; full suite green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 12:19:20 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 467772618e fix(#12 residual): recognise a shutter Function by its 1.008 move, not only a role token
Kris1166's real-file re-test of f20b2e4: main bug gone (light:0, all 29 RM → status) but
15/29 position feedbacks stayed lighting/not_mapped. Root cause (reproduced 1:1): those
actuator-level Functions carry blank or raw-GUID roles — no role token anywhere — so the
shutter-Function gate skipped them even though they hold a 1.008 up/down move. The
anomaly (2/3/1 promoting while its step/stop stayed unknown) is explained: its move has a
role; its step/stop is DPT 1.009 'Enable' (the reporter's own modeling slip, not a step).

- A shutter Function is now recognised by a shutter ROLE token OR by an UNCONTESTED 1.008
  move member (category==shutter; a 1.008 whose name pins another domain must not license
  its neutral siblings — gate-1 audit).
- is_step includes DPT 1.017 (trigger), which the cover builder already treats as a stop.
Gate-1: APPROVED; 0 category/kind/platform drift across 5 real .knxproj (1965 GA); the new
fixtures fail on the old code. Gate-2: the LLM council already endorsed the sibling-DPT
(1.008 move) signal as the primary structural evidence — this implements it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 21:33:25 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 f20b2e4126 fix(#12): recognize a '… RM' (Rückmeldung) shutter feedback as the cover's position status, not a light
Field data by Kris1166 (issue #12). A DPT-5.001 shutter position feedback named only '… RM'
was classified lighting/light and emitted as a bogus HA light entity. Two layers:
- classification (project.py): an ETS Function carrying a shutter role promotes a member to
  shutter — but only on POSITIVE evidence (member is a step/stop 1.007/1.010 OR a 5.001 position
  STATUS, the Function has an up/down move sibling, and the name has no explicit non-shutter
  domain), so a scene-recall/dimmer-feedback/lock sharing a mixed Function is left alone
  (LLM-council review; a Function is a free grouping, not proof of domain).
- status recognition (project.py name_is_status): a standalone 'RM'/'FB' token is now a status —
  the 'rm '/'rm_' substrings and the stopword tokenizer both missed a name ending in bare 'RM'.
- analyze._is_status_ga delegates to name_is_status; generate_ha then wires it as the cover's
  position_state_address.
Passed both gates (self-audit + LLM-council). tests/test_rm_status.py pins it (red/green + negatives:
a light's RM stays light, a stray light / a scene-recall in a shutter Function are not promoted).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 10:02:06 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 5c87169129 harden(#11): operation!=domain gate, deterministic tiebreak, no central-stop steal (LLM-council P0 + gate-1)
Post-fix hardening from an LLM-council review of 0198ae3, with a gate-1 audit pass:
- _is_shutter_control rejects a bare 1.007/1.010 whose tokens are a foreign domain
  (light/dim/socket/HVAC) or a central macro (Alle/Zentral) — a 'stop' is an operation
  signal, not a domain one, so a lighting 'Licht Stopp' or a house-wide 'Alle Stopp' can
  no longer be mis-paired/stolen as a cover's step/stop.
- _rank gains a stable address tiebreak -> deterministic pick across parses on a full tie.
- gate-1 caught 'master' wrongly excluding Master-zone covers; removed. Vent words kept
  admissible (roof-window/flap openers are covers).
- tests groups E/F/G pin the foreign-stop, central-steal, and Master-zone cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 15:01:25 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 0198ae3019 fix(#11): pair every cover to its own step/stop across all 3 real ETS Function structures
Second pass on issue #11, grounded in Kris1166's real ETS-6 field dump (groups A/B/C),
after the first fix (dd8ecac) proved partial. Three levers:

- project.py: ETS Function role (MoveUpDown/StopStepUpDown) authoritatively promotes a
  GA to category=shutter — rescues bare DPT 1.007 step/stops the name classifier left
  'unknown' (group A: 26/33 covers were dropped). Guarded to DPT 1.x/5.x; role strings
  from Kris's real dump, not invented.
- generate_ha.py: cover builder now gathers candidates and picks the BEST per role
  (same-function > name-token overlap > nearest sub) instead of first-match, so a
  function-less roller takes its own step/stop, not an awning's sharing the zone token
  (group C). Bare 1.007/1.010 admitted only with a shutter name signal, so a foreign
  lighting step can't be stolen (audit finding).
- tests: test_cover_pairing.py rebuilt from the real A/B/C structures + a group-D guard
  for the foreign-1.007 case. Fails without the fix, passes with it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 13:13:59 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 dd8ecac090 fix(ha): pair a cover's step/stop within its ETS Function, never across it (issue #11)
generate_ha_package cross-wired move_short_address across unrelated ETS
Functions sharing a zone token (a window and an awning both 'Room A'), and
could leave a cover with no step/stop. Pair siblings by ETS Function
membership (authoritative); a sibling owned by a different function is never
borrowed. Falls back to the existing name-token logic only when the move GA
has no function, so projects without ETS Functions are unchanged.

Adds tests/test_cover_pairing.py (reproduces the field case from #11, fails
without the fix) and wires it into CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 11:01:54 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 c1e9d206c5 feat: fold topology findings into project_report (section 2.4 + totals)
detect_topology_issues now surfaces in the human-review project_report markdown
(new '2.4 Topology & addressing' section) and its severities count into the
report totals — closes the audit's deferred LOW (per CLAUDE.md the human reviews
project_report before ETS import, so topology issues must be visible there).
Functional check: Poltavskiy report renders the coupler INFO; suite 27 passed
(pre-existing test_protocol collection error untouched).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 22:14:11 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 8312d531eb feat: check_topology — per-line device count + individual-address validity (KNX canon)
New design-time check grounded in the KNX standard (KNX Handbook + xknx via
deepwiki). detect_topology_issues() + check_topology() MCP tool (30->31 tools),
folded into analyze_all counters:
- devices per line vs TP1 segment 64 / line 256 (KNX Handbook p.36/40/55) — TP-only
- individual address valid A.L.D (area 0-15, line 0-15, device 0-255; xknx-confirmed)
- duplicate individual address -> error; missing .0 coupler on a multi-line TP -> info
Medium-gated on the real xknxproject string ('Twisted Pair (TP)'), so IP/PL/RF
lines are never falsely flagged (fixed a dead 'medium==IP' guard caught in audit;
regression test added). senior-code-audit: APPROVED. Functional smoke on 2 real
projects: IP lines clean, TP coupler/count notes correct. 64/256 cited to the
Handbook, not attributed to xknx. tests/test_topology.py 7/7; suite 27 passed
(pre-existing unrelated test_protocol collection error untouched).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 21:41:14 +02:00
Nikolay1andClaude Opus 4.8 bf7eba8898 room-library: Theben-benchmark corrections (dogfood on 5 official ETS examples)
Ran KNX Association-ecosystem vendor (Theben) 5 official ETS example .knxproj
through our QA. Findings folded back into the product:

- repair.py _infer_dpt: relative dimming (Brighter/darker, heller/dunkler,
  светлее/темнее) now infers 3.007 DPT_Control_Dimming instead of falling to
  1.001; explicit '% / value' stays 5.001; scene checked before the broad
  value branch. DPTs verified against XKNX/xknx via deepwiki.
- room_library.py: new reusable blocks distilled from vendor patterns —
  shutter_venetian (slat + slat-status + wind/frost safety-lock), climate_floor
  modulating actuating-value + status, ventilation (airing stage + CO2 +
  humidity), lighting_dali, multisensor_air, central scene/all-off/all-on.
- project.py classifier: airing/Lüftung/проветривание -> hvac (was unknown).
- analyze.py: safety_input_no_status INFO exemption for write-only wind/frost
  lock inputs (Theben central wind-safety pattern).
- SCHEMA.md: new function rows; automation_intents implementation: knx|external
  with safety_related => knx rule; DPT-tolerance note (missing-DPT hard error
  applies to generated output; ETS4 object-derived DPT on import is legitimate).
- templates: ventilation slot (bathroom/kitchen), opt-in venetian + wind/frost
  safety intent (living/bedroom), new house-level central.yaml.
- tests: +test_theben_corrections.py (10); golden bedroom 26->28 GA (climate
  actuating-value). pytest 20 passed (pre-existing unrelated test_protocol
  collection error untouched). senior-code-audit: APPROVED.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 11:21:18 +02:00
Nikolay1andClaude Opus 4.8 bdb93cd128 room-library R1: register validate_room_template + compose_rooms MCP tools
Two new tools (28 -> 30): validate_room_template checks a built-in or custom
template against the R1 schema; compose_rooms builds a new project from a room
list and returns the allocation manifest, ETS GA XML/CSV (via the existing
generators on the re-read project), and a device BOM proposal. New projects
only, dry-run by default; writes artifacts into the confined workspace when
dry_run=false and output_dir is given. Never touches a bus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 21:26:23 +02:00
Nikolay1andClaude Opus 4.8 ebc03f7118 room-library R1: template format + resolver/IR + composer core
Add the Room Template Library core: the public YAML template contract
(schema_version, locale-neutral semantic slot_id, ru/en labels, per-slot
basic/comfort presets, area_m2 as a provenance-tagged hint) with 6 built-in
rooms (bedroom, children, living, kitchen, bathroom, corridor) + SCHEMA.md.

room_library.py implements a function-first pipeline: templates+params ->
resolved IR dataclasses (NOT GARecord) -> deterministic, permutation-invariant
allocation (main=domain, middle=role, sub sequential; hard error on sub
exhaustion) -> a real .knxproj ZIP that is re-read via load_project (same path
as third-party projects, so generation never touches the classifier). Includes
validate_room_template, compose(), manifest + device BOM proposal builders.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 21:26:11 +02:00
Nikolay1andClaude Opus 4.8 4a9dd5ce4a fix(generate_ha): Russian function words in identity + exact-identity outranks subset
Dogfooding the voice layer exposed two pairing defects: (1) _FUNC_WORDS had no
Russian entries, so 'вкл/выкл/цвет/яркость' polluted device identity and an RGBW
colour GA could not find its own switch; (2) subset identity matching let
'RGBW подсветка яркость' {zone, подсветка} grab 'камин подсветка' by subset and
its status by tie-break. Sibling pick and status_for_dpt now prefer EXACT
identity equality before subset fallback. 13/13 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 12:31:37 +02:00
Nikolay1andClaude Opus 4.8 6cabea08af fix(P5/policy): dogfood round 3 — location stopwords, greedy-term pruning, reserve check
- location phrases no longer vote a domain: "Boiler room light" is a light in
  the boiler room, not an HVAC function (location stoplist stripped first).
- dropped the bare "температур" hvac term: a temperature GA takes its domain
  from context (уставк/кондиц/тёпл words or its main group), so "Гостиная
  температура" in a Sensors main is a sensor, not an hvac actuator outlier.
- "online/offline/heartbeat/watchdog/связь" are diagnostics terms (checked
  first), so actuator-monitoring GAs named after the load they watch stay diag.
- check_policy: central macros ("Весь свет выкл") are never taxonomy outliers;
  policy_no_reserve now also fires under an INFERRED taxonomy when no reserve
  main exists at all; "весь "/"мастер" added to central-macro tokens.

Messy-house dogfood now reports exactly the planted misplacements (3/3) and the
reserve gap; demo 0 outliers. 13/13 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:36:50 +02:00
Nikolay1andClaude Opus 4.8 ccfedbdeca fix(P5): dogfood round 2 — presence is a sensor, 9.001 soft, passive-range rule
- DPT 1.018 occupancy: category sensor (was diagnostics; the name always says
  sensor and the table fought it into a fake conflict).
- 9.001 temperature is SOFT: room temperature defaults to hvac, but a weather-
  named temperature re-domains to sensor without a fabricated conflict.
- illuminance terms: "освещённость"/"illuminance" are sensor words and must not
  be swallowed by the greedy lighting prefix "освещ".
- passive-range rule: a measurement inside a Sensors/Energy/Diagnostics main
  takes that main's domain; a COMMAND is never retyped by a passive range.

Deliberately NOT tuned further: remaining real-project findings (Minsk 10/685,
Razdory 75/3646, demo 2/239) are defensible mixed-main observations a reviewer
should see once — zeroing them out would overfit one integrator's idiosyncrasy.
13/13 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:33:20 +02:00
Nikolay1andClaude Opus 4.8 3c086fe228 fix(P5): classifier dogfood round — range-map depth bug, fallback-DPT softness, RU terms
Building a deliberately messy synthetic house instantly exposed four real gaps:

- _build_range_name_map: a middle group 0 starts at the same raw address as its
  main, so the modulo heuristic let "Вкл/Выкл" clobber "Освещение" and the
  main-range rescue silently died. Depth in the range tree decides now.
- a category from a whole-main DPT fallback (e.g. 20.609 -> hvac) was treated as
  strong; only an exact (main,sub) table entry is strong now (is_exact_dpt).
- DPT 1.010 start/stop marked soft: ventilation timers use it, not just shutters.
- terms learned: Cyrillic а/с, сплит, вытяжк, яркост (RU only); weather/метео in
  sensor, and sensor now outranks hvac so weather-station temperature is a
  sensor, not an HVAC actuator.

Real-project noise after the round: Minsk 1 outlier/685 GAs (was 31 pre-P5),
demo 2/239, Razdory 46/3646. Regressions in test_domain_classifier. 13/13 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:27:22 +02:00
Nikolay1andClaude Opus 4.8 e9105d45aa feat(P5): contextual domain classification — DPT is one signal, not the verdict
A 1-bit switch is domain-agnostic, yet DPT 1.001 defaulted to 'lighting', so an
"AC on/off" was classified lighting and tripped downstream checks. The domain is
now a combination: explicit name > strong domain-encoding DPT > main-group name;
a bare 1-bit GA with no signal is honestly 'unknown', never a guessed lighting.
An explicit name contradicting a STRONG DPT yields 'unknown' (a genuine conflict,
shown by explain_ga as contested), not a silent pick. Word-boundary matching so
"ac" no longer fires inside "terrace"; name terms broadened (EN+RU HVAC incl.
ac/a-c/air-conditioner, RGB/colour lighting).

check_policy taxonomy-outlier now flags only misplaced ACTUATORS (lighting/
shutter/hvac); sensors, scene links, thresholds and timeout params are the
cross-cutting GAs they are. HA generator still emits a switch for a now-HVAC
simple on/off, so no device is dropped.

Verified on 3 real projects: every AC on/off is HVAC; outlier noise fell demo
11->2, Minsk 31->6, Razdory 200->57. tests/test_domain_classifier.py (13/13 green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 10:44:04 +02:00
Nikolay1andClaude Opus 4.8 1a60264c78 feat(P7b): explainable aggregate scores — expose denominator + formula
Matter readiness, the completeness grade and command/status coverage each now
carry a `math` block: numerator, denominator, the exact formula that reproduces
the percentage, and (for Matter) the functions excluded from the denominator
because their category has no Matter cluster — previously a silent skip and the
biggest "why is this number what it is?" gap. Completeness also states its band
thresholds. Report-only, additive; scores unchanged, only now auditable.
Raised by external review. tests/test_explainable_aggregates.py (12/12 green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 10:15:40 +02:00
Nikolay1andClaude Opus 4.8 f68c85bb48 feat(P7a): harden parsing of untrusted .knxproj (zip-bomb / XXE defense)
A .knxproj is a user-supplied ZIP-of-XML — untrusted input. New safexml.py
centralises hardened parsing for every place we open an archive or parse XML
ourselves (load_project, check_device_parameters, app-program parser):

  XML  - dependency-free reject of DOCTYPE/ENTITY (billion-laughs / XXE;
         legitimate ETS XML never carries one) + defusedxml parser-level
         blocking when installed (added as a dependency).
  ZIP  - pre-flight against absolute caps (archive size, entry count, per-member
         and total decompressed size, compression ratio) rejects zip-bombs;
         member names checked for path-traversal / absolute paths; each member
         read through a streaming cap so a lying header can't exhaust memory.

Violations return a normal error dict, not a traceback. Still read-only.
Raised by external security review. tests/test_safexml.py (11/11 suite green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 10:11:38 +02:00
Nikolay1andClaude Opus 4.8 84c1c7dbd9 fix(P3): root-cause suppression — an empty-name GA no longer cascades
Council #2: the empty GA 2/5/2 spawned missing_status + policy_taxonomy_outlier +
a lighting classification on top of its real defect. An empty name means the
classifier can't be trusted, so detect_missing_status and check_policy now skip
blank-name GAs; the single root cause is empty_name (check_naming). Demo 2/5/2:
3 findings -> 1. tests/test_root_cause.py; full suite green (10 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:29:54 +02:00
Nikolay1andClaude Opus 4.8 10169275e3 feat(P1): explain_ga — provenance/confidence for one GA (27->28 tools)
Three independent reviewers (two external councils + a field integrator) asked for
the same thing: the enriched model mixes ETS facts, DPT structure and name
heuristics, and downstream tools treat it almost as fact. explain_ga makes the
reasoning auditable per GA — evidence per decision with a confidence tier
(authoritative ETS Function > structural DPT > heuristic name), the status-pairing
method, and CONFLICTS (name 'AC' vs DPT 'lighting' -> contested), the silent-
misclassification hotspot. Additive, read-only, no core-model change. Full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:27:10 +02:00
Nikolay1andClaude Opus 4.8 5036a19b4b feat(P2): role-aware feedback completeness — catch a value command with no value status
'Does the function have *a* status' missed a dimmer with on/off status but no
brightness status (demo planted error #3), silently inflating coverage/Matter.
detect_role_completeness flags a brightness/position command (5.001) whose device
has no matching value status (missing_value_status), with a device-identity match
so it doesn't borrow a sibling's status. Demo recall 4/5 -> 5/5. Raised by the
external expert review. Test + demo ground-truth updated. Full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:23:02 +02:00
Nikolay1andClaude Opus 4.8 c53f388dcb fix: council-review noise cuts — scenes/central-macros exempt, language-matched repairs
External expert review (real run on the demo house) flagged: scenes (18.001)
wrongly marked missing_status + an unsafe DPT-1.011 status repair for them;
'All blinds down' warned while 'All lights off' was correctly INFO; and a Russian
'(статус)' suffix synthesised on an English project. Fixes: scene_no_status INFO
+ no synth status for scenes; broadened central-macro detection (generic 'all
blinds/shutters/...'); language-matched repair suffixes. Demo missing_status
warnings ~7 -> 1 genuine. tests/test_council_fixes.py; full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:09:57 +02:00
Nikolay1andClaude Opus 4.8 1e924ede6f feat(param_check): significance layer — a focus list of config-value outliers
Rank findings by significance: config_value (setpoint/hysteresis/time/threshold —
an odd device is usually a real mistake) vs config_flag (mode/type) vs label
(per-room text, often intentional). New focus list surfaces the config-value clear
outliers first. On a real 275-device project this turns 422 raw outliers into a
9-item focus that includes the thermostats whose init-setpoint differs from their
siblings. Multilingual name hints (EN/DE/RU). Test updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:28:32 +02:00
Nikolay1andClaude Opus 4.8 81799169c5 feat: check_policy — Project Policy Profile (26->27 tools)
Validate a project against ITS OWN rules, not a universal standard. With a YAML
profile the declared main-group taxonomy / naming / pairing is authoritative;
with no profile the taxonomy is INFERRED from the project itself and GAs that
deviate from their main group's own majority domain are flagged — never against
an alien standard (a well-organised real 356-GA project drops from 329 false
mismatches vs the default to 31 genuine self-deviations). Answers the recurring
integrator critique that 'your best practices aren't universal'. Example profile
+ test_policy.py included.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:06:59 +02:00
Nikolay1andClaude Opus 4.8 4c36bc182e feat: check_device_parameters — cross-device parameter outlier QA (25->26 tools)
New module param_check.py + MCP tool check_device_parameters: reads per-device
ParameterInstanceRef values from the .knxproj project part (xknxproject does not
expose them), groups identical devices by application program, and flags the odd
one out — clear_outliers (a strong majority with a small minority, e.g. one
thermostat with a different setpoint/hysteresis) and split_configs (balanced
variants → review). Parameter names resolved from the app-program; encrypted
projects skipped honestly. Read-only, no ETS/bus. Validated on real 42-275-device
projects; synthetic test_param_check.py. Answers a community feature request.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 21:53:32 +02:00
Nikolay Miroshnichenko 74ac2ac830 feat(parser): read manufacturer names from the archive's knx_master.xml (dynamic map, static fallback) 2026-07-07 12:40:09 +02:00
Nikolay Miroshnichenko ff93d44166 feat: positional status pairing + self-reporting commands, app-program parser v2 (ComObjectRef merge, version pick), authoritative main names, grader range-name evidence (v0.8.0)
Closes #3, closes #4, closes #5, closes #6
2026-07-07 12:24:24 +02:00
Nikolay MiroshnichenkoandClaude Fable 5 5651be2930 feat: exact device models — local catalog for decompose_device + parse_devices_from_project (v0.7.0)
- device_library: NICKOL_KNX_CATALOG env points at a local device-library
  YAML file/dir; decompose_device returns the exact vendor object model
  (source: catalog-exact) and falls back to generic recipes
  (source: recipe-approximate). Env unset = behaviour unchanged.
- appprog_parser (new) + MCP tool parse_devices_from_project: deterministic
  extraction of exact comm-object models from M-* application programs in a
  .knxproj/.knxprod (order number via nested <Product>, DPST-x-y -> x.00y,
  per-channel block/stride detection, coverage manifest). Read-only,
  PII-safe: never reads the client P-*/0.xml. Now 25 MCP tools.
- tests: test_device_catalog.py + test_appprog_parser.py (synthetic,
  self-contained)
- docs: README/README.ru/docs site/announcements synced to v0.7.0, 25 tools

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 06:48:19 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 ab310b399a feat: matter/energy/diff/iot/completeness + climate & expose (v0.6.0)
Completes the roadmap. The tool now validates, repairs, generates
(HA/ETS/handover/IoT), diffs, grades and drafts acceptance protocols —
all design-time & read-only.

B-tier:
- B2 climate-correctness review note (controller/operation modes, setpoint
  shift cmd+state, mode-without-state flag) in generate_ha.py
- B3 semantic project diff (diffproj.py; diff_projects / diff_loaded)
- B4 per-function acceptance test protocol (advanced.py; generate_test_protocol)
- B5 Matter readiness (advanced.py; check_matter)
- B6 metering/energy DPT check + PV/battery/EVSE scaffold (advanced.py; check_energy)

C-tier:
- C1 KNX IoT Turtle/RDF export (iot.py; generate_knx_iot)
- C2 naming-hygiene suggestions (advanced.py; suggest_names)
- C3 as-built completeness grader (advanced.py; grade_completeness)

A-tier quick wins:
- A5 Areas/voice UI-only note; A6 time/date DPT-19.001 expose block (generate_ha.py)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:31:14 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 1e130bbe19 feat: repair-suggestion engine + relative-dim + cover-invert (v0.5.0)
From validator to repairer: propose concrete fixes, not just flag problems.

- B1 repair-suggestion engine (repair.py) + suggest_repairs MCP tool: infer a
  DPT for a missing-DPT GA, correct a suspect sub-DPT, synthesise a status GA in
  a free slot, or add an absolute-brightness GA. Suggestions only; accepted new
  GAs feed generate_ets_group_addresses; never writes to ETS or the bus.
- A2 relative-only-dimming detector (analyze.py): 3.007 relative dimmer with no
  5.001 absolute-brightness GA in its zone -> HA cannot set a level.
- A3 cover invert/travel-time surfacing (generate_ha.py): verify_cover_invert
  review note lists actuator-dependent flags absent from the .knxproj and warns
  when position lacks a state address.
- A2/A3/B1 regression tests; CHANGELOG + version bump to 0.5.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 19:37:26 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 44e1ae3aa7 feat: sub-DPT linter + KNX Secure posture (v0.4.0)
A1 — sub-DPT sanity linter: when a GA name implies a specific DPT
sub-type (temperature->9.001, power->14.056, brightness/position->5.001),
flag a wrong sub or wrong main. Multilingual, conservative; surfaced as
the subdpt_suspect finding via check_dpt / analyze_all.

A4 — KNX Data Secure posture: secure_posture() + new check_secure MCP
tool. Report-only summary (secured vs plaintext counts, mixed
secure/plaintext middle groups, keyring handover checklist). Reads only
the per-GA Security flag; no key material touched. Handover pack section
5 rewritten to this posture section.

Version bump to 0.4.0; CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 19:26:28 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 b16492eb68 feat(handover): itemise QA findings (addresses + names), not just totals
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 18:51:01 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 d7df3dce60 feat: device-library decompose_device + spec→structure methodology (v0.3.0)
Turn the tool from a .knxproj validator into a design aid.

- device_library.py + decompose_device / list_device_recipes MCP tools:
  expand a device (order number / type / alias) into its group-address
  recipe — command/status/dimming/position/mode objects with DPTs —
  across Zennio + ABB families. Generic vendor facts, typical-wired set.
- docs/spec-to-structure.md: the spec→structure methodology, with a
  measured account of what a spec reproduces (~90%) vs the per-device
  object count it cannot (2–9× per-project parameterisation).
- Ship alongside the Track B generate_handover_pack and the de-noise
  refinements accumulated since 0.2.0.
- CHANGELOG 0.3.0; version bump 0.2.0 → 0.3.0; new device-library tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 18:33:08 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 2f2b8b1f91 fix(handover): read domain names from GroupRanges, not mislabeled GARecord.main_name
Main/middle range names now resolve correctly (e.g. [1] Освещение 1 этаж, [5] Климат)
instead of a middle-group name leaking into the main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:54:44 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 a621ced701 feat(handover): generate_handover_pack — as-built commissioning deliverable (Track B)
Assembles handover.md (equipment inventory, GA-domain map, feedback coverage,
KNX Secure scope, QA state) + standalone topology.svg + group-addresses.csv +
ha-package.yaml from a read-only .knxproj. Reuses existing analysis passes.
Regression test covers all 7 sections, inventory, summary and well-formed SVG.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:17:07 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 87f065dde7 feat(analyze): de-noise [LF] objects, central macros, separator scratch
Three false-alarm downgrades derived from validating on a larger multi-vendor
villa, each turning a false positive into an INFO note without hiding any real
defect:

- intent: divider/separator names (punctuation-only or a marker wrapped in it,
  e.g. "-----addition------", "---") classify as `scratch`, so a missing DPT on
  them is INFO, not a red error.
- analyze: typed GAs wired into Zennio "[LF] ... Data Entry" type-agnostic
  logic-function containers surface as INFO `dpt_on_logic_object` instead of a
  false `dpt_mismatch_co` warning.
- analyze: all-groups broadcast/central-macro commands surface as INFO
  `central_macro_no_status` instead of `missing_status_address`, since a fan-out
  broadcast has no single state to read back.

Adds a regression block covering all three; real DPT mismatches and real
missing-status gaps still warn. Unreleased; no version bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 13:55:37 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 44d7226bf1 chore(release): v0.2.0 — colour/climate assembly + GA-intent noise reduction
Bump version 0.1.2 -> 0.2.0 (pyproject + __init__). Promote the
Unreleased changelog section to [0.2.0] — 2026-06-30. Scrub a client
city label from two test comments (provenance note only, no PII/topology).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:28:37 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 514caa5fd2 feat: colour lights + climate entities + status-pairing fix (Track A)
Colour control assembled into light entities (RGB 232.600, RGBW 251.600,
xyY 242.600, colour-temp 7.600) with statuses, matched by zone identity.
Climate entities generated from HVAC mode zones (temp 9.001 + target status
+ operation/controller mode 20.102/20.105 + valve command_value 5.001),
emitted only when HA-required keys are present, else routed to review.
B1 fix: a command no longer borrows a sibling's status (identity must nest,
status maps to exactly one entity) — removed all shared-status addresses.
New DPTs 232.600/251.600/242.600/7.600/20.105/1.100. HA keys verified vs docs.

Real files: signed demo 6 climate + 4 colour lights; Zennio 685-GA 7 climate;
zero shared-status; all 10 regression groups pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 20:05:59 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 2ffb6d371f feat: GA-intent classification to cut real-project noise (Track D)
Classify every group address as functional/reserve/logic/scratch (intent.py)
and reclassify intentional non-functional GAs instead of crying wolf:
- reserve spare with no DPT -> INFO reserve_without_dpt (not a missing_dpt error)
- reserve name reused across DPTs -> no duplicate_name / inconsistent_dpt
- logic/virtual + scratch GAs -> excluded from missing-status warnings
dpt_mismatch_co and all real functional findings are untouched.

Driven by a real 685-GA Zennio project: false errors 29 -> 6,
missing-status noise 79 -> 45, all 12 real dpt_mismatch_co catches preserved.
Intent breakdown now exposed via list_group_addresses, report inventory and
the analyze_all summary. New regression test covers the reserve/logic/scratch
patterns and proves real DPT + status problems still surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 19:48:28 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 64bb4c87e6 HA mapping: full dimmable lights, wider shutters, datetime DPTs
Round-2 fixes found by running against more public ETS4/5/6 fixtures (yene
DemoCase, KnxProjParser, knxray, open-knxviewer):
- Lights assemble both on/off and brightness status via identity-based pairing
  and fold the on/off command into one light entity (no duplicate switch),
  pairing even when device identity is a single name token. Switches too.
- Cover up/down recognised on 1.008 or any 1.x named up/down; stop on
  1.007/1.010/1.017 or a stop-name. Siblings attach only to the same shutter
  (zone-identity guard) so multiple blinds don't cross-wire.
- Date/time/text DPTs (10/11/16/19) route to review (manual_datetime/manual_text).
- Add regression tests for all three.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:26:09 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 1fc64a7a89 Polish shutter slats and diagnostics alarms (HA mapping)
- Venetian slat GAs (Lamelle/slat/tilt) attach to their parent blind cover as
  move_short_address instead of becoming standalone covers; unmatched slats are
  flagged shutter_slat_unattached for manual attachment.
- 1-bit diagnostics GAs (wind/frost/smoke/leak alarm, fault) become a read-only
  binary_sensor instead of a phantom command switch.
- Add regression tests for both.

Backlog item #6 from VALIDATION-FINDINGS, surfaced by real ETS fixtures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 10:49:24 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 53e5630d45 HA: no silent drops + smarter shutter classification
- generate_ha now lists every unmapped GA in `review` instead of dropping it
  silently ("no silent caps"); YAML header reports the review count.
- Shutter category recognises German/directional names (Behang, Lamelle,
  auf/ab, Raffstore, Markise, RU equivalents). Shutter-looking commands with an
  incomplete DPT get an actionable `shutter_incomplete_dpt` review hint instead
  of vanishing.
- Add regression tests for both.

Backlog items #4 and #5 from VALIDATION-FINDINGS, surfaced by real ETS fixtures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 10:42:22 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 29d39dc147 Wire up ETS Function role pairing for command/status
Implement pairing.function_status_pairs(): pair command<->status GAs from ETS
Function roles (SwitchOnOff<->InfoOnOff etc.), used by check_missing_status and
the HA generator. Fixes false "missing status" on function-paired commands and
gives HA entities the correct state_address even when the feedback GA is named
only "Status". Add a regression test.

Found via real-ETS6 fixture testing: Functions were previously ignored despite
the README claiming they were the primary pairing signal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 10:35:34 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 1b0fe566f7 Fix infinite recursion in load_project MCP tool
The server tool function `load_project` shadowed the imported project loader,
so it called itself instead of the parser -> RecursionError on every real
.knxproj. Alias the import as `load_project_file` and call that.

Add a regression test (server.load_project on a bad path must not raise
RecursionError) and a CHANGELOG entry. Found via end-to-end testing against
real ETS5/ETS6 fixtures from XKNX/xknxproject.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 10:24:17 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 bba8befcde nickol-knx-mcp v0.1.0 — design-time KNX/ETS6 MCP server (public beta)
Design-time MCP server that reads .knxproj (read-only), validates naming/DPT/status,
and generates Home Assistant KNX YAML + ETS-importable group addresses (XML/CSV).
No live bus access — confined-workspace writes only.

Includes: 12 MCP tools, end-to-end smoke test, MIT license, English-first README
(+ Russian), CONTRIBUTING with a real-project test call, SECURITY policy, CHANGELOG,
GitHub Actions CI (Python 3.10–3.12), and issue/PR templates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 09:25:57 +02:00