From af23eec6828951196b44207257ab318fd6f749ce Mon Sep 17 00:00:00 2001 From: Kate Callon <70660419+kcallon@users.noreply.github.com> Date: Thu, 29 Feb 2024 12:51:06 -0800 Subject: [PATCH] Updated Medications View + Adding to Environment Variable (#51) # Working Medications View with Sample Patient Data and Updating Global Environment Variable ## :recycle: Current situation & Problem #21 Previously, the medications view would only pull the display name from the medications fhir resource. This was problematic in several ways--the UI was not developed, patient medications could be added that weren't accurate, and there was no additional information beyond the display name. ## :gear: Release Notes - Updated the patient medication data to be filtered by only including outpatient and activate medications (like from llmonfhir) - Modified existing code from SpeziMedications to create structs specific to Intake such as IntakeMedications and IntakeDosage - Updated medicationOptions to represent some of the top mock patient medications - Updated a MedicationSettingsViewModel to initialize with existing mock patient medications including dosage, intake method, and dose frequency - Added compatibility to global variable ## :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). --- Intake.xcodeproj/project.pbxproj | 49 ++++++- .../xcshareddata/swiftpm/Package.resolved | 11 +- Intake/Home.swift | 2 +- Intake/Intake.swift | 2 +- Intake/Medication View/IntakeDosage.swift | 20 +++ Intake/Medication View/IntakeMedication.swift | 21 +++ .../IntakeMedicationInstance.swift | 30 ++++ .../IntakeMedicationViewModel.swift | 133 ++++++++++++++++++ .../MedicationContentView.swift | 51 +++++++ Intake/MedicationView.swift | 102 -------------- Intake/Mock Data/FHIRStore+Extensions.swift | 101 ++++--------- Intake/Resources/Localizable.xcstrings | 11 +- 12 files changed, 342 insertions(+), 191 deletions(-) create mode 100644 Intake/Medication View/IntakeDosage.swift create mode 100644 Intake/Medication View/IntakeMedication.swift create mode 100644 Intake/Medication View/IntakeMedicationInstance.swift create mode 100644 Intake/Medication View/IntakeMedicationViewModel.swift create mode 100644 Intake/Medication View/MedicationContentView.swift delete mode 100644 Intake/MedicationView.swift diff --git a/Intake.xcodeproj/project.pbxproj b/Intake.xcodeproj/project.pbxproj index 6528a33..dd38b8e 100644 --- a/Intake.xcodeproj/project.pbxproj +++ b/Intake.xcodeproj/project.pbxproj @@ -58,7 +58,12 @@ 2FF53D8B2A8725DE00042B76 /* SpeziMockWebService in Frameworks */ = {isa = PBXBuildFile; productRef = 2FF53D8A2A8725DE00042B76 /* SpeziMockWebService */; }; 2FF53D8D2A8729D600042B76 /* IntakeStandard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FF53D8C2A8729D600042B76 /* IntakeStandard.swift */; }; 511827962B740192002033A0 /* SurgeryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511827952B740191002033A0 /* SurgeryView.swift */; }; - 511827982B7401A8002033A0 /* MedicationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511827972B7401A8002033A0 /* MedicationView.swift */; }; + 51805C122B81853800D17109 /* IntakeMedication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51805C112B81853700D17109 /* IntakeMedication.swift */; }; + 51805C152B81857100D17109 /* IntakeMedicationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51805C142B81857100D17109 /* IntakeMedicationViewModel.swift */; }; + 51805C182B81898700D17109 /* SpeziMedication in Frameworks */ = {isa = PBXBuildFile; productRef = 51805C172B81898700D17109 /* SpeziMedication */; }; + 51805C1A2B818A1A00D17109 /* IntakeDosage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51805C192B818A1A00D17109 /* IntakeDosage.swift */; }; + 51805C1D2B818A4400D17109 /* IntakeMedicationInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51805C1C2B818A4400D17109 /* IntakeMedicationInstance.swift */; }; + 51A027672B82CDA300A195C8 /* MedicationContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A027662B82CDA300A195C8 /* MedicationContentView.swift */; }; 5661551D2AB8384200209B80 /* SwiftPackageList in Frameworks */ = {isa = PBXBuildFile; productRef = 5661551C2AB8384200209B80 /* SwiftPackageList */; }; 566155292AB8447C00209B80 /* Package+LicenseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566155282AB8447C00209B80 /* Package+LicenseType.swift */; }; 5661552E2AB854C000209B80 /* PackageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5661552D2AB854C000209B80 /* PackageHelper.swift */; }; @@ -164,7 +169,11 @@ 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 = ""; }; 511827952B740191002033A0 /* SurgeryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurgeryView.swift; sourceTree = ""; }; - 511827972B7401A8002033A0 /* MedicationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MedicationView.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 = ""; }; + 51805C192B818A1A00D17109 /* IntakeDosage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntakeDosage.swift; sourceTree = ""; }; + 51805C1C2B818A4400D17109 /* IntakeMedicationInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntakeMedicationInstance.swift; sourceTree = ""; }; + 51A027662B82CDA300A195C8 /* MedicationContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MedicationContentView.swift; sourceTree = ""; }; 566155282AB8447C00209B80 /* Package+LicenseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Package+LicenseType.swift"; sourceTree = ""; }; 5661552D2AB854C000209B80 /* PackageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageHelper.swift; sourceTree = ""; }; 5680DD382AB8983D004E6D4A /* PackageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageCell.swift; sourceTree = ""; }; @@ -222,6 +231,7 @@ F42AB1D42B6379B5002E13A6 /* SpeziLLMLocal in Frameworks */, 2FB099AF2A875DF100B20952 /* FirebaseAuth in Frameworks */, 97D73D6A2AD860AD00B47FA0 /* SpeziFirebaseStorage in Frameworks */, + 51805C182B81898700D17109 /* SpeziMedication in Frameworks */, F42AB1D82B6379B5002E13A6 /* SpeziLLMOpenAI in Frameworks */, 2FE5DC6729EDD894004B9AB4 /* SpeziContact in Frameworks */, 5A0C1A1B2B69691000120506 /* SpeziFHIRHealthKit in Frameworks */, @@ -363,6 +373,18 @@ path = Surgery; sourceTree = ""; }; + 519E830A2B7C4F1600A2D92D /* Medication View */ = { + isa = PBXGroup; + children = ( + 51805C112B81853700D17109 /* IntakeMedication.swift */, + 51805C192B818A1A00D17109 /* IntakeDosage.swift */, + 51805C142B81857100D17109 /* IntakeMedicationViewModel.swift */, + 51805C1C2B818A4400D17109 /* IntakeMedicationInstance.swift */, + 51A027662B82CDA300A195C8 /* MedicationContentView.swift */, + ); + path = "Medication View"; + sourceTree = ""; + }; 56F6F29E2AB441640022FE5A /* Contributions */ = { isa = PBXGroup; children = ( @@ -457,7 +479,7 @@ isa = PBXGroup; children = ( F4F4F8802B8C6FC5008FBEED /* Elements.swift */, - 511827972B7401A8002033A0 /* MedicationView.swift */, + 519E830A2B7C4F1600A2D92D /* Medication View */, 511827942B740191002033A0 /* Surgery */, AC2A17272B70684D00F560D0 /* SocialHistory */, 5A2B9FB32B6AFE1F005CA63F /* Allergy Records */, @@ -594,6 +616,7 @@ 5A0C1A182B69691000120506 /* SpeziFHIR */, 5A0C1A1A2B69691000120506 /* SpeziFHIRHealthKit */, 5A2B9F892B69E0AF005CA63F /* SpeziFHIRMockPatients */, + 51805C172B81898700D17109 /* SpeziMedication */, ); productName = Intake; productReference = 653A254D283387FE005D4D48 /* Intake.app */; @@ -690,6 +713,7 @@ 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */, F42AB1D02B6379B5002E13A6 /* XCRemoteSwiftPackageReference "SpeziLLM" */, 5A0C1A162B69667B00120506 /* XCRemoteSwiftPackageReference "SpeziFHIR" */, + 51805C162B81898700D17109 /* XCRemoteSwiftPackageReference "SpeziMedication" */, ); productRefGroup = 653A254E283387FE005D4D48 /* Products */; projectDirPath = ""; @@ -782,12 +806,12 @@ 2FE5DC4529EDD7F2004B9AB4 /* Binding+Negate.swift in Sources */, 2FC975A82978F11A00BA99FE /* Home.swift in Sources */, 2FE5DC4E29EDD7FA004B9AB4 /* ScheduleView.swift in Sources */, + 51805C1D2B818A4400D17109 /* IntakeMedicationInstance.swift in Sources */, AC2A17292B70686000F560D0 /* SocialHistoryQuestions.swift in Sources */, A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */, 2FE5DC3729EDD7CA004B9AB4 /* OnboardingFlow.swift in Sources */, 2F1AC9DF2B4E840E00C24973 /* Intake.docc in Sources */, 2FF53D8D2A8729D600042B76 /* IntakeStandard.swift in Sources */, - 511827982B7401A8002033A0 /* MedicationView.swift in Sources */, 2FE5DC4729EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift in Sources */, 5A2B9FAB2B69E430005CA63F /* FHIRStore+Extensions.swift in Sources */, A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */, @@ -799,7 +823,9 @@ 2FE5DC5029EDD7FA004B9AB4 /* EventContextView.swift in Sources */, 5A2B9F872B69E06B005CA63F /* ResourceSelection.swift in Sources */, 2F4E23832989D51F0013F3D9 /* IntakeTestingSetup.swift in Sources */, + 51805C152B81857100D17109 /* IntakeMedicationViewModel.swift in Sources */, 5A2B9F862B69E06B005CA63F /* SettingsView.swift in Sources */, + 51805C1A2B818A1A00D17109 /* IntakeDosage.swift in Sources */, F4F4F8812B8C6FC5008FBEED /* Elements.swift in Sources */, 2FE5DC5329EDD7FA004B9AB4 /* Bundle+Questionnaire.swift in Sources */, 5A2B9FB62B6AFE5D005CA63F /* AllergyRecords.swift in Sources */, @@ -815,7 +841,9 @@ 511827962B740192002033A0 /* SurgeryView.swift in Sources */, 2FE5DC5229EDD7FA004B9AB4 /* IntakeScheduler.swift in Sources */, F42AB1E52B6383F9002E13A6 /* LLMOpenAITokenOnboarding.swift in Sources */, + 51805C122B81853800D17109 /* IntakeMedication.swift in Sources */, A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */, + 51A027672B82CDA300A195C8 /* MedicationContentView.swift in Sources */, 653A2551283387FE005D4D48 /* Intake.swift in Sources */, 2FE5DC3629EDD7CA004B9AB4 /* HealthKitPermissions.swift in Sources */, 5661552E2AB854C000209B80 /* PackageHelper.swift in Sources */, @@ -1466,6 +1494,14 @@ minimumVersion = 1.0.0; }; }; + 51805C162B81898700D17109 /* XCRemoteSwiftPackageReference "SpeziMedication" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordSpezi/SpeziMedication.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.4.0; + }; + }; 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/FelixHerrmann/swift-package-list"; @@ -1605,6 +1641,11 @@ package = 2FE750CA2A87240100723EAE /* XCRemoteSwiftPackageReference "SpeziMockWebService" */; productName = SpeziMockWebService; }; + 51805C172B81898700D17109 /* SpeziMedication */ = { + isa = XCSwiftPackageProductDependency; + package = 51805C162B81898700D17109 /* XCRemoteSwiftPackageReference "SpeziMedication" */; + productName = SpeziMedication; + }; 5661551C2AB8384200209B80 /* SwiftPackageList */ = { isa = XCSwiftPackageProductDependency; package = 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */; diff --git a/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4c73220..67a1a63 100644 --- a/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -246,12 +246,21 @@ { "identity" : "spezillm", "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordSpezi/SpeziLLM.git", + "location" : "https://github.com/StanfordSpezi/SpeziLLM", "state" : { "revision" : "6892c5dfe258371b6f3287f02b8fec57a611ba70", "version" : "0.7.0" } }, + { + "identity" : "spezimedication", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziMedication.git", + "state" : { + "revision" : "95ca9aebbd23f3842639d6e322785a0ff3620aac", + "version" : "0.4.0" + } + }, { "identity" : "spezimockwebservice", "kind" : "remoteSourceControl", diff --git a/Intake/Home.swift b/Intake/Home.swift index 569cbcd..0d2fe2e 100644 --- a/Intake/Home.swift +++ b/Intake/Home.swift @@ -93,7 +93,7 @@ struct HomeView: View { case .surgical: SurgeryView() case .medical: MedicalHistoryView() case .social: SocialHistoryQuestionView() - case .medication: MedicationView() + case .medication: MedicationContentView() case .concern: SummaryView(chiefComplaint: $data.chiefComplaint) } } diff --git a/Intake/Intake.swift b/Intake/Intake.swift index 3f619e9..80c910c 100644 --- a/Intake/Intake.swift +++ b/Intake/Intake.swift @@ -19,7 +19,7 @@ class NavigationPathWrapper { class DataStore { var allergyData: [AllergyItem] = [] var conditionData: [MedicalHistoryItem] = [] - var medicationData: [MedicationItem] = [] + var medicationData: Set = [] var surgeries: [SurgeryItem] = [] var chiefComplaint: String = "" } diff --git a/Intake/Medication View/IntakeDosage.swift b/Intake/Medication View/IntakeDosage.swift new file mode 100644 index 0000000..8641810 --- /dev/null +++ b/Intake/Medication View/IntakeDosage.swift @@ -0,0 +1,20 @@ +// +// IntakeDosage.swift +// Intake +// +// Created by Kate Callon on 2/17/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 Foundation +import SpeziMedication + +struct IntakeDosage: Dosage { + var localizedDescription: String +} diff --git a/Intake/Medication View/IntakeMedication.swift b/Intake/Medication View/IntakeMedication.swift new file mode 100644 index 0000000..b8ba5a1 --- /dev/null +++ b/Intake/Medication View/IntakeMedication.swift @@ -0,0 +1,21 @@ +// +// IntakeMedication.swift +// Intake +// +// Created by Kate Callon on 2/17/24. +// +// +// This source file is part of the Intake based on the Stanford Spezi Medication Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import SpeziMedication + +struct IntakeMedication: Medication, Comparable { + var localizedDescription: String + var dosages: [IntakeDosage] +} diff --git a/Intake/Medication View/IntakeMedicationInstance.swift b/Intake/Medication View/IntakeMedicationInstance.swift new file mode 100644 index 0000000..51f0140 --- /dev/null +++ b/Intake/Medication View/IntakeMedicationInstance.swift @@ -0,0 +1,30 @@ +// +// IntakeMedicationInstance.swift +// Intake +// +// Created by Kate Callon on 2/17/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 Foundation +import SpeziMedication + +struct IntakeMedicationInstance: MedicationInstance, MedicationInstanceInitializable { + let id: UUID + let type: IntakeMedication + var dosage: IntakeDosage + var schedule: Schedule + + init(type: IntakeMedication, dosage: IntakeDosage, schedule: Schedule) { + self.id = UUID() + self.type = type + self.dosage = dosage + self.schedule = schedule + } +} diff --git a/Intake/Medication View/IntakeMedicationViewModel.swift b/Intake/Medication View/IntakeMedicationViewModel.swift new file mode 100644 index 0000000..acd28ca --- /dev/null +++ b/Intake/Medication View/IntakeMedicationViewModel.swift @@ -0,0 +1,133 @@ +// +// IntakeMedicationViewModel.swift +// Intake +// +// Created by Kate Callon on 2/17/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 Foundation +import class ModelsR4.MedicationRequest +import Spezi +import SpeziFHIR +import SpeziMedication +import SwiftUI + +@Observable +class IntakeMedicationSettingsViewModel: Module, MedicationSettingsViewModel, CustomStringConvertible { + var medicationInstances: Set = [] + let medicationOptions: Set + + var description: String { + guard !medicationInstances.isEmpty else { + return "No Medications" + } + + return medicationInstances + .map { medicationInstance in + let scheduleDescription: String + switch medicationInstance.schedule.frequency { + case let .regularDayIntervals(dayInterval): + scheduleDescription = "RegularDayIntervals: \(dayInterval)" + case let .specificDaysOfWeek(weekdays): + scheduleDescription = "SpecificDaysOfWeek: \(weekdays)" + case .asNeeded: + scheduleDescription = "AsNeeded" + } + + return "\(medicationInstance.type.localizedDescription) - \(medicationInstance.dosage.localizedDescription) - \(scheduleDescription)" + } + .joined(separator: ", ") + } + + init(existingMedications: [FHIRResource]) { // swiftlint:disable:this function_body_length + self.medicationOptions = [ + IntakeMedication( + localizedDescription: "Hydrochlorothiazide 25 MG Oral Tablet", + dosages: [ + IntakeDosage(localizedDescription: "25 MG") + ] + ), + IntakeMedication( + localizedDescription: "Acetaminophen 160 MG Chewable Tablet", + dosages: [ + IntakeDosage(localizedDescription: "160 MG") + ] + ), + IntakeMedication( + localizedDescription: "Fexofenadine hydrochloride 30 MG Oral Tablet", + dosages: [ + IntakeDosage(localizedDescription: "30 MG") + ] + ), + IntakeMedication( + localizedDescription: "NDA020800 0.3 ML Epinephrine 1 MG/ML Auto-Injector", + dosages: [ + IntakeDosage(localizedDescription: "0.3ML / 1 MG/ML") + ] + ) + ] + + var foundMedications: [IntakeMedicationInstance] = [] + if !existingMedications.isEmpty { + for medication in existingMedications { + for option in medicationOptions where option.localizedDescription == medication.displayName { + var medSchedule: SpeziMedication.Schedule + let medRequest = medicationRequest(resource: medication) + if case .boolean(let asNeeded) = medRequest?.dosageInstruction?.first?.asNeeded { + if let asNeededbool = asNeeded.value?.bool { + if asNeededbool { + medSchedule = SpeziMedication.Schedule(frequency: .asNeeded) + } else { + let intValue: Int + let interval = medRequest?.dosageInstruction?.first?.timing?.repeat?.period?.value?.decimal + if let interval = interval { + intValue = interval.int + } else { + continue + } + medSchedule = Schedule(frequency: .regularDayIntervals(intValue)) + } + + guard let firstDosage = option.dosages.first else { + continue + } + + let intakeMedicationInstance = IntakeMedicationInstance( + type: option, + dosage: firstDosage, + schedule: medSchedule + ) + foundMedications.append(intakeMedicationInstance) + } + } + } + } + self.medicationInstances = Set(foundMedications) + } + } + func persist(medicationInstances: Set) async throws { + self.medicationInstances = medicationInstances + } + + func medicationRequest(resource: FHIRResource) -> MedicationRequest? { + guard case let .r4(resource) = resource.versionedResource, + let medicationRequest = resource as? ModelsR4.MedicationRequest else { + return nil + } + return medicationRequest + } +} + +extension Decimal { + var int: Int { + let intVal = NSDecimalNumber(decimal: self).intValue // swiftlint:disable:this legacy_objc_type + return intVal + } +} diff --git a/Intake/Medication View/MedicationContentView.swift b/Intake/Medication View/MedicationContentView.swift new file mode 100644 index 0000000..052c0fc --- /dev/null +++ b/Intake/Medication View/MedicationContentView.swift @@ -0,0 +1,51 @@ +// +// MedicationContentView.swift +// Intake +// +// Created by Kate Callon on 2/18/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 Foundation +import SpeziFHIR +import SpeziMedication +import SwiftUI + +struct MedicationContentView: View { + @Environment(FHIRStore.self) private var fhirStore + @Environment(NavigationPathWrapper.self) private var navigationPath + @Environment(DataStore.self) private var data + @State private var presentSettings = false + + @State private var medicationSettingsViewModel: IntakeMedicationSettingsViewModel? + + var body: some View { + VStack { + if let medicationSettingsViewModel { + MedicationSettings(allowEmtpySave: true, medicationSettingsViewModel: medicationSettingsViewModel) { + data.medicationData = medicationSettingsViewModel.medicationInstances + navigationPath.path.append(NavigationViews.allergies) + } + .navigationTitle("Medication Settings") + } else { + ProgressView() + } + } + .task { + let patientMedications = fhirStore.llmMedications + self.medicationSettingsViewModel = IntakeMedicationSettingsViewModel(existingMedications: patientMedications) + } + } + + init() {} +} + +#Preview { + MedicationContentView() +} diff --git a/Intake/MedicationView.swift b/Intake/MedicationView.swift deleted file mode 100644 index 2efae8d..0000000 --- a/Intake/MedicationView.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// MedicationView.swift -// Intake -// -// Created by Kate Callon on 2/6/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 MedicationItem: Identifiable { - var id = UUID() - var medicationName: String -} - -struct MedicationView: View { - @Environment(FHIRStore.self) private var fhirStore - @Environment(NavigationPathWrapper.self) private var navigationPath - @State private var medications: [MedicationItem] = [] - - var body: some View { - NavigationView { // swiftlint:disable:this closure_body_length - VStack { // swiftlint:disable:this closure_body_length - List { - ForEach($medications) { $item in - HStack { - TextField("Medication", text: $item.medicationName) - Button(action: { - // Action to delete this item - if let index = medications.firstIndex(where: { $0.id == item.id }) { - medications.remove(at: index) - } - }) { - Image(systemName: "xmark.circle") - .accessibilityLabel(Text("DELETE_MEDICATION")) - } - } - } - .onDelete(perform: delete) - - Button(action: { - // Action to add new item - medications.append(MedicationItem(medicationName: "")) - }) { - HStack { - Image(systemName: "plus.circle.fill") - .accessibilityLabel(Text("ADD_MEDICATION")) - Text("Add Field") - } - } - } - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .principal) { - Text("Please list your current medications.") - .font(.system(size: 28)) // Choose a size that fits - .lineLimit(1) - .minimumScaleFactor(0.5) // Adjusts the font size to fit the width of the line - } - } - Button(action: { - // Save output to Firestore and navigate to next screen - // Still need to save output to Firestore - navigationPath.path.append(NavigationViews.allergies) - }) { - Text("Submit") - .foregroundColor(.white) - .padding() - .frame(maxWidth: .infinity) - .background(Color.blue) - .cornerRadius(8) - } - .padding() - } - .onAppear { - // Set a breakpoint on the next line to inspect `fhirStore.conditions` - let patientMedications = fhirStore.medications - for medication in patientMedications where !self.medications.contains(where: { $0.medicationName == medication.displayName }) { - self.medications.append(MedicationItem(medicationName: medication.displayName)) - } - } - } - } - - func delete(at offsets: IndexSet) { - medications.remove(atOffsets: offsets) - } - } - -#Preview { - MedicationView() - .previewWith { - FHIRStore() - } -} diff --git a/Intake/Mock Data/FHIRStore+Extensions.swift b/Intake/Mock Data/FHIRStore+Extensions.swift index fb90761..bf72109 100644 --- a/Intake/Mock Data/FHIRStore+Extensions.swift +++ b/Intake/Mock Data/FHIRStore+Extensions.swift @@ -63,8 +63,36 @@ extension FHIRStore { } ?? false } } + var llmMedications: [FHIRResource] { + let outpatientMedications = medications + .filter { medication in + guard let medicationRequest = medicationRequest(resource: medication), + medicationRequest.category? + .contains(where: { codableconcept in + codableconcept.text?.value?.string.lowercased() == "outpatient" + }) + ?? false else { + return false + } + + return true + } + .uniqueDisplayNames + + let activeMedications = medications + .filter { medication in + guard let medicationRequest = medicationRequest(resource: medication), + medicationRequest.status == .active else { + return false + } + + return true + } + .uniqueDisplayNames + + return outpatientMedications + activeMedications + } - private var llmMedications: [FHIRResource] { func medicationRequest(resource: FHIRResource) -> MedicationRequest? { guard case let .r4(resource) = resource.versionedResource, let medicationRequest = resource as? ModelsR4.MedicationRequest else { @@ -73,77 +101,6 @@ extension FHIRStore { return medicationRequest } - - let outpatientMedications = medications - .filter { medication in - guard let medicationRequest = medicationRequest(resource: medication), - medicationRequest.category? - .contains(where: { codableconcept in - codableconcept.text?.value?.string.lowercased() == "outpatient" - }) - ?? false else { - return false - } - - return true - } - .uniqueDisplayNames - - let activeMedications = medications - .filter { medication in - guard let medicationRequest = medicationRequest(resource: medication), - medicationRequest.status == .active else { - return false - } - - return true - } - .uniqueDisplayNames - - return outpatientMedications + activeMedications - } - -// var allResourcesFunctionCallIdentifier: [String] { -// @AppStorage(StorageKeys.resourceLimit) var resourceLimit = StorageKeys.Defaults.resourceLimit -// -// let relevantResources: [FHIRResource] -// -// if llmRelevantResources.count > resourceLimit { -// relevantResources = llmRelevantResources -// .lazy -// .filter { -// $0.date != nil -// } -// .sorted { -// $0.date ?? .distantPast < $1.date ?? .distantPast -// } -// .suffix(resourceLimit) -// } else { -// relevantResources = llmRelevantResources -// } -// -// return Array(Set(relevantResources.map { $0.functionCallIdentifier })) -// } -// -// -// func loadMockResources() { -// if FeatureFlags.testMode { -// let mockObservation = Observation( -// code: CodeableConcept(coding: [Coding(code: "1234".asFHIRStringPrimitive())]), -// id: FHIRPrimitive("1234"), -// issued: FHIRPrimitive(try? Instant(date: .now)), -// status: FHIRPrimitive(ObservationStatus.final) -// ) -// -// let mockFHIRResource = FHIRResource( -// versionedResource: .r4(mockObservation), -// displayName: "Mock Resource" -// ) -// -// removeAllResources() -// insert(resource: mockFHIRResource) -// } -// } } extension Array where Element == FHIRResource { diff --git a/Intake/Resources/Localizable.xcstrings b/Intake/Resources/Localizable.xcstrings index 733365d..2f6caa3 100644 --- a/Intake/Resources/Localizable.xcstrings +++ b/Intake/Resources/Localizable.xcstrings @@ -63,9 +63,6 @@ }, "Add Field" : { - }, - "ADD_MEDICATION" : { - }, "ADD_REACTION" : { @@ -176,9 +173,6 @@ } } } - }, - "DELETE_MEDICATION" : { - }, "DELETE_REACTION" : { @@ -295,7 +289,7 @@ "Medical Intake Form" : { }, - "Medication" : { + "Medication Settings" : { }, "Medications" : { @@ -429,9 +423,6 @@ }, "Please list conditions you have had" : { - }, - "Please list your current medications." : { - }, "Primary Concern" : {