Skip to content

Commit

Permalink
FHIR 0.5.2 and Improved Resource Handling (#39)
Browse files Browse the repository at this point in the history
# FHIR 0.5.2 and Improved Resource Handling

## ⚙️ Release Notes 
- Update to Spezi FHIR 0.5.2 and all included improvements:
https://github.com/StanfordSpezi/SpeziFHIR/releases/tag/0.5.2
- Display the data in the overview
- Improve the resource selection and reenable the resource limit
- Improve the prompts and resource selection


### Code of Conduct & Contributing Guidelines 

By submitting creating 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).
  • Loading branch information
PSchmiedmayer authored Nov 26, 2023
1 parent ee231da commit a24b5bc
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 21 deletions.
2 changes: 1 addition & 1 deletion LLMonFHIR.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1110,7 +1110,7 @@
repositoryURL = "https://github.com/StanfordSpezi/SpeziFHIR.git";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 0.5.0;
minimumVersion = 0.5.2;
};
};
2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "Spezi" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziFHIR.git",
"state" : {
"revision" : "786cf34055340aeb887265781aab5703bd685359",
"version" : "0.5.1"
"revision" : "d60882bf6f91f2719f33d413e22d1fc3e6f32705",
"version" : "0.5.2"
}
},
{
Expand Down
24 changes: 18 additions & 6 deletions LLMonFHIR/FHIR Display/InspectResourceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,25 @@ struct InspectResourceView: View {
Spacer()
}
} else if let summary = fhirResourceSummary.cachedSummary(forResource: resource) {
Text(summary)
.multilineTextAlignment(.leading)
.contextMenu {
Button("FHIR_RESOURCES_SUMMARY_BUTTON") {
loadSummary(forceReload: true)
}
VStack {
HStack(spacing: 0) {
Text(summary.title)
.font(.title2)
.multilineTextAlignment(.leading)
.bold()
Spacer()
}
HStack(spacing: 0) {
Text(summary.summary)
.multilineTextAlignment(.leading)
.contextMenu {
Button("FHIR_RESOURCES_SUMMARY_BUTTON") {
loadSummary(forceReload: true)
}
}
Spacer()
}
}
} else {
Button("FHIR_RESOURCES_SUMMARY_BUTTON") {
loadSummary()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ class FHIRMultipleResourceInterpreter {

switch functionCall.name {
case LLMFunction.getResourcesName:
callGetResources(functionCall: functionCall)
try await callGetResources(functionCall: functionCall)
default:
break
}
Expand All @@ -150,7 +150,7 @@ class FHIRMultipleResourceInterpreter {
}


private func callGetResources(functionCall: LLMStreamResult.FunctionCall) {
private func callGetResources(functionCall: LLMStreamResult.FunctionCall) async throws {
struct Response: Codable {
let resources: String
}
Expand All @@ -165,12 +165,21 @@ class FHIRMultipleResourceInterpreter {
print("Parsed Resources: \(requestedResources)")

for requestedResource in requestedResources {
for resource in fhirStore.allResources.filter({ $0.functionCallIdentifier == requestedResource }) {
var fittingResources = fhirStore.allResources.filter { $0.functionCallIdentifier == requestedResource }
print("Fitting Resources: \(fittingResources.count)")
if fittingResources.count > 20 {
fittingResources = fittingResources.lazy.sorted(by: { $0.date ?? .distantPast < $1.date ?? .distantPast }).suffix(10)
print("Reduced to the following 20 resources: \(fittingResources.map { $0.functionCallIdentifier }.joined(separator: ","))")
}

for resource in fittingResources {
print("Appending Resource: \(resource)")
let summary = try await resourceSummary.summarize(resource: resource)
print("Summary of Resource generated: \(summary)")
chat.append(
Chat(
role: .function,
content: String(localized: "This is the content of the requested \(requestedResource):\n\n\(resource.jsonDescription)"),
content: String(localized: "This is the summary of the requested \(requestedResource):\n\n\(summary.description)"),
name: LLMFunction.getResourcesName
)
)
Expand Down
11 changes: 10 additions & 1 deletion LLMonFHIR/FHIR Interpretation/FHIRResource+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,20 @@
// SPDX-License-Identifier: MIT
//

import Foundation
import SpeziFHIR


extension FHIRResource {
private static let dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
return dateFormatter
}()

var functionCallIdentifier: String {
resourceType.filter { !$0.isWhitespace } + displayName.filter { !$0.isWhitespace }
resourceType.filter { !$0.isWhitespace }
+ displayName.filter { !$0.isWhitespace }
+ (date.map { FHIRResource.dateFormatter.string(from: $0) } ?? "")
}
}
29 changes: 28 additions & 1 deletion LLMonFHIR/FHIR Interpretation/FHIRStore+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import ModelsR4
import SpeziFHIR
import SwiftUI


extension FHIRStore {
Expand All @@ -16,7 +17,26 @@ extension FHIRStore {
}

var allResourcesFunctionCallIdentifier: [String] {
Array(Set(allResources.map { $0.functionCallIdentifier }))
@AppStorage(StorageKeys.resourceLimit) var resourceLimit = StorageKeys.Defaults.resourceLimit

let relevantResources: [FHIRResource]
if allResources.count > resourceLimit {
var limitedResources: [FHIRResource] = []
limitedResources.append(contentsOf: allergyIntolerances.dateSuffix(maxLength: resourceLimit / 9))
limitedResources.append(contentsOf: conditions.dateSuffix(maxLength: resourceLimit / 9))
limitedResources.append(contentsOf: diagnostics.dateSuffix(maxLength: resourceLimit / 9))
limitedResources.append(contentsOf: encounters.dateSuffix(maxLength: resourceLimit / 9))
limitedResources.append(contentsOf: immunizations.dateSuffix(maxLength: resourceLimit / 9))
limitedResources.append(contentsOf: medications.dateSuffix(maxLength: resourceLimit / 9))
limitedResources.append(contentsOf: observations.dateSuffix(maxLength: resourceLimit / 9))
limitedResources.append(contentsOf: otherResources.dateSuffix(maxLength: resourceLimit / 9))
limitedResources.append(contentsOf: procedures.dateSuffix(maxLength: resourceLimit / 9))
relevantResources = limitedResources
} else {
relevantResources = allResources
}

return Array(Set(relevantResources.map { $0.functionCallIdentifier }))
}


Expand All @@ -38,3 +58,10 @@ extension FHIRStore {
}
}
}


extension Array where Element == FHIRResource {
fileprivate func dateSuffix(maxLength: Int) -> [FHIRResource] {
self.lazy.sorted(by: { $0.date ?? .distantPast < $1.date ?? .distantPast }).suffix(maxLength)
}
}
26 changes: 23 additions & 3 deletions LLMonFHIR/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -1206,7 +1206,7 @@
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "You are the LLM on FHIR application.\nYour task is to interpret FHIR resources from the user's clinical records.\n\nThroughout the conversation with the user, use the \"get_resources\" function to obtain the FHIR health resources necessary to properly answer the users question. For example, if the user asks about their allergies, you must use the \"get_resources\" function to output the FHIR resource titles for allergy records so you can then use them to answer the question. The end goal is to answer the users question in the best way possible while taking the FHIR resources obtained using \"get_resources\" into consideration.\n\nThese are the resource titles of the resources you can request using \"get_resources\":\n{{FHIR_RESOURCE}}\n\nInterpret the resources 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, ideally in a 5th grade reading level.\nYou should provide factual and precise information in a compact summary in short responses.\n\nDo not introduce yourself at the beginning, and start with your interpretation. \n\nImmediately return a short summary of the users health records to start the conversation.\nThe initial summary should be a short and simple summary and NOT just list the titles of the records.\nEnd with a question asking user if they have any questions. Make sure that this question is not generic but specific to their health records.\nMake sure your response is in the same language the user writes to you in.\nThe tense should be present."
"value" : "You are the LLM on FHIR application.\nYour task is to interpret FHIR resources from the user's clinical records.\n\nThroughout the conversation with the user, use the \"get_resources\" function to obtain the FHIR health resources necessary to answer the user's question properly. For example, if the user asks about their allergies, you must use the \"get_resources\" function to output the FHIR resource titles for allergy records so you can then use them to answer the question. The end goal is to answer the user's question in the best way possible while taking the FHIR resources obtained using \"get_resources\" into consideration.\n\nIf there is a Patient FHIR resource available, ensure that you request this resource first to get adequate information about the patient. Only request relevant resources and focus on recent resources. Try to reduce the number of requested resources to a reasonable scope.\n\nThese are the resource titles of the resources you can request using \"get_resources\":\n{{FHIR_RESOURCE}}\n\nInterpret the resources by explaining the 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, ideally at a 5th-grade reading level.\nYou should provide factual and precise information in a compact summary in short responses.\n\nDo not introduce yourself at the beginning, and start with your interpretation. \n\nImmediately return a summary of the user's health records to start the conversation.\nThe initial summary should be short and simple and NOT list the records' titles or any resource's detailed content; stay at a high level.\nEnd with a question asking the user if they have any questions. Make sure that this question is not generic but specific to their health records.\nMake sure your response is in the same language the user writes to you in.\nThe tense should be present."
}
}
}
Expand Down Expand Up @@ -1373,6 +1373,26 @@
}
}
},
"Resource Limit" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Resource Limit"
}
}
}
},
"Resource Limit %lld" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Resource Limit %lld"
}
}
}
},
"Resource Selection" : {
"localizations" : {
"en" : {
Expand Down Expand Up @@ -1785,12 +1805,12 @@
}
}
},
"This is the content of the requested %@:\n\n%@" : {
"This is the summary of the requested %@:\n\n%@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "This is the content of the requested %1$@:\n\n%2$@"
"value" : "This is the summary of the requested %1$@:\n\n%2$@"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ SPDX-FileCopyrightText: 2023 Stanford University

SPDX-License-Identifier: MIT

The patient mock data is generated by Synthea: https://github.com/synthetichealth/synthea:
The patient mock data is generated by Synthea: https://github.com/synthetichealth/synthea: Jason Walonoski, Mark Kramer, Joseph Nichols, Andre Quina, Chris Moesel, Dylan Hall, Carlton Duffett, Kudakwashe Dube, Thomas Gallagher, Scott McLachlan, Synthea: An approach, method, and software mechanism for generating synthetic patients and the synthetic electronic health care record, Journal of the American Medical Informatics Association, Volume 25, Issue 3, March 2018, Pages 230–238, https://doi.org/10.1093/jamia/ocx079
9 changes: 9 additions & 0 deletions LLMonFHIR/Settings/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ struct SettingsView: View {
List {
openAISettings
speechSettings
resourcesLimitSettings
resourcesSettings
promptsSettings
}
Expand All @@ -57,6 +58,14 @@ struct SettingsView: View {
}
}

private var resourcesLimitSettings: some View {
Section("Resource Limit") {
Stepper(value: $resourceLimit, in: 10...2000, step: 10) {
Text("Resource Limit \(resourceLimit)")
}
}
}

private var resourcesSettings: some View {
Section("Resource Selection") {
NavigationLink(value: SettingsDestinations.resourceSelection) {
Expand Down
2 changes: 1 addition & 1 deletion LLMonFHIR/SharedContext/StorageKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
enum StorageKeys {
enum Defaults {
static let enableTextToSpeech = false
static let resourceLimit = 50
static let resourceLimit = 250
}


Expand Down
2 changes: 1 addition & 1 deletion LLMonFHIRUITests/FHIRDisplayTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ final class FHIRDisplayTests: XCTestCase {

app.swipeUp()

let mockResource = app.otherElements.buttons["Mock Resource"]
let mockResource = app.staticTexts["Mock Resource"]
XCTAssertTrue(mockResource.exists, "The 'Mock Resource' does not exist.")

mockResource.tap()
Expand Down

0 comments on commit a24b5bc

Please sign in to comment.