# EMAY Lab

A local research project for extracting EMAY EBM-01 app recordings, viewing stored breathing samples, and exploring an **unvalidated Glasgow algorithm adaptation**. Processing runs in the browser without telemetry. The CapRover build serves the bundled recordings to visitors; browser imports stay in that visitor’s browser.

## Open

Open `index.html` directly in a browser, or run `npm start` from this directory and visit http://127.0.0.1:8765. Python 3 is needed only for serving/extraction. Node is needed only for tests. The included `data.js` preloads the recovered recordings without a network request.

Select a recording, choose a 10/30/60-second window or the entire trace, and move the position slider. Hover to inspect samples. The trace always displays original values. CSV downloads contain original sample order, not fabricated timestamps.

Experimental analysis runs automatically and shows nine component fractions, candidate inspirations, the original overall calculation, and a six-scenario sensitivity table. Select a candidate to zoom to it. Export analysis preserves all assumptions and per-candidate details. Import a future `recordings.json` using Open export; imports stay in memory and are not uploaded or persisted.

## What was recovered

3,757 breathing samples in 8 chunks across sessions 1, 3 and 4, from September 24, 2026 around 17:10–17:21 as stored. Session 2 has metadata only. Session 4 has 1,993 samples. Snoring strings and event metadata are retained in JSON. Original databases are also preserved in the sibling `emay-originals` directory and the original raw-data ZIP.

The SDK `monitorMinute.TempLine` equals `breathe_minute.breathe_line` exactly in all seven overlapping records. One extra 163-sample chunk is only in the SDK. This is the app's stored waveform, not proven unprocessed physical airflow. The source field name is not proof of sensor type.

## Timing and analysis limitations

- 10 Hz is an assumption inferred from 602–605 samples in full minute chunks. No exact timestamps or timezone are known.
- The viewer uses cumulative sample position divided by assumed rate, not elapsed wall time; unverified gaps are not silently presented as verified continuity.
- Each minute chunk is resampled independently to 25 Hz using linear interpolation. No interpolation or breath scoring crosses chunk boundaries. Resampling cannot recover lost detail.
- Polarity, amplitude units, baseline and sensor filtering are unverified. A multiplier does not calibrate signal units.
- Original fixed amplitude thresholds are retained: +5 inspiration threshold, −10 expiration threshold, 1-unit peak bump, variance thresholds 0.75 and 4. Results are sensitive to gain and baseline.
- The upstream program reports nine characteristics but omits `topHeavy` from its final aggregate. We show its eight-component sum (including its rounding) separately from an unrounded mean of all nine flags.
- Original early/late rolling-metric behavior is retained. Undefined rolling metrics contribute no flag, and independently processed chunks add more boundary effects. Scores are exploratory, not directly comparable to a whole-night ResMed run.
- No diagnostic ranges, treatment suggestions, or “normal/abnormal” classifications are applied.

## Extract future recordings

Keep the existing Finder backup if you want incremental backups. After recording, finish an updated backup. Then:

```sh
python3 extract.py '/absolute/path/to/device-backup' --update-viewer
```

The device backup is the directory containing `Manifest.db`, `Manifest.plist`, and `Status.plist`, normally under `~/Library/Application Support/MobileSync/Backup/`.

The extractor refuses unfinished or encrypted backups, copies only EMAY Documents files, verifies SHA-256 copies and the SDK/app waveform match, and writes `private/latest/recordings.json`, CSV, checksums and original databases. `--update-viewer` refreshes the local preload. Existing export files at the destination are replaced; use `--out private/YYYY-MM-DD` to keep each version. It never modifies or deletes the backup. Full Disk Access may be required by macOS.

## Reproducibility

`npm test` checks 3,757 recovered samples, original-algorithm parity on a synthetic 25 Hz waveform, empty/flat signals, invalid input rejection, interpolation, chunk independence and assumption sensitivity. It saves `research-results.json`, an experimental result artifact. These checks establish implementation consistency, not medical validity.

`vendor/PROVENANCE.md` pins the original source commit; `vendor/COPYING` contains its GPL license. The upstream source is unchanged except for an appended export. `engine.js` is the input adapter. The project is GPL-3.0-or-later.

## Next measurements needed

1. Confirm whether the current recordings were awake tests or sleep recordings.
2. Make a brief calibration recording: mark the start time, breathe normally, and observe whether a deliberate inhale moves the app's graph up or down. Record the observation rather than assuming polarity.
3. Request EMAY's sample rate, sensor type, units, filtering, scaling and timing documentation. Do not infer calibration from the sample magnitudes alone.
4. Collect a full-night recording for sleep analysis. Where available, synchronized known-reference flow data can help investigate compatibility, but cannot be replaced by interpolation.

## Privacy and version control

Health data (`data.js`, `recordings.json`, `private/`, and result files) is ignored by Git. The working project includes real data locally. Do not publish or upload this folder or its ZIP without reviewing those files. The local server binds only to 127.0.0.1. No remote Git repository is configured.

## Position and waveform inspection

The position track and downloadable position CSV preserve SDK bodyposition and app body_position. All eight recovered chunks contain code 4; all seven overlapping records agree. Code 4 is tentatively labeled Supine based on the user’s recollection; the raw code is preserved. Confirm against the same EMAY app report before treating this mapping as verified. Position is minute-level, not sample-level; the track follows the viewer’s assumed sample timing.

Use the 5-second window and Show stored sample points to inspect the actual samples. The plot uses straight connections; no spline or smoothing changes the waveform. A visually sharp apex is not itself a clinical interpretation.

## Wellue ring comparison

Use **Import Wellue** for O2Ring S raw files with signature `01 03` and the original timestamped filename. Decoder reads the record count 36 bytes before EOF, then one-second SpO2/pulse/movement triplets after the 10-byte header. Reference: [OSCAR O2RingS parser](https://gitlab.com/CPAPreporter/oscar-code/-/blob/master/oscar/SleepLib/loader_plugins/viatom_loader.cpp). Unsupported/truncated files are rejected. Invalid oximetry is shown as gaps. Movement is the stored raw indicator.

Ring charts share the breathing window, gestures, and saved EMAY event overlays. Alignment uses device-local filename time and proportional placement within EMAY minute chunks; it is approximate, with no timezone conversion or clock synchronization implied. The clock-offset control shifts the ring in seconds. JSON exports include the imported ring readings. Original imported file is preserved in private/ for this dataset; future browser imports remain in memory until exported.

Position calibration: user reported back, right, left, then several other movements for session 6 (2026-09-25 15:56–16:00). Matching app/SDK minute codes are 4, 6, 5, 4. Map 4=back/supine, 6=right, 5=left using the first three positions; later movements are not separately resolved. Codes 1, 2, 3 remain unmapped.

## Deployment

See DEPLOYMENT.md in the source project for the CapRover package and deployment steps.
