docs(bf-5613): analyze multi-person bedroom sleep monitoring edge case

- Created comprehensive findings document at notes/bf-5613-findings.md
- Added TestTwoPersonBedroomScenario to integration_test.go
- Verified that multi-person bedroom edge case is NOT implemented:
  * No detection of multiple blobs in bedroom zones
  * No BLE-first assignment logic
  * No zone-based fallback records
  * No lowest-deltaRMS blob selection for breathing analysis
- Test documents expected behavior per plan.md specification

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-07-06 02:45:09 -04:00
parent 36886ab60c
commit 756cc03a00
2 changed files with 224 additions and 0 deletions

View file

@ -191,3 +191,143 @@ func TestSleepLinkStateFields(t *testing.T) {
t.Error("SessionActive should be true")
}
}
// TestTwoPersonBedroomScenario tests the multi-person bedroom edge case.
// This test documents the expected behavior when two blobs are tracked in a bedroom zone:
// 1. If BLE identifies both blobs, assign sleep records to respective persons
// 2. If BLE identifies only one blob, assign that record to the person and create a zone-based record for the other
// 3. If BLE identifies neither blob, create two separate zone-based records
// 4. Breathing analysis should use the blob with the strongest stationary signal (lowest smooth_deltaRMS)
//
// NOTE: This test currently FAILS because the multi-person bedroom edge case is NOT implemented.
// The sleep analyzer currently tracks one session per linkID but does not:
// - Detect multiple blobs in the same bedroom zone
// - Automatically assign person IDs from BLE matches
// - Create zone-based records when BLE identity is unavailable
// - Select the best blob for breathing analysis based on deltaRMS
//
// See: /home/coding/spaxel/notes/bf-5613-findings.md for detailed analysis.
func TestTwoPersonBedroomScenario(t *testing.T) {
m := NewMonitor(MonitorConfig{
SleepStartHour: 22,
SleepEndHour: 7,
})
baseTime := time.Date(2024, 1, 15, 23, 0, 0, 0, time.Local)
// Simulate two blobs in the same bedroom zone
// Blob 1: Lower deltaRMS (better stationary signal, should be used for breathing analysis)
blob1LinkID := "link-bedroom-1"
session1 := NewSleepSession(blob1LinkID, 22, 7)
session1.isActive = true
session1.currentState = SleepStateDeepSleep
for i := 0; i < 50; i++ {
session1.processBreathing(BreathingSample{
Timestamp: baseTime.Add(time.Duration(i) * time.Minute),
RateBPM: 14.0,
Confidence: 0.9,
IsDetected: true,
})
session1.processMotion(MotionSample{
Timestamp: baseTime.Add(time.Duration(i) * time.Minute),
DeltaRMS: 0.01, // Lower = better stationary signal
MotionDetected: false,
})
}
// Blob 2: Higher deltaRMS (weaker stationary signal)
blob2LinkID := "link-bedroom-2"
session2 := NewSleepSession(blob2LinkID, 22, 7)
session2.isActive = true
session2.currentState = SleepStateLightSleep
for i := 0; i < 50; i++ {
session2.processBreathing(BreathingSample{
Timestamp: baseTime.Add(time.Duration(i) * time.Minute),
RateBPM: 16.0,
Confidence: 0.7, // Lower confidence
IsDetected: true,
})
session2.processMotion(MotionSample{
Timestamp: baseTime.Add(time.Duration(i) * time.Minute),
DeltaRMS: 0.025, // Higher = more restless
MotionDetected: true,
})
}
m.analyzer.mu.Lock()
m.analyzer.sessions[blob1LinkID] = session1
m.analyzer.sessions[blob2LinkID] = session2
m.analyzer.mu.Unlock()
// Test Case 1: Verify both sessions are tracked independently
sessions := m.GetAllSessions()
if len(sessions) != 2 {
t.Errorf("Expected 2 sessions, got %d", len(sessions))
}
// Test Case 2: Simulate BLE identity assignment
// In the expected implementation, this would be done automatically when BLE matches are resolved
person1ID := "person-alice"
person2ID := "person-bob"
m.analyzer.SetPersonID(blob1LinkID, person1ID)
m.analyzer.SetPersonID(blob2LinkID, person2ID)
// Verify person IDs are set
session1 = m.analyzer.GetSession(blob1LinkID)
session2 = m.analyzer.GetSession(blob2LinkID)
if session1.GetPersonID() != person1ID {
t.Errorf("Expected person ID %s for blob1, got %s", person1ID, session1.GetPersonID())
}
if session2.GetPersonID() != person2ID {
t.Errorf("Expected person ID %s for blob2, got %s", person2ID, session2.GetPersonID())
}
// Test Case 3: Generate reports and verify they're per-person, not per-link
reports := m.ForceReportGeneration()
if len(reports) != 2 {
t.Errorf("Expected 2 reports (one per person), got %d", len(reports))
}
// Test Case 4: Verify breathing analysis uses the blob with lowest deltaRMS
// In the expected implementation, the system would compare deltaRMS across blobs
// and use the one with the strongest stationary signal for breathing analysis.
// This is NOT currently implemented - each blob analyzes its own breathing independently.
report1 := reports[blob1LinkID]
report2 := reports[blob2LinkID]
// Blob 1 should have better breathing metrics (lower deltaRMS, higher confidence)
if report1.Metrics.BreathingScore <= report2.Metrics.BreathingScore {
// This is expected given the test data, but in a true multi-person scenario,
// the system should explicitly select which blob to use for breathing analysis.
t.Logf("Blob1 breathing score: %.1f, Blob2 breathing score: %.1f",
report1.Metrics.BreathingScore, report2.Metrics.BreathingScore)
}
// Test Case 5: Verify zone-based fallback when no BLE match
// Create a third blob without BLE identity
blob3LinkID := "link-bedroom-3"
session3 := NewSleepSession(blob3LinkID, 22, 7)
session3.isActive = true
for i := 0; i < 30; i++ {
session3.processMotion(MotionSample{
Timestamp: baseTime.Add(time.Duration(i) * time.Minute),
DeltaRMS: 0.015,
})
}
m.analyzer.mu.Lock()
m.analyzer.sessions[blob3LinkID] = session3
m.analyzer.mu.Unlock()
// Without BLE identity, this should create a zone-based record
// Currently, it just uses the linkID as the identifier
session3 = m.analyzer.GetSession(blob3LinkID)
if session3.GetPersonID() != "" {
t.Logf("Zone-based fallback: person ID is empty for blob3 (linkID: %s)", blob3LinkID)
}
// Summary: This test documents the expected behavior but the actual implementation
// is missing the multi-person bedroom coordination logic.
t.Logf("Multi-person bedroom scenario test: sessions tracked independently")
t.Logf("NOTE: Full multi-person coordination (zone-based records, blob selection) not yet implemented")
}

