Skip to content

Commit b8a6134

Browse files
Fix deep sleep metric detection by sorting synonym patterns by specificity
- Sort metric patterns by word count to prioritize multi-word metrics (deep sleep, sleep score, heart rate) over single-word metrics (sleep, steps) - Prevents 'sleep' pattern from matching 'deep sleep' queries - Applied fix to both Kotlin (SlotExtractor.kt) and Swift (SlotExtractor.swift) for platform parity
1 parent 3d14578 commit b8a6134

File tree

2 files changed

+60
-12
lines changed

2 files changed

+60
-12
lines changed

examples/whisper.android/app/src/main/java/com/whispercppdemo/intent/SlotExtractor.kt

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ class SlotExtractor {
389389
private val distanceRegex = Regex("\\b(?:far|distance|km|kilometers|kilometre|kilometres|mile|miles|meter|meters|metre|metres|feet|ft|yard|yards|yd|long|length|covered|travelled|traveled|route|path|journey|span|range|how far|mileage)\\b", RegexOption.IGNORE_CASE)
390390
private val sleepRegex = Regex("\\b(?:sleep|slept|sleeping|asleep|nap|napped|napping|rest|rested|resting|snooze|snoozed|snoozing|doze|dozed|dozing|slumber|bedtime|night|overnight|bed|zzz)\\b", RegexOption.IGNORE_CASE)
391391
private val sleepQualityRegex = Regex("\\b(?:quality|score|rating|rate|well|badly|good|bad|poor|deep|light|efficiency|grade|rank|analysis|performance|how well)\\b", RegexOption.IGNORE_CASE)
392+
private val deepSleepRegex = Regex("\\b(?:deep\\s+sleep|deep\\s+sleeping|deep\\s+slumber|rem\\s+sleep|rem|slow\\s+wave\\s+sleep|sws|restorative\\s+sleep|profound\\s+sleep|heavy\\s+sleep|sound\\s+sleep|stage\\s+(?:3|4|three|four))\\b", RegexOption.IGNORE_CASE)
392393
private val heartRegex = Regex("\\b(?:heart|cardiac|cardio|cardiovascular|pulse|beat|beats|beating|bpm|rhythm|ticker)\\b", RegexOption.IGNORE_CASE)
393394
private val caloriesRegex = Regex("\\b(?:calorie|calories|kcal|energy|burn|burned|burnt|burning|expend|expended|consume|consumed|intake|kilojoule|kilojoules|kj|food energy|metabolic|metabolism|fat)\\b", RegexOption.IGNORE_CASE)
394395
private val oxygenRegex = Regex("\\b(?:oxygen|o2|spo2|saturation|sat|blood oxygen|pulse ox|oximeter|oximetry|breathing|respiratory|respiration|air|breathe)\\b", RegexOption.IGNORE_CASE)
@@ -539,22 +540,31 @@ class SlotExtractor {
539540
}
540541

541542
private fun extractMetric(processedText: String, originalText: String): String? {
543+
// Sort metrics by specificity (multi-word metrics first) to avoid false matches
544+
// e.g., "deep sleep" should be checked before "sleep"
545+
val sortedMetrics = synonymPatterns.entries.sortedByDescending { it.key.split(" ").size }
546+
542547
// Direct synonym matching on processed text first
543-
for ((metric, pattern) in synonymPatterns) {
548+
for ((metric, pattern) in sortedMetrics) {
544549
if (pattern.containsMatchIn(processedText)) {
545550
return metric
546551
}
547552
}
548553

549554
// Fallback to original text
550-
for ((metric, pattern) in synonymPatterns) {
555+
for ((metric, pattern) in sortedMetrics) {
551556
if (pattern.containsMatchIn(originalText)) {
552557
return metric
553558
}
554559
}
555560

556561
// Context-based inference with expanded patterns
557562

563+
// Deep Sleep context - check BEFORE general sleep
564+
if (deepSleepRegex.containsMatchIn(originalText)) {
565+
return "deep sleep"
566+
}
567+
558568
// Walking/Movement context
559569
if (walkingMovementRegex.containsMatchIn(originalText)) {
560570
return if (distanceRegex.containsMatchIn(originalText)) {
@@ -2438,17 +2448,30 @@ class SlotExtractor {
24382448
Regex("\\benergy\\s+(?:consumption|used|expended)\\b", RegexOption.IGNORE_CASE),
24392449
Regex("\\bhow\\s+much\\s+(?:did\\s+I\\s+)?burn\\b", RegexOption.IGNORE_CASE)
24402450
),
2451+
"deep sleep" to listOf(
2452+
Regex("\\bdeep\\s+sleep\\b|\\bdeep\\s+sleeping\\b|\\bdeep\\s+slumber\\b", RegexOption.IGNORE_CASE),
2453+
Regex("\\brem\\s+sleep\\b|\\brem\\b", RegexOption.IGNORE_CASE),
2454+
Regex("\\bdeep\\s+(?:rest|phase|stage)\\b", RegexOption.IGNORE_CASE),
2455+
Regex("\\b(?:stage\\s+)?(?:3|4|three|four)(?:\\s+sleep)?\\b", RegexOption.IGNORE_CASE),
2456+
Regex("\\bslow\\s+wave\\s+sleep\\b|\\bsws\\b", RegexOption.IGNORE_CASE),
2457+
Regex("\\brestorative\\s+sleep\\b", RegexOption.IGNORE_CASE),
2458+
Regex("\\bhow\\s+(?:much|long|well).*deep\\s+sleep\\b", RegexOption.IGNORE_CASE),
2459+
Regex("\\bdeep\\s+sleep\\s+(?:time|duration|hours|quality|data)\\b", RegexOption.IGNORE_CASE),
2460+
Regex("\\b(?:total|nightly)\\s+deep\\s+sleep\\b", RegexOption.IGNORE_CASE),
2461+
Regex("\\bhours?\\s+(?:of\\s+)?deep\\s+sleep\\b", RegexOption.IGNORE_CASE),
2462+
Regex("\\bprofound\\s+sleep\\b|\\bheavy\\s+sleep\\b|\\bsound\\s+sleep\\b", RegexOption.IGNORE_CASE)
2463+
),
24412464
"sleep" to listOf(
24422465
Regex("\\bsleep\\b|\\bslept\\b|\\bsleeping\\b|\\basleep\\b", RegexOption.IGNORE_CASE),
24432466
Regex("\\brest\\b|\\brested\\b|\\bresting\\b", RegexOption.IGNORE_CASE),
2444-
Regex("\\bhow\\s+(?:much|long|well).*sleep\\b", RegexOption.IGNORE_CASE),
2467+
Regex("\\bhow\\s+(?:much|long|well).*sleep\\b(?!.*deep)", RegexOption.IGNORE_CASE),
24452468
Regex("\\b(?:last\\s+)?night'?s\\s+sleep\\b|\\btonight'?s\\s+sleep\\b", RegexOption.IGNORE_CASE),
2446-
Regex("\\bsleep\\s+(?:time|duration|hours|quality|data|tracking|pattern)\\b", RegexOption.IGNORE_CASE),
2447-
Regex("\\b(?:total|nightly)\\s+sleep\\b", RegexOption.IGNORE_CASE),
2448-
Regex("\\bhours?\\s+(?:of\\s+)?sleep\\b|\\bslept\\s+(?:for\\s+)?\\d+\\s+hours?\\b", RegexOption.IGNORE_CASE),
2449-
Regex("\\b(?:deep|light|rem)\\s+sleep\\b", RegexOption.IGNORE_CASE),
2469+
Regex("\\bsleep\\s+(?:time|duration|hours|quality|data|tracking|pattern)\\b(?!.*deep)", RegexOption.IGNORE_CASE),
2470+
Regex("\\b(?:total|nightly)\\s+sleep\\b(?!.*deep)", RegexOption.IGNORE_CASE),
2471+
Regex("\\bhours?\\s+(?:of\\s+)?sleep\\b(?!.*deep)|\\bslept\\s+(?:for\\s+)?\\d+\\s+hours?\\b", RegexOption.IGNORE_CASE),
2472+
Regex("\\blight\\s+sleep\\b", RegexOption.IGNORE_CASE),
24502473
Regex("\\bnap\\b|\\bnapped\\b|\\bnapping\\b|\\bsnooze\\b", RegexOption.IGNORE_CASE),
2451-
Regex("\\bsleep\\s+(?:score|rating|efficiency|quality)\\b", RegexOption.IGNORE_CASE)
2474+
Regex("\\bsleep\\s+(?:score|rating|efficiency|quality)\\b(?!.*deep)", RegexOption.IGNORE_CASE)
24522475
),
24532476
"weight" to listOf(
24542477
Regex("\\bweight\\b|\\bweigh\\b|\\bweighing\\b|\\bweighed\\b", RegexOption.IGNORE_CASE),

examples/whisper.swiftui/whisper.swiftui.demo/Intent/SlotExtractor.swift

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ class SlotExtractor {
389389
private let distanceRegex = try! NSRegularExpression(pattern: "\\b(?:far|distance|km|kilometers|kilometre|kilometres|mile|miles|meter|meters|metre|metres|feet|ft|yard|yards|yd|long|length|covered|travelled|traveled|route|path|journey|span|range|how far|mileage)\\b", options: [.caseInsensitive])
390390
private let sleepRegex = try! NSRegularExpression(pattern: "\\b(?:sleep|slept|sleeping|asleep|nap|napped|napping|rest|rested|resting|snooze|snoozed|snoozing|doze|dozed|dozing|slumber|bedtime|night|overnight|bed|zzz)\\b", options: [.caseInsensitive])
391391
private let sleepQualityRegex = try! NSRegularExpression(pattern: "\\b(?:quality|score|rating|rate|well|badly|good|bad|poor|deep|light|efficiency|grade|rank|analysis|performance|how well)\\b", options: [.caseInsensitive])
392+
private let deepSleepRegex = try! NSRegularExpression(pattern: "\\b(?:deep\\s+sleep|deep\\s+sleeping|deep\\s+slumber|rem\\s+sleep|rem|slow\\s+wave\\s+sleep|sws|restorative\\s+sleep|profound\\s+sleep|heavy\\s+sleep|sound\\s+sleep|stage\\s+(?:3|4|three|four))\\b", options: [.caseInsensitive])
392393
private let heartRegex = try! NSRegularExpression(pattern: "\\b(?:heart|cardiac|cardio|cardiovascular|pulse|beat|beats|beating|bpm|rhythm|ticker)\\b", options: [.caseInsensitive])
393394
private let caloriesRegex = try! NSRegularExpression(pattern: "\\b(?:calorie|calories|kcal|energy|burn|burned|burnt|burning|expend|expended|consume|consumed|intake|kilojoule|kilojoules|kj|food energy|metabolic|metabolism|fat)\\b", options: [.caseInsensitive])
394395
private let oxygenRegex = try! NSRegularExpression(pattern: "\\b(?:oxygen|o2|spo2|saturation|sat|blood oxygen|pulse ox|oximeter|oximetry|breathing|respiratory|respiration|air|breathe)\\b", options: [.caseInsensitive])
@@ -569,22 +570,31 @@ class SlotExtractor {
569570

570571

571572
private func extractMetric(processedText: String, originalText: String) -> String? {
573+
// Sort metrics by specificity (multi-word metrics first) to avoid false matches
574+
// e.g., "deep sleep" should be checked before "sleep"
575+
let sortedMetrics = synonymPatterns.sorted { $0.key.components(separatedBy: " ").count > $1.key.components(separatedBy: " ").count }
576+
572577
// Direct synonym matching on processed text first
573-
for (metric, pattern) in synonymPatterns {
578+
for (metric, pattern) in sortedMetrics {
574579
if pattern.numberOfMatches(in: processedText, options: [], range: NSRange(location: 0, length: processedText.count)) > 0 {
575580
return metric
576581
}
577582
}
578583

579584
// Fallback to original text
580-
for (metric, pattern) in synonymPatterns {
585+
for (metric, pattern) in sortedMetrics {
581586
if pattern.numberOfMatches(in: originalText, options: [], range: NSRange(location: 0, length: originalText.count)) > 0 {
582587
return metric
583588
}
584589
}
585590

586591
// Context-based inference with expanded patterns
587592

593+
// Deep Sleep context - check BEFORE general sleep
594+
if deepSleepRegex.numberOfMatches(in: originalText, options: [], range: NSRange(location: 0, length: originalText.count)) > 0 {
595+
return "deep sleep"
596+
}
597+
588598
// Walking/Movement context
589599
if walkingMovementRegex.numberOfMatches(in: originalText, options: [], range: NSRange(location: 0, length: originalText.count)) > 0 {
590600
return distanceRegex.numberOfMatches(in: originalText, options: [], range: NSRange(location: 0, length: originalText.count)) > 0 ? "distance" : "steps"
@@ -2464,10 +2474,25 @@ class SlotExtractor {
24642474
"\\bfat\\s+burn|metabolic|metabolism\\b",
24652475
"\\bexpended|consumed|intake\\b"
24662476
],
2477+
"deep sleep": [
2478+
"\\bdeep\\s+sleep\\b|\\bdeep\\s+sleeping\\b|\\bdeep\\s+slumber\\b",
2479+
"\\brem\\s+sleep\\b|\\brem\\b",
2480+
"\\bdeep\\s+(?:rest|phase|stage)\\b",
2481+
"\\b(?:stage\\s+)?(?:3|4|three|four)(?:\\s+sleep)?\\b",
2482+
"\\bslow\\s+wave\\s+sleep\\b|\\bsws\\b",
2483+
"\\brestorative\\s+sleep\\b",
2484+
"\\bhow\\s+(?:much|long|well).*deep\\s+sleep\\b",
2485+
"\\bdeep\\s+sleep\\s+(?:time|duration|hours|quality|data)\\b",
2486+
"\\b(?:total|nightly)\\s+deep\\s+sleep\\b",
2487+
"\\bhours?\\s+(?:of\\s+)?deep\\s+sleep\\b",
2488+
"\\bprofound\\s+sleep\\b|\\bheavy\\s+sleep\\b|\\bsound\\s+sleep\\b"
2489+
],
24672490
"sleep": [
2468-
"\\bsleep|slept|sleeping|rest|rested\\b",
2491+
"\\bsleep|slept|sleeping|rest|rested\\b(?!.*deep)",
24692492
"\\bnap|napped|napping|slumber\\b",
2470-
"\\bbedtime|night\\s+sleep|sleep\\s+time\\b"
2493+
"\\bbedtime|night\\s+sleep|sleep\\s+time\\b(?!.*deep)",
2494+
"\\bhow\\s+(?:much|long|well).*sleep\\b(?!.*deep)",
2495+
"\\blight\\s+sleep\\b"
24712496
],
24722497
"sleep score": [
24732498
"\\bsleep\\s+(?:quality|score|rating|performance|analysis|efficiency)\\b",

0 commit comments

Comments
 (0)