Skip to content

Commit ef5e895

Browse files
Priyankas007dusixianviraj28mmmervecerit
authored
ANC Visualization (#56)
# ANC Visualizations Fix ## ♻️ Current situation & Problem We had issues with adding ANC Visualizations before. had periphery errors. ## ⚙️ Release Notes Fixed the errors, added tests. ## 📝 Code of Conduct & Contributing Guidelines By submitting creating this pull request, you agree to follow our [Code of Conduct](https://github.com/CS342/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/CS342/.github/blob/main/CONTRIBUTING.md): - [X ] I agree to follow the [Code of Conduct](https://github.com/CS342/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/CS342/.github/blob/main/CONTRIBUTING.md). --------- Co-authored-by: Sixian Du <[email protected]> Co-authored-by: viraj28m <[email protected]> Co-authored-by: Merve Cerit <[email protected]>
1 parent 22eae44 commit ef5e895

File tree

9 files changed

+207
-52
lines changed

9 files changed

+207
-52
lines changed

NeutroFeverGuard.xcodeproj/project.pbxproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,6 @@
107107
EB5D7CF92D8108BC0046BF8E /* AccountButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DFE8A82ABE551400428242 /* AccountButton.swift */; };
108108
EB6FDBCE2D80133A008E9F90 /* HKVisualization.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6FDBCB2D80133A008E9F90 /* HKVisualization.swift */; };
109109
EB6FDBCF2D80133A008E9F90 /* HKVisualizationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6FDBCC2D80133A008E9F90 /* HKVisualizationItem.swift */; };
110-
EB6FDBD02D80133A008E9F90 /* HKVisualization.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6FDBCB2D80133A008E9F90 /* HKVisualization.swift */; };
111-
EB6FDBD12D80133A008E9F90 /* HKVisualizationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6FDBCC2D80133A008E9F90 /* HKVisualizationItem.swift */; };
112110
EBFA80652D81FBE600596C3C /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC3E29EDD7ED004B9AB4 /* FeatureFlags.swift */; };
113111
F223937A2D5D4095006C8EB4 /* DataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F22393792D5D4092006C8EB4 /* DataError.swift */; };
114112
F268C4562D7F928C0020026F /* SymptomManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F268C4552D7F928B0020026F /* SymptomManager.swift */; };
@@ -718,8 +716,6 @@
718716
EB02C6612D5D52E90035AA89 /* NeutroFeverGuardTests.swift in Sources */,
719717
B0DF8DFD2D7C123200581A00 /* HealthDataFetchable.swift in Sources */,
720718
B0DF8DF72D7BFEFB00581A00 /* FeverMonitorTests.swift in Sources */,
721-
EB6FDBD02D80133A008E9F90 /* HKVisualization.swift in Sources */,
722-
EB6FDBD12D80133A008E9F90 /* HKVisualizationItem.swift in Sources */,
723719
B03D0E482D7D24AA0024F2CA /* DataError.swift in Sources */,
724720
EBFA80652D81FBE600596C3C /* FeatureFlags.swift in Sources */,
725721
C4C8A3B32D71204200F313AE /* HelperFuncTests.swift in Sources */,

