diff --git a/Package.swift b/Package.swift index 21c0d60..f5d7717 100644 --- a/Package.swift +++ b/Package.swift @@ -28,7 +28,7 @@ let package = Package( .package(url: "https://github.com/StanfordBDHG/HealthKitOnFHIR", .upToNextMinor(from: "0.2.4")), .package(url: "https://github.com/StanfordSpezi/Spezi", .upToNextMinor(from: "0.8.0")), .package(url: "https://github.com/StanfordSpezi/SpeziHealthKit.git", .upToNextMinor(from: "0.4.0")), - .package(url: "https://github.com/StanfordSpezi/SpeziML.git", .upToNextMinor(from: "0.3.0")) + .package(url: "https://github.com/StanfordSpezi/SpeziML.git", branch: "publicModel")// .upToNextMinor(from: "0.3.0")) ], targets: [ .target( diff --git a/Sources/SpeziFHIRInterpretation/FHIRResourceInterpreter.swift b/Sources/SpeziFHIRInterpretation/FHIRResourceInterpreter.swift index b58dc17..2d5da8d 100644 --- a/Sources/SpeziFHIRInterpretation/FHIRResourceInterpreter.swift +++ b/Sources/SpeziFHIRInterpretation/FHIRResourceInterpreter.swift @@ -19,10 +19,13 @@ public class FHIRResourceInterpreter { private let resourceProcesser: FHIRResourceProcesser - init(localStorage: LocalStorage, openAIComponent: OpenAIModel) { + /// - Parameters: + /// - localStorage: Local storage module that needs to be passed to the ``FHIRResourceInterpreter`` to allow it to cache interpretations. + /// - openAIModel: OpenAI module that needs to be passed to the ``FHIRResourceInterpreter`` to allow it to retrieve interpretations. + public init(localStorage: LocalStorage, openAIModel: OpenAIModel) { self.resourceProcesser = FHIRResourceProcesser( localStorage: localStorage, - openAIComponent: openAIComponent, + openAIModel: openAIModel, storageKey: "FHIRResourceInterpreter.Interpretations", prompt: FHIRPrompt.interpretation ) diff --git a/Sources/SpeziFHIRInterpretation/FHIRResourceProcesser.swift b/Sources/SpeziFHIRInterpretation/FHIRResourceProcesser.swift index a3053be..74ab2c7 100644 --- a/Sources/SpeziFHIRInterpretation/FHIRResourceProcesser.swift +++ b/Sources/SpeziFHIRInterpretation/FHIRResourceProcesser.swift @@ -18,7 +18,7 @@ class FHIRResourceProcesser { private let localStorage: LocalStorage - private let openAIComponent: OpenAIModel + private let openAIModel: OpenAIModel private let storageKey: String private let prompt: FHIRPrompt @@ -36,12 +36,12 @@ class FHIRResourceProcesser { init( localStorage: LocalStorage, - openAIComponent: OpenAIModel, + openAIModel: OpenAIModel, storageKey: String, prompt: FHIRPrompt ) { self.localStorage = localStorage - self.openAIComponent = openAIComponent + self.openAIModel = openAIModel self.storageKey = storageKey self.prompt = prompt self.results = (try? localStorage.read(storageKey: storageKey)) ?? [:] @@ -54,7 +54,7 @@ class FHIRResourceProcesser { return result } - let chatStreamResults = try await openAIComponent.queryAPI(withChat: [systemPrompt(forResource: resource)]) + let chatStreamResults = try await openAIModel.queryAPI(withChat: [systemPrompt(forResource: resource)]) var result = "" for try await chatStreamResult in chatStreamResults { diff --git a/Sources/SpeziFHIRInterpretation/FHIRResourceSummary.swift b/Sources/SpeziFHIRInterpretation/FHIRResourceSummary.swift index c161dbd..4aca079 100644 --- a/Sources/SpeziFHIRInterpretation/FHIRResourceSummary.swift +++ b/Sources/SpeziFHIRInterpretation/FHIRResourceSummary.swift @@ -19,10 +19,13 @@ public class FHIRResourceSummary { private let resourceProcesser: FHIRResourceProcesser - init(localStorage: LocalStorage, openAIComponent: OpenAIModel) { + /// - Parameters: + /// - localStorage: Local storage module that needs to be passed to the ``FHIRResourceSummary`` to allow it to cache summaries. + /// - openAIModel: OpenAI module that needs to be passed to the ``FHIRResourceSummary`` to allow it to retrieve summaries. + public init(localStorage: LocalStorage, openAIModel: OpenAIModel) { self.resourceProcesser = FHIRResourceProcesser( localStorage: localStorage, - openAIComponent: openAIComponent, + openAIModel: openAIModel, storageKey: "FHIRResourceSummary.Summaries", prompt: FHIRPrompt.summary ) diff --git a/Tests/UITests/TestApp/ExampleModule.swift b/Tests/UITests/TestApp/ExampleModule.swift new file mode 100644 index 0000000..8bb9171 --- /dev/null +++ b/Tests/UITests/TestApp/ExampleModule.swift @@ -0,0 +1,28 @@ +// +// This source file is part of the Stanford Spezi open-source project +// +// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) +// +// SPDX-License-Identifier: MIT +// + +import Spezi +import SpeziFHIRInterpretation +import SpeziLocalStorage +import class SpeziOpenAI.OpenAIModule +import class SpeziOpenAI.OpenAIModel + + +class ExampleModule: Module { + @Dependency private var localStorage: LocalStorage + @Dependency private var openAI: OpenAIModule + + @Model private var resourceSummary: FHIRResourceSummary + @Model private var resourceInterpreter: FHIRResourceInterpreter + + + func configure() { + resourceSummary = FHIRResourceSummary(localStorage: localStorage, openAIModel: openAI.model) + resourceInterpreter = FHIRResourceInterpreter(localStorage: localStorage, openAIModel: openAI.model) + } +} diff --git a/Tests/UITests/TestApp/TestAppDelegate.swift b/Tests/UITests/TestApp/TestAppDelegate.swift index b3d3d8b..368d528 100644 --- a/Tests/UITests/TestApp/TestAppDelegate.swift +++ b/Tests/UITests/TestApp/TestAppDelegate.swift @@ -13,6 +13,8 @@ import SwiftUI class TestAppDelegate: SpeziAppDelegate { override var configuration: Configuration { - Configuration(standard: FHIR()) { } + Configuration(standard: FHIR()) { + ExampleModule() + } } } diff --git a/Tests/UITests/TestAppUITests/FHIRMockDataStorageProviderTests.swift b/Tests/UITests/TestAppUITests/FHIRMockDataStorageProviderTests.swift index 53df1f0..b964e87 100644 --- a/Tests/UITests/TestAppUITests/FHIRMockDataStorageProviderTests.swift +++ b/Tests/UITests/TestAppUITests/FHIRMockDataStorageProviderTests.swift @@ -10,10 +10,77 @@ import XCTest final class SpeziFHIRTests: XCTestCase { - func testSpeziFHIR() throws { + func testSpeziFHIRMockPatients() throws { let app = XCUIApplication() app.launch() XCTAssert(app.staticTexts["Allergy Intolerances: 0"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Conditions: 0"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Diagnostics: 0"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Encounters: 0"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Immunizations: 0"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Medications: 0"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Observations: 0"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Other Resources: 0"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Procedures: 0"].waitForExistence(timeout: 2)) + + app.buttons["Select Mock Patient"].tap() + + XCTAssert(app.buttons["Jamison785 Denesik803"].waitForExistence(timeout: 20)) + app.buttons["Jamison785 Denesik803"].tap() + + sleep(3) + + app.navigationBars["Select Mock Patient"].buttons["Dismiss"].tap() + + XCTAssert(app.staticTexts["Allergy Intolerances: 0"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Conditions: 70"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Diagnostics: 205"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Encounters: 82"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Immunizations: 12"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Medications: 31"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Observations: 769"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Other Resources: 302"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Procedures: 106"].waitForExistence(timeout: 2)) + + app.buttons["Select Mock Patient"].tap() + + XCTAssert(app.buttons["Maye976 Dickinson688"].waitForExistence(timeout: 20)) + app.buttons["Maye976 Dickinson688"].tap() + + sleep(3) + + app.navigationBars["Select Mock Patient"].buttons["Dismiss"].tap() + + XCTAssert(app.staticTexts["Allergy Intolerances: 0"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Conditions: 37"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Diagnostics: 113"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Encounters: 86"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Immunizations: 11"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Medications: 55"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Observations: 169"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Other Resources: 322"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Procedures: 225"].waitForExistence(timeout: 2)) + } + + + func testPromptSettings() throws { + let app = XCUIApplication() + app.launch() + + XCTAssert(app.buttons["Settings"].waitForExistence(timeout: 2)) + app.buttons["Settings"].tap() + + XCTAssert(app.buttons["Summary Prompt"].waitForExistence(timeout: 2)) + app.buttons["Summary Prompt"].tap() + + XCTAssert(app.buttons["Save Prompt"].waitForExistence(timeout: 2)) + app.buttons["Save Prompt"].tap() + app.navigationBars["Summary Prompt"].buttons["Prompt Settings"].tap() + + XCTAssert(app.buttons["Interpretation Prompt"].waitForExistence(timeout: 2)) + app.buttons["Interpretation Prompt"].tap() + app.navigationBars["Interpretation Prompt"].buttons["Prompt Settings"].tap() + app.navigationBars["Prompt Settings"].buttons["Dismiss"].tap() } } diff --git a/Tests/UITests/UITests.xcodeproj/TestApp.xctestplan b/Tests/UITests/UITests.xcodeproj/TestApp.xctestplan index 9cc5ba8..098de68 100644 --- a/Tests/UITests/UITests.xcodeproj/TestApp.xctestplan +++ b/Tests/UITests/UITests.xcodeproj/TestApp.xctestplan @@ -15,6 +15,21 @@ "containerPath" : "container:..\/..", "identifier" : "SpeziFHIR", "name" : "SpeziFHIR" + }, + { + "containerPath" : "container:..\/..", + "identifier" : "SpeziFHIRHealthKit", + "name" : "SpeziFHIRHealthKit" + }, + { + "containerPath" : "container:..\/..", + "identifier" : "SpeziFHIRInterpretation", + "name" : "SpeziFHIRInterpretation" + }, + { + "containerPath" : "container:..\/..", + "identifier" : "SpeziFHIRMockPatients", + "name" : "SpeziFHIRMockPatients" } ] }, diff --git a/Tests/UITests/UITests.xcodeproj/project.pbxproj b/Tests/UITests/UITests.xcodeproj/project.pbxproj index 851afb7..0678f2e 100644 --- a/Tests/UITests/UITests.xcodeproj/project.pbxproj +++ b/Tests/UITests/UITests.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 2F34D14E2B0CF1F1009300C1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F34D14D2B0CF1F1009300C1 /* ContentView.swift */; }; 2F34D1502B0CF42F009300C1 /* PromptSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F34D14F2B0CF42F009300C1 /* PromptSettings.swift */; }; 2F34D1522B0CF59A009300C1 /* MockPatientSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F34D1512B0CF59A009300C1 /* MockPatientSelection.swift */; }; + 2F34D1542B0D833F009300C1 /* ExampleModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F34D1532B0D833F009300C1 /* ExampleModule.swift */; }; 2F35E9D62B015EB200CB89FF /* SpeziFHIRInterpretation in Frameworks */ = {isa = PBXBuildFile; productRef = 2F35E9D52B015EB200CB89FF /* SpeziFHIRInterpretation */; }; 2F36AD33299DB72400B1077C /* FHIRMockDataStorageProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F36AD32299DB72400B1077C /* FHIRMockDataStorageProviderTests.swift */; }; 2F6D139A28F5F386007C25D6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2F6D139928F5F386007C25D6 /* Assets.xcassets */; }; @@ -34,6 +35,7 @@ 2F34D14D2B0CF1F1009300C1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 2F34D14F2B0CF42F009300C1 /* PromptSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptSettings.swift; sourceTree = ""; }; 2F34D1512B0CF59A009300C1 /* MockPatientSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPatientSelection.swift; sourceTree = ""; }; + 2F34D1532B0D833F009300C1 /* ExampleModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleModule.swift; sourceTree = ""; }; 2F36AD32299DB72400B1077C /* FHIRMockDataStorageProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FHIRMockDataStorageProviderTests.swift; sourceTree = ""; }; 2F6D139228F5F384007C25D6 /* TestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2F6D139928F5F386007C25D6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -90,11 +92,12 @@ 2F6D139428F5F384007C25D6 /* TestApp */ = { isa = PBXGroup; children = ( + 2FA7382B290ADFAA007ACEB9 /* TestApp.swift */, + 2FD021DC299E0F2900E5B91B /* TestAppDelegate.swift */, + 2F34D1532B0D833F009300C1 /* ExampleModule.swift */, 2F34D14D2B0CF1F1009300C1 /* ContentView.swift */, 2F34D1512B0CF59A009300C1 /* MockPatientSelection.swift */, 2F34D14F2B0CF42F009300C1 /* PromptSettings.swift */, - 2FA7382B290ADFAA007ACEB9 /* TestApp.swift */, - 2FD021DC299E0F2900E5B91B /* TestAppDelegate.swift */, 2F6D139928F5F386007C25D6 /* Assets.xcassets */, ); path = TestApp; @@ -248,6 +251,7 @@ files = ( 2FA7382C290ADFAA007ACEB9 /* TestApp.swift in Sources */, 2FD021DD299E0F2900E5B91B /* TestAppDelegate.swift in Sources */, + 2F34D1542B0D833F009300C1 /* ExampleModule.swift in Sources */, 2F34D1502B0CF42F009300C1 /* PromptSettings.swift in Sources */, 2F34D1522B0CF59A009300C1 /* MockPatientSelection.swift in Sources */, 2F34D14E2B0CF1F1009300C1 /* ContentView.swift in Sources */, diff --git a/Tests/UITests/UITests.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Tests/UITests/UITests.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5507330..a578ff6 100644 --- a/Tests/UITests/UITests.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Tests/UITests/UITests.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/StanfordSpezi/SpeziML.git", "state" : { - "revision" : "b71b4a38ebfa066872435e44c09ff618da840ebe", - "version" : "0.3.0" + "branch" : "publicModel", + "revision" : "50dbb5f61fa88d200e80136040ddf021e40f4b8b" } }, { diff --git a/Tests/UITests/UITests.xcodeproj/xcshareddata/xcschemes/TestApp.xcscheme b/Tests/UITests/UITests.xcodeproj/xcshareddata/xcschemes/TestApp.xcscheme index 81bb5e9..ba86621 100644 --- a/Tests/UITests/UITests.xcodeproj/xcshareddata/xcschemes/TestApp.xcscheme +++ b/Tests/UITests/UITests.xcodeproj/xcshareddata/xcschemes/TestApp.xcscheme @@ -8,16 +8,16 @@ + buildForRunning = "NO" + buildForProfiling = "NO" + buildForArchiving = "NO" + buildForAnalyzing = "NO"> + BlueprintIdentifier = "SpeziFHIR" + BuildableName = "SpeziFHIR" + BlueprintName = "SpeziFHIR" + ReferencedContainer = "container:../.."> + + + + + + + + + + + +