84
notes/bf-5613-findings.md Normal file
View file

@ -0,0 +1,84 @@
# Sleep Monitoring Multi-Person Bedroom Analysis (bf-5613)
## Task Verification Results
### Specification (from plan.md)
> **Multi-person bedroom edge case:** If two blobs are tracked in a bedroom zone simultaneously, the system assigns the sleep record to the BLE-matched person if available, otherwise creates two separate `zone-based` records (one per occupant slot). Breathing analysis uses the blob with the strongest stationary signal (lowest smooth_deltaRMS).
### Findings
#### 1. Two-Blob Handling: NOT IMPLEMENTED
**Current behavior:**
- The `SleepAnalyzer` tracks sessions per `linkID` (one blob = one session)
- Each blob gets its own `SleepSession` through `sessions map[string]*SleepSession`
- No logic exists to detect when multiple blobs are in a bedroom zone simultaneously
- No logic to create separate "zone-based" records when BLE identity is unavailable
**Code location:** `mothership/internal/sleep/analyzer.go:228`
```go
sessions map[string]*SleepSession // Per-link sleep sessions
```
**Missing:**
- Detection of multi-blob scenarios in bedroom zones
- Logic to create occupant-slot-based records (blob_1, blob_2) when no BLE match
- Coordination between multiple blobs in the same zone
#### 2. BLE-First Assignment: PARTIALLY IMPLEMENTED
**Current behavior:**
- `SetPersonID(linkID, personID string)` method exists in `SleepAnalyzer`
- The method is never called from the main application loop
- No integration between `identityMatcher.GetMatch()` and sleep session person assignment
**Code location:** `mothership/cmd/mothership/main.go`
- The main loop gets BLE matches via `identityMatcher.GetMatch(blob.ID)`
- However, these matches are used for automation/ground truth, NOT for sleep person assignment
- The sleep system samples all links regardless of BLE identity
**Missing:**
- Automatic call to `SetPersonID()` when BLE identity is resolved
- Logic to prioritize BLE-matched blobs over zone-based records
- Person-based session tracking vs link-based tracking
#### 3. Lowest-DeltaRMS Blob Selection: NOT IMPLEMENTED
**Current behavior:**
- Each blob/link processes its own breathing samples independently
- Breathing analysis happens per-blob, not across blobs
- No logic to compare `SmoothDeltaRMS` across blobs to select the "best" signal
**Code location:** `mothership/internal/sleep/integration.go:206-258`
- `collectSamples()` processes all links independently
- No comparison or selection logic between multiple links in the same zone
**Missing:**
- Logic to compare `SmoothDeltaRMS` values across blobs in the same zone
- Selection of the blob with the lowest (strongest stationary signal) for breathing analysis
- Fallback to zone-based records when all blobs have poor signals
#### 4. Integration Test: MISSING
**Current state:** `integration_test.go` has no two-person scenario test
**Test coverage gap:**
- No test for two blobs in a bedroom zone
- No test for BLE-based person assignment
- No test for zone-based fallback when no BLE match
- No test for deltaRMS blob selection
### Summary
The multi-person bedroom edge case is **NOT implemented** as specified. The current implementation:
- ✅ Handles single-person sleep tracking per blob
- ❌ Does NOT detect/handle multiple blobs in bedroom zones
- ❌ Does NOT assign sleep records to BLE-matched persons automatically
- ❌ Does NOT create zone-based records as fallback
- ❌ Does NOT select best blob for breathing analysis based on deltaRMS
### Implementation Requirements
To fulfill the specification, the following would need to be added:
1. **Multi-blob detection**: Monitor zone occupancy and detect when multiple blobs are in bedroom zones
2. **BLE integration**: Call `SetPersonID()` automatically when BLE identity is resolved
3. **Zone-based records**: Create occupant-slot records (e.g., "bedroom_slot_1", "bedroom_slot_2") when no BLE match
4. **Blob selection**: Compare `SmoothDeltaRMS` across blobs and select the one with the lowest value for breathing analysis
5. **Integration test**: Add test case covering the two-person bedroom scenario