|
Bogdan Timofte
authored
a month ago
|
1
|
# HealthProbe – Multi-Model Development Guide
|
|
|
2
|
|
|
|
3
|
## Overview
|
|
|
4
|
|
|
|
5
|
HealthProbe is built by multiple AI models, each owning a distinct domain.
|
|
|
6
|
This document defines boundaries, interfaces, and handoff contracts.
|
|
|
7
|
|
|
Bogdan Timofte
authored
3 weeks ago
|
8
|
**Agentic reality:** The repo is developed largely via agents (Codex CLI, Claude, and dedicated model sessions). When updating product scope, update docs first, then implement behind flags, and add tests for the new behavior.
|
|
|
9
|
|
|
Bogdan Timofte
authored
a month ago
|
10
|
---
|
|
|
11
|
|
|
|
12
|
## Model Allocation
|
|
|
13
|
|
|
|
14
|
| Domain | Owner | Tools |
|
|
|
15
|
|--------|-------|-------|
|
|
|
16
|
| **UI / SwiftUI Views** | Claude Code | Xcode, SwiftUI, CLAUDE.md |
|
|
Bogdan Timofte
authored
3 weeks ago
|
17
|
| **Archive Store** | Dedicated model session | SQLite/local archive format, HealthKit metadata mapping |
|
|
|
18
|
| **Data Models (SwiftData)** | Dedicated model session | Xcode, Swift; derived UI/cache/settings/log models only |
|
|
Bogdan Timofte
authored
a month ago
|
19
|
| **HealthKit Integration** | Dedicated model session | Xcode, HealthKit docs |
|
|
|
20
|
| **Anomaly Detection Algorithms** | Dedicated model session | Swift, statistical references |
|
|
Bogdan Timofte
authored
3 weeks ago
|
21
|
| **Context Monitoring** | Dedicated model session | Xcode; logs Health/iCloud state as context only |
|
|
Bogdan Timofte
authored
a month ago
|
22
|
| **Documentation** | Claude Code + dedicated session | Markdown |
|
|
|
23
|
| **Tests** | Dedicated model session | XCTest, Swift Testing |
|
|
|
24
|
|
|
|
25
|
---
|
|
|
26
|
|
|
|
27
|
## Directory Ownership
|
|
|
28
|
|
|
|
29
|
```
|
|
|
30
|
HealthProbe/
|
|
|
31
|
├── Views/ ← Claude Code (UI)
|
|
|
32
|
├── ViewModels/ ← Claude Code (UI)
|
|
|
33
|
├── Utilities/ ← Claude Code (shared helpers, mocks)
|
|
Bogdan Timofte
authored
3 weeks ago
|
34
|
├── Models/ ← Models agent (SwiftData UI/cache schemas)
|
|
|
35
|
├── Services/ ← Services agent (HealthKit, archive store, anomaly, context)
|
|
Bogdan Timofte
authored
a month ago
|
36
|
└── Tests/ ← Tests agent
|
|
|
37
|
```
|
|
|
38
|
|
|
|
39
|
**Rule:** Each agent writes only within its owned directories.
|
|
|
40
|
Cross-boundary changes require an explicit interface contract (protocol) first.
|
|
|
41
|
|
|
Bogdan Timofte
authored
3 weeks ago
|
42
|
**Documentation scope:** `HealthProbe/Doc/` is shared. Keep it consistent with shipped behavior, and add a dated entry when objectives change.
|
|
|
43
|
|
|
Bogdan Timofte
authored
a month ago
|
44
|
---
|
|
|
45
|
|
|
|
46
|
## Interface Contracts
|
|
|
47
|
|
|
|
48
|
All service boundaries are defined as Swift protocols.
|
|
|
49
|
Claude Code (UI) consumes protocols — never concrete implementations.
|
|
|
50
|
|
|
|
51
|
### HealthMonitorProtocol
|
|
|
52
|
|
|
|
53
|
```swift
|
|
|
54
|
/// Owned by: Services agent
|
|
|
55
|
/// Consumed by: UI (DashboardViewModel)
|
|
|
56
|
protocol HealthMonitorProtocol {
|
|
|
57
|
var currentStatus: HealthStatus { get }
|
|
|
58
|
var lastChecked: Date? { get }
|
|
|
59
|
func runCheck() async throws
|
|
|
60
|
}
|
|
|
61
|
```
|
|
|
62
|
|
|
|
63
|
### AnomalyStoreProtocol
|
|
|
64
|
|
|
|
65
|
```swift
|
|
|
66
|
/// Owned by: Services agent
|
|
|
67
|
/// Consumed by: UI (AnomalyListViewModel)
|
|
|
68
|
protocol AnomalyStoreProtocol {
|
|
|
69
|
var anomalies: [DetectedAnomaly] { get }
|
|
|
70
|
func markResolved(_ anomaly: DetectedAnomaly) async throws
|
|
|
71
|
}
|
|
|
72
|
```
|
|
|
73
|
|
|
|
74
|
### AuditTrailProtocol
|
|
|
75
|
|
|
|
76
|
```swift
|
|
|
77
|
/// Owned by: Services agent
|
|
|
78
|
/// Consumed by: UI (AuditTrailView)
|
|
|
79
|
protocol AuditTrailProtocol {
|
|
|
80
|
var entries: [AuditTrailEntry] { get }
|
|
|
81
|
func export() async throws -> Data // JSON
|
|
|
82
|
}
|
|
|
83
|
```
|
|
|
84
|
|
|
Bogdan Timofte
authored
3 weeks ago
|
85
|
### ContextMonitorProtocol
|
|
Bogdan Timofte
authored
a month ago
|
86
|
|
|
|
87
|
```swift
|
|
|
88
|
/// Owned by: Services agent
|
|
Bogdan Timofte
authored
3 weeks ago
|
89
|
/// Consumed by: UI (ContextViewModel)
|
|
|
90
|
protocol ContextMonitorProtocol {
|
|
Bogdan Timofte
authored
a month ago
|
91
|
var iCloudEnabled: Bool { get }
|
|
Bogdan Timofte
authored
3 weeks ago
|
92
|
var lastObservedAt: Date? { get }
|
|
|
93
|
var stateChanges: [ContextStateChange] { get }
|
|
Bogdan Timofte
authored
a month ago
|
94
|
}
|
|
|
95
|
```
|
|
|
96
|
|
|
|
97
|
---
|
|
|
98
|
|
|
|
99
|
## Shared Types (Models Agent)
|
|
|
100
|
|
|
|
101
|
These types are defined once in `Models/` and shared across all agents:
|
|
|
102
|
|
|
|
103
|
```swift
|
|
|
104
|
// Models/TypeDistributionBin.swift
|
|
|
105
|
@Model
|
|
|
106
|
final class TypeDistributionBin {
|
|
|
107
|
var bucketStart: Date
|
|
|
108
|
var bucketEnd: Date
|
|
|
109
|
var count: Int
|
|
|
110
|
}
|
|
|
111
|
|
|
|
112
|
// Models/TypeCount.swift
|
|
Bogdan Timofte
authored
3 weeks ago
|
113
|
// TypeCount owns zero or more TypeDistributionBin records.
|
|
|
114
|
// These bins store sample counts and import anchors, not raw health values.
|
|
|
115
|
|
|
|
116
|
// Interface updated 2026-05-12 — see AGENTS.md
|
|
|
117
|
// Models/HealthRecord.swift
|
|
|
118
|
// HealthRecord stores one anonymized HealthKit record fingerprint plus its start/end dates.
|
|
|
119
|
// It intentionally does not store raw health values, device identifiers, or source metadata.
|
|
|
120
|
// UI may compare HealthRecord fingerprints between adjacent snapshots to expose losses
|
|
|
121
|
// that are masked by newly-added records with the same total count.
|
|
|
122
|
// High-volume snapshots store these records in TypeCount.recordArchiveData instead of
|
|
|
123
|
// creating one SwiftData model per record, avoiding main-thread stalls after import.
|
|
|
124
|
|
|
|
125
|
// Interface updated 2026-05-13 — see AGENTS.md
|
|
|
126
|
// TypeDistributionBin also stores content hashes and HealthKit query anchors.
|
|
|
127
|
// Import uses a global anchored query per data type so follow-up snapshots fetch only
|
|
|
128
|
// HealthKit deltas instead of scanning calendar blocks with fixed per-query latency.
|
|
Bogdan Timofte
authored
a month ago
|
129
|
|
|
Bogdan Timofte
authored
3 weeks ago
|
130
|
// Interface updated 2026-05-18 — see AGENTS.md
|
|
|
131
|
// SwiftData is not the forensic source of truth. TypeCount and related rows store
|
|
|
132
|
// precomputed UI/index data only. Complete HealthKit samples and metadata belong
|
|
|
133
|
// in the local archive store, in one schema that can preserve relationships across
|
|
|
134
|
// data types, sources, devices, workouts, and metadata.
|
|
Bogdan Timofte
authored
3 weeks ago
|
135
|
|
|
Bogdan Timofte
authored
3 weeks ago
|
136
|
// Interface updated 2026-05-17 — see AGENTS.md
|
|
|
137
|
// Models/TypeCount.detailCacheData stores precomputed detail data for the current
|
|
|
138
|
// TypeCount compared with the immediately previous snapshot on the same device.
|
|
|
139
|
// The cache contains aggregate added/disappeared counts, capped preview records for
|
|
|
140
|
// UI drill-down, and daily change bins for temporal charts. It must be computed when
|
|
|
141
|
// snapshots are saved and refreshed for neighboring snapshots when snapshot deletion
|
|
|
142
|
// changes chain links. Existing stores are backfilled incrementally with a strict
|
|
|
143
|
// per-launch TypeCount cap to avoid decoding many large archives in one run.
|
|
|
144
|
|
|
Bogdan Timofte
authored
3 weeks ago
|
145
|
// Interface updated 2026-05-17 — see AGENTS.md
|
|
|
146
|
// Models/HealthSnapshot.contentEquivalentSnapshotID marks snapshots whose TypeCount
|
|
|
147
|
// content is identical to a previous snapshot on the same device. These snapshots are
|
|
|
148
|
// retained as temporal labels but behave as aliases to the representative content
|
|
|
149
|
// snapshot for expensive detail cache/diff work.
|
|
|
150
|
|
|
|
151
|
// Interface updated 2026-05-17 — see AGENTS.md
|
|
|
152
|
// Models/TypeCount.contentEquivalentTypeCountID marks individual data types whose
|
|
|
153
|
// content is identical to the previous snapshot's same TypeCount. This allows a
|
|
|
154
|
// snapshot to contain real changes for some metrics while long-stable metrics behave
|
|
|
155
|
// as temporal aliases and skip per-type detail cache/diff work.
|
|
|
156
|
|
|
Bogdan Timofte
authored
3 weeks ago
|
157
|
// Interface updated 2026-05-17 — see AGENTS.md
|
|
|
158
|
// Models/HealthSnapshot stores cached overview scalars for UI consumption:
|
|
|
159
|
// tracked type count, aggregate record count, and overall oldest/newest record dates.
|
|
|
160
|
// These values must be computed during snapshot save while TypeCount data is already
|
|
|
161
|
// in memory, so snapshot list/detail screens never recompute them by traversing
|
|
|
162
|
// snapshot.typeCounts on the UI thread.
|
|
|
163
|
|
|
|
164
|
// Interface updated 2026-05-17 — see AGENTS.md
|
|
|
165
|
// Models/SnapshotDelta stores cached list/detail summary scalars derived from TypeDelta.
|
|
|
166
|
// Overview screens consume these scalars and type-delta summaries directly instead of
|
|
|
167
|
// recalculating per-snapshot changes from HealthSnapshot.typeCounts.
|
|
|
168
|
|
|
Bogdan Timofte
authored
a month ago
|
169
|
// Models/DetectedAnomaly.swift
|
|
|
170
|
enum AnomalyType: String, Codable {
|
|
|
171
|
case historicalInsertion = "historical_insertion"
|
|
|
172
|
case silentDeletion = "silent_deletion"
|
|
|
173
|
case duplicate = "duplicate"
|
|
|
174
|
case divergence = "divergence"
|
|
|
175
|
}
|
|
|
176
|
|
|
|
177
|
enum Severity: String, Codable, Comparable {
|
|
|
178
|
case info, warning, critical
|
|
|
179
|
}
|
|
|
180
|
|
|
|
181
|
enum HealthStatus: String {
|
|
|
182
|
case healthy, warning, critical, unknown
|
|
|
183
|
}
|
|
|
184
|
```
|
|
|
185
|
|
|
|
186
|
Any model changes must be announced in this file before other agents consume them.
|
|
|
187
|
|
|
|
188
|
---
|
|
|
189
|
|
|
|
190
|
## Handoff Process
|
|
|
191
|
|
|
|
192
|
When a module is ready to be consumed by another agent:
|
|
|
193
|
|
|
|
194
|
1. **Define the protocol** in `Services/Protocols/` (services agent)
|
|
|
195
|
2. **Implement a mock** in `Utilities/Mocks.swift` (Claude Code)
|
|
|
196
|
3. **Build UI against the mock** (Claude Code)
|
|
|
197
|
4. **Replace mock with real implementation** (services agent)
|
|
|
198
|
5. **Integration test** (tests agent)
|
|
|
199
|
|
|
|
200
|
This allows UI development and service development to proceed in parallel.
|
|
|
201
|
|
|
|
202
|
---
|
|
|
203
|
|
|
|
204
|
## Algorithms & Detection Logic
|
|
|
205
|
|
|
|
206
|
The following modules involve non-trivial logic and should be reviewed carefully:
|
|
|
207
|
|
|
|
208
|
| Module | File | Description |
|
|
|
209
|
|--------|------|-------------|
|
|
|
210
|
| **Anomaly Detector** | `Services/AnomalyDetector.swift` | Statistical detection: insertions, deletions, duplicates, divergence |
|
|
|
211
|
| **Divergence Engine** | `Services/DivergenceEngine.swift` | Time-series trend analysis, σ comparison |
|
|
|
212
|
| **Fingerprinter** | `Services/SampleFingerprinter.swift` | Duplicate detection via sample hashing |
|
|
|
213
|
| **Snapshot Comparator** | `Services/SnapshotComparator.swift` | Diff between two HealthKit snapshots |
|
|
|
214
|
| **Distribution Comparator** | `Services/SnapshotDiffService.swift` | Daily per-type distribution diff to reveal old-data disappearance masked by new data |
|
|
|
215
|
|
|
|
216
|
**Guidelines for algorithm modules:**
|
|
|
217
|
- Document assumptions explicitly (e.g., "assumes continuous monitoring since install")
|
|
|
218
|
- All thresholds (e.g., `age > 7 days`) must be configurable constants, not magic numbers
|
|
|
219
|
- Include unit tests for edge cases (empty snapshots, partial data, clock skew)
|
|
|
220
|
- No UI code; return plain Swift types only
|
|
|
221
|
|
|
|
222
|
---
|
|
|
223
|
|
|
|
224
|
## Privacy Directives — All Agents
|
|
|
225
|
|
|
|
226
|
**Mandatory across all modules:**
|
|
|
227
|
- No credentials, API keys, tokens, or certificates in any file
|
|
|
228
|
- No personal data: names, emails, phone numbers, dates of birth
|
|
|
229
|
- No device identifiers: UDID, serial number, advertising ID, device name
|
|
|
230
|
- No account identifiers: Apple ID, iCloud account info, CloudKit record IDs
|
|
|
231
|
- No raw health values in code, tests, previews, logs, or comments
|
|
|
232
|
- No location data or patterns enabling re-identification
|
|
|
233
|
- Synthetic data only in tests and previews
|
|
|
234
|
|
|
Bogdan Timofte
authored
3 weeks ago
|
235
|
**Clarification:** “No raw health values” applies to this repository’s contents. The app may optionally store a user's raw HealthKit samples *locally on-device* for forensic backup purposes, but such samples must never appear in source control, logs, or docs.
|
|
|
236
|
|
|
Bogdan Timofte
authored
a month ago
|
237
|
---
|
|
|
238
|
|
|
|
239
|
## Communication Between Agents
|
|
|
240
|
|
|
|
241
|
When one agent needs to communicate a decision or change to another:
|
|
|
242
|
|
|
|
243
|
1. **Update this file** (`AGENTS.md`) with the protocol/interface change
|
|
|
244
|
2. **Update the relevant protocol** in `Services/Protocols/`
|
|
|
245
|
3. **Add a comment** in the affected file: `// Interface updated YYYY-MM-DD — see AGENTS.md`
|
|
|
246
|
|
|
|
247
|
---
|
|
|
248
|
|
|
|
249
|
## Current Status
|
|
|
250
|
|
|
|
251
|
| Module | Status | Owner |
|
|
|
252
|
|--------|--------|-------|
|
|
|
253
|
| SwiftData Models | ✅ Done | Models agent |
|
|
|
254
|
| HealthKit Integration | ✅ Done | Services agent |
|
|
|
255
|
| Snapshot Diff Service | ✅ Done | Services agent |
|
|
|
256
|
| Service Protocols | ⏳ Not started | Services agent |
|
|
|
257
|
| Anomaly Detection | ⏳ Not started | Services agent |
|
|
|
258
|
| Sync Monitor | ⏳ Not started | Services agent |
|
|
|
259
|
| UI – App entry + TabView | ✅ Done | Claude Code |
|
|
|
260
|
| UI – Dashboard | ✅ Done (functional, minimal) | Claude Code |
|
|
|
261
|
| UI – Snapshots + Detail | ✅ Done | Claude Code |
|
|
|
262
|
| UI – Data Types | ✅ Done | Claude Code |
|
|
|
263
|
| UI – Settings | ✅ Done | Claude Code |
|
|
|
264
|
| Unit Tests | ⏳ Not started | Tests agent |
|