Skip to content

feat(core): local persistence layer for events, samples & insights - #7

Open
shroominic wants to merge 1 commit into
feat/health-data-modelsfrom
feat/local-persistence
Open

feat(core): local persistence layer for events, samples & insights#7
shroominic wants to merge 1 commit into
feat/health-data-modelsfrom
feat/local-persistence

Conversation

@shroominic

Copy link
Copy Markdown
Owner

Stacked on #5 (feat/health-data-models). Rebase to main after #5 merges, then this PR's base can move to main.

What & why

The storage foundation that journaling, on-device AI insights, and HealthKit import build on. Builds directly on the cgm_core domain models from #5.

Store chosen: sqflite (relational)

Picked over hive/isar because the repository's core access pattern is "records of type X in time window [a, b)". SQLite indexes the timestamp/type columns and answers those queries in SQL rather than scanning every record in Dart, it scales comfortably to the many activity/heart-rate rows a HealthKit import produces, and it exposes explicit schema-version migration hooks (onCreate/onUpgrade). It's the lowest-risk, most idiomatic on-device Flutter store. Stays fully local — no cloud sync. The existing CGM reading history keeps its shared_preferences persistence, untouched.

Repository API (HealthRepository, pure Dart in cgm_core)

  • Lifecycle: init() (runs migrations, idempotent), close(), clear().
  • Events: upsertEvent / upsertEvents (bulk) / deleteEvent / getEvent / queryEvents({window, types}).
  • Activity / Sleep / HeartRate samples: bulk upsert*, delete*({window}) (returns rows removed), query*({window[, types]}).
  • AI insights: upsertInsight / deleteInsight / queryInsights({window, categories}).
  • TimeWindow — half-open [start, end), either bound optional; composes for paging.
  • Queries return results sorted chronologically ascending.

New AiInsight model added to cgm_core (it didn't exist yet): id, createdAt, category, title/body, optional derivation window, confidence, model, tags; implements TimelineEntry (new TimelineEntryKind.aiInsight); full JSON round-trip.

Implementations

  • InMemoryHealthRepository (in cgm_core) — for tests / ephemeral use.
  • SqfliteHealthRepository (in the app) — versioned schema (schemaVersion = 1) with a forward-migration chain shared by onCreate and onUpgrade; filterable columns (epoch-ms timestamps, type/category keys) promoted out of a JSON data column and indexed. openHealthRepository() opens it in the app-documents dir.

Tests (no device/simulator needed)

  • A reusable runHealthRepositoryContractTests suite verifying round-trip, half-open window queries, type/category filtering, bulk insert, delete-by-window, replace-by-id, and clear — run against the in-memory impl.
  • App-side flutter test runs the same contract against SqfliteHealthRepository via sqflite_common_ffi (in-process SQLite), plus migration/idempotent-init tests.
  • cgm_core: dart analyze clean, 42 tests pass. App: flutter analyze clean (only a pre-existing unrelated info), 30 tests pass, flutter build ios --simulator --debug builds clean (pods resolve).

Unblocks

Journaling (events), on-device AI insights (persisted history), and HealthKit/Health Connect import (bulk sample insert).

🤖 Generated with Claude Code

Introduce a pure-Dart HealthRepository abstraction in cgm_core covering
HealthEvent, Activity/Sleep/HeartRate samples, and a new AiInsight model,
with add/update/delete, query-by-window (half-open) + type filtering, and
bulk insert for imports. Ships an in-memory implementation plus a reusable
contract test suite.

The Flutter app provides a concrete SqfliteHealthRepository (sqflite chosen
over hive/isar: relational + query-friendly so window/type filters are
index-backed, scales to many imported rows, and gives explicit schema
migration hooks). Stays fully local; CGM reading history keeps its existing
shared_preferences persistence untouched.

- cgm_core: AiInsight model, HealthRepository + TimeWindow, InMemory impl
- app: SqfliteHealthRepository (versioned schema + onCreate/onUpgrade),
  openHealthRepository() opener, FFI-backed in-process tests (no device)
- adds sqflite/path/path_provider (+ sqflite_common_ffi dev dep)

Unblocks journaling, on-device AI insights, and HealthKit import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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