Skip to content

Add oura dashboard: local web health dashboard (Rust + models) - #5

Open
Th0rgal wants to merge 72 commits into
mainfrom
web-dashboard
Open

Add oura dashboard: local web health dashboard (Rust + models)#5
Th0rgal wants to merge 72 commits into
mainfrom
web-dashboard

Conversation

@Th0rgal

@Th0rgal Th0rgal commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Summary

A single-page, local-first health dashboard following notes/dashboard-v2-brainstorm.md. Open it with oura dashboardhttp://127.0.0.1:8090. Everything is computed and served locally.

  • Rust does the work (crates/oura-cli/src/dashboard.rs): reads oura.db, computes per-night HRV / resting-HR / skin-temp, SpO2 % (Oura's R→% calibration), baselines + deltas, the Device & Data Health panel, and the digest. Serves the page + /api/summary.
  • AI models via the Python runners, shelled out like oura sessions: sleep hypnogram, activity, cardiovascular age. Added --json to run_sleep_model.py and run_cva_model.py.
  • Frontend in a separate dashboard/ folder: vanilla HTML/CSS/JS, no build/deps. Auto light/dark (prefers-color-scheme), skeleton loading, reduced-motion safe, one accent + one radius, system + mono type. Per-night hypnograms (DEEP/LIGHT/REM/WAKE), vitals tiles + sparklines, activity grouped by day, device panel with what's-measuring + insight status.
  • New oura dashboard subcommand (loopback-only). Default build includes it.

Axes (per the v2 vision)

Sleep & recovery · Cardiovascular · Blood oxygen · Activity · Device & data health (the new control-plane axis).

Test

Verified in Playwright: light, dark, and mobile all render correctly. cargo build (default, no torch) clean.

No model weights or personal data committed.


Note

High Risk
Large surface area: BLE ring sync and auth key handling on iOS, new HTTP dashboard APIs, and duplicated model paths (Python vs LibTorch) that must stay aligned with build_summary.

Overview
This PR turns the repo into a two-client product (web + iOS) around one JSON contract from crates/oura-summary (build_summary), plus a local oura dashboard server.

Web: New oura dashboard in oura-cli serves loopback HTML/JS from dashboard/web/, computes non-model metrics in Rust, shells out to the existing Python runners for sleep/CVA/activity (with --json), and caches /api/summary on DB/profile changes. README documents the command; related routes also wire DNA and blood explorer pages.

Shared Rust: Workspace grows oura-summary, oura-core (UniFFI), oura-ffi, and oura-dna; CLI depends on summary/DNA and ureq for public PGS fetches. oura-link gains tracing for mobile sync.

iOS (apps/ios/OuraApp): SwiftUI “Observatory” UI reads the same summary via oura-core; BLETransport + RingSync drive Rust auth/sync into a writable DB with retries and diagnostics; TORCH builds run SleepNet/CVA/activity through TorchBridge (ports of the Python runners). xcodegen specs, xcframework build scripts, TestFlight/Xcode Cloud docs, and CLAUDE.md / docs/clients-web-and-ios.md spell out keeping both clients in sync.

Repo hygiene: .gitignore expanded for local health data, genomes, iOS/libtorch artifacts, and plan drafts.

Reviewed by Cursor Bugbot for commit 0c04d2d. Bugbot is set up for automated code reviews on this repo. Configure here.

A single-page, local-first health dashboard following notes/dashboard-v2-brainstorm.md.

- Rust (crates/oura-cli/src/dashboard.rs) owns the DB + non-model calcs: per-night
  HRV/RHR/skin-temp, SpO2 % via Oura's R→% calibration, baselines + deltas, the
  Device & Data Health panel, and the digest. It serves the page + /api/summary.
- The torch models (sleep hypnogram, activity, CVA) run via the Python runners,
  shelled out like `oura sessions`. Added `--json` to run_sleep_model.py and
  run_cva_model.py for machine-readable output.
- Frontend in a separate dashboard/web/ folder: vanilla HTML/CSS/JS, no build, no
  external deps. Auto light/dark via prefers-color-scheme, skeleton loading,
  reduced-motion safe, one accent + one radius, system + mono type. Activity grouped
  by day, per-night hypnograms, vitals tiles with sparklines.
- New `oura dashboard --port --tz-offset --sex/--age/--height/--weight` subcommand
  (loopback-only). Default build includes it; models still run via Python.

Verified in Playwright: light, dark, and mobile all render correctly.
Comment thread crates/oura-cli/src/dashboard.rs Outdated
Comment thread crates/oura-cli/src/dashboard.rs Outdated
Comment thread crates/oura-cli/src/dashboard.rs Outdated
Th0rgal added 5 commits June 27, 2026 07:41
- Display serif (system-only, offline) for the digest 'what changed' statement and
  the big hero numbers (vascular age, SpO2); data/tables stay mono.
- Small UPPERCASE letter-spaced labels for panel headings, tile labels, day headers
  (the scientific/editorial meta voice).
- Italic serif sub-lines under the hero numbers; more air on panels.
Verified in Playwright (light, dark, mobile). No external/Google fonts — keeps the
dashboard fully offline.
Functional:
- Sync the ring from the header (POST /api/sync runs `oura sync` as a subprocess,
  using the dashboard's --name/--key-file), then auto-refreshes. CSRF-guarded.
- Battery in the header + Device panel, read offline from stored
  `battery_level_changed` debug events (battery % + voltage).
- Editable user profile (age/sex/height/weight/ring) the ring can't measure, stored
  in a gitignored profile.json next to oura.db and used by the CVA model + runners
  (GET/POST /api/profile, CSRF-guarded; run_cva_model.py reads it as defaults).
  Fixes the wrong "-11.2 yr vs your age" — now driven by your real age.

Visual:
- Vendored Phosphor icons (MIT, offline) on panel headers, actions, chips.
- Leaner palette: neutral outline tags, subtle status dots instead of filled pills,
  monochrome thin sparklines with a faint area fill.

Verified in Playwright (light/dark): battery, icons, profile edit → CVA re-run.
Comment thread dashboard/web/app.js
Th0rgal added 3 commits June 27, 2026 13:19
- Device & data health now shows the ring's identity, read offline from the
  `device` table: Ring ID (serial), firmware, API version, MAC. Added
  Store::device_info() (device row joined with sync_state).
- "Last sync" now uses the real sync_state.last_sync_unix instead of the event
  anchor, so it reflects the actual last drain.
- Sync errors are surfaced in a visible toast with an actionable hint
  ("Couldn't find your ring. Take it off the charger…") instead of only a
  tooltip that read "Failed". Success shows the synced summary line.

Verified in Playwright: identity renders, sync toast shows the real reason.
… fixes

- estimate active kcal per detected session; show it in the session popover
- cache build_summary (invalidate on db/profile mtime); run sleep/CVA/activity
  models concurrently; batch all nights through one sleep-model process
- resolve --db to an absolute path so Rust and the Python runners open the same
  file (Bugbot: Python models use wrong DB)
- HRV digest %: divide the delta by the prior-night mean baseline, matching the
  vitals tile (Bugbot: HRV digest percent wrong)
- recovery line keys off HRV direction (RHR-down as fallback), not the raw sign
  of an HR-rise fragment (Bugbot: recovery text ignores resting HR)
- surface profile-save errors instead of closing the dialog on a 200+{error}
  response (Bugbot: profile save ignores server errors)
- vivid-but-glassy hypnogram colors; dot-style toasts; activity icons
…nvert, decode notes

- `oura sleep-score`: SleepNet hypnogram → durations/efficiency + calibrated
  contributor curves & combiner weights (tools/score_sleep.py, fit_*.py)
- score-weights doc + data-recovery-map: the app (not cloud) is the analytics tier
- run_stress_model.py runner
- Ring Runner: persist horizontal/vertical invert toggles across restarts
- real_steps decode: byte-layout notes confirmed vs the native parser
Comment thread crates/oura-cli/src/dashboard.rs Outdated
Comment thread crates/oura-cli/src/dashboard.rs Outdated
Comment thread crates/oura-cli/src/dashboard.rs
Comment thread crates/oura-cli/src/dashboard.rs Outdated
- route API on the path only, stripping any query string, so /api/summary?cb=…
  no longer 404s (Bugbot: API path ignores query string)
- guard the vitals trend against a zero baseline → no Inf/NaN in the JSON
  (Bugbot: vitals delta divides by zero)
- "Real steps" capability tile checks feature_1 OR feature_2, matching the event
  the Steps stream actually counts (Bugbot: real steps toggle wrong event)
- last-sync fields come only from a real recorded sync timestamp; absent → null
  (UI shows "—") instead of faking freshness from the latest event's capture time
  (Bugbot: wrong fallback for last sync)
Comment thread tools/score_sleep.py Outdated
When the scored night can't be matched to a stored bedtime, fall back to the
night's own midpoint (start_ds, end_ds) instead of indexing bts[-1] — which used
the wrong night's end and raised IndexError when bts was empty (e.g. --start/--end
with no bedtime_period rows). timing_features now takes end_ds.
Comment thread dashboard/web/app.js Outdated
A session crossing midnight had its bar sized from full duration_min while end was
clamped to 1440, so the bar ran wider than its lane and the tooltip/popover time
range didn't match the duration. Keep a true uncapped end (endTrue) for the
duration/range label, and size the bar from the day-clamped (end - start).
Comment thread tools/score_sleep.py
Comment thread dashboard/web/app.js
…ror state

- score_sleep: compute the 7-day midpoint regularity from a circular mean so it's a
  true circular delta — frame-invariant (independent of --tz / ds→clock phase) and
  correct across the midnight wrap, where the naive arithmetic mean broke
  (Bugbot: sleep timing mixes timezones)
- dashboard: on an /api/summary {error} or fetch failure, clear every panel's
  loading skeleton (not just the digest) so the page reads as errored instead of
  stuck mid-load (Bugbot: summary errors leave skeletons)
Comment thread crates/oura-cli/src/dashboard.rs
Comment thread crates/oura-cli/src/dashboard.rs Outdated
- cached_summary re-stats oura.db / profile.json after the (slow) build and only
  stores the result if they didn't change underneath it, so a sync that lands during
  a build can't be masked by a stale cache entry; also guards against a slower
  concurrent build clobbering a fresher entry (Bugbot: summary cache stale after sync)
- startup line now reads "open_oura dashboard running — open http://… in your browser"
  instead of the arrow-y debug-looking string (Bugbot: wrong dashboard startup message)
Comment thread crates/oura-cli/src/dashboard.rs Outdated
Comment thread crates/oura-cli/src/dashboard.rs Outdated
Th0rgal added 3 commits June 29, 2026 09:53
…atest, matching %)

