1 contributor
import Foundation
struct TypeDiff: Identifiable {
let id: String
let typeIdentifier: String
let displayName: String
let currentCount: Int
let previousCount: Int
let previousTracked: Bool
var delta: Int { currentCount - previousCount }
var percentChange: Double? {
guard previousTracked, previousCount > 0 else { return nil }
return Double(delta) / Double(previousCount) * 100
}
}
enum DiffFilter: String, CaseIterable {
case all = "All"
case changed = "Changed"
case increased = "Increased"
case decreased = "Decreased"
}
final class SnapshotDiffService {
static let shared = SnapshotDiffService()
func diff(current: HealthSnapshot, baseline: HealthSnapshot) -> [TypeDiff] {
let baselineMap = Dictionary(
uniqueKeysWithValues: (baseline.typeCounts ?? []).map { ($0.typeIdentifier, $0.count) }
)
return (current.typeCounts ?? []).map { tc in
let prior = baselineMap[tc.typeIdentifier]
return TypeDiff(
id: tc.typeIdentifier,
typeIdentifier: tc.typeIdentifier,
displayName: tc.displayName,
currentCount: tc.count,
previousCount: prior ?? 0,
previousTracked: prior != nil
)
}.sorted { $0.displayName.localizedCompare($1.displayName) == .orderedAscending }
}
func totalAbsoluteChange(current: HealthSnapshot, baseline: HealthSnapshot) -> Int {
let baselineMap = Dictionary(
uniqueKeysWithValues: (baseline.typeCounts ?? []).map { ($0.typeIdentifier, $0) }
)
return (current.typeCounts ?? []).reduce(0) { partial, currentType in
guard currentType.quality == .complete,
let previousType = baselineMap[currentType.typeIdentifier],
previousType.quality == .complete else {
return partial
}
return partial + abs(currentType.count - previousType.count)
}
}
func apply(filter: DiffFilter, to diffs: [TypeDiff]) -> [TypeDiff] {
switch filter {
case .all: return diffs
case .changed: return diffs.filter { $0.previousTracked && $0.delta != 0 }
case .increased: return diffs.filter { $0.previousTracked && $0.delta > 0 }
case .decreased: return diffs.filter { $0.previousTracked && $0.delta < 0 }
}
}
func nearest(to targetDate: Date, in snapshots: [HealthSnapshot]) -> HealthSnapshot? {
snapshots
.filter { $0.timestamp <= targetDate }
.max { $0.timestamp < $1.timestamp }
}
}