diff --git a/Intake.xcodeproj/project.pbxproj b/Intake.xcodeproj/project.pbxproj index a4453e4..9dc56b1 100644 --- a/Intake.xcodeproj/project.pbxproj +++ b/Intake.xcodeproj/project.pbxproj @@ -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 */; }; @@ -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 */; }; @@ -211,6 +213,8 @@ 5A2B9FAA2B69E430005CA63F /* FHIRStore+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FHIRStore+Extensions.swift"; sourceTree = ""; }; 5A2B9FB52B6AFE5D005CA63F /* AllergyRecords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllergyRecords.swift; sourceTree = ""; }; 5A2B9FBB2B6C7B29005CA63F /* ReactionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionView.swift; sourceTree = ""; }; + 5AAB83A62B9C04E70008407A /* LLMFiltering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LLMFiltering.swift; sourceTree = ""; }; + 5AAB83B22B9EBB070008407A /* ReactionSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionSectionView.swift; sourceTree = ""; }; 5AEA5F202B82DDD000F1577A /* LLMAssistantView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LLMAssistantView.swift; sourceTree = ""; }; 5AEA5F2B2B8680F300F1577A /* AddAllergy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAllergy.swift; sourceTree = ""; }; 5AEA5F3A2B90081B00F1577A /* ScrollablePDF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollablePDF.swift; sourceTree = ""; }; @@ -479,6 +483,7 @@ 5AEA5F2B2B8680F300F1577A /* AddAllergy.swift */, 5AEA5F492B96F63A00F1577A /* AllergyLLMAssistant.swift */, 5A2B9FBB2B6C7B29005CA63F /* ReactionView.swift */, + 5AAB83B22B9EBB070008407A /* ReactionSectionView.swift */, 5AEA5F462B93034A00F1577A /* ReactionPDF.swift */, 51A360152B965819004E7E12 /* AllergyLLMAssistant.swift */, ); @@ -537,6 +542,7 @@ 2FE5DC2D29EDD792004B9AB4 /* Resources */, 2FC9759D2978E30800BA99FE /* Supporting Files */, 5AEA5F3A2B90081B00F1577A /* ScrollablePDF.swift */, + 5AAB83A62B9C04E70008407A /* LLMFiltering.swift */, 5AEA5F412B90710B00F1577A /* EditPatient.swift */, ); path = Intake; @@ -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 */, diff --git a/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9f82483..9fb77a5 100644 --- a/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,4 @@ { - "originHash" : "cdbe60a3382a8a962c7fcc00210e56e13314cb3e246d0e01fe8e25a1268623d8", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -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" } }, { @@ -380,5 +379,5 @@ } } ], - "version" : 3 + "version" : 2 } diff --git a/Intake/Allergy Records/AddAllergy.swift b/Intake/Allergy Records/AddAllergy.swift index 11117c5..ba6efdb 100644 --- a/Intake/Allergy Records/AddAllergy.swift +++ b/Intake/Allergy Records/AddAllergy.swift @@ -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#>, allergyRecords: <#T##Binding<[AllergyItem]>#>, showingReaction: .constant(true), allergyRecords: .constant([])) diff --git a/Intake/Allergy Records/AllergyRecords.swift b/Intake/Allergy Records/AllergyRecords.swift index 2526036..e6c14b1 100644 --- a/Intake/Allergy Records/AllergyRecords.swift +++ b/Intake/Allergy Records/AllergyRecords.swift @@ -13,6 +13,8 @@ import Foundation import ModelsR4 import SpeziFHIR +import SpeziLLM +import SpeziLLMOpenAI import SwiftUI struct AllergyItem: Identifiable, Equatable { @@ -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 @@ -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 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 { @@ -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) @@ -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 @@ -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 ?? "")) } } } @@ -180,6 +237,9 @@ struct AllergyList: View { ) } } + + let filter = LLMFiltering(session: session, data: data) + try await filter.filterAllergies() } func delete(at offsets: IndexSet) { diff --git a/Intake/Allergy Records/ReactionPDF.swift b/Intake/Allergy Records/ReactionPDF.swift index 2862afe..4a48329 100644 --- a/Intake/Allergy Records/ReactionPDF.swift +++ b/Intake/Allergy Records/ReactionPDF.swift @@ -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") } } } diff --git a/Intake/Allergy Records/ReactionSectionView.swift b/Intake/Allergy Records/ReactionSectionView.swift new file mode 100644 index 0000000..7ad3d80 --- /dev/null +++ b/Intake/Allergy Records/ReactionSectionView.swift @@ -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) + } +} diff --git a/Intake/Intake.swift b/Intake/Intake.swift index 9065352..3e7f87a 100644 --- a/Intake/Intake.swift +++ b/Intake/Intake.swift @@ -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 @@ -60,6 +66,7 @@ struct Intake: App { let navigationPath = NavigationPathWrapper() let data = DataStore() let reachedEnd = ReachedEndWrapper() + let loaded = LoadedWrapper() var body: some Scene { WindowGroup { @@ -78,6 +85,7 @@ struct Intake: App { .environment(navigationPath) .environment(data) .environment(reachedEnd) + .environment(loaded) } } } diff --git a/Intake/LLMFiltering.swift b/Intake/LLMFiltering.swift new file mode 100644 index 0000000..95191a8 --- /dev/null +++ b/Intake/LLMFiltering.swift @@ -0,0 +1,137 @@ +// +// LLMFiltering.swift +// Intake +// +// Created by Akash Gupta on 3/8/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 SpeziLLM +import SpeziLLMOpenAI +import SwiftUI + + +class LLMFiltering { + private var LLMFiltering = true + private var session: LLMOpenAISession + private var data: DataStore + + init(session: LLMOpenAISession, data: DataStore) { + self.session = session + self.data = data + } + + func filter(surgeries: [String]) async -> [String] { + let stopWords = [ + "screen", + "medication", + "examination", + "assess", + "development", + "notification", + "clarification", + "discussion ", + "option", + "review", + "evaluation", + "management", + "consultation", + "referral", + "interpretation", + "discharge", + "certification", + "preparation" + ] + + let manualFilter = surgeries.filter { !self.containsAnyWords(item: $0.lowercased(), words: stopWords) } + + if !self.LLMFiltering { + return manualFilter + } + + do { + return try await self.LLMFilter(names: manualFilter) + } catch { + print("Error filtering with LLM: \(error)") + print("Returning manually filtered surgeries") + return manualFilter + } + } + + func containsAnyWords(item: String, words: [String]) -> Bool { + words.contains { item.contains($0) } + } + + func LLMFilter(names: [String]) async throws -> [String] { + let LLMResponse = try await self.queryLLM(names: names) + let filteredNames = LLMResponse.components(separatedBy: ", ") + + return filteredNames + } + + func queryLLM(names: [String]) async throws -> String { + var responseText = "" + + await MainActor.run { + session.context.append(userInput: names.joined(separator: ", ")) + } + for try await token in try await session.generate() { + responseText.append(token) + } + + return responseText + } + + + func filterSurgeries() async throws -> [SurgeryItem] { + @Environment(DataStore.self) var data + let filteredNames = try await self.LLMFilter(names: []) + let filteredSurgeries = data.surgeries.filter { self.containsAnyWords(item: $0.surgeryName, words: filteredNames) } + var cleaned = filteredSurgeries + for index in cleaned.indices { + let oldName = cleaned[index].surgeryName + if let newName: String = filteredNames.first(where: { oldName.contains($0) }) { + cleaned[index].surgeryName = newName + } + } + return cleaned + } + + func filterConditions() async throws { + let conditions = data.conditionData.map { $0.condition } + let filteredNames = try await self.LLMFilter(names: conditions) + let filteredConditions = data.conditionData.filter { self.containsAnyWords(item: $0.condition, words: filteredNames) } + var cleaned = filteredConditions + + for index in cleaned.indices { + print(index) + let oldName = cleaned[index].condition + if let newName: String = filteredNames.first(where: { oldName.contains($0) }) { + cleaned[index].condition = newName + } + } + data.conditionData = cleaned + } + + func filterAllergies() async throws { + let allergies = data.allergyData.map { $0.allergy } + let filteredNames = try await self.LLMFilter(names: allergies) + let filteredAllergies = data.allergyData.filter { self.containsAnyWords(item: $0.allergy, words: filteredNames) } + var cleaned = filteredAllergies + + for index in cleaned.indices { + let oldName = cleaned[index].allergy + if let newName: String = filteredNames.first(where: { oldName.contains($0) }) { + cleaned[index].allergy = newName + } + } + data.allergyData = cleaned + } +} diff --git a/Intake/Medical History/MedicalHistoryView.swift b/Intake/Medical History/MedicalHistoryView.swift index 221ff72..e1a0bb6 100644 --- a/Intake/Medical History/MedicalHistoryView.swift +++ b/Intake/Medical History/MedicalHistoryView.swift @@ -13,6 +13,8 @@ import Foundation import ModelsR4 import SpeziFHIR +import SpeziLLM +import SpeziLLMOpenAI import SwiftUI struct MedicalHistoryItem: Identifiable, Equatable { @@ -25,19 +27,34 @@ struct MedicalHistoryView: 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 showAddSheet = false @State private var showingChat = false + + @LLMSessionProvider var session: LLMOpenAISession var body: some View { - VStack { - medicalHistoryForm - SubmitButton(nextView: NavigationViews.surgical) - .padding() + if loaded.conditionData { + VStack { + medicalHistoryForm + SubmitButton(nextView: NavigationViews.surgical) + .padding() + } + .sheet(isPresented: $showingChat, content: chatSheetView) + } else { + ProgressView() + .task { + do { + try await loadConditions() + } catch { + print("Failed to load") + } + loaded.conditionData = true + } } - .onAppear(perform: loadConditions) - .sheet(isPresented: $showingChat, content: chatSheetView) } - + private var medicalHistoryForm: some View { Form { Section(header: Text("Please list conditions you have had")) { @@ -91,6 +108,36 @@ struct MedicalHistoryView: View { .foregroundColor(.gray) } + init() { + let systemPrompt = """ + You are a helpful assistant that filters lists of conditions. You will be given\ + an array of strings. Each string will be the name of a condition. + + 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 condition. 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 addConditionAction() { data.conditionData.append(MedicalHistoryItem(condition: "", active: false)) } @@ -123,7 +170,7 @@ struct MedicalHistoryView: View { ) } - private func loadConditions() { + private func loadConditions() async throws { let conditions = fhirStore.conditions var active = "" let invalid = [ @@ -150,7 +197,8 @@ struct MedicalHistoryView: View { } } } - print(data.conditionData) + let filter = LLMFiltering(session: session, data: data) + try await filter.filterConditions() } func delete(at offsets: IndexSet) { diff --git a/Intake/Resources/Localizable.xcstrings b/Intake/Resources/Localizable.xcstrings index b5821ee..fafec63 100644 --- a/Intake/Resources/Localizable.xcstrings +++ b/Intake/Resources/Localizable.xcstrings @@ -515,6 +515,9 @@ }, "No past surgeries" : { + }, + "No Reactions" : { + }, "NOTIFICATION_PERMISSIONS_BUTTON" : { "extractionState" : "stale",