Both the vitals tiles and the digest now read a single vital_stat: latest is the
*most recent night's* value (None if that night had no sample, so an older night is
never shown as current), baseline is the mean of the prior nights, and the percent
change comes from one shared helper.

- latest no longer slides to an older night when the newest night lacks samples
  (Bugbot: vitals "latest" skips empty nights)
- the digest HRV % is now the exact value the tile shows, including the zero-baseline
  guard (no fragment), instead of d/base.max(1.0) (Bugbot: digest HRV percent mismatches tiles)
- Advanced & debugging panel: export the 16-byte ring key (copy / .key / QR) to set
  up another device without re-pairing, or import one (paste / .key / QR), written to
  --key-file (0600, lowercased, validated as 32 hex chars). New GET/POST /api/ring-key
  behind the existing CSRF + loopback guards.
- capability toggles now reflect the *real* on-ring feature mode: each sync snapshots
  SetFeatureMode status to a gitignored feature_modes.json next to oura.db; the panel
  falls back to "events seen recently" until a mode is captured.
- docs: document the advanced panel, key export/import, the feature-modes snapshot,
  the JSON API + CSRF/loopback guards, and the cached/parallel/batched summary.
…etrics

tools/fit_scores_all.py extends the Sleep two-layer approach (weights × contributor
curves) to Readiness and Activity, engineering lag-1 / trailing-mean features for the
"today vs personal baseline" contributors. Recovered weights confirmed for all three
(ceiling R²=0.84–0.998); end-to-end Sleep R²=0.97, with the Readiness/Activity gap
isolated to baseline-relative / multi-day-load contributors that need accumulated
history. docs/algorithms written up accordingly.
Comment thread dashboard/web/app.js Outdated
Comment thread tools/fit_sleep_score.py Outdated
Th0rgal added 2 commits June 30, 2026 07:34
…ock end

