Skip to content

Fix piezo processor selecting SEQNO.RAW instead of sensor data#247

Merged
ng merged 1 commit intodevfrom
fix/246-exclude-seqno-raw
Mar 22, 2026
Merged

Fix piezo processor selecting SEQNO.RAW instead of sensor data#247
ng merged 1 commit intodevfrom
fix/246-exclude-seqno-raw

Conversation

@ng
Copy link
Copy Markdown
Contributor

@ng ng commented Mar 22, 2026

Summary

  • Filter SEQNO.RAW from RawFileFollower._find_latest() candidates so the metadata file is never mistaken for sensor data
  • This was already done in the prototype (prototype_v2.py:511) but never ported to production

Test plan

  • All 57 existing piezo-processor tests pass
  • Verify on device with SEQNO.RAW present that actual data file is selected

Fixes #246

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added sensor stream enable/disable control for manual monitoring.
    • Implemented snooze alarm functionality.
    • Enhanced real-time device status updates via WebSocket pub/sub.
  • Bug Fixes

    • Optimized CI workflow to lint only changed TypeScript files.
    • Improved error handling for sensor display crashes.
  • UI/UX Improvements

    • Refined header and temperature control layouts.
    • Enhanced cursor feedback on interactive buttons.
    • Restructured sensors dashboard for better organization.
  • Documentation

    • Updated architecture and deployment guides.
    • Added new architectural decision record for event-driven updates.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 22, 2026

Warning

Rate limit exceeded

@ng has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 17 minutes and 18 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6193f8c5-b9a7-4de3-a40a-00e705ed6a97

📥 Commits

Reviewing files that changed from the base of the PR and between 62c4abe and c3f014a.

📒 Files selected for processing (3)
  • .github/workflows/test.yml
  • modules/common/raw_follower.py
  • src/hardware/dacMonitor.instance.ts
📝 Walkthrough

Walkthrough

This PR implements an event-bus architectural shift: removing iOS "processing ownership" claim-based state management, introducing broadcastMutationStatus() helper to emit device status after hardware mutations, converting the WebSocket from bidirectional to read-only pub/sub, adding frame normalization for sensor data, updating project branding from "SleepyPod" to "sleepypod," and optimizing client-side ReactFlow rendering. It also fixes a bug where SEQNO.RAW metadata file was selected over actual sensor data.

Changes

