Skip to content

Commit

Permalink
Convert survey results into FHIR and upload to Firestore (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
vishnuravi authored Feb 23, 2023
1 parent 15d982d commit be83c4d
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .firebaserc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"projects": {
"default": "cs3422023utah"
"default": "cs342-2023-utah"
}
}
8 changes: 4 additions & 4 deletions Utah.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
23B0449B299DB70A008CD957 /* GetUpAndGoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23B0449A299DB70A008CD957 /* GetUpAndGoTests.swift */; };
27A7B3E829A7B2C8000B32B4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 27A7B3E729A7B2C8000B32B4 /* GoogleService-Info.plist */; };
2F49B7762980407C00BCB272 /* CardinalKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7752980407B00BCB272 /* CardinalKit */; };
2F49B7782980407C00BCB272 /* FHIR in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7772980407C00BCB272 /* FHIR */; };
2F49B77A2980407C00BCB272 /* HealthKitDataSource in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7792980407C00BCB272 /* HealthKitDataSource */; };
Expand All @@ -30,7 +31,6 @@
2F905711299E3E0D003D3802 /* FirebaseAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 2F905710299E3E0D003D3802 /* FirebaseAccount */; };
2F905713299E3E0D003D3802 /* FirestoreDataStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 2F905712299E3E0D003D3802 /* FirestoreDataStorage */; };
2F905715299E3E0D003D3802 /* FirestoreStoragePrefixUserIdAdapter in Frameworks */ = {isa = PBXBuildFile; productRef = 2F905714299E3E0D003D3802 /* FirestoreStoragePrefixUserIdAdapter */; };
2F905719299E4205003D3802 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2F905717299E4205003D3802 /* GoogleService-Info.plist */; };
2FC9759F2978E39600BA99FE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2FC9759E2978E39600BA99FE /* Localizable.strings */; };
2FC975A82978F11A00BA99FE /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FC975A72978F11A00BA99FE /* Home.swift */; };
653A2551283387FE005D4D48 /* Utah.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A2550283387FE005D4D48 /* Utah.swift */; };
Expand Down Expand Up @@ -65,12 +65,12 @@

/* Begin PBXFileReference section */
23B0449A299DB70A008CD957 /* GetUpAndGoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUpAndGoTests.swift; sourceTree = "<group>"; };
27A7B3E729A7B2C8000B32B4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
2F49B77329803E8F00BCB272 /* UtahModules */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = UtahModules; sourceTree = "<group>"; };
2F4E237D2989A2FE0013F3D9 /* OnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = "<group>"; };
2F4E23822989D51F0013F3D9 /* UtahAppTestingSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtahAppTestingSetup.swift; sourceTree = "<group>"; };
2F4E23882989DB400013F3D9 /* HealthKitUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitUploadTests.swift; sourceTree = "<group>"; };
2F5E32BC297E05EA003432F8 /* UtahAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtahAppDelegate.swift; sourceTree = "<group>"; };
2F905717299E4205003D3802 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
2FAEC07F297F583900C11C42 /* Utah.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Utah.entitlements; sourceTree = "<group>"; };
2FC94CD4298B0A1D009C8209 /* Utah.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Utah.xctestplan; sourceTree = "<group>"; };
2FC9759E2978E39600BA99FE /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -140,7 +140,7 @@
children = (
2FAEC07F297F583900C11C42 /* Utah.entitlements */,
653A258928339462005D4D48 /* Info.plist */,
2F905717299E4205003D3802 /* GoogleService-Info.plist */,
27A7B3E729A7B2C8000B32B4 /* GoogleService-Info.plist */,
653A255428338800005D4D48 /* Assets.xcassets */,
2FC9759E2978E39600BA99FE /* Localizable.strings */,
);
Expand Down Expand Up @@ -348,9 +348,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2F905719299E4205003D3802 /* GoogleService-Info.plist in Resources */,
653A255528338800005D4D48 /* Assets.xcassets in Resources */,
2FC9759F2978E39600BA99FE /* Localizable.strings in Resources */,
27A7B3E829A7B2C8000B32B4 /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "0df86ea17d5d281415be74f2290df8431644f156",
"version" : "10.4.0"
"revision" : "dce2e1abc6c0d5e830ff1cffe3f8633fda64001e",
"version" : "10.3.0"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "9a09ece724128e8d1e14c5133b87c0e236844ac0",
"version" : "10.4.0"
"revision" : "71eb6700dd53a851473c48d392f00a3ab26699a6",
"version" : "10.1.0"
}
},
{
Expand Down
26 changes: 13 additions & 13 deletions Utah/Supporting Files/GoogleService-Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,32 @@
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>CLIENT_ID</string>
<string>515655571673-lb80ks5ts4uuok8h6bedjm1lcs2phr2v.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>REVERSED_CLIENT_ID</string>
<string>com.googleusercontent.apps.515655571673-lb80ks5ts4uuok8h6bedjm1lcs2phr2v</string>
<key>API_KEY</key>
<string>API_KEY</string>
<string>AIzaSyDTCzr0dv4FrJ-sGHEmgXSVygWHKxvCzzA</string>
<key>GCM_SENDER_ID</key>
<string>GCM_SENDER_ID</string>
<string>515655571673</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>edu.stanford.cs342.2023.utah</string>
<key>PROJECT_ID</key>
<string>cs3422023utah</string>
<string>cs342-2023-utah</string>
<key>STORAGE_BUCKET</key>
<string>STORAGE_BUCKET</string>
<string>cs342-2023-utah.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false/>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false/>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true/>
<true></true>
<key>IS_GCM_ENABLED</key>
<true/>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true/>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:123456789012:ios:1234567890123456789012</string>
<string>1:515655571673:ios:e7be75d831177550f206c8</string>
</dict>
</plist>
</plist>
4 changes: 2 additions & 2 deletions Utah/UtahAppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import FHIR
import FHIRToFirestoreAdapter
import FirebaseAccount
import FirebaseAuth
import FirebaseCore
import FirestoreDataStorage
import FirestoreStoragePrefixUserIdAdapter
import HealthKit
Expand Down Expand Up @@ -45,8 +46,7 @@ class UtahAppDelegate: CardinalKitAppDelegate {
adapter: {
FHIRToFirestoreAdapter()
FirestoreStoragePrefixUserIdAdapter()
},
settings: .emulator
}
)
}