- security: GET /api/ring-key discloses the ring auth key, so it now requires the
  same-origin X-Oura-Dash header like the mutating endpoints — a cross-origin page
  (or DNS-rebind attempt, already blocked by the Host guard) can't read the key; only
  the dashboard's own page can. fetchRingKey() sends the header.
- actogram: display the API's wall-clock `end` string (which already wraps past
  midnight) instead of formatting a numeric endTrue that produced invalid "24:xx"
  times; the day-clamped endMin is kept only for bar geometry.
Compute the 7-day midpoint regularity with a circular mean + circular distance on
the 24h clock, like tools/score_sleep.py, so a schedule that straddles a wrap isn't
scored as wildly irregular. Equivalent to the previous arithmetic mean on normal
(contiguous) bedtimes, so the calibration is unchanged.
Comment thread crates/oura-cli/src/dashboard.rs
…n them)

A successful /api/feature toggle now writes the new mode (0 off / 1 automatic) into
feature_modes.json right away, and the summary cache keys on that file's mtime too —
so a reload shows the new capability state instead of the pre-toggle one held until
the next sync re-snapshots. (Bugbot: capability toggles stale until sync)
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_c4e42015-47f0-4a90-ad13-63b32116273f)

@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_c38797d5-9329-4d31-b5b5-ca9acf6e4abc)

