Skip to main content

Overview

The Synheart runtime computes three daily scores: Each scorer takes a typed input bundle, returns a typed result, and emits a JSON shape that’s byte-equivalent across Flutter / Kotlin / Swift. The Synheart Core SDKs expose Dart / Kotlin / Swift data classes that round-trip with the runtime’s wire format.

Sleep score

Three input shapes — pick the one matching your data source:
  • Segmented: stage-by-stage timeline (Apple HealthKit, some Wear OS sources, BLE chest straps)
  • Aggregated: totals only (vendor APIs that don’t expose stages — Whoop summary, basic Health Connect, self-report)
  • Vendor score: a single 0-100 number from a vendor that has its own pipeline (Whoop Recovery, Garmin Body Battery)

Kotlin

Swift

SleepScoreResult carries score: Int?, confidence: Double, path: SleepPath (stage / aggregated / vendor_score / proxy), mode: SleepScoreMode (cold_start / short_history / stable), components, adjustments, effectiveWeights, and a reason: SleepScoreReason? for absent/degraded scores.

Recovery score

Three-stage scorer — the engine adapts the formula to the available history:
  • Stage 1 (FirstDay) — 1 night of sleep + (HR or HRV)
  • Stage 2 (ShortHistory) — ≥ 3 nights with HR/HRV trends
  • Stage 3 (Personalized) — ≥ 7 nights + stable wearable baselines
RecoveryScoreResult carries score: Int, stage (first_day / short_history / personalized), mode (estimate / trended / personalized), components, confidence, and a List<RecoveryFactor> explaining the score (hrv_above_baseline, resting_hr_elevated, strong_sleep_quality, etc.).

Readiness score

Layers acute load / fatigue / history context on top of today’s Recovery anchor:
ReadinessScoreResult carries score: Int, band: ReadinessBand (.rest / .light / .normal / .push), recoveryAnchor: Int, components, confidence, and a List<ReadinessFactor> (strong_recovery, acute_load_optimal, sleep_debt, etc.).

Sleep questionnaire

Phase-2 self-report input lane — converts subjective answers into the same AggregatedTotals shape vendor payloads produce, so the engine scores them without special-casing.
The engine accepts null deep/REM on the aggregated path and falls back to duration + continuity components — that’s deliberate. Don’t invent stage durations from subjective signals; surface subjective quality / rested-feeling in the UI alongside the score instead.

Cross-language parity

All three scorers emit identical snake_case JSON on every platform:
  • Flutter: lib/src/models/{sleep,recovery,readiness}_score.dart + sleep_questionnaire.dart
  • Kotlin: synheart-core/src/main/kotlin/ai/synheart/core/models/{SleepScore,RecoveryScore,ReadinessScore,SleepQuestionnaire}.kt
  • Swift: SynheartCore/Models/{SleepScore,RecoveryScore,ReadinessScore,SleepQuestionnaire}.swift
A Flutter, Android, and iOS app talking to the same backend produce byte-equivalent scorer payloads.