Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions crates/genie-core/src/tools/quick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2571,10 +2571,16 @@ fn home_status_target(text: &str) -> Option<String> {
});
}

if contains_any(
&target,
&["lock", "locks", "door lock", "door locks", "door"],
) {
// Match the lock/door tokens as whole words, not substrings: a bare
// `contains_any` fired on "c[lock]" / "b[lock]" and "out[door]", so "is the
// clock on" misrouted to home_status "locks" instead of abstaining. Mirrors
// the ice/iron/cooktop/cover whole-word fixes above. The multi-word "door
// lock" / "door locks" entries are redundant once "lock"/"door" match as
// words, and are dropped.
if target
.split_whitespace()
.any(|word| matches!(word, "lock" | "locks" | "door" | "doors"))
{
return Some(if target.split_whitespace().count() == 1 {
"locks".into()
} else {
Expand Down Expand Up @@ -5550,6 +5556,39 @@ mod tests {
}
}

#[test]
fn lock_and_door_status_match_whole_words_not_substrings() {
// The lock/door branch matched its tokens with a substring `contains_any`,
// so "c[lock]" / "b[lock]" and "out[door]" misrouted to home_status
// "locks" (or a garbled multi-word entity) instead of abstaining. Mirrors
// the ice/iron/cooktop/cover whole-word fixes.
for utterance in [
"is the clock on",
"is the wall clock right",
"is the block heater on",
] {
assert!(
route(utterance)
.map(|c| c.arguments.get("entity").and_then(|e| e.as_str()) != Some("locks"))
.unwrap_or(true),
"{utterance:?} must not resolve to the locks status entity"
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// Genuine lock/door queries still resolve: a bare token collapses to
// "locks", a named device keeps its full entity.
for (utterance, entity) in [
("are the doors locked", "locks"),
("is the door locked", "locks"),
("is the side door locked", "side door"),
("is the garage door closed", "garage door"),
] {
let call = route(utterance).unwrap_or_else(|| panic!("no route for {utterance:?}"));
assert_eq!(call.name, "home_status", "{utterance:?}");
assert_eq!(call.arguments["entity"], entity, "{utterance:?}");
}
}

#[test]
fn control_entity_drops_leading_indefinite_article() {
// clean_control_entity stripped a leading "the " but left "a"/"an", so
Expand Down
Loading