Skip to content

fix(quick-router): keep a named qualifier on a dryer status target - #846

Merged
matedev01 merged 2 commits into
GeniePod:mainfrom
rsnetworkinginc:fix/dryer-status-keeps-qualifier
Jul 27, 2026
Merged

fix(quick-router): keep a named qualifier on a dryer status target#846
matedev01 merged 2 commits into
GeniePod:mainfrom
rsnetworkinginc:fix/dryer-status-keeps-qualifier

Conversation

@rsnetworkinginc

@rsnetworkinginc rsnetworkinginc commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

The dryer branch of home_status_target canonicalizes every match to the bare laundry "dryer", discarding any qualifier the caller named — so a question about the hair dryer in the bathroom returns a status readout for the laundry dryer, a different appliance in a different room, with no indication the assistant answered about something else. Every sibling device branch in the same function already keeps the qualifier. Closes #845.

utterance on main expected
is the hair dryer on home_status{entity:"dryer"} home_status{entity:"hair dryer"}
is the hand dryer on home_status{entity:"dryer"} home_status{entity:"hand dryer"}
is the basement dryer on home_status{entity:"dryer"} home_status{entity:"basement dryer"}

For contrast, the sibling branches on main today: is the kitchen plug on"kitchen plug", is the side door locked"side door", is the front gate closed"front gate", is the garage freezer too warm"garage freezer".

Changes

  • Return the canonical "dryer" only for an unqualified target, and pass a qualified one through — the same behaviour the switches, thermostat, covers, locks, lights and freezer branches already have.
  • The sibling split_whitespace().count() == 1 test is not directly reusable here: " done" is not one of the STATUS_SUFFIXES that clean_status_target strips, so is the dryer done cleans to the two-word target "dryer done" and a plain count check would emit that garbled entity where main correctly emits "dryer". Key on the device word's position instead — a qualifier precedes the device (hair dryer), a leftover state word trails it (dryer done) — which distinguishes the two cases and, as a bonus, still canonicalizes the "drying machine" synonym since it does not end in the device word.
  • Add dryer_status_keeps_a_named_qualifier: three qualified devices keep their qualifier; the bare device, check the dryer, the drying machine synonym and the dryer done state-word form all still resolve to "dryer".

One branch body changed; every other path in home_status_target is untouched.

Real Behavior Proof

  • I have built and run the affected code locally (or noted why I could not).
  • I have verified the change end-to-end on Jetson hardware.
  • I have NOT verified on Jetson hardware, and I explain the equivalent verification path or validation gap below.

Tested profile / hardware (check all that apply):

  • jetson
  • raspberry_pi
  • portable_sbc
  • laptop
  • mac
  • CI-only / docs-only
  • Not run locally

What I ran

x86_64 Linux dev machine (laptop profile), rustc 1.94.0, branch cut from 7c67906. The changed path is pure string routing inside quick::route — no audio, Home Assistant, or hardware dependency — so route() tests exercise it end-to-end. I probed route() over qualified/unqualified dryer phrasings and the sibling branches against unmodified main first, then wrote the regression test and confirmed it fails before applying the fix.

# 1. repro against unmodified main (fix reverted, test kept)
cargo test -p genie-core --lib dryer_status_keeps_a_named_qualifier

# 2. after the fix — the full local CI set
cargo fmt --all -- --check
RUSTFLAGS="-D warnings" cargo clippy --workspace --all-targets --locked -- -D warnings
GENIE_RUN_RELEASE_TESTS=1 cargo build --workspace --locked --all-targets
GENIE_RUN_RELEASE_TESTS=1 cargo test  --workspace --locked --all-targets
cargo test --workspace --locked --doc
cargo run --locked -p genie-ctl -- bfcl-predict-quick --cases tests/bfcl/home_tool_cases.jsonl --out target/bfcl-live-predictions.jsonl
cargo run --locked -p genie-ctl -- bfcl-score --cases tests/bfcl/home_tool_cases.jsonl --predictions target/bfcl-live-predictions.jsonl --min-strict 96
RUSTFLAGS="-D warnings" cargo clippy -p genie-core -p genie-ctl --no-default-features --all-targets --locked -- -D warnings
cargo test -p genie-core -p genie-ctl --no-default-features --all-targets --locked

What I observed

Probe on unmodified main:

is the hair dryer on            -> home_status {"entity":"dryer"}
is the hand dryer on            -> home_status {"entity":"dryer"}
is the towel dryer on           -> home_status {"entity":"dryer"}
is the basement dryer on        -> home_status {"entity":"dryer"}
is the dryer done               -> home_status {"entity":"dryer"}
is the drying machine on        -> home_status {"entity":"dryer"}
# sibling branches, same utterance shape, qualifier preserved:
is the kitchen plug on          -> home_status {"entity":"kitchen plug"}
is the side door locked         -> home_status {"entity":"side door"}
is the garage freezer too warm  -> home_status {"entity":"garage freezer"}

Repro before the fix:

assertion `left == right` failed: "is the hair dryer on"
  left: String("dryer")
 right: "hair dryer"
test result: FAILED. 0 passed; 1 failed

After the fix: hair dryer / hand dryer / basement dryer keep their qualifier; is the dryer on, check the dryer, is the drying machine on and is the dryer done all still return "dryer", byte-identical to main.

