doc: decode signals planning

This commit is contained in:
2026-03-28 19:43:37 +02:00
parent 9517f0f964
commit dba07cc957

68
doc/plan/signals.md Normal file
View File

@@ -0,0 +1,68 @@
# Plan: Decode wmedf signal data, expose via CLI and JSON export
## Context
The .wmedf parser currently extracts only the session start timestamp. The binary signal data (18 channels of continuous waveforms — pressure, respiratory flow, SpO2, heart rate, etc.) is ignored. This data is clinically critical: it enables visual confirmation of apnea types, desaturation tracking, and cardiac correlation.
## Binary format summary
- Standard EDF, 18 channels, 4864-byte header (256 global + 18×256 signal)
- Signal headers use EDF interleaved layout (all labels, then all transducers, etc.)
- Data records: 70 bytes each (35 × i16 LE), 18 seconds per record
- Samples per record varies by channel: Pressure(5), RespFlow(10), SPRstatus(5), all others(1) = 35 total
- Num records: derive from `(file_size - 4864) / 70` (header field unreliable)
- Conversion: `physical = phys_min + (raw - dig_min) * (phys_max - phys_min) / (dig_max - dig_min)`
## Files to modify
- `crates/tidal-core/src/entities.rs` — move `sample_rate_hz` to `SignalChannel`, add new `SignalLabel` variants
- `crates/tidal-devices/src/lowenstein/wmedf.rs` — full signal header + data record decoding
- `crates/tidal-store/src/sqlite.rs` — use per-channel sample rate in read/write paths
- `crates/tidal-cli/src/main.rs` — add `--signals` flag to `session` command
- `crates/tidal-cli/src/export.rs` — add signal summaries to JSON export
## Step 1: Update entities
Move `sample_rate_hz` from `SignalBlock` to `SignalChannel`. Add `SignalLabel` variants: `EEPAPTarget`, `TotalLeakage`, `RSBI`, `AMV`, `SPRStatus`. Update `Display`/`FromStr`.
## Step 2: Update tidal-store for per-channel sample rate
- Write path: use `ch.sample_rate_hz` instead of `block.sample_rate_hz`
- Read path: store rate into each `SignalChannel`, construct `SignalBlock { channels }` without block-level rate
- Update tests
## Step 3: Implement wmedf signal decoder
In `wmedf.rs`:
1. Parse signal headers from the interleaved EDF layout (labels at 256, units at 1984, phys min/max, dig min/max, samples_per_record at 4144)
2. Map EDF label strings to `SignalLabel` variants (e.g. "EEPAPsoll" → `EEPAPTarget`, "HeartFrequency" → `HeartRate`)
3. Parse data records: iterate 70-byte chunks, read i16 LE values, distribute across channels by samples_per_record
4. Convert digital → physical values per the EDF formula
5. Compute per-channel sample_rate_hz: `samples_per_record / 18.0` (record duration)
6. Build `SignalBlock` with populated `SignalChannel` entries
## Step 4: Add `--signals` to CLI session command
When `--signals` is set, load session with signals and display summary table:
```
Signals 18 channels
Label Unit Samples Rate(Hz) Min Max Mean
Pressure hPa 3630 0.278 6.0 12.3 8.4
RespiratoryFlow l/min 7260 0.556 -42.0 55.3 1.2
SpO2 % 726 0.056 88.0 97.0 93.2
...
```
## Step 5: Add signal summaries to JSON export
Add `--signals` flag to export command. When set, load sessions with signals and include per-channel summary (label, unit, sample_rate_hz, sample_count, min, max, mean) in JSON output. Raw samples excluded from JSON — too large.
## Verification
1. `cargo build` — no errors or warnings
2. `cargo test` — all tests pass (including updated signal_blob_round_trip test)
3. `rm ~/.local/share/tidal/tidal.db && tidal import ~/lowenstein/therapy_extracted --from 2026-03-26 --to 2026-03-28` — imports with signals
4. `tidal session 300306-003344 --signals` — shows 18 channels with plausible values
5. `tidal export --format json --from 2026-03-26 --to 2026-03-28 --signals` — JSON includes signal summaries
6. Verify: Pressure values in hPa range (4-20), SpO2 in % range (80-100), HeartRate in bpm range (40-120)