Skip to content

Commit

Permalink
Updated Medications View + Adding to Environment Variable (#51)
Browse files Browse the repository at this point in the history
# Working Medications View with Sample Patient Data and Updating Global
Environment Variable

## ♻️ 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.


## ⚙️ 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



## 📚 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).
  • Loading branch information
kcallon authored Feb 29, 2024
1 parent be917b2 commit af23eec
Show file tree
Hide file tree
Showing 12 changed files with 342 additions and 191 deletions.
49 changes: 45 additions & 4 deletions Intake.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -164,7 +169,11 @@
2FE5DCAC29EE6107004B9AB4 /* AccountOnboarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountOnboarding.swift; sourceTree = "<group>"; };
2FF53D8C2A8729D600042B76 /* IntakeStandard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntakeStandard.swift; sourceTree = "<group>"; };
511827952B740191002033A0 /* SurgeryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurgeryView.swift; sourceTree = "<group>"; };
511827972B7401A8002033A0 /* MedicationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MedicationView.swift; sourceTree = "<group>"; };
51805C112B81853700D17109 /* IntakeMedication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntakeMedication.swift; sourceTree = "<group>"; };
51805C142B81857100D17109 /* IntakeMedicationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntakeMedicationViewModel.swift; sourceTree = "<group>"; };
51805C192B818A1A00D17109 /* IntakeDosage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntakeDosage.swift; sourceTree = "<group>"; };
51805C1C2B818A4400D17109 /* IntakeMedicationInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntakeMedicationInstance.swift; sourceTree = "<group>"; };
51A027662B82CDA300A195C8 /* MedicationContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MedicationContentView.swift; sourceTree = "<group>"; };
566155282AB8447C00209B80 /* Package+LicenseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Package+LicenseType.swift"; sourceTree = "<group>"; };
5661552D2AB854C000209B80 /* PackageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageHelper.swift; sourceTree = "<group>"; };
5680DD382AB8983D004E6D4A /* PackageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageCell.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -363,6 +373,18 @@
path = Surgery;
sourceTree = "<group>";
};
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 = "<group>";
};
56F6F29E2AB441640022FE5A /* Contributions */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -457,7 +479,7 @@
isa = PBXGroup;
children = (
F4F4F8802B8C6FC5008FBEED /* Elements.swift */,
511827972B7401A8002033A0 /* MedicationView.swift */,
519E830A2B7C4F1600A2D92D /* Medication View */,
511827942B740191002033A0 /* Surgery */,
AC2A17272B70684D00F560D0 /* SocialHistory */,
5A2B9FB32B6AFE1F005CA63F /* Allergy Records */,
Expand Down Expand Up @@ -594,6 +616,7 @@
5A0C1A182B69691000120506 /* SpeziFHIR */,
5A0C1A1A2B69691000120506 /* SpeziFHIRHealthKit */,
5A2B9F892B69E0AF005CA63F /* SpeziFHIRMockPatients */,
51805C172B81898700D17109 /* SpeziMedication */,
);
productName = Intake;
productReference = 653A254D283387FE005D4D48 /* Intake.app */;
Expand Down Expand Up @@ -690,6 +713,7 @@
5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */,
F42AB1D02B6379B5002E13A6 /* XCRemoteSwiftPackageReference "SpeziLLM" */,
5A0C1A162B69667B00120506 /* XCRemoteSwiftPackageReference "SpeziFHIR" */,
51805C162B81898700D17109 /* XCRemoteSwiftPackageReference "SpeziMedication" */,
);
productRefGroup = 653A254E283387FE005D4D48 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -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 */,
Expand All @@ -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 */,
Expand All @@ -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 */,
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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" */;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion Intake/Home.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Intake/Intake.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class NavigationPathWrapper {
class DataStore {
var allergyData: [AllergyItem] = []
var conditionData: [MedicalHistoryItem] = []
var medicationData: [MedicationItem] = []
var medicationData: Set<IntakeMedicationInstance> = []
var surgeries: [SurgeryItem] = []
var chiefComplaint: String = ""
}
Expand Down
20 changes: 20 additions & 0 deletions Intake/Medication View/IntakeDosage.swift
Original file line number Diff line number Diff line change
@@ -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
}
21 changes: 21 additions & 0 deletions Intake/Medication View/IntakeMedication.swift
Original file line number Diff line number Diff line change
@@ -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]
}
30 changes: 30 additions & 0 deletions Intake/Medication View/IntakeMedicationInstance.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
133 changes: 133 additions & 0 deletions Intake/Medication View/IntakeMedicationViewModel.swift
Original file line number Diff line number Diff line change
@@ -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<IntakeMedicationInstance> = []
let medicationOptions: Set<IntakeMedication>

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<IntakeMedicationInstance>) 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
}
}
Loading

0 comments on commit af23eec

Please sign in to comment.