@@ -25,7 +25,7 @@ There are no real deployments, only test installations. Existing prototype datab |
||
| 25 | 25 |
|------|----------------|--------------------| |
| 26 | 26 |
| Product docs | Updated | Keep `HealthProbe/Doc/README.md` as canonical index | |
| 27 | 27 |
| HealthKit capture | Capture now opens one archive observation per user-visible snapshot, attaches HealthKit pages, deleted-object evidence, and type verification to that observation id before finishing it, no longer aborts initial full-history imports after a fixed 30-minute wall-clock cap while page-level HealthKit timeouts remain in place, defers grouped observation summary/daily aggregate rebuilds until per-type verification instead of rebuilding after every imported page, and persists large HealthKit pages in smaller archive chunks while using type-specific import strategies: conservative paging for the heaviest metrics, more aggressive pages/chunks for ordinary metrics, adaptive write chunk sizing, batched deleted-object persistence, explicit task yields, and lower-allocation streaming loops to avoid long monolithic SQLite stalls | Continue moving UI/cache reads to archive-backed observation ids and revisit full checkpoint/resume and background collection separately | |
| 28 |
-| SQLite archive | Archive v2 schema, snapshot-level observation grouping, differential write path, v2 verification/delete bookkeeping, daily aggregate rebuilds, integrity report, v2 record reads, SQL diff/count/aggregate/provenance/consolidation-evidence APIs, large synthetic diff pagination coverage, formal timing/memory metrics, and XCTest coverage are in place; the legacy `archive_samples` mirror has been removed, the hot write path now reuses prepared SQLite statements within grouped page writes instead of reparsing the same SQL for every sample, caches repeated sample-type/source/source-revision/device/metadata id lookups within grouped writes, processes sample rows in a lower-allocation streaming loop, batches same-page deleted-object evidence in one transaction, adds composite indexes for visibility-range and sample-uuid hot lookups, and opens SQLite connections with import-friendly busy timeout / synchronous / temp-store pragmas | Continue moving capture/Dashboard actions to archive/cache DTOs | |
|
| 28 |
+| SQLite archive | Archive v2 schema, snapshot-level observation grouping, differential write path, v2 verification/delete bookkeeping, daily aggregate rebuilds, integrity report, v2 record reads, SQL diff/count/aggregate/provenance/consolidation-evidence APIs, large synthetic diff pagination coverage, formal timing/memory metrics, and XCTest coverage are in place; the legacy `archive_samples` mirror has been removed, the hot write path now reuses prepared SQLite statements within grouped page writes instead of reparsing the same SQL for every sample, caches repeated sample-type/source/source-revision/device/metadata id lookups within grouped writes, skips redundant visibility close/existence checks when grouped imports create a brand-new sample or payload version, processes sample rows in a lower-allocation streaming loop, batches same-page deleted-object evidence in one transaction, adds composite indexes for visibility-range and sample-uuid hot lookups, and opens SQLite connections with import-friendly busy timeout / synchronous / temp-store pragmas | Continue moving capture/Dashboard actions to archive/cache DTOs | |
|
| 29 | 29 |
| Core Data cache | Initial programmatic Core Data model, full-cache rebuild service, read DTOs for observation/type/diff/health rows, and Dashboard archive-cache status wiring are in place | Move remaining export/report paths to cache DTOs and add targeted partial invalidation | |
| 30 | 30 |
| SwiftData cache | Exists; test builds now reset legacy prototype UI/archive/cache stores once for archive v2 so old SwiftData-only snapshots are not treated as backed-up observations. Metric timeout calibration, local device profile settings, operation logging, ContentView preview, Settings data maintenance, legacy detail/PDF views, unused legacy repair/observer services, Dashboard view/view-model access, and legacy anomaly/count-drop review have moved outside SwiftData or been removed. Remaining SwiftData imports are inventoried in [`SwiftData-Retirement-Inventory.md`](SwiftData-Retirement-Inventory.md) | Treat as disposable prototype data; stop returning/storing `HealthSnapshot` bridge handles before removing `ModelContainer` | |
| 31 | 31 |
| UI | Prototype exists; Dashboard status reads archive/cache observation rows and shows cache health, and Dashboard view/view-model code no longer imports SwiftData or reads `ModelContext`; capture/review actions now route through DTOs and snapshot ids, with the remaining legacy bridge isolated in `HealthKitService`. Snapshots and Data Types tab roots no longer import SwiftData, load Core Data cached observation rows, and open archive/cache-backed detail rows; `SnapshotArchiveDetailView` and `DataTypeArchiveDetailView` read Core Data type/diff summaries and page record drill-down through SQLite; unused legacy SwiftData snapshot/type detail and PDF views have been deleted; record-change evolution and temporal distribution screens now receive DTO rows/cache input instead of querying SwiftData directly; export preview reads the archive export API before showing/exporting JSON; simplified detail mode replaces heavy charts with summary rows on small/accessibility layouts or when enabled in Settings; visible change labels now use neutral new/missing/change-review language; Settings can now schedule a full test-database reset for the next app launch | Stop writing prototype `HealthSnapshot` bridge rows during capture/review | |
@@ -2117,22 +2117,50 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
|
||
| 2117 | 2117 |
db: db, |
| 2118 | 2118 |
statementCache: statementCache |
| 2119 | 2119 |
) |
| 2120 |
- try closeOpenVisibilityRanges( |
|
| 2121 |
- sampleID: sampleResult.id, |
|
| 2122 |
- excludingVersionID: versionResult.id, |
|
| 2123 |
- closedAtObservationID: observationID, |
|
| 2124 |
- observedAt: row.observedAt, |
|
| 2125 |
- db: db, |
|
| 2126 |
- statementCache: statementCache |
|
| 2127 |
- ) |
|
| 2128 |
- try insertOpenVisibilityRangeIfNeeded( |
|
| 2129 |
- sampleID: sampleResult.id, |
|
| 2130 |
- versionID: versionResult.id, |
|
| 2131 |
- observationID: observationID, |
|
| 2132 |
- observedAt: row.observedAt, |
|
| 2133 |
- db: db, |
|
| 2134 |
- statementCache: statementCache |
|
| 2135 |
- ) |
|
| 2120 |
+ if sampleResult.inserted {
|
|
| 2121 |
+ try insertOpenVisibilityRange( |
|
| 2122 |
+ sampleID: sampleResult.id, |
|
| 2123 |
+ versionID: versionResult.id, |
|
| 2124 |
+ observationID: observationID, |
|
| 2125 |
+ observedAt: row.observedAt, |
|
| 2126 |
+ db: db, |
|
| 2127 |
+ statementCache: statementCache |
|
| 2128 |
+ ) |
|
| 2129 |
+ } else if versionResult.inserted {
|
|
| 2130 |
+ try closeOpenVisibilityRanges( |
|
| 2131 |
+ sampleID: sampleResult.id, |
|
| 2132 |
+ excludingVersionID: versionResult.id, |
|
| 2133 |
+ closedAtObservationID: observationID, |
|
| 2134 |
+ observedAt: row.observedAt, |
|
| 2135 |
+ db: db, |
|
| 2136 |
+ statementCache: statementCache |
|
| 2137 |
+ ) |
|
| 2138 |
+ try insertOpenVisibilityRange( |
|
| 2139 |
+ sampleID: sampleResult.id, |
|
| 2140 |
+ versionID: versionResult.id, |
|
| 2141 |
+ observationID: observationID, |
|
| 2142 |
+ observedAt: row.observedAt, |
|
| 2143 |
+ db: db, |
|
| 2144 |
+ statementCache: statementCache |
|
| 2145 |
+ ) |
|
| 2146 |
+ } else {
|
|
| 2147 |
+ try closeOpenVisibilityRanges( |
|
| 2148 |
+ sampleID: sampleResult.id, |
|
| 2149 |
+ excludingVersionID: versionResult.id, |
|
| 2150 |
+ closedAtObservationID: observationID, |
|
| 2151 |
+ observedAt: row.observedAt, |
|
| 2152 |
+ db: db, |
|
| 2153 |
+ statementCache: statementCache |
|
| 2154 |
+ ) |
|
| 2155 |
+ try insertOpenVisibilityRangeIfNeeded( |
|
| 2156 |
+ sampleID: sampleResult.id, |
|
| 2157 |
+ versionID: versionResult.id, |
|
| 2158 |
+ observationID: observationID, |
|
| 2159 |
+ observedAt: row.observedAt, |
|
| 2160 |
+ db: db, |
|
| 2161 |
+ statementCache: statementCache |
|
| 2162 |
+ ) |
|
| 2163 |
+ } |
|
| 2136 | 2164 |
|
| 2137 | 2165 |
return ArchiveV2SampleWriteResult(sampleTypeID: sampleTypeID, kind: writeKind) |
| 2138 | 2166 |
} |
@@ -2777,6 +2805,24 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
|
||
| 2777 | 2805 |
bindInt64(versionID, to: 2, in: statement) |
| 2778 | 2806 |
} |
| 2779 | 2807 |
guard existing == nil else { return }
|
| 2808 |
+ try insertOpenVisibilityRange( |
|
| 2809 |
+ sampleID: sampleID, |
|
| 2810 |
+ versionID: versionID, |
|
| 2811 |
+ observationID: observationID, |
|
| 2812 |
+ observedAt: observedAt, |
|
| 2813 |
+ db: db, |
|
| 2814 |
+ statementCache: statementCache |
|
| 2815 |
+ ) |
|
| 2816 |
+ } |
|
| 2817 |
+ |
|
| 2818 |
+ private func insertOpenVisibilityRange( |
|
| 2819 |
+ sampleID: Int64, |
|
| 2820 |
+ versionID: Int64, |
|
| 2821 |
+ observationID: Int64, |
|
| 2822 |
+ observedAt: Date, |
|
| 2823 |
+ db: OpaquePointer?, |
|
| 2824 |
+ statementCache: SQLiteStatementCache? = nil |
|
| 2825 |
+ ) throws {
|
|
| 2780 | 2826 |
try withStatement( |
| 2781 | 2827 |
""" |
| 2782 | 2828 |
INSERT OR IGNORE INTO sample_visibility_ranges ( |