Skip to content

UNDA — Data Flow ​

Status: Draft. Reflects the current prototype: local-first, sqflite on device, no network calls in production paths.

High-level flow ​

             ┌─────────────────────────────────────────────────────────┐
             │                   User's device                         │
             │                                                         │
   user ──►  │   UI (Flutter widgets) ──► Riverpod state ──► sqflite   │
             │                                     │            │       │
             │                                     └── read ──► │       │
             │                                                  ▼       │
             │                                             unda.db      │
             │                                          (unencrypted    │
             │                                           in v0.1 — B10) │
             └─────────────────────────────────────────────────────────┘

   (planned) Personalize → MockImporter → ProfileInferenceService → profileProvider
   (planned) Settings → sync toggle ON → Supabase (EU region), row-level security
   (planned) WorkoutDetail → .fit export → user shares file (never uploaded by UNDA)

Actual current flows ​

F1. Onboarding → Profile persistence ​

  1. User picks cycle length + last period date.
  2. OnboardingController.finish() → profileProvider.state = new Profile(...).
  3. PersistedProfileNotifier writes to profiles table with sync_enabled=false, onboarding_complete=true.
  4. Nothing leaves the device.

F2. Symptom logging ​

  1. User taps a chip on Calendar.
  2. SymptomLog.toggle(date, symptom) → sqflite cycle_days upsert.
  3. Nothing leaves the device.

F3. Mark session completed ​

  1. User taps "Mark completed", picks felt-score 1–5.
  2. SessionLog.logCompleted(...) → sqflite workout_sessions insert.
  3. Nothing leaves the device.

F4. Personalize demo import (MockImporter) ​

  1. User taps a demo scenario.
  2. MockImporter.fetchRecent(...) returns fixture activities (no network).
  3. ProfileInferenceService.infer(...) produces an InferredProfile.
  4. User confirms → profile updated in sqflite.
  5. Nothing leaves the device.

Planned flows (not yet wired) ​

FP1. Cloud sync (opt-in) ​

  • Supabase EU region.
  • Row-level security keyed on auth.uid() — user only sees their own rows.
  • Sync direction: local ↔ remote, last-write-wins per row (updated_at).
  • Transfer basis: within EU/EEA; no international transfer in the default configuration.
  • Consent event required: health_data_processing and cloud_sync (see consent-register.md).

FP2. Strava / Intervals.icu / Garmin import (opt-in per provider) ​

  • OAuth in the user's browser.
  • Tokens go to secure storage (iOS Keychain / Android EncryptedSharedPreferences) via flutter_secure_storage.
  • Fetch: recent activities (default 90 days backfill, then incremental).
  • Storage: only fields listed in data-inventory.md under imported_activity. Raw JSON blobs are not persisted.
  • Per-provider consent event: strava / intervals_icu / garmin.
  • Disconnect: stops future sync and deletes previously imported activity rows (documented per §8.4).

F6. Push workout to Intervals.icu calendar (shipped) ​

  1. User opens a UNDA workout, taps "Add to Intervals plan" with a chosen date.
  2. IntervalsIcuImporter.pushEvent() POSTs to intervals.icu/api/v1/athlete/{id}/events with name, description (auto-generated from steps), type, moving_time, start_date_local, category=WORKOUT.
  3. On 200/201, we store the returned event_id in pushed_intervals_events keyed by (workout_slug, target_date) so the UI reflects the state and can offer Remove.
  4. Remove flow: DELETE /events/{event_id}, then delete the local bookkeeping row.
  5. Content going out is the workout name + step description — no cycle data, no symptoms, no personal identifiers beyond what Intervals already has via the API key.

F5. Export (shipped) ​

  1. User taps "Export my data" in Privacy Center.
  2. DataExporter.buildZip() writes UNDA-export-YYYYMMDD-HHmmss.zip to getTemporaryDirectory() with: README.txt, profile.json, cycle.csv, sessions.csv, consents.csv, integrations.json.
  3. share_plus invokes the OS share sheet. User chooses destination (Files, iCloud, AirDrop, mail, etc.).
  4. Nothing leaves the device via UNDA-controlled channels — the user picks the recipient.

Data leaves the device only when... ​

  • Cloud sync is explicitly ON.
  • An integration OAuth is explicitly authorized.
  • The user chooses to share/export.

Every one of these paths must be gated by a versioned consent record (§4).

UNDA is a fitness and training support product. It is not a medical device.