@Th0rgal

Th0rgal commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1158582b95

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/oura-cli/src/blood.rs Outdated
Comment on lines +371 to +372
if markers.len() < MIN_EXPECTED_MARKERS {
merge_fallback_markers(&mut markers, llm_fallback_markers(&text, &mut warnings));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep blood PDF parsing local

When a PDF has fewer than four parsed markers and the process happens to have AGENT_BACKEND_API_KEY set, this fallback is invoked automatically during /api/blood/import*; llm_fallback_markers then sends extracted lab-result lines to https://agent-backend.thomas.md/v1. That contradicts the dashboard/blood module's local-only behavior and can leak blood results without an explicit opt-in, so the remote normalizer should be disabled unless the user explicitly requests it.

Useful? React with 👍 / 👎.

Comment thread crates/oura-summary/src/lib.rs Outdated
}
}
}
beds.sort();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Sort nights by wall-clock time after ring resets

The epoch code above handles ring_timestamp resets, but this sort still orders bedtime periods by raw deciseconds. After a ring reboot, newer nights start again near zero and sort before older pre-reboot nights; the later reverse and vital_stat(...).latest then treat the old high-ds night as newest, skewing the digest, latest HRV/RHR, and sleep debt. Sort bedtime periods by unix_s(start_ds) instead.

Useful? React with 👍 / 👎.

Comment thread crates/oura-dna/src/score.rs Outdated
Comment on lines +213 to +217
pub fn add_homref(&mut self, variant_idx: usize) {
let v = &self.spec.variants[variant_idx];
self.matched += 1;
self.span += 2.0 * self.spec.effective_weight(v.weight).abs();
// dosage 0 → no contribution, not a top contributor

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Count homozygous-reference effect alleles in scores

For gVCF/reference-block inputs, this assumes every homozygous-reference target has effect-allele dosage 0. That is only true when the effect allele is non-reference; many PGS/built-in variants can have the effect allele equal to REF, in which case a hom-ref sample should contribute dosage 2. As written, those variants are marked covered but their contribution is dropped, biasing scores for gVCF genomes.

Useful? React with 👍 / 👎.

@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_7defeee0-06b8-4d4d-aff2-70891d9241a9)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c8fb184cce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/ios/OuraApp/project-ci.yml
Comment thread crates/oura-summary/src/lib.rs Outdated
.filter(|e| {
ds >= e.min_ds - EPOCH_RESET_SLACK_DS && ds <= e.max_ds + EPOCH_RESET_SLACK_DS
})
.min_by_key(|e| e.max_ds - e.min_ds)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Choose epochs with event context after resets

