|
Bogdan Timofte
authored
a month ago
|
1
|
import Foundation
|
|
|
2
|
|
|
Bogdan Timofte
authored
a week ago
|
3
|
struct DataTypeSnapshotContext: Equatable, Sendable {
|
|
Bogdan Timofte
authored
a week ago
|
4
|
let observationID: Int64
|
|
Bogdan Timofte
authored
a week ago
|
5
|
let observedAt: Date
|
|
|
6
|
}
|
|
|
7
|
|
|
Bogdan Timofte
authored
a month ago
|
8
|
@Observable
|
|
|
9
|
final class DataTypesViewModel {
|
|
|
10
|
var filter: DiffFilter = .all
|
|
|
11
|
var comparisonMode: ComparisonMode = .previous
|
|
Bogdan Timofte
authored
a week ago
|
12
|
var observationRows: [CachedArchiveObservationRow] = []
|
|
|
13
|
var observationRowsError: String?
|
|
Bogdan Timofte
authored
2 weeks ago
|
14
|
var archiveDiffs: [TypeDiff]?
|
|
|
15
|
var archiveDiffError: String?
|
|
Bogdan Timofte
authored
a month ago
|
16
|
|
|
Bogdan Timofte
authored
a week ago
|
17
|
var observationContexts: [DataTypeSnapshotContext] {
|
|
|
18
|
observationRows
|
|
|
19
|
.sorted { $0.observedAt > $1.observedAt }
|
|
|
20
|
.map {
|
|
|
21
|
DataTypeSnapshotContext(
|
|
|
22
|
observationID: $0.observationID,
|
|
|
23
|
observedAt: $0.observedAt
|
|
|
24
|
)
|
|
|
25
|
}
|
|
|
26
|
}
|
|
|
27
|
|
|
Bogdan Timofte
authored
a week ago
|
28
|
func diffs() -> [TypeDiff] {
|
|
|
29
|
guard let archiveDiffs else { return [] }
|
|
|
30
|
return apply(filter: filter, to: archiveDiffs)
|
|
Bogdan Timofte
authored
a month ago
|
31
|
}
|
|
|
32
|
|
|
Bogdan Timofte
authored
a week ago
|
33
|
func baseline(for snapshot: DataTypeSnapshotContext, in snapshots: [DataTypeSnapshotContext]) -> DataTypeSnapshotContext? {
|
|
Bogdan Timofte
authored
2 weeks ago
|
34
|
resolveBaseline(for: snapshot, in: snapshots)
|
|
|
35
|
}
|
|
|
36
|
|
|
|
37
|
func clearArchiveDiffs() {
|
|
|
38
|
archiveDiffs = nil
|
|
|
39
|
archiveDiffError = nil
|
|
|
40
|
}
|
|
|
41
|
|
|
Bogdan Timofte
authored
a week ago
|
42
|
@MainActor
|
|
|
43
|
func loadArchiveRows(limit: Int = 200) async {
|
|
|
44
|
do {
|
|
|
45
|
let cache = try CoreDataArchiveCacheStore()
|
|
|
46
|
observationRows = try cache.observationRows(limit: limit)
|
|
|
47
|
observationRowsError = nil
|
|
|
48
|
} catch {
|
|
|
49
|
observationRows = []
|
|
|
50
|
observationRowsError = error.localizedDescription
|
|
|
51
|
}
|
|
|
52
|
}
|
|
|
53
|
|
|
Bogdan Timofte
authored
2 weeks ago
|
54
|
@MainActor
|
|
Bogdan Timofte
authored
a week ago
|
55
|
func loadArchiveDiffs(current: DataTypeSnapshotContext?, snapshots: [DataTypeSnapshotContext]) async {
|
|
Bogdan Timofte
authored
2 weeks ago
|
56
|
guard let current,
|
|
Bogdan Timofte
authored
a week ago
|
57
|
let baseline = resolveBaseline(for: current, in: snapshots) else {
|
|
Bogdan Timofte
authored
2 weeks ago
|
58
|
clearArchiveDiffs()
|
|
|
59
|
return
|
|
|
60
|
}
|
|
|
61
|
|
|
|
62
|
do {
|
|
|
63
|
let cache = try CoreDataArchiveCacheStore()
|
|
Bogdan Timofte
authored
a week ago
|
64
|
let currentSummaries = try cache.typeSummaries(observationID: current.observationID)
|
|
|
65
|
let baselineSummaries = try cache.typeSummaries(observationID: baseline.observationID)
|
|
Bogdan Timofte
authored
2 weeks ago
|
66
|
let currentByType = Dictionary(uniqueKeysWithValues: currentSummaries.map { ($0.sampleTypeIdentifier, $0) })
|
|
|
67
|
let baselineByType = Dictionary(uniqueKeysWithValues: baselineSummaries.map { ($0.sampleTypeIdentifier, $0) })
|
|
|
68
|
let allTypeIdentifiers = Set(currentByType.keys).union(baselineByType.keys)
|
|
|
69
|
|
|
|
70
|
var archiveRows: [TypeDiff] = []
|
|
|
71
|
archiveRows.reserveCapacity(allTypeIdentifiers.count)
|
|
|
72
|
|
|
|
73
|
for typeIdentifier in allTypeIdentifiers {
|
|
|
74
|
let summary = currentByType[typeIdentifier]
|
|
|
75
|
let baselineSummary = baselineByType[typeIdentifier]
|
|
|
76
|
let diff = try await SQLiteHealthArchiveStore.shared.diffSummary(HealthArchiveDiffRequest(
|
|
Bogdan Timofte
authored
a week ago
|
77
|
fromObservationID: baseline.observationID,
|
|
|
78
|
toObservationID: current.observationID,
|
|
Bogdan Timofte
authored
2 weeks ago
|
79
|
sampleTypeIdentifier: typeIdentifier
|
|
|
80
|
))
|
|
|
81
|
archiveRows.append(TypeDiff(
|
|
|
82
|
id: typeIdentifier,
|
|
|
83
|
typeIdentifier: typeIdentifier,
|
|
|
84
|
displayName: summary?.displayName ?? baselineSummary?.displayName ?? typeIdentifier,
|
|
|
85
|
currentCount: summary?.visibleRecordCount ?? 0,
|
|
|
86
|
previousCount: baselineSummary?.visibleRecordCount ?? 0,
|
|
|
87
|
previousTracked: baselineSummary != nil,
|
|
|
88
|
appearedCount: diff.appearedCount,
|
|
|
89
|
disappearedCount: diff.disappearedCount,
|
|
|
90
|
representationChangedCount: diff.representationChangedCount
|
|
|
91
|
))
|
|
|
92
|
}
|
|
|
93
|
|
|
|
94
|
archiveDiffs = archiveRows.sorted {
|
|
|
95
|
$0.displayName.localizedCompare($1.displayName) == .orderedAscending
|
|
|
96
|
}
|
|
|
97
|
archiveDiffError = nil
|
|
|
98
|
} catch {
|
|
|
99
|
archiveDiffs = nil
|
|
|
100
|
archiveDiffError = error.localizedDescription
|
|
|
101
|
}
|
|
|
102
|
}
|
|
|
103
|
|
|
Bogdan Timofte
authored
a week ago
|
104
|
private func resolveBaseline(
|
|
|
105
|
for snapshot: DataTypeSnapshotContext,
|
|
|
106
|
in snapshots: [DataTypeSnapshotContext]
|
|
|
107
|
) -> DataTypeSnapshotContext? {
|
|
|
108
|
let sorted = snapshots.sorted { $0.observedAt > $1.observedAt }
|
|
Bogdan Timofte
authored
a month ago
|
109
|
switch comparisonMode {
|
|
|
110
|
case .previous:
|
|
Bogdan Timofte
authored
a week ago
|
111
|
return sorted.first { $0.observedAt < snapshot.observedAt }
|
|
Bogdan Timofte
authored
a month ago
|
112
|
case .selected:
|
|
|
113
|
return nil // DataTypesView uses .previous by default; selection lives in SnapshotsTab
|
|
|
114
|
case .relativeTime(let interval):
|
|
Bogdan Timofte
authored
a week ago
|
115
|
let target = snapshot.observedAt.addingTimeInterval(-interval)
|
|
|
116
|
return snapshots
|
|
|
117
|
.filter { $0.observedAt <= target }
|
|
|
118
|
.max { $0.observedAt < $1.observedAt }
|
|
|
119
|
}
|
|
|
120
|
}
|
|
|
121
|
|
|
|
122
|
private func apply(filter: DiffFilter, to diffs: [TypeDiff]) -> [TypeDiff] {
|
|
|
123
|
switch filter {
|
|
|
124
|
case .all: return diffs
|
|
|
125
|
case .changed: return diffs.filter { $0.previousTracked && $0.hasChanges }
|
|
|
126
|
case .increased: return diffs.filter { $0.previousTracked && $0.delta > 0 }
|
|
|
127
|
case .decreased: return diffs.filter { $0.previousTracked && $0.delta < 0 }
|
|
Bogdan Timofte
authored
a month ago
|
128
|
}
|
|
|
129
|
}
|
|
|
130
|
}
|