Skip to content

Commit

Permalink
Merge branch 'main' into navigation/move-primary-concern
Browse files Browse the repository at this point in the history
  • Loading branch information
nriedman authored Mar 11, 2024
2 parents 9726cc4 + 081ea98 commit b958283
Show file tree
Hide file tree
Showing 14 changed files with 693 additions and 144 deletions.
24 changes: 24 additions & 0 deletions Intake.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -111,6 +112,7 @@
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 */; };
ACFFA1CE2B8FD7190006E6D4 /* SmokingHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACFFA1CD2B8FD7190006E6D4 /* SmokingHistory.swift */; };
F42AB1D22B6379B5002E13A6 /* SpeziLLM in Frameworks */ = {isa = PBXBuildFile; productRef = F42AB1D12B6379B5002E13A6 /* SpeziLLM */; };
Expand Down Expand Up @@ -175,6 +177,7 @@
2FE5DC5529EDD811004B9AB4 /* SocialSupportQuestionnaire.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SocialSupportQuestionnaire.json; sourceTree = "<group>"; };
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>"; };
3C89F66C2B9D948B00A4F52D /* PatientInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatientInfo.swift; sourceTree = "<group>"; };
511827952B740191002033A0 /* SurgeryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurgeryView.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>"; };
Expand Down Expand Up @@ -226,6 +229,7 @@
A9DFE8A82ABE551400428242 /* AccountButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountButton.swift; sourceTree = "<group>"; };
A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSheet.swift; sourceTree = "<group>"; };
ACAA47802B571C7F0032D21F /* Questionnaire.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Questionnaire.json; sourceTree = "<group>"; };
ACF862BD2B96E29600ACBA1E /* ExportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportView.swift; sourceTree = "<group>"; };
ACFFA1CD2B8FD7190006E6D4 /* SmokingHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmokingHistory.swift; sourceTree = "<group>"; };
ACFFA1D02B8FD8BB0006E6D4 /* MenstrualHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenstrualHistory.swift; sourceTree = "<group>"; };
F42AB1DB2B637C8C002E13A6 /* LLMOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LLMOnboardingView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -380,6 +384,14 @@
path = Helper;
sourceTree = "<group>";
};
3C89F6682B9D939500A4F52D /* General Data View */ = {
isa = PBXGroup;
children = (
3C89F66C2B9D948B00A4F52D /* PatientInfo.swift */,
);
path = "General Data View";
sourceTree = "<group>";
};
511827942B740191002033A0 /* Surgery */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -499,6 +511,8 @@
653A254F283387FE005D4D48 /* Intake */ = {
isa = PBXGroup;
children = (
3C89F6682B9D939500A4F52D /* General Data View */,
ACF862BC2B96E28400ACBA1E /* Export */,
F4F4F8802B8C6FC5008FBEED /* Elements.swift */,
519E830A2B7C4F1600A2D92D /* Medication View */,
511827942B740191002033A0 /* Surgery */,
Expand Down Expand Up @@ -570,6 +584,14 @@
path = SocialHistory;
sourceTree = "<group>";
};
ACF862BC2B96E28400ACBA1E /* Export */ = {
isa = PBXGroup;
children = (
ACF862BD2B96E29600ACBA1E /* ExportView.swift */,
);
path = Export;
sourceTree = "<group>";
};
F42AB1DA2B637C5F002E13A6 /* ChiefComplaint */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -826,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 */,
Expand Down Expand Up @@ -874,6 +897,7 @@
F42AB1E52B6383F9002E13A6 /* LLMOpenAITokenOnboarding.swift in Sources */,
51805C122B81853800D17109 /* IntakeMedication.swift in Sources */,
A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */,
ACF862BE2B96E29600ACBA1E /* ExportView.swift in Sources */,
51A027672B82CDA300A195C8 /* MedicationContentView.swift in Sources */,
653A2551283387FE005D4D48 /* Intake.swift in Sources */,
2FE5DC3629EDD7CA004B9AB4 /* HealthKitPermissions.swift in Sources */,
Expand Down
125 changes: 25 additions & 100 deletions Intake/ChiefComplaint/LLMInteraction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<LLMOpenAISchema> 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
}
Expand Down Expand Up @@ -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<LLMOpenAISchema> 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
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion Intake/ChiefComplaint/SummaryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Expand Down
34 changes: 18 additions & 16 deletions Intake/EditPatient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
25 changes: 25 additions & 0 deletions Intake/Elements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Loading

0 comments on commit b958283

Please sign in to comment.