Expand Down
7 changes: 5 additions & 2 deletions UtahModules/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ let package = Package(
.library(name: "UtahTrends", targets: ["UtahTrends"])
],
dependencies: [
.package(url: "https://github.com/StanfordBDHG/CardinalKit.git", .upToNextMinor(from: "0.3.1"))
.package(url: "https://github.com/StanfordBDHG/CardinalKit.git", .upToNextMinor(from: "0.3.1")),
.package(url: "https://github.com/firebase/firebase-ios-sdk", from: "10.3.0")
],
targets: [
.target(
Expand Down Expand Up @@ -67,7 +68,9 @@ let package = Package(
.target(name: "UtahSharedContext"),
.product(name: "FHIR", package: "CardinalKit"),
.product(name: "Questionnaires", package: "CardinalKit"),
.product(name: "Scheduler", package: "CardinalKit")
.product(name: "Scheduler", package: "CardinalKit"),
.product(name: "FirebaseFirestore", package: "firebase-ios-sdk"),
.product(name: "FirebaseStorage", package: "firebase-ios-sdk")
]
),
.target(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

// swiftlint:disable function_body_length
// swiftlint:disable closure_body_length
// swiftlint:disable legacy_objc_type

import Foundation
import ResearchKit
Expand Down Expand Up @@ -57,9 +56,9 @@ struct EdmontonViewController: UIViewControllerRepresentable {

// Question 2
let q2Choices = [
ORKTextChoice(text: "0", value: 0 as NSNumber),
ORKTextChoice(text: "1-2", value: 1 as NSNumber),
ORKTextChoice(text: ">2", value: 2 as NSNumber)
ORKTextChoice(text: "0", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "1-2", value: "1" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: ">2", value: "2" as NSSecureCoding & NSCopying & NSObjectProtocol)
]
let q2ChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: q2Choices)
let q2Step = ORKQuestionStep(
Expand All @@ -74,11 +73,11 @@ struct EdmontonViewController: UIViewControllerRepresentable {

// Question 3
let q3Choices = [
ORKTextChoice(text: "Excellent", value: 0 as NSNumber),
ORKTextChoice(text: "Very Good", value: 0 as NSNumber),
ORKTextChoice(text: "Good", value: 0 as NSNumber),
ORKTextChoice(text: "Fair", value: 1 as NSNumber),
ORKTextChoice(text: "Poor", value: 2 as NSNumber)
ORKTextChoice(text: "Excellent", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "Very Good", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "Good", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "Fair", value: "1" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "Poor", value: "2" as NSSecureCoding & NSCopying & NSObjectProtocol)
]
let q3ChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: q3Choices)
let q3Step = ORKQuestionStep(
Expand All @@ -93,9 +92,9 @@ struct EdmontonViewController: UIViewControllerRepresentable {

// Question 4
let q4Choices = [
ORKTextChoice(text: "0-1", value: 0 as NSNumber),
ORKTextChoice(text: "2-4", value: 1 as NSNumber),
ORKTextChoice(text: "5-8", value: 2 as NSNumber)
ORKTextChoice(text: "0-1", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "2-4", value: "1" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "5-8", value: "2" as NSSecureCoding & NSCopying & NSObjectProtocol)
]
let q4ChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: q4Choices)
let q4Step = ORKQuestionStep(
Expand All @@ -113,9 +112,9 @@ struct EdmontonViewController: UIViewControllerRepresentable {

// Question 5
let q5Choices = [
ORKTextChoice(text: "Always", value: 0 as NSNumber),
ORKTextChoice(text: "Sometimes", value: 1 as NSNumber),
ORKTextChoice(text: "Never", value: 2 as NSNumber)
ORKTextChoice(text: "Always", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "Sometimes", value: "1" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "Never", value: "2" as NSSecureCoding & NSCopying & NSObjectProtocol)
]
let q5ChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: q5Choices)
let q5Step = ORKQuestionStep(
Expand All @@ -129,7 +128,10 @@ struct EdmontonViewController: UIViewControllerRepresentable {
steps += [q5Step]

// Question 6-10
let q6to10Choices = [ ORKTextChoice(text: "Yes", value: 1 as NSNumber), ORKTextChoice(text: "No", value: 0 as NSNumber) ]
let q6to10Choices = [
ORKTextChoice(text: "Yes", value: "1" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "No", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol)
]
let q6to10ChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: q6to10Choices)
let questions = [
"Do you use five or more different prescription medications on a regular basis?",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,91 @@
//

// swiftlint:disable lower_acl_than_parent

import Firebase
import FirebaseStorage
import Foundation
import ModelsR4
import ResearchKit


class EdmontonViewCoordinator: NSObject, ORKTaskViewControllerDelegate {
// called when the survey is completed, need to figure out how to upload data to firestore
public func taskViewController(
_ taskViewController: ORKTaskViewController,
didFinishWith reason: ORKTaskViewControllerFinishReason,
error: Error?
) {
switch reason {
case .completed:
// Convert the responses into a FHIR object using ResearchKitOnFHIR
let fhirResponse = taskViewController.result.fhirResponse

// Add a patient identifier to the response so we know who did this survey
fhirResponse.subject = Reference(reference: FHIRPrimitive(FHIRString("Patient/PATIENT_ID")))

do {
// Parse the FHIR object into JSON
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(fhirResponse)

// Print out the JSON for debugging
let json = String(decoding: data, as: UTF8.self)
print(json)

// Convert the FHIR object to a dictionary and upload to Firebase
let jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
let identifier = fhirResponse.id?.value?.string ?? UUID().uuidString

guard let jsonDict else {
return
}

let database = Firestore.firestore()
database.collection("edmontonsurveys").document(identifier).setData(jsonDict) { err in
if let err {
print("Error writing document: \(err)")
} else {
print("Document successfully written.")
}
}

// Upload any files that are attached to the FHIR object to Firebase
if let responseItems = fhirResponse.item {
for item in responseItems {
if case let .attachment(value) = item.answer?.first?.value {
guard let fileURL = value.url?.value?.url else {
continue
}

// Get a reference to the Cloud Storage service
let storageRef = Storage.storage().reference()

// Create a reference for the new file
// and put it in the "edmonton" file on Cloud Storage
let fileName = fileURL.lastPathComponent
let fileRef = storageRef.child("edmonton/" + fileName)

// Upload the file to Cloud Storage using the reference
fileRef.putFile(from: fileURL, metadata: nil) { _, error in
if let error {
print("An error occurred: \(error)")
} else {
print("\(fileName) was uploaded successfully!")
}
}
}
}
}
} catch {
// Something didn't work!
print(error.localizedDescription)
}
default:
break
}

// We're done, dismiss the survey
taskViewController.dismiss(animated: true, completion: nil)
taskViewController.dismiss(animated: true, completion: nil)
}
}
14 changes: 6 additions & 8 deletions UtahModules/Sources/UtahSchedule/WIQ/WIQViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
// SPDX-License-Identifier: MIT
//

// swiftlint:disable legacy_objc_type

import ResearchKit
import SwiftUI
import UIKit
Expand All @@ -30,13 +28,13 @@ struct WIQViewController: UIViewControllerRepresentable {
instruction.text = "Patient Mobility Assessment"

steps += [instruction]

let wiqChoices = [
ORKTextChoice(text: "No Difficulty", value: 0 as NSNumber),
ORKTextChoice(text: "Slight Difficulty", value: 1 as NSNumber),
ORKTextChoice(text: "Some Difficulty", value: 2 as NSNumber),
ORKTextChoice(text: "Much Difficulty", value: 3 as NSNumber),
ORKTextChoice(text: "Unable to Do", value: 4 as NSNumber)
ORKTextChoice(text: "No Difficulty", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "Slight Difficulty", value: "1" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "Some Difficulty", value: "2" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "Much Difficulty", value: "3" as NSSecureCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: "Unable to Do", value: "4" as NSSecureCoding & NSCopying & NSObjectProtocol)
]

let wiqAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: wiqChoices)
Expand Down
Loading

0 comments on commit be83c4d

Please sign in to comment.