feat(sleep): elevate sleep to first-party tables#226
Open
ryceg wants to merge 42 commits into
Open
Conversation
SleepStageType, SleepSessionType, SleepDetectionMethod, and SleepSource enums with JSON string serialization for the dedicated sleep table system.
…ometric samples Three new EF Core entities for first-party sleep data: SleepSessionEntity (ITenantScoped + IAuditable), SleepStageEntity, and SleepBiometricSampleEntity. All use snake_case columns, UUID v7 PKs, and follow the StateSpanEntity pattern.
Add DbSets, indexes, FK relationships, and UUID v7 generators for SleepSessionEntity, SleepStageEntity, and SleepBiometricSampleEntity.
Creates sleep_sessions, sleep_stages, and sleep_biometric_samples tables with Row Level Security tenant isolation policies.
CRUD + query/count repository port for SleepSession, following the same shape as IStateSpanRepository.
…ert dedup Implements ISleepSessionRepository using the ITenantDbContextFactory pattern. Includes 12 unit tests covering date-range filtering, tenant isolation, child entity includes, source+originalId dedup on upsert, and delete behavior.
Wrap upsert/update delete-then-insert operations in explicit transactions to prevent partial writes. Replace bare catch clauses with specific exception types (JsonException). Fix ToDomainModel to use entity.Id for round-trip safety. Avoid mutating caller's input in UpdateSessionAsync. Remove redundant .AsQueryable(). Add UpdateSessionAsync test coverage.
…, and DI registrations Introduces the service layer for sleep sessions: ISleepService contract in Core.Contracts.Sleep, thin SleepService passthrough in API.Services.Sleep, and scoped DI registrations for both the service and repository.
Paginated list (GET), detail by ID (GET), create/upsert (POST), update (PUT), and delete (DELETE) for sleep sessions.
…panService Replace the StateSpan-based sleep query with ISleepService.GetSessionsAsync, expanding sleep sessions into per-stage spans for richer actogram rendering.
…ions table Sleep-type activities (sleep/nap/rest/sleeping) submitted via the v1 Activity API are now persisted as SleepSession records via ISleepService instead of StateSpan records. GET endpoints merge sleep sessions (projected back to Activity format) alongside non-sleep StateSpan activities. ActivityCategories no longer includes StateSpanCategory.Sleep.
Data migration that converts existing StateSpanCategory.Sleep rows into the new first-party sleep tables. Groups adjacent spans by calendar date into sessions, maps state strings to SleepStage enum values, and deletes the migrated StateSpan rows. One-way migration (Down is a no-op).
Sleep data now lives in the dedicated sleep_sessions table, so the StateSpanCategory.Sleep enum value, the /sleep convenience endpoint, and all references in mappers, chart stages, and tests are removed.
…e Sleep references Move UpdateSessionAsync's entity fetch inside the retry strategy lambda to prevent tracked-entity issues on retry. Inject ISleepService into DataFetchStage and project sleep sessions as activity spans in DtoMappingStage. Remove StateSpanCategory.Sleep references from frontend components (time-spans, alert rule builder, activity icon) and clean up XML doc comments in StateSpansController.
Contributor
Preview Container ImagesPublished for commit
This comment is updated on each push to this PR. |
| new { collection = "activity", id } | ||
| ); | ||
| await _events.OnDeletedAsync(null, cancellationToken); | ||
| _logger.LogDebug("Successfully deleted sleep session for activity ID: {Id}", id); |
| { | ||
| _logger.LogDebug( | ||
| "Upserting sleep session with ID: {Id}, Type: {Type}, Source: {Source}", | ||
| session.Id, session.Type, session.Source); |
…tch types Extract nullable .Value accesses to local variables in BuildFilteredQuery so the analyzer can prove null-safety through the expression tree closure. Replace generic catch(Exception) in the sleep activity loop with specific InvalidOperationException + ArgumentException catches, re-throwing OperationCanceledException to preserve cancellation semantics.
Resolves conflicts in portal package.json, svelte.config.js, and pnpm-lock.yaml. Portal adapter-cloudflare → adapter-static revert from main is authoritative; sleep branch does not own those portal changes.
…leepStageType.Unknown
Create Sleep/Report/ with SleepStageBreakdown, SleepOvernightTir, SleepHypoEvent, SleepDawnPhenomenon, SleepWakeEvent, and SleepSingleNightReport. Add [JsonConverter(JsonStringEnumConverter<T>)] to SleepScoreSource and SleepHypoSeverity for consistent string serialization with the rest of the sleep domain enums.
…nt DTO timestamps with SleepSession
…ive slope supported)
… populate MeanHrvMs, add night summary test
- SleepReportService: on-demand report assembly joining session + CGM data;
GetSingleNightReportAsync and GetTrendsReportAsync with batch glucose query
- SleepReportController: GET api/v4/sleep/report/single-night/{sessionId} and
GET api/v4/sleep/report/trends with 90-day cap; [RemoteQuery] on both endpoints
- Register ISleepReportService in DI
- Generated sleepReports.generated.remote.ts (getSingleNight, getTrends)
Review fixes:
- DawnRiseDelta in 7d-vs-prior-7d delta was always null; now computed
- ResolveScore returns (int?, SleepScoreSource?) — null when no stage data
- ScoreSource is null in SleepNightSummary when SleepScore is null
- LowestBg in SleepNightSummary is the session CGM minimum, not hypo-only
- Dawn phenomenon DeltaBg and RateOfClimbPerHour use last-vs-first direction
- TIR and hypo event windows start at asleepAt (StartTime + SleepLatencyMs)
- SleepSingleNightReport.Score surfaces the resolved score value
- SleepStageReferenceRangeSet.Default properties are now init-only
Tests: 33 sleep unit tests, all passing
| this.sensorGlucose = new SensorGlucoseClient(apiBaseUrl, http); | ||
| this.services = new ServicesClient(apiBaseUrl, http); | ||
| this.setup = new SetupClient(apiBaseUrl, http); | ||
| this.sleep = new SleepClient(apiBaseUrl, http); |
| this.services = new ServicesClient(apiBaseUrl, http); | ||
| this.setup = new SetupClient(apiBaseUrl, http); | ||
| this.sleep = new SleepClient(apiBaseUrl, http); | ||
| this.sleepReport = new SleepReportClient(apiBaseUrl, http); |
| var efficiency = (breakdown.DeepMinutes + breakdown.RemMinutes + breakdown.LightMinutes) / total; | ||
| var deepFrac = breakdown.DeepMinutes / total; | ||
| var remFrac = breakdown.RemMinutes / total; | ||
| var disruption = Math.Min(20, breakdown.AwakeMinutes * 0.6 + hypoCount * 4); |
| return (session.SleepScore.Value, SleepScoreSource.Device); | ||
|
|
||
| var total = (double)breakdown.TotalMinutes; | ||
| if (total == 0) return (null, null); |
Back-to-back pushes to main raced two regenerate jobs, with the loser hitting a content conflict in generated schemas.ts during git pull --rebase. Add a concurrency group so only the latest source regenerates, and fall back to checking out our freshly regenerated files if the rebase still conflicts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
sleep_sessions,sleep_stages,sleep_biometric_samples) with RLS, replacing the genericStateSpanCategory.Sleepapproach/api/v4/sleep/sessions)ISleepServiceSleepfromStateSpanCategoryand cleans up all backend + frontend referencesDesign
See
docs/plans/2026-05-16-sleep-first-party-table-design.mdfor the full design rationale, cross-platform stage mapping, and connector integration contract.Test plan
/api/v4/sleep/sessions