From a8de6e9937b48a49a019dd008f6d049dd6975cdf Mon Sep 17 00:00:00 2001 From: Nina Boord <86579493+ninaboord@users.noreply.github.com> Date: Sun, 10 Mar 2024 20:44:20 -0700 Subject: [PATCH 1/2] Patient generalData flow refractor and PatientInfo View (#64) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # *Patient generalData flow refractor and PatientInfo View* ## :recycle: Current situation & Problem Before this PR, we did not have a way for the patient to enter or change their personal information if they were not connected to FHIRStore. - If the patient is NOT connected to FHIRStore, PatientInfo View allows patient to update their general data (name, dob, age..), and view updates global patient variables - Global variables now passed into LLM, so LLMInteraction code is now cleaner (moved my original code that did extraction from FHIRStore now into PatientInfo for better code cleanliness! - The Chief Complaint is now AFTER the forms. This is to set up for the next bonus pull request of passing in patient medical history into the LLM. ## :gear: Release Notes calculateAge, getValue, and getInfo functions are now inside of a view, and loadData() is a private function that then updates the data onAppear of the Chief Complaint. This code refractor is necessary for a future PR I will be doing where I change the order of the navigation stack so that Chief Complaint is actually the last item on the nav stack (before scrollable pdf and export pdf). Screenshot 2024-03-10 at 8 37 55 PM ## :books: Documentation n/a ## :pencil: 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). --- Intake.xcodeproj/project.pbxproj | 12 ++ Intake/ChiefComplaint/LLMInteraction.swift | 125 ++++------------ Intake/ChiefComplaint/SummaryView.swift | 2 +- Intake/EditPatient.swift | 34 ++--- Intake/Elements.swift | 25 ++++ Intake/General Data View/PatientInfo.swift | 159 +++++++++++++++++++++ Intake/Home.swift | 4 +- Intake/Resources/Localizable.xcstrings | 54 +++++-- Intake/ScrollablePDF.swift | 25 ++-- Intake/SocialHistory/SmokingHistory.swift | 2 +- Intake/Surgery/SurgeryView.swift | 2 +- 11 files changed, 298 insertions(+), 146 deletions(-) create mode 100644 Intake/General Data View/PatientInfo.swift diff --git a/Intake.xcodeproj/project.pbxproj b/Intake.xcodeproj/project.pbxproj index 8c53aa0..a4453e4 100644 --- a/Intake.xcodeproj/project.pbxproj +++ b/Intake.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ 2FE5DCB129EE6107004B9AB4 /* AccountOnboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DCAC29EE6107004B9AB4 /* AccountOnboarding.swift */; }; 2FF53D8B2A8725DE00042B76 /* SpeziMockWebService in Frameworks */ = {isa = PBXBuildFile; productRef = 2FF53D8A2A8725DE00042B76 /* SpeziMockWebService */; }; 2FF53D8D2A8729D600042B76 /* IntakeStandard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FF53D8C2A8729D600042B76 /* IntakeStandard.swift */; }; + 3C89F66D2B9D948B00A4F52D /* PatientInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C89F66C2B9D948B00A4F52D /* PatientInfo.swift */; }; 511827962B740192002033A0 /* SurgeryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511827952B740191002033A0 /* SurgeryView.swift */; }; 51805C122B81853800D17109 /* IntakeMedication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51805C112B81853700D17109 /* IntakeMedication.swift */; }; 51805C152B81857100D17109 /* IntakeMedicationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51805C142B81857100D17109 /* IntakeMedicationViewModel.swift */; }; @@ -176,6 +177,7 @@ 2FE5DC5529EDD811004B9AB4 /* SocialSupportQuestionnaire.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SocialSupportQuestionnaire.json; sourceTree = ""; }; 2FE5DCAC29EE6107004B9AB4 /* AccountOnboarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountOnboarding.swift; sourceTree = ""; }; 2FF53D8C2A8729D600042B76 /* IntakeStandard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntakeStandard.swift; sourceTree = ""; }; + 3C89F66C2B9D948B00A4F52D /* PatientInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatientInfo.swift; sourceTree = ""; }; 511827952B740191002033A0 /* SurgeryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurgeryView.swift; sourceTree = ""; }; 51805C112B81853700D17109 /* IntakeMedication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntakeMedication.swift; sourceTree = ""; }; 51805C142B81857100D17109 /* IntakeMedicationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntakeMedicationViewModel.swift; sourceTree = ""; }; @@ -382,6 +384,14 @@ path = Helper; sourceTree = ""; }; + 3C89F6682B9D939500A4F52D /* General Data View */ = { + isa = PBXGroup; + children = ( + 3C89F66C2B9D948B00A4F52D /* PatientInfo.swift */, + ); + path = "General Data View"; + sourceTree = ""; + }; 511827942B740191002033A0 /* Surgery */ = { isa = PBXGroup; children = ( @@ -501,6 +511,7 @@ 653A254F283387FE005D4D48 /* Intake */ = { isa = PBXGroup; children = ( + 3C89F6682B9D939500A4F52D /* General Data View */, ACF862BC2B96E28400ACBA1E /* Export */, F4F4F8802B8C6FC5008FBEED /* Elements.swift */, 519E830A2B7C4F1600A2D92D /* Medication View */, @@ -837,6 +848,7 @@ F42AB1DF2B637C9D002E13A6 /* LLMInteraction.swift in Sources */, 51A360162B965819004E7E12 /* AllergyLLMAssistant.swift in Sources */, F42AB1F22B71B4D2002E13A6 /* AllergyViewTest.swift in Sources */, + 3C89F66D2B9D948B00A4F52D /* PatientInfo.swift in Sources */, 2FE5DC3829EDD7CA004B9AB4 /* InterestingModules.swift in Sources */, 2FE5DC3529EDD7CA004B9AB4 /* Consent.swift in Sources */, 51A360182B9659AE004E7E12 /* MedicalHistoryLLMAssistant.swift in Sources */, diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index 7a71da7..9d32b42 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -19,79 +19,35 @@ import SpeziLLMLocal import SpeziLLMOpenAI import SwiftUI -func calculateAge(from dobString: String, with format: String = "yyyy-MM-dd") -> String { - if dobString.isEmpty { - return "" - } - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = format - - guard let birthDate = dateFormatter.date(from: dobString) else { - return "Invalid date format or date string." - } - - let ageComponents = Calendar.current.dateComponents([.year], from: birthDate, to: Date()) - if let age = ageComponents.year { - return "\(age)" - } else { - return "Could not calculate age" - } -} -func getValue(forKey key: String, from jsonString: String) -> String? { - guard let jsonData = jsonString.data(using: .utf8) else { - print("Error: Cannot create Data from JSON string") - return nil - } +struct LLMInteraction: View { + // swiftlint:disable type_contents_order + @State private var fullName: String = "" + @State private var firstName: String = "" + @State private var dob: String = "" + @State private var gender: String = "" + @Environment(LLMRunner.self) var runner: LLMRunner + @Environment(FHIRStore.self) private var fhirStore + @Environment(DataStore.self) private var data + @Environment(NavigationPathWrapper.self) private var navigationPath - do { - if let dictionary = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] { - if key == "name" { - if let nameArray = dictionary[key] as? [[String: Any]], !nameArray.isEmpty { - let nameDict = nameArray[0] // Accessing the first name object - if let family = nameDict["family"] as? String, - let givenArray = nameDict["given"] as? [String], - !givenArray.isEmpty { - let given = givenArray.joined(separator: " ") // Assuming there might be more than one given name - - return "\(given) \(family)" - } - } - } else { - return dictionary[key] as? String - } - } else { - print("Error: JSON is not a dictionary") - } - } catch { - print("Error: \(error.localizedDescription)") - } - - return nil -} + @Binding var presentingAccount: Bool + @LLMSessionProvider var session: LLMOpenAISession -func getInfo(patient: FHIRResource, field: String) -> String { - let jsonDescription = patient.jsonDescription + @State var showOnboarding = true + @State var greeting = true - if let infoValue = getValue(forKey: field, from: jsonDescription) { - print("Info found: \(infoValue)") - return infoValue - } + @State var stringBox: StringBox = .init() + @State var showSheet = false - print("Key \(field) not found") - return "" -} - - -struct LLMInteraction: View { @Observable class StringBox: Equatable { var llmResponseSummary: String - + init() { self.llmResponseSummary = "" } - + static func == (lhs: LLMInteraction.StringBox, rhs: LLMInteraction.StringBox) -> Bool { lhs.llmResponseSummary == rhs.llmResponseSummary } @@ -123,20 +79,6 @@ struct LLMInteraction: View { return nil } } - - @Environment(LLMRunner.self) var runner: LLMRunner - @Environment(FHIRStore.self) private var fhirStore - @Environment(DataStore.self) private var data - @Environment(NavigationPathWrapper.self) private var navigationPath - - @Binding var presentingAccount: Bool - @LLMSessionProvider var session: LLMOpenAISession - - @State var showOnboarding = true - @State var greeting = true - - @State var stringBox: StringBox = .init() - @State var showSheet = false var body: some View { @Bindable var data = data @@ -154,37 +96,20 @@ struct LLMInteraction: View { } .onAppear { - var fullName: String = "" - var firstName: String = "" - var dob: String = "" - var gender: String = "" - if let patient = fhirStore.patient { - fullName = getInfo(patient: patient, field: "name").filter { !$0.isNumber } - dob = getInfo(patient: patient, field: "birthDate") - gender = getInfo(patient: patient, field: "gender") - - let age = calculateAge(from: dob) - let nameString = fullName.components(separatedBy: " ") - - data.generalData.name = fullName - data.generalData.birthdate = dob - data.generalData.sex = gender - data.generalData.age = age - - firstName = nameString.first ?? "First Name is empty" - print(firstName == "First Name is empty" ? "First Name is empty" : "") - - + let nameString = data.generalData.name.components(separatedBy: " ") + if let firstNameValue = nameString.first { + firstName = firstNameValue + } let systemMessage = """ - The first name of the patient is \(String(describing: firstName)) and the patient is \(String(describing: age))\ - years old. The patient's gender is \(String(describing: gender)) Please speak with\ + The first name of the patient is \(String(describing: firstName)) and the patient is \(String(describing: data.generalData.age)) \ + years old. The patient's sex is \(String(describing: data.generalData.sex)) Please speak with\ the patient as you would a person of this age group, using as simple words as possible\ if the patient is young. Address them by their first name when you ask questions. """ session.context.append( systemMessage: systemMessage ) - } + if greeting { if firstName.isEmpty { diff --git a/Intake/ChiefComplaint/SummaryView.swift b/Intake/ChiefComplaint/SummaryView.swift index 6b5ef6e..c037d62 100644 --- a/Intake/ChiefComplaint/SummaryView.swift +++ b/Intake/ChiefComplaint/SummaryView.swift @@ -34,7 +34,7 @@ struct SummaryView: View { var body: some View { VStack(alignment: .leading, spacing: 20) { ComplaintForm(chiefComplaint: $chiefComplaint) - SubmitButton(nextView: NavigationViews.medical) + SubmitButton(nextView: NavigationViews.pdfs) .padding() } } diff --git a/Intake/EditPatient.swift b/Intake/EditPatient.swift index d5e5f6f..735d6c1 100644 --- a/Intake/EditPatient.swift +++ b/Intake/EditPatient.swift @@ -12,33 +12,35 @@ // import Foundation +import SpeziFHIR import SwiftUI - struct EditPatientView: View { @Environment(DataStore.self) private var data + @Environment(FHIRStore.self) private var fhirStore var body: some View { @Bindable var data = data - VStack { - Form { - Section(header: Text("Name")) { - TextField("Name", text: $data.generalData.name) - } - Section(header: Text("Date of Birth")) { - TextField("Date of Birth", text: $data.generalData.birthdate) - } - Section(header: Text("Age")) { - TextField("Age", text: $data.generalData.age) - } - Section(header: Text("Sex")) { - TextField("Sex", text: $data.generalData.sex) + + VStack { + Form { + Section(header: Text("Name")) { + TextField("Name", text: $data.generalData.name) + } + Section(header: Text("Date of Birth")) { + TextField("Date of Birth", text: $data.generalData.birthdate) + } + Section(header: Text("Age")) { + TextField("Age", text: $data.generalData.age) + } + Section(header: Text("Sex")) { + TextField("Sex", text: $data.generalData.sex) + } } + SubmitButton(nextView: NavigationViews.pdfs) } - SubmitButton(nextView: NavigationViews.pdfs) } } -} // // #Preview { diff --git a/Intake/Elements.swift b/Intake/Elements.swift index fc8e220..50558b1 100644 --- a/Intake/Elements.swift +++ b/Intake/Elements.swift @@ -51,3 +51,28 @@ struct SubmitButton: View { } } } + +struct SubmitButtonWithAction: View { + @Environment(NavigationPathWrapper.self) private var navigationPath + @Environment(ReachedEndWrapper.self) private var end + var nextView: NavigationViews + var onButtonTap: () -> Void + + var body: some View { + Button(action: { + onButtonTap() + if end.reachedEnd { + navigationPath.path.append(NavigationViews.pdfs) + } else { + navigationPath.path.append(nextView) + } + }) { + Text("Next") + .foregroundColor(.white) + .padding() + .frame(maxWidth: .infinity) + .background(Color.blue) + .cornerRadius(8) + } + } +} diff --git a/Intake/General Data View/PatientInfo.swift b/Intake/General Data View/PatientInfo.swift new file mode 100644 index 0000000..ad6d929 --- /dev/null +++ b/Intake/General Data View/PatientInfo.swift @@ -0,0 +1,159 @@ +//// +//// SwiftUIView.swift +//// Intake +//// +//// Created by Nina Boord on 3/9/24. +//// +// This source file is part of the Intake based on the Stanford Spezi Template Medication project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// +import SpeziFHIR +import SwiftUI +// swiftlint:disable type_contents_order +struct PatientInfo: View { + @Environment(DataStore.self) private var data + @Environment(NavigationPathWrapper.self) private var navigationPath + @Environment(FHIRStore.self) private var fhirStore + + + @State private var fullName: String = "" + @State private var firstName: String = "" + @State private var birthdate: String = "" + @State private var gender: String = "" + @State private var sexOption: String = "" + @State private var birthdateDateFormat = Date() + + func calculateAge(from dobString: String, with format: String = "yyyy-MM-dd") -> String { + if dobString.isEmpty { + return "" + } + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = format + + guard let birthDate = dateFormatter.date(from: dobString) else { + return "Invalid date format or date string." + } + + let ageComponents = Calendar.current.dateComponents([.year], from: birthDate, to: Date()) + if let age = ageComponents.year { + return "\(age)" + } else { + return "Could not calculate age" + } + } + + func getValue(forKey key: String, from jsonString: String) -> String? { + guard let jsonData = jsonString.data(using: .utf8) else { + print("Error: Cannot create Data from JSON string") + return nil + } + + do { + if let dictionary = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] { + if key == "name" { + if let nameArray = dictionary[key] as? [[String: Any]], !nameArray.isEmpty { + let nameDict = nameArray[0] // Accessing the first name object + if let family = nameDict["family"] as? String, + let givenArray = nameDict["given"] as? [String], + !givenArray.isEmpty { + let given = givenArray.joined(separator: " ") // Assuming there might be more than one given name + + return "\(given) \(family)" + } + } + } else { + return dictionary[key] as? String + } + } else { + print("Error: JSON is not a dictionary") + } + } catch { + print("Error: \(error.localizedDescription)") + } + + return nil + } + + func getInfo(patient: FHIRResource, field: String) -> String { + let jsonDescription = patient.jsonDescription + + if let infoValue = getValue(forKey: field, from: jsonDescription) { + print("Info found: \(infoValue)") + return infoValue + } + + print("Key \(field) not found") + return "" + } + + var body: some View { + @Bindable var data = data + Form { + Section(header: Text("Patient Information")) { + HStack { + TextField("Full name", text: $data.generalData.name) + Spacer() + } + HStack { + DatePicker("Date of Birth:", selection: $birthdateDateFormat, in: ...Date(), displayedComponents: .date) + .datePickerStyle(DefaultDatePickerStyle()) + } + HStack { + let options = ["Female", "Male"] + Picker("Sex", selection: $sexOption) { + ForEach(options, id: \.self) { option in + Text(option).tag(option) + } + } + .pickerStyle(MenuPickerStyle()) + } + } + + SubmitButtonWithAction(nextView: .medical, onButtonTap: { + updateData() + }) + } + .onAppear { + loadData() + } + } + + private func loadData() { + if let patient = fhirStore.patient { + fullName = getInfo(patient: patient, field: "name").filter { !$0.isNumber } + birthdate = getInfo(patient: patient, field: "birthDate") + gender = getInfo(patient: patient, field: "gender") + let age = calculateAge(from: birthdate) + + // string to date + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + if let dob = dateFormatter.date(from: birthdate) { + birthdateDateFormat = dob + } + sexOption = gender + data.generalData = PatientData(name: fullName, birthdate: birthdate, age: age, sex: gender) + } + } + + private func updateData() { + // date to string + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + birthdate = dateFormatter.string(from: birthdateDateFormat) + data.generalData.birthdate = birthdate + let age = calculateAge(from: birthdate) + data.generalData.sex = sexOption + data.generalData.age = age + } +} + + +struct PatientInfo_Previews: PreviewProvider { + static var previews: some View { + PatientInfo() + } +} diff --git a/Intake/Home.swift b/Intake/Home.swift index b568ec3..20ace34 100644 --- a/Intake/Home.swift +++ b/Intake/Home.swift @@ -23,6 +23,7 @@ enum NavigationViews: String { case patient case pdfs case inspect + case general } struct HomeView: View { @@ -84,7 +85,7 @@ struct HomeView: View { Spacer() Button(action: { - navigationPath.path.append(NavigationViews.chat) + navigationPath.path.append(NavigationViews.general) }) { Text("Start") .font(.headline) @@ -110,6 +111,7 @@ struct HomeView: View { case .patient: EditPatientView() case .pdfs: ScrollablePDF() case .inspect: InspectSurgeryView(surgery: $data.surgeries[data.surgeries.count - 1], isNew: true) + case .general: PatientInfo() } } diff --git a/Intake/Resources/Localizable.xcstrings b/Intake/Resources/Localizable.xcstrings index e37db7d..b82f420 100644 --- a/Intake/Resources/Localizable.xcstrings +++ b/Intake/Resources/Localizable.xcstrings @@ -121,13 +121,14 @@ "Auto-fill Intake Form" : { }, - - "Calculation" : { }, "Chat" : { + }, + "Chief Complaint:" : { + }, "CHIEF_COMPLAINT_SYSTEM_PROMPT" : { "extractionState" : "manual", @@ -225,6 +226,9 @@ } } } + }, + "Date" : { + }, "Date of Birth" : { @@ -261,12 +265,18 @@ }, "Ex: Smoked for 10 years, quit 5 years ago..." : { + }, + "Female" : { + }, "FHIR_RESOURCES_CHAT_CANCEL" : { }, "fix medication" : { + }, + "Full name" : { + }, "Get Started" : { @@ -326,6 +336,9 @@ }, "Inactive" : { + }, + "Intake Form" : { + }, "Integrate your Records" : { @@ -349,7 +362,9 @@ "John Doe" : { }, - + "Last Menstrural Period" : { + + }, "Last period's end date" : { }, @@ -381,10 +396,11 @@ "Medical History" : { }, + "MEDICAL HISTORY" : { + }, "Medical History Assistant" : { - }, "Medical Intake Form" : { @@ -395,12 +411,13 @@ "Medications" : { }, - "Medications Assistant" : { }, - "Menstrual Information" : { + "Medications:" : { + }, + "Menstrual Information" : { }, "Message" : { @@ -553,22 +570,25 @@ "Pack years: %.2f" : { }, - "Patient Form" : { + "Past Medical History:" : { }, - "Performed" : { + "Past Surgical History:" : { }, - "Please add your past surgeries" : { + "Patient did not enter chief complaint." : { }, - "Past Medical History:" : { + "Patient Form" : { }, - "Past Surgical History:" : { + "Patient Information" : { }, - "Patient did not enter chief complaint." : { + "Performed" : { + + }, + "Please add your past surgeries" : { }, "Please list conditions you have had" : { @@ -672,20 +692,26 @@ "SETTINGS_TITLE" : { }, - "Sex:" : { + "Sex" : { }, - "Sex" : { + "Sex:" : { }, "Share" : { + }, + "Share Intake form" : { + }, "Share with provider of your choice." : { }, "Skip" : { + }, + "Smoking history" : { + }, "Smoking History" : { diff --git a/Intake/ScrollablePDF.swift b/Intake/ScrollablePDF.swift index 3147f51..67adc3b 100644 --- a/Intake/ScrollablePDF.swift +++ b/Intake/ScrollablePDF.swift @@ -20,7 +20,7 @@ struct HeaderTitle: View { @Environment(NavigationPathWrapper.self) private var navigationPath let title: String var nextView: NavigationViews - + var body: some View { HStack { Text(title) @@ -41,15 +41,15 @@ struct ScrollablePDF: View { private struct ConditionSection: View { @Environment(DataStore.self) private var data @Environment(NavigationPathWrapper.self) private var navigationPath - + var body: some View { Section(header: HeaderTitle(title: "Conditions", nextView: NavigationViews.medical)) { List(data.conditionData, id: \.id) { item in HStack { - Text(item.condition) - Spacer() - Text(item.active ? "Active" : "Inactive") - .foregroundColor(.secondary) + Text(item.condition) + Spacer() + Text(item.active ? "Active" : "Inactive") + .foregroundColor(.secondary) } } } @@ -58,7 +58,7 @@ struct ScrollablePDF: View { private struct ExportButton: View { @Environment(NavigationPathWrapper.self) private var navigationPath - + var body: some View { Button(action: { }) { @@ -75,13 +75,12 @@ struct ScrollablePDF: View { private struct SurgerySection: View { @Environment(DataStore.self) private var data @Environment(NavigationPathWrapper.self) private var navigationPath - + var body: some View { Section(header: HeaderTitle(title: "Surgical History", nextView: NavigationViews.surgical)) { List(data.surgeries, id: \.id) { item in HStack { Text(item.surgeryName) - Spacer() .foregroundColor(.secondary) } } @@ -92,7 +91,7 @@ struct ScrollablePDF: View { private struct MedicationSection: View { @Environment(DataStore.self) private var data @Environment(NavigationPathWrapper.self) private var navigationPath - + var body: some View { Section(header: HeaderTitle(title: "Medications", nextView: NavigationViews.medication)) { VStack(alignment: .leading) { @@ -105,7 +104,7 @@ struct ScrollablePDF: View { private struct ChiefComplaint: View { @Environment(DataStore.self) private var data @Environment(NavigationPathWrapper.self) private var navigationPath - + var body: some View { Section(header: HeaderTitle(title: "Chief Complaint", nextView: NavigationViews.concern)) { Text(data.chiefComplaint) @@ -116,6 +115,8 @@ struct ScrollablePDF: View { private struct PatientInfo: View { @Environment(DataStore.self) private var data @Environment(NavigationPathWrapper.self) private var navigationPath + @Environment(FHIRStore.self) private var fhirStore + var body: some View { Section(header: HeaderTitle(title: "Patient Information", nextView: NavigationViews.patient)) { List { @@ -166,7 +167,7 @@ struct ScrollablePDF: View { private func reactionPDFView() -> some View { ReactionPDF(index: selectedIndex, showingReaction: $showingReaction) } - + private func allergyButton(index: Int) -> some View { Button(action: { self.selectedIndex = index diff --git a/Intake/SocialHistory/SmokingHistory.swift b/Intake/SocialHistory/SmokingHistory.swift index 8f5c310..e59b6ed 100644 --- a/Intake/SocialHistory/SmokingHistory.swift +++ b/Intake/SocialHistory/SmokingHistory.swift @@ -54,7 +54,7 @@ struct SmokingHistoryView: View { data.smokingHistory = SmokingHistoryItem(packYears: packYears, additionalDetails: additionalDetails) } - SubmitButton(nextView: NavigationViews.pdfs) + SubmitButton(nextView: NavigationViews.chat) .padding() } .navigationTitle("Social History") diff --git a/Intake/Surgery/SurgeryView.swift b/Intake/Surgery/SurgeryView.swift index 6ab462d..f8a9ed8 100644 --- a/Intake/Surgery/SurgeryView.swift +++ b/Intake/Surgery/SurgeryView.swift @@ -292,7 +292,7 @@ struct SurgeryView: View { let LLMResponse = try await self.queryLLM(surgeryNames: surgeryNames) let filteredNames = LLMResponse.components(separatedBy: ", ") - let filteredSurgeries = surgeries.filter { self.containsAnyWords(item: $0.surgeryName, words: filteredNames) } + var filteredSurgeries = surgeries.filter { self.containsAnyWords(item: $0.surgeryName, words: filteredNames) } return self.cleanSurgeryNames(surgeries: filteredSurgeries, filteredNames: filteredNames) } From 081ea982e54ddb8af3d8ad6c0908e56019fa4389 Mon Sep 17 00:00:00 2001 From: Kate Callon <70660419+kcallon@users.noreply.github.com> Date: Sun, 10 Mar 2024 21:10:45 -0700 Subject: [PATCH 2/2] Minor Updates to Medications including Scrollable PDF (#66) # *Minor Updates to Medications including Scrollable PDF* ## :recycle: Current situation & Problem The medications final view was missing the frequency of the medications. Additionally, some patients didn't have any medications assigned to them, so I added some options. ## :gear: Release Notes *Add a bullet point list summary of the feature and possible migration guides if this is a breaking change so this section can be added to the release notes.* *Include code snippets that provide examples of the feature implemented or links to the documentation if it appends or changes the public interface.* ## :books: 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.* ## :white_check_mark: 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.* ## :pencil: 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). --- .../xcshareddata/swiftpm/Package.resolved | 3 ++- .../IntakeMedicationViewModel.swift | 26 ++++++++++++++++++- .../MedicationContentView.swift | 2 +- Intake/Resources/Localizable.xcstrings | 13 +++++++--- Intake/ScrollablePDF.swift | 12 +++++++-- 5 files changed, 48 insertions(+), 8 deletions(-) diff --git a/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4b69e21..9f82483 100644 --- a/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "cdbe60a3382a8a962c7fcc00210e56e13314cb3e246d0e01fe8e25a1268623d8", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -379,5 +380,5 @@ } } ], - "version" : 2 + "version" : 3 } diff --git a/Intake/Medication View/IntakeMedicationViewModel.swift b/Intake/Medication View/IntakeMedicationViewModel.swift index acd28ca..45342ac 100644 --- a/Intake/Medication View/IntakeMedicationViewModel.swift +++ b/Intake/Medication View/IntakeMedicationViewModel.swift @@ -69,7 +69,31 @@ class IntakeMedicationSettingsViewModel: Module, MedicationSettingsViewModel, Cu IntakeMedication( localizedDescription: "NDA020800 0.3 ML Epinephrine 1 MG/ML Auto-Injector", dosages: [ - IntakeDosage(localizedDescription: "0.3ML / 1 MG/ML") + IntakeDosage(localizedDescription: "0.3 ML/1 MG/ML") + ] + ), + IntakeMedication( + localizedDescription: "Clopidogrel 75 MG Oral Tablet", + dosages: [ + IntakeDosage(localizedDescription: "75 MG") + ] + ), + IntakeMedication( + localizedDescription: "Verapamil Hydrochloride 40 MG", + dosages: [ + IntakeDosage(localizedDescription: "40 MG") + ] + ), + IntakeMedication( + localizedDescription: "Simvastatin 20 MG Oral Tablet", + dosages: [ + IntakeDosage(localizedDescription: "20 MG") + ] + ), + IntakeMedication( + localizedDescription: "amLODIPine 2.5 MG Oral Tablet", + dosages: [ + IntakeDosage(localizedDescription: "2.5 MG") ] ) ] diff --git a/Intake/Medication View/MedicationContentView.swift b/Intake/Medication View/MedicationContentView.swift index 91d2b41..9873525 100644 --- a/Intake/Medication View/MedicationContentView.swift +++ b/Intake/Medication View/MedicationContentView.swift @@ -32,7 +32,7 @@ struct MedicationContentView: View { data.medicationData = medicationSettingsViewModel.medicationInstances navigationPath.path.append(NavigationViews.allergies) } - .navigationTitle("Medication Settings") + .navigationTitle("Medications") .navigationBarItems(trailing: NavigationLink(destination: MedicationLLMAssistant(presentingAccount: .constant(false))) { Text("Chat") }) diff --git a/Intake/Resources/Localizable.xcstrings b/Intake/Resources/Localizable.xcstrings index b82f420..620f482 100644 --- a/Intake/Resources/Localizable.xcstrings +++ b/Intake/Resources/Localizable.xcstrings @@ -12,6 +12,16 @@ }, "%.2f" : { + }, + "%@ - %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ - %2$@" + } + } + } }, "%@ Reactions" : { @@ -404,9 +414,6 @@ }, "Medical Intake Form" : { - }, - "Medication Settings" : { - }, "Medications" : { diff --git a/Intake/ScrollablePDF.swift b/Intake/ScrollablePDF.swift index 67adc3b..77f2394 100644 --- a/Intake/ScrollablePDF.swift +++ b/Intake/ScrollablePDF.swift @@ -93,9 +93,17 @@ struct ScrollablePDF: View { @Environment(NavigationPathWrapper.self) private var navigationPath var body: some View { + let medicationData = data.medicationData Section(header: HeaderTitle(title: "Medications", nextView: NavigationViews.medication)) { - VStack(alignment: .leading) { - Text("fix medication") + ForEach(Array(medicationData), id: \.self) { medicationInstance in + List { + VStack(alignment: .leading, spacing: 0) { + Text(medicationInstance.type.localizedDescription) + .font(.headline) + Text("\(medicationInstance.dosage.localizedDescription) - \(medicationInstance.schedule.frequency.description)") + .font(.subheadline) + } + } } } }