Gate results on this branch:

  • cargo fmt --all -- --check — clean.
  • cargo clippy --workspace --all-targets --locked -- -D warnings — clean; --no-default-features clippy for genie-core/genie-ctl — clean.
  • cargo test --workspace --locked --all-targetstools::quick 98 passed / 0 failed, genie-core lib 938 passed / 0 failed / 6 ignored, every other target green.
  • cargo test --workspace --locked --doc — green.
  • BFCL live quick-router accuracy gatestrict_accuracy: 96.15% (25/26) against the --min-strict 96 floor, the single holdout being the documented two-call multi-homework-timer case (quick-router multi-intent prompts emit a single, wrong-priority tool #533). Byte-identical to main.
  • --no-default-features test for genie-core/genie-ctl — green.

binary_size_budget fails identically on unmodified main with my local rustc 1.94.0 (6.80 MB against the 6.8 MB budget), so it is a toolchain artifact on my box rather than a regression here. I measured both sides to be sure: main = 7,131,736 bytes, this branch = 7,131,160 bytes — 576 bytes smaller.

Jetson gap: no Jetson hardware and no aarch64 cross toolchain on this box (cargo check --target aarch64-unknown-linux-gnu stops at cc-rs: failed to find tool "aarch64-linux-gnu-gcc"), so the cross build is left to the Cross-compile (aarch64 / Jetson) CI job. The change is a pure &str token comparison with no platform-dependent behaviour, no I/O, and no new dependency, so it is architecture-independent by construction.

Test plan

  1. cargo test -p genie-core --lib dryer_status_keeps_a_named_qualifier — passes here, fails on main.
  2. cargo test -p genie-core --lib tools::quick — the other 97 router tests, including the sibling cover_and_gate_status_match_whole_words_not_substrings and status_entity_drops_both_state_word_and_time_qualifier, are unchanged.

Notes for reviewers

  • The cleanest long-term fix is probably to add " done" (and friends) to STATUS_SUFFIXES and then use the plain sibling word-count test everywhere. I deliberately did not do that here: STATUS_SUFFIXES is shared by every device branch, so widening it is a much larger blast radius than this one-branch bug warrants, and it deserves its own PR with its own regression sweep. Happy to follow up with it if you'd like.
  • Scoped to the dryer branch only. The neighbouring package/delivery branch canonicalizes unconditionally in the same way, but its two keywords are not device names a user qualifies by room, so the exposure is different and I left it alone.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed dryer status query handling to preserve caller-provided device qualifiers (e.g., “hair dryer”, “hand dryer”, “basement dryer”) instead of collapsing everything to a generic “dryer.”
    • Maintained existing routing for unqualified, synonym, and state-extended phrases (e.g., “dryer done” still maps to “dryer”).
  • Tests
    • Added a regression test verifying qualified versus unqualified dryer phrase routing behavior.

@github-actions github-actions Bot added the bug Something isn't working label Jul 23, 2026
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dbf7932c-e20a-49c3-b259-bd28c6c7839c

📥 Commits

Reviewing files that changed from the base of the PR and between 3aa3f24 and fb7df18.

📒 Files selected for processing (1)
  • crates/genie-core/src/tools/quick.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/genie-core/src/tools/quick.rs

📝 Walkthrough

Walkthrough

The home_status dryer routing preserves named qualifiers such as “hair dryer” while canonicalizing bare, synonymous, and state-extended dryer queries. Regression coverage validates these routing cases.

Changes

Dryer status routing

Layer / File(s) Summary
Preserve qualified dryer targets and validate canonicalization
crates/genie-core/src/tools/quick.rs
The dryer target logic retains qualifiers before whole-word dryer/dryers, while bare “dryer”, “drying machine”, and state-extended forms remain mapped to dryer. Regression tests cover the routing behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: bug

Suggested reviewers: matedev01

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: preserving named dryer qualifiers in quick-router.
Linked Issues check ✅ Passed The code preserves qualified dryer entities while keeping bare dryer and synonym/state cases canonicalized as required by #845.
Out of Scope Changes check ✅ Passed The PR stays focused on the dryer branch and its regression test, with no obvious unrelated changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@matedev01

Copy link
Copy Markdown
Member

This now conflicts with main in crates/genie-core/src/tools/quick.rs — several other quick-router fixes landed just ahead of this one. Please rebase onto main and push; I'll review once it's mergeable.

The dryer branch of home_status_target canonicalized every match to the
bare laundry "dryer", so "is the hair dryer on" and "is the basement
dryer on" both reported a different appliance in a different room than
the one asked about. Every sibling branch (switches, thermostat, covers,
locks, lights, freezer) already keeps the qualifier the caller named.

Key on the device word position rather than a plain word count: a
qualifier precedes the device ("hair dryer") while a leftover state word
trails it ("dryer done", since " done" is not a STATUS_SUFFIXES entry),
so the bare device, the "drying machine" synonym and the state-word form
all still canonicalize to "dryer".

@matedev01 matedev01 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the dryer branch canonicalized every match to the bare laundry 'dryer', so 'is the hair dryer on' / 'is the basement dryer on' reported a different appliance in a different room than the one asked about. Keys on word position (qualifier precedes the device word vs. a trailing state word like 'done' follows it) to correctly distinguish a genuine qualifier from the 'dryer done' edge case — nice catch that a plain word-count test would have broken that. Verified: clippy -D warnings clean, quick-router tests (102) pass, fmt clean, live BFCL strict_accuracy 96.15% (no regression).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] quick-router: "is the hair dryer on" reports the laundry dryer — the dryer branch drops the qualifier every sibling keeps

2 participants