Skip to content

Commit

Permalink
Improve Public API and Improve FHIRResourceSummaryView (#12)
Browse files Browse the repository at this point in the history
# Improve Public API and Improve FHIRResourceSummaryView

## ⚙️ Release Notes 
- Improve Public API and Improve FHIRResourceSummaryView


## 📝 Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).
  • Loading branch information
PSchmiedmayer authored Nov 23, 2023
1 parent 4ce54a6 commit 82c771f
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 43 deletions.
9 changes: 6 additions & 3 deletions Sources/SpeziFHIR/FHIRStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import Combine
import Observation
import class ModelsR4.Bundle
import enum ModelsDSTU2.ResourceProxy
import Spezi


/// Manage FHIR resources grouped into automatically computed and updated categories.
/// Module to manage FHIR resources grouped into automatically computed and updated categories.
///
/// The ``FHIRStore`` is automatically injected in the environment if you use the ``FHIR`` standard or can be used as a standalone module.
@Observable
public class FHIRStore {
public class FHIRStore: Module, EnvironmentAccessible, DefaultInitializable {
@ObservationIgnored private var _resources: [FHIRResource]


Expand Down Expand Up @@ -73,7 +76,7 @@ public class FHIRStore {
}


init() {
public required init() {
self._resources = []
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import SpeziFHIR
import SpeziHealthKit


extension FHIR: HealthKitConstraint {
extension FHIRStore {
private static let hkHealthStore: HKHealthStore? = {
guard HKHealthStore.isHealthDataAvailable() else {
return nil
Expand All @@ -24,17 +24,21 @@ extension FHIR: HealthKitConstraint {
}()


/// Add a HealthKit sample to the FHIR store.
/// - Parameter sample: The sample that should be added.
public func add(sample: HKSample) async {
do {
let resource = try await transform(sample: sample)
store.insert(resource: resource)
insert(resource: resource)
} catch {
print("Could not transform HKSample: \(error)")
}
}

/// Remove a HealthKit sample delete object from the FHIR store.
/// - Parameter sample: The sample delete object that should be removed.
public func remove(sample: HKDeletedObject) async {
store.remove(resource: sample.uuid.uuidString)
remove(resource: sample.uuid.uuidString)
}


Expand All @@ -52,7 +56,7 @@ extension FHIR: HealthKitConstraint {
displayName: clinicalResource.displayName
)
case let electrocardiogram as HKElectrocardiogram:
guard let hkHealthStore = FHIR.hkHealthStore else {
guard let hkHealthStore = Self.hkHealthStore else {
fallthrough
}

Expand Down
10 changes: 9 additions & 1 deletion Sources/SpeziFHIRInterpretation/FHIRResourceInterpreter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,17 @@ public class FHIRResourceInterpreter {
/// - forceReload: A boolean value that indicates whether to reload and reprocess the resource.
/// - Returns: An asynchronous `String` representing the interpretation of the resource.
@discardableResult
public func summarize(resource: FHIRResource, forceReload: Bool = false) async throws -> String {
public func interpret(resource: FHIRResource, forceReload: Bool = false) async throws -> String {
try await resourceProcesser.process(resource: resource, forceReload: forceReload)
}

/// Retrieve the cached interpretation of a given FHIR resource. Returns a human-readable interpretation or `nil` if it is not present.
///
/// - Parameter resource: The resource where the cached interpretation should be loaded from.
/// - Returns: The cached interpretation. Returns `nil` if the resource is not present.
public func cachedInterpretation(forResource resource: FHIRResource) -> String? {
resourceProcesser.results[resource.id]
}
}


Expand Down
8 changes: 8 additions & 0 deletions Sources/SpeziFHIRInterpretation/FHIRResourceSummary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ public class FHIRResourceSummary {
public func summarize(resource: FHIRResource, forceReload: Bool = false) async throws -> String {
try await resourceProcesser.process(resource: resource, forceReload: forceReload)
}

/// Retrieve the cached summary of a given FHIR resource. Returns a human-readable summary or `nil` if it is not present.
///
/// - Parameter resource: The resource where the cached summary should be loaded from.
/// - Returns: The cached summary. Returns `nil` if the resource is not present.
public func cachedSummary(forResource resource: FHIRResource) -> String? {
resourceProcesser.results[resource.id]
}
}


Expand Down
58 changes: 35 additions & 23 deletions Sources/SpeziFHIRInterpretation/FHIRResourceSummaryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,51 @@ import SwiftUI
/// Displays a FHIR resource, a summary if loaded, and provides a mechanism to load a summary using a context menu.
public struct FHIRResourceSummaryView: View {
@Environment(FHIRResourceSummary.self) private var fhirResourceSummary
@State private var summary: String?

@State private var viewState: ViewState = .idle

private let resource: FHIRResource


public var body: some View {
if let summary, !summary.isEmpty {
VStack(alignment: .leading, spacing: 4) {
Text(resource.displayName)
Text(summary)
.font(.caption)
}
.multilineTextAlignment(.leading)
} else {
VStack(alignment: .leading, spacing: 4) {
Text(resource.displayName)
if viewState == .processing {
ProgressView()
.progressViewStyle(.circular)
.padding(.vertical, 6)
Group {
if let summary = fhirResourceSummary.cachedSummary(forResource: resource) {
VStack(alignment: .leading, spacing: 4) {
Text(resource.displayName)
Text(summary)
.font(.caption)
}
}
.contextMenu {
Button("Load Resource Summary") {
Task {
viewState = .processing
summary = try? await fhirResourceSummary.summarize(resource: resource)
viewState = .idle
}
.multilineTextAlignment(.leading)
} else {
VStack(alignment: .leading, spacing: 4) {
Text(resource.displayName)
if viewState == .processing {
ProgressView()
.progressViewStyle(.circular)
.padding(.vertical, 6)
}
}
.contextMenu {
Button(String(localized: "Create Resource Summary", bundle: .module)) {
Task {
viewState = .processing
do {
try await fhirResourceSummary.summarize(resource: resource)
viewState = .idle
} catch {
viewState = .error(
AnyLocalizedError(
error: error,
defaultErrorDescription: String(localized: "Could not create FHIR Summary", bundle: .module)
)
)
}
}
}
}
}
}
.viewStateAlert(state: $viewState)
}


Expand Down
30 changes: 20 additions & 10 deletions Sources/SpeziFHIRInterpretation/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
@@ -1,44 +1,54 @@
{
"sourceLanguage" : "en",
"strings" : {
"Customize the %@." : {
"Could not create FHIR Summary" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Customize the %@."
"value" : "Could not create FHIR Summary"
}
}
}
},
"Interpretation Prompt" : {
"comment" : "Title of the interpretation prompt.",
"Create Resource Summary" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Interpretation Prompt"
"value" : "Create Resource Summary"
}
}
}
},
"Interpretation Prompt Content" : {
"comment" : "Content of the interpretation prompt.",
"Customize the %@." : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Your task is to interpret the following FHIR resource from the user's clinical record.\n\nInterpret the resource by explaining its data relevant to the user's health.\nExplain the relevant medical context in a language understandable by a user who is not a medical professional.\nYou should provide factual and precise information in a compact summary in short responses.\n\nThe following JSON representation defines the FHIR resource that you should interpret:\n{{FHIR_RESOURCE}}\n\nImmediately return an interpretation to the user, starting the conversation.\nDo not introduce yourself at the beginning, and start with your interpretation.\n\nChat with the user in the same language they chat in.\nChat with the user in {{LOCALE}}"
"value" : "Customize the %@."
}
}
}
},
"Interpretation Prompt" : {
"comment" : "Title of the interpretation prompt.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Interpretation Prompt"
}
}
}
},
"Load Resource Summary" : {
"Interpretation Prompt Content" : {
"comment" : "Content of the interpretation prompt.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Load Resource Summary"
"value" : "Your task is to interpret the following FHIR resource from the user's clinical record.\n\nInterpret the resource by explaining its data relevant to the user's health.\nExplain the relevant medical context in a language understandable by a user who is not a medical professional.\nYou should provide factual and precise information in a compact summary in short responses.\n\nThe following JSON representation defines the FHIR resource that you should interpret:\n{{FHIR_RESOURCE}}\n\nImmediately return an interpretation to the user, starting the conversation.\nDo not introduce yourself at the beginning, and start with your interpretation.\n\nChat with the user in the same language they chat in.\nChat with the user in {{LOCALE}}"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import SwiftUI
/// Customize LLM ``FHIRPrompt``s.
///
/// Allows users to edit and save a prompt associated with a specific ``FHIRPrompt`` type, including where to insert FHIR resources dynamically in the prompt.
public struct PromptSettingsView: View {
public struct FHIRPromptSettingsView: View {
private let promptType: FHIRPrompt
private let onSave: () -> Void
@State private var prompt: String = ""
Expand Down
2 changes: 1 addition & 1 deletion Tests/UITests/TestApp/PromptSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct PromptSettings: View {
}
}
.navigationDestination(for: FHIRPrompt.self) { prompt in
PromptSettingsView(promptType: prompt) {
FHIRPromptSettingsView(promptType: prompt) {
print("Saved \(prompt.localizedDescription)")
}
}
Expand Down

0 comments on commit 82c771f

Please sign in to comment.