On-device AI wellness intelligence for Oura Ring — privacy-first Android SDK.
Noctua (the owl genus — nocturnal wisdom) is an open-source Kotlin toolkit that combines a complete, typed client for the Oura API v2 with an on-device AI layer that turns raw biometrics into explainable insights and a next-day readiness forecast — without your health data ever leaving the phone.
| Dashboard | AI Coach (on-device) | Connect |
|---|---|---|
![]() | ![]() | ![]() |
Captured from the example app running in demo mode on a Pixel 7 Pro emulator.
Most wearable companion apps ship your biometric history to a cloud LLM to generate "insights". Noctua takes the opposite stance:
| Cloud AI companions | Noctua | |
|---|---|---|
| Raw HRV / sleep / temperature data | uploaded to a server | never leaves the device |
| Insight logic | opaque | transparent, unit-tested rules + open model |
| Works offline | ✗ | ✓ |
| Latency | network round-trip | < 5 ms on-device |
graph TD
A[Oura Cloud API v2] -->|OAuth2 / PAT| B[noctua-core<br/>typed Kotlin client]
B --> C[WellnessSnapshot<br/>readiness · sleep · activity · HRV]
C --> D[noctua-ai<br/>FeatureExtractor]
D --> E1[HeuristicInsightEngine<br/>explainable rules]
D --> E2[ExecuTorchForecaster<br/>.pte neural model, on-device]
E1 --> F[NoctuaReport]
E2 --> F
E2 -.missing runtime.-> E3[LinearHeuristicForecaster<br/>zero-dependency fallback]
E3 --> F
F --> G[example-app<br/>Jetpack Compose]
| Module | What it is |
|---|---|
noctua-core | Pure-Kotlin Oura API v2 client — OAuth2 helpers, auto-refreshing tokens, all usercollection endpoints, pagination, sandbox support. Runs on Android and any JVM backend. |
noctua-ai | On-device intelligence: feature extraction (sleep debt, HRV z-score vs personal baseline, readiness trend), explainable heuristic insights, and a neural readiness forecaster bridged to ExecuTorch. |
example-app | Material 3 Compose app — score rings, 14-day readiness trend, AI coach feed, OAuth/token connect flow, and a built-in demo mode that needs no Oura account. |
model/ | PyTorch → ExecuTorch export script for the readiness forecaster. |
noctua://callback.The modules are plain Gradle project dependencies (publish to Maven or use
via includeBuild / JitPack):
dependencies {
implementation("com.noctua:noctua-core:0.1.0")
implementation("com.noctua:noctua-ai:0.1.0")
// Optional: enable the neural forecaster
implementation("org.pytorch:executorch-android:1.0.0")
}
val oura = OuraClient.Builder()
.token("YOUR_TOKEN")
.build()
// Coroutine-first; pagination is handled for you.
val readiness = oura.dailyReadiness(startDate = "2026-08-01", endDate = "2026-08-21")
val sleep = oura.dailySleep(startDate = "2026-08-01", endDate = "2026-08-21")
val periods = oura.sleep(startDate = "2026-08-01", endDate = "2026-08-21")
OAuth2 (client-side flow) in two lines:
val url = OuraOAuth.authorizationUrl(clientId, redirectUri = "myapp://callback")
// open `url` in a Custom Tab, then in your deep-link handler:
val token = OuraOAuth.parseClientSideRedirect(intent.dataString!!).accessToken
For long-lived apps, OAuthTokenProvider refreshes expiring tokens
automatically via Oura's refresh_token grant.
val ai = NoctuaAI()
val report = ai.analyze(WellnessSnapshot(
readiness = readiness,
sleep = sleep,
activity = oura.dailyActivity("2026-08-01", "2026-08-21"),
sleepPeriods = periods,
))
println(report.forecastedReadiness) // e.g. 74 — tomorrow's predicted score
report.insights.forEach { println("• ${it.title} (${it.confidence}%)") }
// • Sleep debt accumulating (88%)
// • HRV below your baseline (80%)
cd model
pip install torch executorch
python export_readiness_forecaster.py # → readiness_forecaster.pte
Ship the .pte with your app and swap the forecaster:
val ai = NoctuaAI(forecaster = ExecuTorchForecaster(pteFile.absolutePath))
If the ExecuTorch runtime or model file is absent, Noctua silently falls back to the bundled linear model — the app never breaks.
| Endpoint | OuraClient method | Scope |
|---|---|---|
/v2/usercollection/personal_info | personalInfo() | personal |
daily_sleep / daily_readiness / daily_activity | dailySleep() · dailyReadiness() · dailyActivity() | daily |
daily_spo2 · daily_stress · daily_resilience | dailySpo2() · dailyStress() · dailyResilience() | spo2 / daily |
daily_cardiovascular_age · vO2_max | dailyCardiovascularAge() · vo2Max() | heart_health |
sleep (detailed periods) · sleep_time | sleep() · sleepTime() | daily |
heartrate (time series) | heartrate(start, end) ISO-8601 datetimes | heartrate |
workout · session · tag / enhanced_tag | workouts() · sessions() · tags() · enhancedTags() | workout / session / tag |
rest_mode_period · ring_configuration | restModePeriods() · ringConfigurations() | daily / ring_configuration |
Sandbox (/v2/sandbox/...) | Builder().sandbox(true) | none |
Errors map to typed OuraException subtypes: Unauthorized, RateLimited
(Oura allows ~5000 req / 5 min), Http, Network, Serialization.
git clone https://github.com/RanjithRagavan/Noctua.git
cd Noctua
./gradlew :example-app:installDebug
The app boots into demo mode with a deterministic 21-day dataset, so you can evaluate the full UX — score rings, trend chart, forecast card, AI coach — before connecting a real ring. The screenshots above show exactly what demo mode renders.
/v2/webhook/subscription)noctua-aiIssues and PRs welcome. The heuristics in HeuristicInsightEngine are
deliberately readable — improving them with better evidence is a great first
contribution. Run ./gradlew test before submitting.
Apache 2.0 — use it in personal or commercial apps.
Noctua is an independent open-source project and is not affiliated with, endorsed by, or sponsored by Ōura Health Oy.
7 commits
Kotlin
96.9%
Python
3.1%
On-device AI wellness intelligence for Oura Ring — privacy-first Android SDK.
Noctua (the owl genus — nocturnal wisdom) is an open-source Kotlin toolkit that combines a complete, typed client for the Oura API v2 with an on-device AI layer that turns raw biometrics into explainable insights and a next-day readiness forecast — without your health data ever leaving the phone.
| Dashboard | AI Coach (on-device) | Connect |
|---|---|---|
![]() | ![]() | ![]() |
Captured from the example app running in demo mode on a Pixel 7 Pro emulator.
Most wearable companion apps ship your biometric history to a cloud LLM to generate "insights". Noctua takes the opposite stance:
| Cloud AI companions | Noctua | |
|---|---|---|
| Raw HRV / sleep / temperature data | uploaded to a server | never leaves the device |
| Insight logic | opaque | transparent, unit-tested rules + open model |
| Works offline | ✗ | ✓ |
| Latency | network round-trip | < 5 ms on-device |
graph TD
A[Oura Cloud API v2] -->|OAuth2 / PAT| B[noctua-core<br/>typed Kotlin client]
B --> C[WellnessSnapshot<br/>readiness · sleep · activity · HRV]
C --> D[noctua-ai<br/>FeatureExtractor]
D --> E1[HeuristicInsightEngine<br/>explainable rules]
D --> E2[ExecuTorchForecaster<br/>.pte neural model, on-device]
E1 --> F[NoctuaReport]
E2 --> F
E2 -.missing runtime.-> E3[LinearHeuristicForecaster<br/>zero-dependency fallback]
E3 --> F
F --> G[example-app<br/>Jetpack Compose]
| Module | What it is |
|---|---|
noctua-core | Pure-Kotlin Oura API v2 client — OAuth2 helpers, auto-refreshing tokens, all usercollection endpoints, pagination, sandbox support. Runs on Android and any JVM backend. |
noctua-ai | On-device intelligence: feature extraction (sleep debt, HRV z-score vs personal baseline, readiness trend), explainable heuristic insights, and a neural readiness forecaster bridged to ExecuTorch. |
example-app | Material 3 Compose app — score rings, 14-day readiness trend, AI coach feed, OAuth/token connect flow, and a built-in demo mode that needs no Oura account. |
model/ | PyTorch → ExecuTorch export script for the readiness forecaster. |
noctua://callback.The modules are plain Gradle project dependencies (publish to Maven or use
via includeBuild / JitPack):
dependencies {
implementation("com.noctua:noctua-core:0.1.0")
implementation("com.noctua:noctua-ai:0.1.0")
// Optional: enable the neural forecaster
implementation("org.pytorch:executorch-android:1.0.0")
}
val oura = OuraClient.Builder()
.token("YOUR_TOKEN")
.build()
// Coroutine-first; pagination is handled for you.
val readiness = oura.dailyReadiness(startDate = "2026-08-01", endDate = "2026-08-21")
val sleep = oura.dailySleep(startDate = "2026-08-01", endDate = "2026-08-21")
val periods = oura.sleep(startDate = "2026-08-01", endDate = "2026-08-21")
OAuth2 (client-side flow) in two lines:
val url = OuraOAuth.authorizationUrl(clientId, redirectUri = "myapp://callback")
// open `url` in a Custom Tab, then in your deep-link handler:
val token = OuraOAuth.parseClientSideRedirect(intent.dataString!!).accessToken
For long-lived apps, OAuthTokenProvider refreshes expiring tokens
automatically via Oura's refresh_token grant.
val ai = NoctuaAI()
val report = ai.analyze(WellnessSnapshot(
readiness = readiness,
sleep = sleep,
activity = oura.dailyActivity("2026-08-01", "2026-08-21"),
sleepPeriods = periods,
))
println(report.forecastedReadiness) // e.g. 74 — tomorrow's predicted score
report.insights.forEach { println("• ${it.title} (${it.confidence}%)") }
// • Sleep debt accumulating (88%)
// • HRV below your baseline (80%)
cd model
pip install torch executorch
python export_readiness_forecaster.py # → readiness_forecaster.pte
Ship the .pte with your app and swap the forecaster:
val ai = NoctuaAI(forecaster = ExecuTorchForecaster(pteFile.absolutePath))
If the ExecuTorch runtime or model file is absent, Noctua silently falls back to the bundled linear model — the app never breaks.
| Endpoint | OuraClient method | Scope |
|---|---|---|
/v2/usercollection/personal_info | personalInfo() | personal |
daily_sleep / daily_readiness / daily_activity | dailySleep() · dailyReadiness() · dailyActivity() | daily |
daily_spo2 · daily_stress · daily_resilience | dailySpo2() · dailyStress() · dailyResilience() | spo2 / daily |
daily_cardiovascular_age · vO2_max | dailyCardiovascularAge() · vo2Max() | heart_health |
sleep (detailed periods) · sleep_time | sleep() · sleepTime() | daily |
heartrate (time series) | heartrate(start, end) ISO-8601 datetimes | heartrate |
workout · session · tag / enhanced_tag | workouts() · sessions() · tags() · enhancedTags() | workout / session / tag |
rest_mode_period · ring_configuration | restModePeriods() · ringConfigurations() | daily / ring_configuration |
Sandbox (/v2/sandbox/...) | Builder().sandbox(true) | none |
Errors map to typed OuraException subtypes: Unauthorized, RateLimited
(Oura allows ~5000 req / 5 min), Http, Network, Serialization.
git clone https://github.com/RanjithRagavan/Noctua.git
cd Noctua
./gradlew :example-app:installDebug
The app boots into demo mode with a deterministic 21-day dataset, so you can evaluate the full UX — score rings, trend chart, forecast card, AI coach — before connecting a real ring. The screenshots above show exactly what demo mode renders.
/v2/webhook/subscription)noctua-aiIssues and PRs welcome. The heuristics in HeuristicInsightEngine are
deliberately readable — improving them with better evidence is a great first
contribution. Run ./gradlew test before submitting.
Apache 2.0 — use it in personal or commercial apps.
Noctua is an independent open-source project and is not affiliated with, endorsed by, or sponsored by Ōura Health Oy.
7 commits
Kotlin
96.9%
Python
3.1%