Skip to content

Commit 0fc5683

Browse files
authored
Heart Health Dashboard (#9)
# Heart Health Dashboard ## ♻️ Current situation & Problem closes #4 ## ⚙️ Release Notes - adds an initial version of the Heart Health Dashboard ## 📚 Documentation n/a ## ✅ Testing n/a ### Code of Conduct & Contributing Guidelines By creating and submitting this pull request, you agree to follow our [Code of Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md): - [x] I agree to follow the [Code of Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md).
1 parent e1895ce commit 0fc5683

67 files changed

Lines changed: 5893 additions & 82 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.swiftlint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,7 @@ line_length: # Lines should not span too many characters.
426426
ignores_urls: true # default: false
427427
ignores_function_declarations: false # default: false
428428
ignores_interpolated_strings: true # default: false
429+
ignores_multiline_strings: true # default: false
429430

430431
nesting: # Types should be nested at most 2 level deep, and functions should be nested at most 5 levels deep.
431432
type_level:

MyHeartCounts.xcodeproj/project.pbxproj

Lines changed: 60 additions & 31 deletions
Large diffs are not rendered by default.

MyHeartCounts.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

MyHeartCounts.xcodeproj/xcshareddata/xcschemes/MyHeartCounts.xcscheme

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
</CommandLineArgument>
115115
<CommandLineArgument
116116
argument = "--overrideFirebaseConfig plist=GoogleService-Info-Override"
117-
isEnabled = "NO">
117+
isEnabled = "YES">
118118
</CommandLineArgument>
119119
<CommandLineArgument
120120
argument = "--dump-cli-options-and-exit"
@@ -127,10 +127,15 @@
127127
value = "YES"
128128
isEnabled = "YES">
129129
</EnvironmentVariable>
130+
<EnvironmentVariable
131+
key = "IDELogRedirectionPolicy"
132+
value = "oslogToStdio"
133+
isEnabled = "NO">
134+
</EnvironmentVariable>
130135
</EnvironmentVariables>
131136
</LaunchAction>
132137
<ProfileAction
133-
buildConfiguration = "Release"
138+
buildConfiguration = "Debug"
134139
shouldUseLaunchSchemeArgsEnv = "YES"
135140
savedToolIdentifier = ""
136141
useCustomWorkingDirectory = "NO"

MyHeartCounts/Account/AccountSheet.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import SpeziAccount
1111
import SpeziLicense
1212
import SpeziStudy
13-
import SwiftData
1413
import SwiftUI
1514

1615

MyHeartCounts/Active Tasks/TimedWalkingTestResult+FHIR.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ extension TimedWalkingTestResult {
1717
withMapping: HealthKitOnFHIR.HKSampleMapping,
1818
issuedDate: ModelsR4.FHIRPrimitive<ModelsR4.Instant>?
1919
) throws -> ModelsR4.ResourceProxy {
20-
try .observation(fhirObservation(issuedDate: issuedDate))
20+
.observation(try fhirObservation(issuedDate: issuedDate))
2121
}
2222

2323

@@ -72,7 +72,7 @@ extension TimedWalkingTestResult {
7272
}
7373
}
7474