NeutroFeverGuard/DataType.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ struct BloodPressureEntry {
132132
- Lab values: include the number associated with the lab name above
133133
*/
134134

135-
struct LabEntry: Codable {
135+
struct LabEntry: Codable, Equatable {
136136
var date: Date
137137
var values: [LabTestType: Double]
138138

NeutroFeverGuard/HKVisualization.swift

Lines changed: 88 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// periphery:ignore all
21
// swiftlint:disable all
2+
// periphery:ignore all
33
// This source file is part of the NeutroFeverGuard based on the Stanford Spezi Template Application project
44
//
55
// SPDX-FileCopyrightText: 2025 Stanford University
@@ -12,7 +12,16 @@ import HealthKit
1212
import SpeziAccount
1313
import SwiftUI
1414

15-
// Parses the raw HealthKit data.
15+
16+
struct HKData: Identifiable, Codable {
17+
var id = UUID()
18+
var date: Date
19+
var sumValue: Double
20+
var avgValue: Double
21+
var minValue: Double
22+
var maxValue: Double
23+
}
24+
1625
func parseSampleQueryData(results: [HKSample], quantityTypeIDF: HKQuantityTypeIdentifier) -> [HKData] {
1726
// Retrieve quantity value and time for each data point.
1827

@@ -79,23 +88,16 @@ func handleAuthorizationError(_ error: Error) -> String {
7988
}
8089
}
8190

82-
struct HKData: Identifiable {
83-
var date: Date
84-
var id = UUID()
85-
var sumValue: Double
86-
var avgValue: Double
87-
var minValue: Double
88-
var maxValue: Double
89-
}
90-
9191
struct HKVisualization: View {
92-
// swiftlint:disable closure_body_length
92+
@Environment(LabResultsManager.self) private var labResultsManager
9393
@State var bodyTemperatureData: [HKData] = []
9494
@State var heartRateData: [HKData] = []
9595
@State var oxygenSaturationData: [HKData] = []
9696
@State var heartRateScatterData: [HKData] = []
9797
@State var oxygenSaturationScatterData: [HKData] = []
9898
@State var bodyTemperatureScatterData: [HKData] = []
99+
@State var neutrophilData: [HKData] = []
100+
@State var neutrophilScatterData: [HKData] = []
99101

100102
var vizList: some View {
101103
self.readAllHKData()
@@ -106,7 +108,7 @@ struct HKVisualization: View {
106108
data: heartRateData,
107109
xName: "Time",
108110
yName: "Heart Rate (bpm)",
109-
title: "Heart Rate Over Time",
111+
title: "Heart Rate",
110112
threshold: 100,
111113
scatterData: heartRateScatterData
112114
)
@@ -121,7 +123,7 @@ struct HKVisualization: View {
121123
data: bodyTemperatureData,
122124
xName: "Time",
123125
yName: "Body Temperature (°F)",
124-
title: "Body Temperature Over Time",
126+
title: "Body Temperature",
125127
threshold: 99.0,
126128
scatterData: bodyTemperatureScatterData
127129
)
@@ -136,7 +138,7 @@ struct HKVisualization: View {
136138
data: oxygenSaturationData,
137139
xName: "Time",
138140
yName: "Oxygen Saturation (%)",
139-
title: "Oxygen Saturation Over Time",
141+
title: "Oxygen Saturation",
140142
threshold: 94.0,
141143
scatterData: oxygenSaturationScatterData
142144
)
@@ -145,6 +147,21 @@ struct HKVisualization: View {
145147
.foregroundColor(.gray)
146148
}
147149
}
150+
Section {
151+
if !neutrophilData.isEmpty {
152+
HKVisualizationItem(
153+
data: neutrophilData,
154+
xName: "Date",
155+
yName: "Neutrophil Count",
156+
title: "Absolute Neutrophil Count",
157+
threshold: 500, // Adjust the threshold if necessary
158+
scatterData: neutrophilScatterData
159+
)
160+
} else {
161+
Text("No neutrophil count data available.")
162+
.foregroundColor(.gray)
163+
}
164+
}
148165
}
149166
}
150167

@@ -164,6 +181,8 @@ struct HKVisualization: View {
164181
.onAppear {
165182
// Ensure that data up-to-date when the view is activated.
166183
self.readAllHKData(ensureUpdate: true)
184+
labResultsManager.loadLabResults()
185+
loadNeutrophilData() // Ensure this is called
167186
}
168187
.toolbar {
169188
if account != nil {
@@ -173,6 +192,41 @@ struct HKVisualization: View {
173192
}
174193
}
175194

195+
private func loadNeutrophilData() {
196+
if FeatureFlags.mockVizData {
197+
loadMockDataNew()
198+
return
199+
}
200+
let rawData = labResultsManager.getAllAncValues().filter {
201+
$0.date >= Calendar.current.date(byAdding: .day, value: -7, to: Date()) ?? Date()
202+
}
203+
204+
// Convert to HKData for bar plot
205+
neutrophilData = rawData.map { record in
206+
HKData(
207+
date: record.date,
208+
sumValue: record.ancValue,
209+
avgValue: record.ancValue,
210+
minValue: record.ancValue,
211+
maxValue: record.ancValue
212+
)
213+
}
214+
215+
// Create scatter data (with some random variation to separate points)
216+
neutrophilScatterData = rawData.map { record in
217+
HKData(
218+
date: record.date,
219+
sumValue: record.ancValue + Double.random(in: -0.5...0.5), // Add slight variation for visualization
220+
avgValue: -1.0,
221+
minValue: -1.0,
222+
maxValue: -1.0
223+
)
224+
}
225+
226+
print("✅ Converted neutrophil data: \(neutrophilData)")
227+
print("✅ Scatter neutrophil data: \(neutrophilScatterData)")
228+
}
229+
176230
func readAllHKData(ensureUpdate: Bool = false) {
177231
if FeatureFlags.mockVizData {
178232
loadMockDataNew()
@@ -331,7 +385,8 @@ struct HKVisualization: View {
331385
self.heartRateData = minMaxAvgStatData
332386
self.bodyTemperatureData = minMaxAvgStatData
333387
self.oxygenSaturationData = minMaxAvgStatData
334-
388+
self.neutrophilData = [HKData(date: today, sumValue: 0, avgValue: 500, minValue: 1, maxValue: 1000)]
389+
335390
// ✅ Heart Rate Scatter Data (60-100 bpm normal range)
336391
self.heartRateScatterData = [
337392
HKData(date: today, sumValue: 75, avgValue: 75, minValue: 75, maxValue: 75),
@@ -352,8 +407,24 @@ struct HKVisualization: View {
352407
HKData(date: yesterday, sumValue: 97, avgValue: 97, minValue: 97, maxValue: 97),
353408
HKData(date: twoDaysAgo, sumValue: 96, avgValue: 96, minValue: 96, maxValue: 96)
354409
]
410+
411+
let mockNeutrophilData = [
412+
(date: today, wbc: 1000, neutrophils: 50),
413+
(date: yesterday, wbc: 1000, neutrophils: 5),
414+
(date: twoDaysAgo, wbc: 1200, neutrophils: 20)
415+
]
416+
417+
self.neutrophilScatterData = mockNeutrophilData.map { record in
418+
let ancValue = (Double(record.neutrophils) / 100.0) * Double(record.wbc)
419+
return HKData(
420+
date: record.date,
421+
sumValue: ancValue,
422+
avgValue: ancValue,
423+
minValue: ancValue,
424+
maxValue: ancValue
425+
)
426+
}
355427
}
356-
// swiftlint:enable closure_body_length
357428
}
358429

359430
func parseStat(statistics: HKStatistics, quantityTypeIDF: HKQuantityTypeIdentifier) -> HKData? {
@@ -392,7 +463,3 @@ func parseValue(quantity: HKQuantity, quantityTypeIDF: HKQuantityTypeIdentifier)
392463
return -1.0
393464
}
394465
}
395-
396-
#Preview {
397-
398-
}

NeutroFeverGuard/HKVisualizationItem.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// swiftlint:disable all
2-
// periphery:ignore all
31
// This source file is part of the NeutroFeverGuard based on the Stanford Spezi Template Application project
42
//
53
// SPDX-FileCopyrightText: 2025 Stanford University
@@ -12,8 +10,6 @@ import Foundation
1210
import SwiftUI
1311

1412
struct HKVisualizationItem: View {
15-
// periphery:ignore
16-
let id = UUID()
1713
let data: [HKData]
1814
let xName: LocalizedStringResource
1915
let yName: LocalizedStringResource

NeutroFeverGuard/LabResultsManager.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class LabResultsManager: Module, EnvironmentAccessible {
4242
loadLabResults() // Refresh lab results
4343
}
4444

45-
private func loadLabResults() {
45+
func loadLabResults() {
4646
var results: [LabEntry] = []
4747

4848
do {
@@ -120,6 +120,20 @@ class LabResultsManager: Module, EnvironmentAccessible {
120120
}
121121
return (neutrophils / 100.0) * wbc
122122
}
123+
124+
func getAllAncValues() -> [(date: Date, ancValue: Double)] {
125+
var ancValues: [(date: Date, ancValue: Double)] = []
126+
127+
for record in labRecords {
128+
if let neutrophils = record.values[.neutrophils],
129+
let wbc = record.values[.whiteBloodCell] {
130+
let ancValue = (neutrophils / 100.0) * wbc
131+
ancValues.append((date: record.date, ancValue: ancValue))
132+
}
133+
}
134+
135+
return ancValues
136+
}
123137

124138
func getANCStatus() -> (text: String, color: Color) {
125139
guard let ancValue = getAncValue() else {

NeutroFeverGuard/Resources/Localizable.xcstrings

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@
5959
},
6060
"1-10" : {
6161

62+
},
63+
"Absolute Neutrophil Count" : {
64+
6265
},
6366
"Absolute Neutrophil Counts" : {
6467
"localizations" : {
@@ -154,6 +157,9 @@
154157
},
155158
"Bluetooth Devices" : {
156159

160+
},
161+
"Body Temperature" : {
162+
157163
},
158164
"Body Temperature (°F)" : {
159165
"localizations" : {
@@ -166,6 +172,7 @@
166172
}
167173
},
168174
"Body Temperature Over Time" : {
175+
"extractionState" : "stale",
169176
"localizations" : {
170177
"en" : {
171178
"stringUnit" : {
@@ -371,6 +378,9 @@
371378
}
372379
}
373380
}
381+
},
382+
"Heart Rate" : {
383+
374384
},
375385
"Heart Rate (bpm)" : {
376386
"localizations" : {
@@ -383,6 +393,7 @@
383393
}
384394
},
385395
"Heart Rate Over Time" : {
396+
"extractionState" : "stale",
386397
"localizations" : {
387398
"en" : {
388399
"stringUnit" : {
@@ -639,6 +650,9 @@
639650
}
640651
}
641652
}
653+
},
654+
"Neutrophil Count" : {
655+
642656
},
643657
"Next" : {
644658
"localizations" : {
@@ -691,6 +705,9 @@
691705
},
692706
"No medications recorded" : {
693707

708+
},
709+
"No neutrophil count data available." : {
710+
694711
},
695712
"No oxygen saturation data available." : {
696713
"localizations" : {
@@ -748,6 +765,9 @@
748765
},
749766
"Other factors" : {
750767

768+
},
769+
"Oxygen Saturation" : {
770+
751771
},
752772
"Oxygen Saturation (%)" : {
753773
"localizations" : {
@@ -760,6 +780,7 @@
760780
}
761781
},
762782
"Oxygen Saturation Over Time" : {
783+
"extractionState" : "stale",
763784
"localizations" : {
764785
"en" : {
765786
"stringUnit" : {

0 commit comments

Comments
 (0)