Fresh evidence beyond the prior sort issue: when a post-reboot epoch overlaps raw ring_timestamp values from an older boot, this min_by_key picks one epoch using only ds, so all events at that raw decisecond—including older bedtime periods and samples—are mapped to the narrowest/newer epoch's anchor. In reset histories this shifts old nights into recent dates and feeds the wrong nights into recent-cut/vitals/sleep-debt calculations; keep the epoch assigned while walking events or pass event context into the conversion instead of resolving by raw ds alone.

Useful? React with 👍 / 👎.

@Th0rgal

Th0rgal commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_45a0bca6-d792-4757-ad55-55bcf00d7726)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 713484bdc7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/oura-summary/src/lib.rs
Comment thread crates/oura-summary/src/lib.rs Outdated
}

// the model seam — sleep / cva / activity (Python subprocess or on-device .ptl)
let sleep_ranges: Vec<[i64; 2]> = nights.iter().map(|nt| [nt.start_ds, nt.end_ds]).collect();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Pass boot epoch through SleepNet batches

For databases spanning a ring reboot, raw ring_timestamp ranges can overlap. This batch drops nt.epoch_idx and sends only [start_ds, end_ds]; the Python runner filters samples by that raw range and the results are keyed only by start_ds, so a night can be scored with samples from another boot or overwrite a same-counter night. Include an epoch/window identity in the batch and in the returned key.

Useful? React with 👍 / 👎.

Comment thread crates/oura-cli/src/dashboard.rs
@Th0rgal

Th0rgal commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_0d616815-ab1f-4fe3-bd0e-277a17a27904)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fcc46211b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/oura-summary/src/lib.rs Outdated
let db = db_abs.as_path();
let demo = read_profile(db);
let store = Store::open(db).context("opening DB")?;
let events = store.decoded_events().context("reading events")?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Filter dashboard events by serial

When a DB contains more than one ring serial (the schema and cursors are per-serial, so this happens after syncing a replacement/second ring into the same DB), this reads every decoded event while the device panel/next cursor come from a single most-recent device. The resulting summary can merge nights, vitals, activity, and event counts from different rings under one device; filter decoded events to the selected serial or make the summary explicitly serial-scoped.

Useful? React with 👍 / 👎.

Comment thread tools/run_activity_model.py Outdated
Comment on lines +102 to +104
from epoch_time import build_epochs, make_unix_s
epochs = build_epochs([(r[0], r[3]) for r in rows])
_unix_s = make_unix_s(epochs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Carry epoch assignments into activity timestamps

For DBs spanning a ring reboot, raw ring_timestamp values overlap across boot epochs. This keeps only the epoch ranges and later maps events with make_unix_s(ds), discarding each row's actual epoch assignment; overlapping decisecond values can therefore be dated to the wrong boot/day, which corrupts activity sessions and daily activity in the dashboard. Use build_epoch_assignments and pass the per-row epoch into timestamp conversion, like the sleep runner does.

Useful? React with 👍 / 👎.

Comment thread crates/oura-dna/src/lib.rs
@Th0rgal

Th0rgal commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_cf96cbc5-521f-4c8b-b460-8cdc81683171)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67e3e11b94

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread dashboard/web/blood.js Outdated
h("span", { class: "bl-imp-name" }, im.file),
h("span", { class: "bl-imp-date" }, fmtDate(im.date)),
h("span", { class: "bl-imp-meta" }, `${im.markers} markers · ${im.lab || "lab"} · ${im.status || "ok"} · ${fmtSize(im.size)}`),
...(im.warnings && im.warnings.length ? [h("span", { class: "bl-imp-warn" }, im.warnings.join("; "))] : []))));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fix the blood panel syntax so the page can load