Cohort / File(s) Summary
Branding & Terminology Updates
README.md, .claude/docs/*, docs/adr/*, docs/hardware/*, docs/*.md, src/server/routers/README.md, scripts/README.md, src/hardware/tests/README.md
Renamed project from "SleepyPod" to "sleepypod"; updated hardware device references from "Eight Sleep Pod" to "Pod" across documentation, ADRs, and README files. Updated architecture diagrams to reflect new terminology and WebSocket/broadcast-based design.
WebSocket Streaming Architecture
src/streaming/piezoStream.ts, src/streaming/processingState.ts, src/streaming/broadcastMutationStatus.ts, src/streaming/normalizeFrame.ts
Removed iOS "processing ownership" control plane (claim/heartbeat/release) from piezoStream.ts; deleted processingState.ts module entirely. Added broadcastMutationStatus.ts helper to emit deviceStatus frames by overlaying mutation results onto DacMonitor.getLastStatus(). New normalizeFrame.ts exports firmware-specific wire interfaces and normalizes bedTemp/frzTemp/frzHealth/frzTherm/capSense2 sensor payloads for client consumption.
Device Router & Hardware Mutations
src/server/routers/device.ts, src/server/routers/biometrics.ts, src/hardware/snoozeManager.ts, src/scheduler/jobManager.ts
Added broadcastMutationStatus() calls after successful hardware operations in setTemperature, setPower, setAlarm, clearAlarm, snoozeAlarm mutations. Removed getProcessingStatus procedure from biometrics router. Added broadcast logic to scheduled temperature/power/alarm jobs via jobManager.ts. New imports for fahrenheitToLevel and broadcastMutationStatus.
Client-side Sensor Stream
src/hooks/useSensorStream.ts, src/components/Sensors/DataPipeline.tsx
Removed heartbeat/claim-release protocol from WebSocket client; now uses normalizeFrame helper to process incoming frames. Eliminated per-frame normalization code. Refactored DataPipeline from dynamic ReactFlow setup to static constant-based nodes/edges with memo optimization; removed useSensorStreamStatus dependency.
Sensor Screen & Components
src/components/Sensors/SensorsScreen.tsx, src/components/SideSelector/SideSelector.tsx, src/components/TempScreen/TempScreen.tsx, src/components/TemperatureDial/TemperatureDial.tsx, src/components/Header/Header.module.css
Restructured SensorsScreen layout, added stream enable/disable control, adjusted spacing with negative top margins. Added cursor-pointer styling to buttons. Removed MovementChart import. Updated TemperatureDial viewBox to reduce SVG height; adjusted label rendering. Modified CSS margins/padding in header and components.
Error Handling & Testing
app/[lang]/sensors/error.tsx, src/streaming/tests/normalizeFrame.test.ts
Added new error boundary component for sensors page displaying crash UI with error message and retry button. New comprehensive Vitest suite validating normalizeFrame against firmware payload fixtures (frzHealth, frzTemp, frzTherm, bedTemp2, capSense2, sentinel handling).
Documentation & Architecture
docs/trpc-api-architecture.md, docs/DEPLOYMENT.md, .claude/docs/project-info.md
Updated tRPC architecture docs to document WebSocket-first with tRPC polling fallback, mutation-triggered broadcasts (~200ms), and new snoozeAlarm mutation. Mermaid diagrams updated to show explicit WebSocket fan-out after mutations and scheduler jobs. Removed branding references in deployment docs.
Hardware Monitor & RAW File Handling
src/hardware/dacMonitor.instance.ts, modules/common/raw_follower.py
Changed import paths from @/src/streaming/piezoStream to ../streaming/piezoStream. Filtered SEQNO.RAW from candidate files in _find_latest() to prevent selecting metadata file as sensor data source.
CI/Build Configuration
.github/workflows/test.yml
Modified Lint job to compute changed TS/TSX files between base and HEAD (limited to 200 entries) and run ESLint only on those, rather than entire codebase. Added fetch-depth: 0 for full Git history.

Sequence Diagram

sequenceDiagram
    actor User
    participant tRPC API
    participant DacMonitor
    participant DAC Hardware
    participant WebSocket Server
    participant Client

    User->>tRPC API: setTemperature(side, target)
    tRPC API->>DAC Hardware: write command via dacTransport
    DAC Hardware-->>tRPC API: success
    tRPC API->>DacMonitor: broadcastMutationStatus(side, {targetTemperature})
    DacMonitor->>DacMonitor: getLastStatus()
    DacMonitor->>WebSocket Server: broadcastFrame({type: 'deviceStatus', ...})
    WebSocket Server->>Client: emit frame (~200ms latency)
    Client->>Client: normalizeFrame(payload)
    Client->>User: update UI with new temperature
    
    Note over DacMonitor: Authoritative 2s polling<br/>continues independently
Loading
sequenceDiagram
    participant Scheduler
    participant DacMonitor
    participant DAC Hardware
    participant WebSocket Server
    
    Scheduler->>Scheduler: scheduled job tick
    Scheduler->>DAC Hardware: executeJob (temperature/power/alarm)
    DAC Hardware-->>Scheduler: success
    Scheduler->>DacMonitor: broadcastMutationStatus(side, {targetTemperature, ...})
    DacMonitor->>WebSocket Server: broadcastFrame({type: 'deviceStatus', ...})
    WebSocket Server-->>Client: stream update (immediate)
    
    par DacMonitor polling
        DacMonitor->>DAC Hardware: query status
        DAC Hardware-->>DacMonitor: current state
        DacMonitor->>WebSocket Server: broadcastFrame (every 2s)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

This diff involves heterogeneous changes across multiple architectural layers: removal of a substantial control plane (processing state/claim-release), introduction of a new fire-and-forget broadcast pattern with overlaid mutation state, refactoring of client streaming to use shared normalization, UI component optimizations, and distributed sensor frame handling logic. The normalizeFrame.ts module (182 lines) introduces non-trivial firmware-specific wire format handling with sentinel value conversions. While many documentation and branding updates are repetitive (reducing per-file effort), the core logic changes span routers, streaming, hooks, hardware monitors, and scheduler—requiring cross-module reasoning.

Possibly related issues

Possibly related PRs

Poem

🐰 Bouncing through the code with glee,
Claims and heartbeats now set free!
Broadcasts flow like morning dew,
DacMonitor polls, WebSocket streams true.
Frame normalization, clean and neat,
sleepypod's dance is now complete!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes extensive out-of-scope changes beyond the stated fix: documentation updates (project naming, ADR additions), WebSocket architecture refactoring, streaming broadcast infrastructure, error component additions, CI workflow modifications, and UI styling adjustments unrelated to issue #246. Isolate the SEQNO.RAW fix (raw_follower.py change only) into a minimal PR; move all other changes (docs, streaming, UI, CI) to separate focused PRs with their own tracking issues.
Docstring Coverage ⚠️ Warning Docstring coverage is 46.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main bug fix in the changeset: filtering SEQNO.RAW from raw file candidates in the piezo processor.
Linked Issues check ✅ Passed The PR directly addresses the core requirement from issue #246: filtering SEQNO.RAW out of RawFileFollower._find_latest() candidates to prevent parsing metadata as sensor data.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/246-exclude-seqno-raw

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a production bug in the Python biometrics pipeline where RawFileFollower._find_latest() could select SEQNO.RAW (a metadata/index file) as the “newest” RAW file and then tail/parse it as CBOR, resulting in zero biometric processing.

Changes:

  • Exclude SEQNO.RAW from RawFileFollower._find_latest() candidate selection so only actual sensor data RAW files are tailed.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ng ng force-pushed the fix/246-exclude-seqno-raw branch 2 times, most recently from 62c4abe to b422c60 Compare March 22, 2026 20:35
Filter SEQNO.RAW from RawFileFollower._find_latest() so the metadata
file is never mistaken for sensor data. Already done in prototype but
never ported to production.

Also: scope CI lint to changed files only, fix dacMonitor dynamic
import path for test resolution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ng ng force-pushed the fix/246-exclude-seqno-raw branch from b422c60 to c3f014a Compare March 22, 2026 20:36
@ng ng merged commit d62b952 into dev Mar 22, 2026
5 checks passed
@ng ng deleted the fix/246-exclude-seqno-raw branch March 22, 2026 20:37
Copy link
Copy Markdown
Contributor Author

@ng ng left a comment

Choose a reason for hiding this comment

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

Adversarial Code Review — PR #247

Depth: Standard (Sonnet Optimizer + Sonnet Skeptic)
Verdict: Approve with minor suggestions

The fix is correct

The SEQNO.RAW exclusion directly addresses the root cause. The Optimizer suggested also adding a file-size guard (matching the prototype's 100K threshold), but the Skeptic correctly identified that threshold is from a batch analysis script — the live follower is designed to tail partially-written files. Adding a size guard would introduce startup data gaps. Rejected.

Suggested improvements (non-blocking)

1. Add inline comment (raw_follower.py:49)
Without context, a future maintainer may remove the filter as dead code:

# SEQNO.RAW is a firmware metadata file, not sensor data (see #246)

2. Add unit tests for _find_latest()
This is a regression fix for a production outage with zero test coverage. The piezo-processor tests stub out RawFileFollower entirely. Suggested test cases:

  • Only SEQNO.RAW present → returns None
  • SEQNO.RAW has newer mtime than data file → returns data file
  • Empty directory → returns None

Pre-existing issues noted (not this PR's fault)

  • raw_follower.py:66: open(latest, "rb") outside try/except — file deletion between _find_latest() and open() crashes the generator
  • calibrator/main.py:89: sorted(..., key=lambda p: p.stat().st_mtime) with no FileNotFoundError protection — crashes on file rotation

Full review artifacts: .claude/reviews/fix-246-exclude-seqno-raw/

ng added a commit that referenced this pull request Mar 22, 2026
## Summary
- Add inline comment explaining why `SEQNO.RAW` is excluded from
`_find_latest()`
- Add 4 unit tests for the exclusion behavior to guard against
regression

Follow-up from adversarial review of #247.

## Test plan
- [x] All 4 new tests pass
- [x] Covers: empty dir, SEQNO-only, SEQNO with newer mtime, multiple
data files

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 1.1.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Piezo processor selects SEQNO.RAW instead of sensor data file

2 participants