75-
private func builObservationComponent(code: String, system: String, unit: String, value: Double) -> ObservationComponent {
75+
func builObservationComponent(code: String, system: String, unit: String, value: Double) -> ObservationComponent {
7676
ObservationComponent(
7777
code: CodeableConcept(coding: [Coding(code: code.asFHIRStringPrimitive(), system: system.asFHIRURIPrimitive())]),
7878
value: .quantity(.init(

MyHeartCounts/Health Import/HistoricalSamplesToFHIRJSONProcessor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ struct HistoricalSamplesToFHIRJSONProcessor: BatchProcessor {
2424

2525
private func storeSamples<Sample>(_ samples: consuming [Sample], of sampleType: SampleType<Sample>) throws -> URL {
2626
let fileManager = FileManager.default
27-
let resources = try samples.mapIntoResourceProxies()
27+
let resources = try samples.mapIntoResourceProxies(extensions: [.includeAbsoluteTimeRange])
2828
_ = consume samples
2929
let encoded = try JSONEncoder().encode(resources)
3030
_ = consume resources

MyHeartCounts/HealthObservation.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,21 @@ protocol HealthObservation {
1717
var id: UUID { get }
1818
var sampleTypeIdentifier: String { get }
1919

20-
func resource(withMapping: HKSampleMapping, issuedDate: FHIRPrimitive<Instant>?) throws -> ResourceProxy
20+
func resource(
21+
withMapping mapping: HKSampleMapping,
22+
issuedDate: FHIRPrimitive<Instant>?
23+
) throws -> ResourceProxy
2124
}
2225

2326

2427
extension HKSample: HealthObservation {
2528
var sampleTypeIdentifier: String {
2629
sampleType.identifier
2730
}
31+
32+
func resource(withMapping mapping: HKSampleMapping, issuedDate: FHIRPrimitive<Instant>?) throws -> ResourceProxy {
33+
try resource(withMapping: mapping, issuedDate: issuedDate, extensions: [])
34+
}
2835
}
2936

3037

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// This source file is part of the My Heart Counts iOS application based on the Stanford Spezi Template Application project
3+
//
4+
// SPDX-FileCopyrightText: 2025 Stanford University
5+
//
6+
// SPDX-License-Identifier: MIT
7+
//
8+
9+
import Foundation
10+
import HealthKit
11+
12+
13+
struct Achievement: Hashable, Codable, Sendable {
14+
enum Goal: Hashable, Codable, Sendable {
15+
enum ReachLevelTarget: Hashable, Codable, Sendable { // swiftlint:disable:this type_contents_order
16+
case absolute(Double)
17+
case newMaximum
18+
}
19+
case reachLevel(ReachLevelTarget)
20+
case maintainLevel
21+
}
22+
23+
let title: String
24+
let goal: Goal
25+
26+
init(title: String, goal: Goal) {
27+
self.title = title
28+
self.goal = goal
29+
}
30+
}
31+
32+
33+
extension Achievement {
34+
enum ResolvedGoal: Hashable, Sendable {
35+
/// The goal is achieved by reaching (or surpassing) a specified quantity.
36+
/// - parameter quantity: The target quantity
37+
/// - parameter invert: Whether the goal should be inverted;
38+
/// i.e., whether it should be considered achieved if an actual value is below, rather than above, the quantity.
39+
/// - parameter baselineQuantity: The quantity we take as the baseline with calculating progress. For non-inverted goals, this should be zero.
40+
case reachOrSurpass(_ quantity: HKQuantity, invert: Bool = false, baselineQuantity: HKQuantity)
41+
42+
static func reachOrSurpass(_ quantity: HKQuantity, unit: HKUnit) -> Self {
43+
.reachOrSurpass(quantity, invert: false, baselineQuantity: HKQuantity(unit: unit, doubleValue: 0))
44+
}
45+
46+
/// Evaluates a concrete value against the goal's value.
47+
///
48+
/// - returns: A `Double` value, indicating how close the `measuredValue` is to the goal's value
49+
func evaluate(_ measuredQuantity: HKQuantity, unit: HKUnit) -> Double {
50+
switch self {
51+
case let .reachOrSurpass(targetQuantity, invert, baselineQuantity):
52+
let measuredValue = measuredQuantity.doubleValue(for: unit)
53+
let targetValue = targetQuantity.doubleValue(for: unit)
54+
if !invert {
55+
return measuredValue / targetValue
56+
} else {
57+
let baselineValue = baselineQuantity.doubleValue(for: unit)
58+
return baselineValue.distance(to: measuredValue) / baselineValue.distance(to: targetValue)
59+
}
60+
}
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)