When /blood loads, the browser parses this script before running any of the report code, and the extra closing parenthesis here makes the entire file fail with a syntax error. That leaves the blood panel permanently blank/broken even though the Rust /api/blood/* endpoints may work.

Useful? React with 👍 / 👎.

Comment thread apps/ios/OuraApp/EventStore.swift
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_d60d5839-5cc0-434c-a90e-908fe38cfeaa)

@Th0rgal

Th0rgal commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8437dc5b11

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/oura-cli/src/main.rs Outdated
.arg("--tz")
.arg(tz_offset.to_string());
if let Some(c) = csv {
cmd.arg("--csv").arg(c);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don’t forward an unsupported --csv flag

When oura sleep-score --csv ... is used, this forwards --csv to tools/score_sleep.py, but that script’s argparse does not define a --csv option, so the child exits with an unrecognized-argument error and the advertised calibration CSV option is unusable. Either add support in the script or stop exposing/forwarding this flag.

Useful? React with 👍 / 👎.

Comment thread crates/oura-cli/src/main.rs
Comment thread apps/ios/OuraApp/CvaModel.swift Outdated
// raw PPG bodies in time order (tag 129)
var tss: [Int64] = [], bodies: [[UInt8]] = []
var stmt: OpaquePointer?
if sqlite3_prepare_v2(db, "SELECT ring_timestamp, body FROM events WHERE tag=129 AND body IS NOT NULL ORDER BY ring_timestamp", -1, &stmt, nil) == SQLITE_OK {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Order CVA PPG by epoch/insertion

After a ring reboot, ring_timestamp resets, so ordering CVA PPG rows only by ring_timestamp interleaves different boot epochs; the subsequent gap split will not catch negative jumps and can concatenate unrelated PPG bursts into one model segment, corrupting vascular age/PWV. Use insertion order plus the epoch assignment logic already used by EventStore for the other on-device models.

Useful? React with 👍 / 👎.

Comment thread tools/run_activity_model.py Outdated
if b2 is None:
continue
data.append(unpack27(b1, b2))
tsms.append(int(_unix_s(ts) * 1000)) # unix ms

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Restore timestamp helper for stepmotion

With STEPMOTION=1 and the stepmotion decoder present, this path reaches _unix_s, which no longer exists after the epoch-aware refactor; the exception is caught and the code silently falls back to the NaN stub, so opt-in real gait is never actually fed to the activity model. Compute tsms from unix_in_epoch/event_epochs or restore a helper with the new signature.

Useful? React with 👍 / 👎.

@Th0rgal

Th0rgal commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_9220dc35-3f26-42e1-8190-380530caa789)

@Th0rgal

Th0rgal commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

@codex review

1 similar comment
@Th0rgal

Th0rgal commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0c04d2d5bb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

#if TORCH
if base.error == nil {
let full = Core.withModels(base)
DispatchQueue.main.async { s = full }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Guard against stale model loads after sync reloads

In TORCH builds this assigns the slow model result unconditionally after it finishes. If a sync completes while an earlier model pass is still running, reload() sets s = nil and starts a newer load, but the older background closure can finish later and overwrite the freshly loaded DB state with a summary based on the pre-sync base. Track a load generation/token before assigning the model result so stale work is dropped.

Useful? React with 👍 / 👎.

Comment on lines 94 to +95
"SELECT ring_timestamp, tag, decoded_json, captured_unix FROM events "
"WHERE decoded_json IS NOT NULL ORDER BY ring_timestamp"
"WHERE decoded_json IS NOT NULL ORDER BY id"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Filter activity-model inputs to the active ring

When the same SQLite DB contains events from more than one synced ring, this query feeds all serials into the activity model even though the summary code filters non-model data to the selected device serial. The returned sessions are copied directly into the dashboard's activity array, so workouts from another ring/wearer can appear on the current device's dashboard. Pass the active serial into the runner and add an events.serial filter here.

Useful? React with 👍 / 👎.


var events: [Ev] = []
var stmt: OpaquePointer?
let sql = "SELECT ring_timestamp, tag, decoded_json, captured_unix FROM events WHERE decoded_json IS NOT NULL ORDER BY id"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Filter on-device model reads to the current serial

After the iOS app syncs more than one ring into the writable DB, the base summary chooses one device serial, but both on-device SleepStaging and ActivityModel call this helper and receive every serial's events. Since ring timestamps and boot epochs are per device, the TORCH models can consume another ring's IBI/MET/temp data and fold those stages or workouts into the current summary. Query only the active serial (or pass it from the base summary) instead of reading all decoded events.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant