Skip to content

Commit

Permalink
Llm filter data (#67)
Browse files Browse the repository at this point in the history
# LLM Filter Data*

## ♻️ Current situation & Problem
*Link any open issues or pull requests (PRs) related to this PR. Please
ensure that all non-trivial PRs are first tracked and discussed in an
existing GitHub issue or discussion.*


## ⚙️ Release Notes 
*This PR creates a class that filters the data by removing things within
"()". It uses and LLM and starts to cut down on filtering by content,
but the system prompts need to be more tailored towards filtering the
data better. It applies this class to conditions and allergies


## 📚 Documentation
*Please ensure that you properly document any additions in conformance
to [Spezi Documentation
Guide](https://github.com/StanfordSpezi/.github/blob/main/DOCUMENTATIONGUIDE.md).*
*You can use this section to describe your solution, but we encourage
contributors to document your reasoning and changes using in-line
documentation.*


## ✅ Testing
*Please ensure that the PR meets the testing requirements set by CodeCov
and that new functionality is appropriately tested.*
*This section describes important information about the tests and why
some elements might not be testable.*


## 📝 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: nriedman <[email protected]>
  • Loading branch information
apgupta3303 and nriedman committed Mar 11, 2024
1 parent 06a8726 commit 105eaf1
Show file tree
Hide file tree
Showing 10 changed files with 354 additions and 83 deletions.
10 changes: 9 additions & 1 deletion Intake.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
5A2B9FAB2B69E430005CA63F /* FHIRStore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2B9FAA2B69E430005CA63F /* FHIRStore+Extensions.swift */; };
5A2B9FB62B6AFE5D005CA63F /* AllergyRecords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2B9FB52B6AFE5D005CA63F /* AllergyRecords.swift */; };
5A2B9FBC2B6C7B29005CA63F /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2B9FBB2B6C7B29005CA63F /* ReactionView.swift */; };
5AAB83A72B9C04E70008407A /* LLMFiltering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AAB83A62B9C04E70008407A /* LLMFiltering.swift */; };
5AAB83B32B9EBB070008407A /* ReactionSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AAB83B22B9EBB070008407A /* ReactionSectionView.swift */; };
5AEA5F212B82DDD000F1577A /* LLMAssistantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA5F202B82DDD000F1577A /* LLMAssistantView.swift */; };
5AEA5F2C2B8680F300F1577A /* AddAllergy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA5F2B2B8680F300F1577A /* AddAllergy.swift */; };
5AEA5F3B2B90081B00F1577A /* ScrollablePDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA5F3A2B90081B00F1577A /* ScrollablePDF.swift */; };
Expand All @@ -112,8 +114,8 @@
A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DFE8A82ABE551400428242 /* AccountButton.swift */; };
A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */; };
ACAA47812B571C800032D21F /* Questionnaire.json in Resources */ = {isa = PBXBuildFile; fileRef = ACAA47802B571C7F0032D21F /* Questionnaire.json */; };
ACF862BE2B96E29600ACBA1E /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF862BD2B96E29600ACBA1E /* ExportView.swift */; };
ACDF32ED2B9D0F4300B127E2 /* MenstrualHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACFFA1D02B8FD8BB0006E6D4 /* MenstrualHistory.swift */; };
ACF862BE2B96E29600ACBA1E /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF862BD2B96E29600ACBA1E /* ExportView.swift */; };
ACFFA1CE2B8FD7190006E6D4 /* SmokingHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACFFA1CD2B8FD7190006E6D4 /* SmokingHistory.swift */; };
F42AB1D22B6379B5002E13A6 /* SpeziLLM in Frameworks */ = {isa = PBXBuildFile; productRef = F42AB1D12B6379B5002E13A6 /* SpeziLLM */; };
F42AB1D42B6379B5002E13A6 /* SpeziLLMLocal in Frameworks */ = {isa = PBXBuildFile; productRef = F42AB1D32B6379B5002E13A6 /* SpeziLLMLocal */; };
Expand Down Expand Up @@ -211,6 +213,8 @@
5A2B9FAA2B69E430005CA63F /* FHIRStore+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FHIRStore+Extensions.swift"; sourceTree = "<group>"; };
5A2B9FB52B6AFE5D005CA63F /* AllergyRecords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllergyRecords.swift; sourceTree = "<group>"; };
5A2B9FBB2B6C7B29005CA63F /* ReactionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionView.swift; sourceTree = "<group>"; };
5AAB83A62B9C04E70008407A /* LLMFiltering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LLMFiltering.swift; sourceTree = "<group>"; };
5AAB83B22B9EBB070008407A /* ReactionSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionSectionView.swift; sourceTree = "<group>"; };
5AEA5F202B82DDD000F1577A /* LLMAssistantView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LLMAssistantView.swift; sourceTree = "<group>"; };
5AEA5F2B2B8680F300F1577A /* AddAllergy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAllergy.swift; sourceTree = "<group>"; };
5AEA5F3A2B90081B00F1577A /* ScrollablePDF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollablePDF.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -479,6 +483,7 @@
5AEA5F2B2B8680F300F1577A /* AddAllergy.swift */,
5AEA5F492B96F63A00F1577A /* AllergyLLMAssistant.swift */,
5A2B9FBB2B6C7B29005CA63F /* ReactionView.swift */,
5AAB83B22B9EBB070008407A /* ReactionSectionView.swift */,
5AEA5F462B93034A00F1577A /* ReactionPDF.swift */,
51A360152B965819004E7E12 /* AllergyLLMAssistant.swift */,
);
Expand Down Expand Up @@ -537,6 +542,7 @@
2FE5DC2D29EDD792004B9AB4 /* Resources */,
2FC9759D2978E30800BA99FE /* Supporting Files */,
5AEA5F3A2B90081B00F1577A /* ScrollablePDF.swift */,
5AAB83A62B9C04E70008407A /* LLMFiltering.swift */,
5AEA5F412B90710B00F1577A /* EditPatient.swift */,
);
path = Intake;
Expand Down Expand Up @@ -889,8 +895,10 @@
566155292AB8447C00209B80 /* Package+LicenseType.swift in Sources */,
5680DD392AB8983D004E6D4A /* PackageCell.swift in Sources */,
5AEA5F422B90710B00F1577A /* EditPatient.swift in Sources */,
5AAB83A72B9C04E70008407A /* LLMFiltering.swift in Sources */,
F42AB1EC2B6DBF21002E13A6 /* SummaryView.swift in Sources */,
2F5E32BD297E05EA003432F8 /* IntakeDelegate.swift in Sources */,
5AAB83B32B9EBB070008407A /* ReactionSectionView.swift in Sources */,
511827962B740192002033A0 /* SurgeryView.swift in Sources */,
2FE5DC5229EDD7FA004B9AB4 /* IntakeScheduler.swift in Sources */,
5AEA5F3B2B90081B00F1577A /* ScrollablePDF.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"originHash" : "cdbe60a3382a8a962c7fcc00210e56e13314cb3e246d0e01fe8e25a1268623d8",
"pins" : [
{
"identity" : "abseil-cpp-binary",
Expand Down Expand Up @@ -231,8 +230,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziHealthKit.git",
"state" : {
"revision" : "35628084d3977aa897015b0b0c21cfe4d556f1aa",
"version" : "0.5.2"
"revision" : "1e9cb5a6036ac7f4ff37ea1c3ed4898103339ad1",
"version" : "0.5.3"
}
},
{
Expand Down Expand Up @@ -380,5 +379,5 @@
}
}
],
"version" : 3
"version" : 2
}
38 changes: 0 additions & 38 deletions Intake/Allergy Records/AddAllergy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,44 +54,6 @@ struct EditAllergyView: View {
}
}

struct ReactionSectionView: View {
@Environment(DataStore.self) private var data
var index: Int

var body: some View {
Form { // Use Form instead of List
Section(header: headerTitle) {
@Bindable var data = data
ForEach($data.allergyData[index].reaction) { $item in
HStack {
TextField("Reactions", text: $item.reaction)
}
}
.onDelete(perform: delete)
Button(action: {
data.allergyData[index].reaction.append(ReactionItem(reaction: ""))
}) {
HStack {
Image(systemName: "plus.circle.fill")
.accessibilityLabel(Text("ADD_REACTION"))
Text("Add Field")
}
}
}
}
}

private var headerTitle: some View {
HStack {
Text("Reactions")
Spacer()
EditButton()
}
}
func delete(at offsets: IndexSet) {
data.allergyData[index].reaction.remove(atOffsets: offsets)
}
}

// #Preview {
// EditAllergyView(allergyItem: AllergyItem(allergy: "", reaction: []), showingReaction: <#T##Binding<Bool>#>, allergyRecords: <#T##Binding<[AllergyItem]>#>, showingReaction: .constant(true), allergyRecords: .constant([]))
Expand Down
86 changes: 73 additions & 13 deletions Intake/Allergy Records/AllergyRecords.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import Foundation
import ModelsR4
import SpeziFHIR
import SpeziLLM
import SpeziLLMOpenAI
import SwiftUI

struct AllergyItem: Identifiable, Equatable {
Expand All @@ -25,10 +27,6 @@ struct AllergyItem: Identifiable, Equatable {
}
}

// struct ReactionViewDetails {
// var showingReaction: Bool
// var
// }

struct ChatButton: View {
// Use @Binding to create a two-way binding to the parent view's showingChat state
Expand All @@ -54,20 +52,35 @@ struct AllergyList: View {
@Environment(FHIRStore.self) private var fhirStore
@Environment(NavigationPathWrapper.self) private var navigationPath
@Environment(DataStore.self) private var data
@Environment(LoadedWrapper.self) private var loaded

@State private var showingReaction = false
@State private var selectedIndex = 0
@State private var showingChat = false
@State private var presentingAccount = false

@LLMSessionProvider<LLMOpenAISchema> var session: LLMOpenAISession

var body: some View {
VStack {
allergyForm
SubmitButton(nextView: NavigationViews.menstrual)
.padding()
if loaded.allergyData {
VStack {
allergyForm
SubmitButton(nextView: NavigationViews.menstrual)
.padding()
}
.sheet(isPresented: $showingChat, content: chatSheetView)
.sheet(isPresented: $showingReaction, content: editAllergySheetView)
} else {
ProgressView()
.task {
do {
try await loadAllergies()
} catch {
print("Failed to load")
}
loaded.allergyData = true
}
}
.onAppear(perform: loadAllergies)
.sheet(isPresented: $showingChat, content: chatSheetView)
.sheet(isPresented: $showingReaction, content: editAllergySheetView)
}
private var allergyForm: some View {
Form {
Expand Down Expand Up @@ -102,7 +115,37 @@ struct AllergyList: View {
}
}
}

init() {
let systemPrompt = """
You are a helpful assistant that filters lists of allergies. You will be given\
an array of strings. Each string will be the name of a allergy.
For example, if you are given the following list:
Mammography (procedure), Certification procedure (procedure), Cytopathology\
procedure, preparation of smear, genital source (procedure), Transplant of kidney\
(procedure),
you should return something like this:
Transplant of kidney, Mammography.
In your response, return only the name of the allergy. Remove words in parenthesis
like (disorder), so "Aortic valve stenosis (disorder)" would turn to "Aortic valve stenosis".
Do not make anything up, and do not change the name of the condition under any
circumstances. Thank you!
"""

self._session = LLMSessionProvider(
schema: LLMOpenAISchema(
parameters: .init(
modelType: .gpt3_5Turbo,
systemPrompt: systemPrompt
)
)
)
}

private func allergyEntryRow(index: Int) -> some View {
HStack {
Text(data.allergyData[index].allergy)
Expand Down Expand Up @@ -145,8 +188,20 @@ struct AllergyList: View {
private func editAllergySheetView() -> some View {
EditAllergyView(index: selectedIndex, showingReaction: $showingReaction)
}

private func removeTextWithinParentheses(from string: String) -> String {
let pattern = "\\s*\\([^)]+\\)"
do {
let regex = try NSRegularExpression(pattern: pattern)
let range = NSRange(string.startIndex..., in: string)
return regex.stringByReplacingMatches(in: string, options: [], range: range, withTemplate: "")
} catch {
print("Invalid regex: \(error.localizedDescription)")
return string
}
}

private func loadAllergies() {
private func loadAllergies() async throws {
var allergies: [FHIRString] = []
var allReactions: [[ReactionItem]] = []
let intolerances = fhirStore.allergyIntolerances
Expand All @@ -162,7 +217,9 @@ struct AllergyList: View {
for reaction in reactions {
let manifestations = reaction.manifestation
for manifestation in manifestations {
reactionsForAllergy.append(ReactionItem(reaction: manifestation.text?.value?.string ?? "Default"))
var reactionName = manifestation.text?.value?.string
reactionName = removeTextWithinParentheses(from: reactionName ?? "")
reactionsForAllergy.append(ReactionItem(reaction: reactionName ?? ""))
}
}
}
Expand All @@ -180,6 +237,9 @@ struct AllergyList: View {
)
}
}

let filter = LLMFiltering(session: session, data: data)
try await filter.filterAllergies()
}

func delete(at offsets: IndexSet) {
Expand Down
26 changes: 8 additions & 18 deletions Intake/Allergy Records/ReactionPDF.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,16 @@ struct ReactionPDF: View {
@Binding private var showingReaction: Bool
var body: some View {
NavigationView {
ReactionSectionView(index: index)
// VStack {
// Form {
// ForEach(data.allergyData[index].reaction) { item in
// Text(item.reaction)
// }
// }
// .navigationTitle("Medical History")
//// .navigationTitle("\(data.allergyData[index].allergy) Reactions")
// .navigationBarItems(trailing: EditButton())
// }
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
HStack {
Text("\(data.allergyData[index].allergy) Reactions")
.lineLimit(1) // Ensure the title is single-lined
.truncationMode(.tail)
.font(.headline) // Adjust the font size if needed
VStack {
Form {
if data.allergyData[index].reaction.isEmpty {
Text("No Reactions")
}
ForEach(data.allergyData[index].reaction) { item in
Text(item.reaction)
}
}
.navigationTitle("\(data.allergyData[index].allergy) Reactions")
}
}
}
Expand Down
56 changes: 56 additions & 0 deletions Intake/Allergy Records/ReactionSectionView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// ReactionSectionView.swift
// Intake
//
// Created by Akash Gupta on 3/10/24.
//
// This source file is part of the Intake based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2023 Stanford University
//
// SPDX-License-Identifier: MIT
//

import Foundation
import SpeziFHIR
import SwiftUI


struct ReactionSectionView: View {
@Environment(DataStore.self) private var data
var index: Int

var body: some View {
Form { // Use Form instead of List
Section(header: headerTitle) {
@Bindable var data = data
ForEach($data.allergyData[index].reaction) { $item in
HStack {
TextField("Reactions", text: $item.reaction)
}
}
.onDelete(perform: delete)
Button(action: {
data.allergyData[index].reaction.append(ReactionItem(reaction: ""))
}) {
HStack {
Image(systemName: "plus.circle.fill")
.accessibilityLabel(Text("ADD_REACTION"))
Text("Add Field")
}
}
}
}
}

private var headerTitle: some View {
HStack {
Text("Reactions")
Spacer()
EditButton()
}
}
func delete(at offsets: IndexSet) {
data.allergyData[index].reaction.remove(atOffsets: offsets)
}
}
8 changes: 8 additions & 0 deletions Intake/Intake.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ class ReachedEndWrapper {
var surgeriesLoaded = false
}

@Observable
class LoadedWrapper {
var conditionData = false
var allergyData = false
}

@main
struct Intake: App {
@UIApplicationDelegateAdaptor(IntakeDelegate.self) var appDelegate
Expand All @@ -60,6 +66,7 @@ struct Intake: App {
let navigationPath = NavigationPathWrapper()
let data = DataStore()
let reachedEnd = ReachedEndWrapper()
let loaded = LoadedWrapper()

var body: some Scene {
WindowGroup {
Expand All @@ -78,6 +85,7 @@ struct Intake: App {
.environment(navigationPath)
.environment(data)
.environment(reachedEnd)
.environment(loaded)
}
}
}
Loading

0 comments on commit 105eaf1

Please sign in to comment.