Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix NCI Trial API type error, update used GPT models, fix warnings and deprecations, Swift 6 language mode concurrency compatibility #28

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ extension JSONEncodable where Self: Encodable {
}
}

extension String: CodingKey {
extension String: @retroactive CodingKey {

public var stringValue: String {
return self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ public struct StructuredEligibility: Codable, JSONEncodable, Hashable {
public var acceptsHealthyVolunteers: Bool?
public var minAge: String?
public var minAgeNumber: Int?
public var minAgeInYears: Int?
public var minAgeInYears: Double?

public init(maxAge: String? = nil, maxAgeNumber: Int? = nil, minAgeUnit: String? = nil, maxAgeUnit: String? = nil, maxAgeInYears: Int? = nil, gender: String? = nil, acceptsHealthyVolunteers: Bool? = nil, minAge: String? = nil, minAgeNumber: Int? = nil, minAgeInYears: Int? = nil) {
public init(maxAge: String? = nil, maxAgeNumber: Int? = nil, minAgeUnit: String? = nil, maxAgeUnit: String? = nil, maxAgeInYears: Int? = nil, gender: String? = nil, acceptsHealthyVolunteers: Bool? = nil, minAge: String? = nil, minAgeNumber: Int? = nil, minAgeInYears: Double? = nil) {
self.maxAge = maxAge
self.maxAgeNumber = maxAgeNumber
self.minAgeUnit = minAgeUnit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

// https://stackoverflow.com/a/50281094/976628
public class OpenISO8601DateFormatter: DateFormatter {
public class OpenISO8601DateFormatter: DateFormatter, @unchecked Sendable {
static let withoutSeconds: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
Expand Down
2 changes: 1 addition & 1 deletion NCIClinicalTrialsSearchAPI/docs/StructuredEligibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Name | Type | Description | Notes
**acceptsHealthyVolunteers** | **Bool** | | [optional]
**minAge** | **String** | | [optional]
**minAgeNumber** | **Int** | | [optional]
**minAgeInYears** | **Int** | | [optional]
**minAgeInYears** | **Double** | | [optional]

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

Expand Down
8 changes: 4 additions & 4 deletions OwnYourData/ClinicalTrials/NCITrialsModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import SpeziLocation

@Observable
class NCITrialsModule: Module, EnvironmentAccessible {
@ObservationIgnored @Dependency private var locationModule: SpeziLocation
@ObservationIgnored @Dependency(SpeziLocation.self) private var locationModule

private let apiKey: String
private(set) var trials: [TrialDetail] = []
var zipCode: String = "10025"
var searchDistance: String = "100"
var searchDistance: String = "50" // Miles


init(apiKey: String) {
self.apiKey = apiKey
Expand Down
2 changes: 1 addition & 1 deletion OwnYourData/ClinicalTrials/NICTrialsAPIDateFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import Foundation


class NICTrialsAPIDateFormatter: DateFormatter {
class NICTrialsAPIDateFormatter: DateFormatter, @unchecked Sendable {
static let withoutSeconds: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
Expand Down
2 changes: 1 addition & 1 deletion OwnYourData/ClinicalTrials/TrialDetail+Identifiable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import OpenAPIClient


extension TrialDetail: Identifiable {
extension TrialDetail: @retroactive Identifiable {
public var id: String? {
self.nciId
}
Expand Down
2 changes: 1 addition & 1 deletion OwnYourData/Documents/PDFDocument+Transferable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import PDFKit
import SwiftUI


extension PDFDocument: Transferable {
extension PDFDocument: @retroactive Transferable {
public static var transferRepresentation: some TransferRepresentation {
DataRepresentation(
contentType: .pdf,
Expand Down
2 changes: 1 addition & 1 deletion OwnYourData/Helper/CodableArray+RawRepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation


extension Array: RawRepresentable where Element: Codable {
extension Array: @retroactive RawRepresentable where Element: Codable {
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let rawValue = String(data: data, encoding: .utf8) else {
Expand Down
4 changes: 2 additions & 2 deletions OwnYourData/Home.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct HomeView: View {
}

@Environment(Account.self) var account: Account?
@Environment(FHIRStore.self) var fjirStore
@Environment(FHIRStore.self) var fhirStore
philippzagar marked this conversation as resolved.
Show resolved Hide resolved

@State private var presentingAccount = false
@State private var showMatchingView = false
Expand All @@ -34,7 +34,7 @@ struct HomeView: View {
.font(.system(size: 60).weight(.semibold))
.foregroundColor(.accentColor)
.multilineTextAlignment(.center)
if fjirStore.llmRelevantResources.isEmpty {
if fhirStore.llmRelevantResources.isEmpty {
InstructionsView()
}
OwnYourDataButton(title: "Match Me") {
Expand Down
21 changes: 19 additions & 2 deletions OwnYourData/OwnYourDataDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import SwiftUI
class OwnYourDataDelegate: SpeziAppDelegate {
// See https://swiftpackageindex.com/stanfordspezi/spezi/documentation/spezi/speziappdelegate/configuration
override var configuration: Configuration {
// swiftlint:disable:next closure_body_length
Configuration(standard: OwnYourDataStandard()) {
if !FeatureFlags.disableFirebase {
// See https://swiftpackageindex.com/stanfordspezi/speziaccount/documentation/speziaccount/initial-setup
Expand Down Expand Up @@ -64,8 +65,24 @@ class OwnYourDataDelegate: SpeziAppDelegate {
LLMRunner {
LLMOpenAIPlatform(configuration: .init(concurrentStreams: 20, apiToken: self.openAIToken))
}
FHIRInterpretationModule()

FHIRInterpretationModule(
summaryLLMSchema: LLMOpenAISchema(
parameters: .init(
modelType: .gpt4_o,
systemPrompts: []
)
),
interpretationLLMSchema: LLMOpenAISchema(
parameters: .init(
modelType: .gpt4_o,
systemPrompts: []
)
),
multipleResourceInterpretationOpenAIModel: .gpt4_o,
resourceCountLimit: 250,
allowedResourcesFunctionCallIdentifiers: nil
)

DocumentManager()

SpeziLocation()
Expand Down
37 changes: 16 additions & 21 deletions OwnYourData/OwnYourDataStandard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import SwiftUI


actor OwnYourDataStandard: Standard, EnvironmentAccessible, HealthKitConstraint, OnboardingConstraint, AccountStorageConstraint {
actor OwnYourDataStandard: Standard, EnvironmentAccessible, HealthKitConstraint, ConsentConstraint, AccountStorageConstraint {
enum OwnYourDataStandardError: Error {
case userNotAuthenticatedYet
}
Expand All @@ -32,8 +32,8 @@
Firestore.firestore().collection("users")
}

@Dependency var fhirStore: FHIRStore
@Dependency var accountStorage: FirestoreAccountStorage?
@Dependency(FHIRStore.self) var fhirStore
@Dependency(FirestoreAccountStorage.self) var accountStorage: FirestoreAccountStorage?

@AccountReference var account: Account

Expand Down Expand Up @@ -106,39 +106,34 @@

/// Stores the given consent form in the user's document directory with a unique timestamped filename.
///
/// - Parameter consent: The consent form's data to be stored as a `PDFDocument`.
func store(consent: PDFDocument) async {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd_HHmmss"
let dateString = formatter.string(from: Date())

/// - Parameter consent: The consent form's data to be stored as a `SpeziOnboarding.ConsentDocumentExport`.
func store(consent: SpeziOnboarding.ConsentDocumentExport) async throws {
guard let consentData = await consent.pdf.dataRepresentation() else {
logger.error("Could not store consent form.")
return
}

Check warning on line 115 in OwnYourData/OwnYourDataStandard.swift

View check run for this annotation

Codecov / codecov/patch

OwnYourData/OwnYourDataStandard.swift#L110-L115

Added lines #L110 - L115 were not covered by tests
guard !FeatureFlags.disableFirebase else {
guard let basePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
logger.error("Could not create path for writing consent form to user document directory.")
return
}
let filePath = basePath.appending(path: "consentForm_\(dateString).pdf")
consent.write(to: filePath)

let filePath = await basePath.appending(path: "\(consent.documentIdentifier).pdf")
try consentData.write(to: filePath)

Check warning on line 124 in OwnYourData/OwnYourDataStandard.swift

View check run for this annotation

Codecov / codecov/patch

OwnYourData/OwnYourDataStandard.swift#L121-L124

Added lines #L121 - L124 were not covered by tests
return
}

Check warning on line 127 in OwnYourData/OwnYourDataStandard.swift

View check run for this annotation

Codecov / codecov/patch

OwnYourData/OwnYourDataStandard.swift#L127

Added line #L127 was not covered by tests
do {
guard let consentData = consent.dataRepresentation() else {
logger.error("Could not store consent form.")
return
}

let metadata = StorageMetadata()
metadata.contentType = "application/pdf"
_ = try await userBucketReference.child("consent/\(dateString).pdf").putDataAsync(consentData, metadata: metadata)
_ = try await userBucketReference.child("consent/\(consent.documentIdentifier).pdf").putDataAsync(consentData, metadata: metadata)

Check warning on line 131 in OwnYourData/OwnYourDataStandard.swift

View check run for this annotation

Codecov / codecov/patch

OwnYourData/OwnYourDataStandard.swift#L131

Added line #L131 was not covered by tests
} catch {
logger.error("Could not store consent form: \(error)")
}
}


func create(_ identifier: AdditionalRecordId, _ details: SignupDetails) async throws {
guard let accountStorage else {
preconditionFailure("Account Storage was requested although not enabled in current configuration.")
Expand Down
42 changes: 35 additions & 7 deletions OwnYourData/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Find National Cancer Institute supported trials.\n\nWe noticed that you haven't saved any health records on your phone yet.\n\nPlease follow the instructions to retrieve your health records: [Apple Support - View health records on your iPhone or iPod touch](https://support.apple.com/en-us/HT208680).\n\nYou can find a list of supported institutions at [Apple Support - Institutions that support health records on iPhone and iPod touch](https://platform.openai.com/account/api-keys).\n\nPlease ensure that OwnYourData has access to your health records in the Apple Health App. You can find these settings in the privacy section of your profile in Apple Health."
"value" : "Find National Cancer Institute supported trials.\n\nWe noticed that you havent saved any health records on your phone yet.\n\nPlease follow the instructions to retrieve your health records: [Apple Support - View health records on your iPhone or iPod touch](https://support.apple.com/en-us/HT208680).\n\nYou can find a list of supported institutions at [Apple Support - Institutions that support health records on iPhone and iPod touch](https://platform.openai.com/account/api-keys).\n\nPlease ensure that OwnYourData has access to your health records in the Apple Health App. You can find these settings in the privacy section of your profile in Apple Health."
}
}
}
Expand Down Expand Up @@ -101,10 +101,24 @@

},
"Identifying best matching trials ..." : {

"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Identifying best matching trials …"
}
}
}
},
"Inspecting FHIR resources ..." : {

"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Inspecting FHIR resources …"
}
}
}
},
"Keyword Identification Prompt" : {
"comment" : "Title of the keyword identification prompt."
Expand All @@ -113,7 +127,14 @@

},
"Learn More ..." : {

"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Learn More …"
}
}
}
},
"License Information" : {

Expand Down Expand Up @@ -164,7 +185,7 @@
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Your task is to identify a minimal set of distinct and unique keywords to conduct a trial search for a patient diagnosed with cancer. 
\nUtilize the \"get_resources\" function to access the patient's FHIR resources. \nThe generic patient information will be passed into this context after this prompt.\nUtilize the function call as often as needed until you have a comprehensive picture of the patient's health status and you feel confident that you can respond with a few distinct keywords for the NIC trials API search.
\nAvoid any generic terms like \"cancer\" and other elements that might appear in all trial descriptions.
Only try to provide 5 or less keywords.\nTry to be as concrete and as narrow as possible based on the relevant FHIR resources.\n
Do not engage in any conversation; only respond with a list of keywords separated by commas without any other context, introduction, or surrounding information. \nThe resulting strings will be parsed for further processing."
"value" : "Your task is to identify a minimal set of distinct and unique keywords to conduct a trial search for a patient diagnosed with cancer. 
\nUtilize the get_resources function to access the patients FHIR resources. \nThe generic patient information will be passed into this context after this prompt.\nUtilize the function call as often as needed until you have a comprehensive picture of the patients health status and you feel confident that you can respond with a few distinct keywords for the NIC trials API search.
\nAvoid any generic terms like cancer and other elements that might appear in all trial descriptions.
Only try to provide 5 or less keywords.\nTry to be as concrete and as narrow as possible based on the relevant FHIR resources.\n
Do not engage in any conversation; only respond with a list of keywords separated by commas without any other context, introduction, or surrounding information. \nThe resulting strings will be parsed for further processing."
}
}
}
Expand All @@ -175,7 +196,7 @@
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Your task is to identify a set of matching trials for the patient diagnosed with cancer using the NCI trials API. 

You will be provided with a set of keywords identified in a previous run of an LLM based on the patient's FHIR health records.
\nYou can request any health records using the get_records function calling mechanisms. Utilize the function call as often as needed until you have a comprehensive picture of the patient's health status.

Utilize the get_trials function to retrieve information about the possible trials retrieved from the NCI API using the keywords identified in a previous run.
Ensure that the trial description and inclusion criteria match the patient's health records.
\nRespond with all trial identifiers that seem a good match.
The trial identifies must be separated by commas.\nIt is encouraged to return 3-5 possible matching trials to provide the patient some choice but ensure that the trials are matching the patient profile.
Only use the trial identifiers that are parameter options for the get_trials function; do not make up or combine trial identifiers.
\nDo not engage in any conversation; only respond with a list of identifiers separated by commas without any other context, introduction, or surrounding information. \nThe resulting identifiers will be parsed for further processing."
"value" : "Your task is to identify a set of matching trials for the patient diagnosed with cancer using the NCI trials API. 

You will be provided with a set of keywords identified in a previous run of an LLM based on the patient’s FHIR health records.
\nYou can request any health records using the get_records function calling mechanisms. Utilize the function call as often as needed until you have a comprehensive picture of the patient’s health status.

Utilize the get_trials function to retrieve information about the possible trials retrieved from the NCI API using the keywords identified in a previous run.
Ensure that the trial description and inclusion criteria match the patient’s health records.
\nRespond with all trial identifiers that seem a good match.
The trial identifies must be separated by commas.\nIt is encouraged to return 3-5 possible matching trials to provide the patient some choice but ensure that the trials are matching the patient profile.
Only use the trial identifiers that are parameter options for the get_trials function; do not make up or combine trial identifiers.
\nDo not engage in any conversation; only respond with a list of identifiers separated by commas without any other context, introduction, or surrounding information. \nThe resulting identifiers will be parsed for further processing."
}
}
}
Expand All @@ -184,7 +205,14 @@

},
"Loading NCI trials based on FHIR resources ..." : {

"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Loading NCI trials based on FHIR resources …"
}
}
}
},
"Location Access" : {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

Title: \(trial.briefTitle ?? "") (\(trial.officialTitle ?? ""))
Description: \(trial.detailDescription ?? "")
Incluision Criteria: \(trial.eligibility?.unstructured?.compactMap { $0.description }.joined() ?? "")
Inclusion Criteria: \(trial.eligibility?.unstructured?.compactMap { $0.description }.joined() ?? "")

Check warning on line 49 in OwnYourData/TrialsMatching/LLMFunctions/GetTrialsLLMFunction.swift

View check run for this annotation

Codecov / codecov/patch

OwnYourData/TrialsMatching/LLMFunctions/GetTrialsLLMFunction.swift#L49

Added line #L49 was not covered by tests
"""
}
}
Expand Down
Loading
Loading