A FHIR Contract
resource constitutes a consent document that can be rendered using a ORKTaskViewController
view controller and can be signed with a patient reference.
It can also contain eligibility criteria which a participant must answer first in order to be able to start consenting.
Contract
resource for eligibility determination and the consent task.StudyIntroConfiguration
, read from JSON file, to optionally change eligibility messages.
ConsentTask
(anORKOrderedTask
subclass) to let ResearchKit guide through consent.ORKConsentDocument
and anORKSteps
array.Contract
resource signed with aPatient
reference.- Local URL to the consent PDF containing signature and date.
Eligibility criteria can be included in the Contract
resource as the contract's subject
, represented as a Group
resource.
Here's an example group that requires participants to be older than 18 and reside in the US. This is a Group resource that is contained in the Contract resource it applies to and referenced accordingly:
{
"id": "org.chip.c-tracker.consent",
"resourceType": "Contract",
...
"subject": [{
"reference": "#eligibility"
}],
"contained": [{
"id": "eligibility",
"resourceType": "Group",
"type": "person",
"actual": false,
"characteristic": [{
"code": {
"text": "Are you 18 years of age or older?"
},
"valueBoolean": true,
"exclude": false
},
{
"code": {
"text": "Do you live in the United States of America?"
},
"valueBoolean": true,
"exclude": false
}]
}]
}
To read eligibility and consent data from a bundled consent called Consent.json
you can do the following.
This example will also use the bundled file Consent_full.html
to show a custom HTML page in the “Agree” step instead of auto-generating that page from all consent sections.
This is optional and, if omitted, the consent will be composed of all the individual consenting sections.
You could use this method in combination with setupUI()
shown in StudyIntro/README.md
.
func startEligibilityAndConsent(intro: StudyIntroCollectionViewController) {
self.controller = try! ConsentController(bundledContract: "Consent") // retain
controller.options.reviewConsentDocument = "Consent_full" // optional
let center = NSNotificationCenter.defaultCenter()
center.addObserver(self, selector: "userDidConsent",
name: C3UserDidConsentNotification, object: nil)
let elig = controller.eligibilityStatusViewController(intro.config)
if let navi = intro.navigationController {
navi.pushViewController(elig, animated: true)
}
else {
// you did not put the intro view controller on a navigation controller
}
}
func userDidConsent() {
// Your user is consented. A generated PDF will be written to
// `ConsentController.signedConsentPDFURL()` on a background queue, so
// might not yet be available. Usually, the user is now prompted to grant
// necessary permissions (notifications, HealthKit, Motion, ...)
}
First, the user will be asked your eligibility questions, and – if they are met – presents the consent task as a modal view controller.
If the user cancels or declines consent, the view controller is dismissed and the eligibility view controller popped from its navigation controller.
If the user consents, the consent view controller is likewise dismissed and you'll receive the C3UserDidConsentNotification
notification.
To represent consent sections that can be shown on screen, we usa a Contract
resource and instantiate each Contract.term
element as a ORKConsentSection
.
These sections are added to a ORKConsentDocument
's section
property to represent the "visual" consenting step.
The properties to use are:
type
: one ofORKConsentSectionType
(without the ORKConsentSectionType part)text
: the section's summary
Several ResearchKit-specific parameters require the use of an extension.
We use nested extensions under the parent URI http://fhir-registry.smarthealthit.org/StructureDefinition/ORKConsentSection
.
The nested extensions are:
title
: A string representing the title of the sectionimage
: A string for an image name, included in the app bundle, that will be assigned the section'scustomImage
property.animation
: A string for a movie name, included in the app bundle, that will be assigned the section'scustomAnimationURL
property.htmlContent
: A string representing the full HTML content, to be shown when “Learn More” is tapped.htmlContentFile
: A name of an HTML file (without file extension) that contains the HTML to be shown when “Learn More” is tapped; needs to be added to the App Bundle
Example:
{
"id": "org.chip.c-tracker.consent",
"resourceType": "Contract",
"type": {
"coding": [{
"system": "http://hl7.org/fhir/contracttypecodes",
"code": "consent"
}]
},
"issued": "2015-08-18",
"applies": {
"start": "2015-08-18"
},
"authority": [{
"reference": "#bch"
}],
"term": [{
"type": {
"coding": [{
"system": "http://researchkit.org/docs/Constants/ORKConsentSectionType.html",
"code": "Privacy"
}]
},
"extension": [{
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/ORKConsentSection",
"extension": [{
"url": "title",
"valueString": "Privacy"
},
{
"url": "htmlContentFile",
"valueString": "5_privacyprotection"
}]
}],
"text": "Your data will be sent to a secure database, ..."
}]
}
Usually, participants are also asked whether they consent to sharing their data with other qualified researchers or only the team running the study.
You can turn it off by setting ConsentTaskOptions.askForSharing
to false.
To populate the team name used when the participant is asked if she's willing to share her data, the authority.name
property of the Contract is consulted.
At this time this must be an Organization element that is contained in the contract.
The ConsentResult
that is sent as a notification when the participant finishes consent contains the property shareWidely
.
This property is set to:
- true if the participant consents to sharing data
- false if only the study team should access the data
- nil if the question was not asked
Additionally, the Contract.signer
element is extended with http://fhir-registry.smarthealthit.org/StructureDefinition/consents-to-data-sharing
, set to true
if the participant consents to sharing data widely, false otherwise.
The extension is not present when the question is not asked.
Here is a short example, see examples/Contract/sample-consent-signed.json for a fully signed Contract:
{
"resourceType": "Contract",
"id": "org.chip.c-tracker.consent",
...
"authority": [{
"reference": "#team"
}],
"signer": [{
"extension": [{
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/consents-to-data-sharing",
"valueBoolean": true
}],
"party": {...},
"signature": [...],
"type": {...}
}],
"contained": [{
"resourceType": "Organization",
"id": "team",
"name": "C Tracker Team"
}]
}
By default the user is asked to create a passcode right after signing on screen.
This is controlled by ConsentTaskOptions.askToCreatePasscode
.
After the user has done so, you can use ResearchKit to show the passcode screen when the app launches:
import ResearchKit
func applicationWillEnterForeground(application: UIApplication) {
if ORKPasscodeViewController.isPasscodeStoredInKeychain() {
let entry = ORKPasscodeViewController.
passcodeAuthenticationViewControllerWithText(nil, delegate: self)
<# vc #>.presentViewController(entry, animated: true, completion: nil)
}
}
func passcodeViewControllerDidFinishWithSuccess(viewController: UIViewController) {
...
viewController.dismissViewControllerAnimated(true, completion: nil)
}
func passcodeViewControllerDidFailAuthentication(viewController: UIViewController) {
}
You can easily prompt the user to grant access to HealthKit, CoreMotion, Notifications and others.
Take a look at SystemServices
; all you have to do is set wantedServicePermissions
on the consent controller's options.
By default the user is not prompted to give access.
A signed Contract
resource can be generated by providing a Patient resource and subsequently be sent to the SMART/FHIR backend:
let server = <# SMART client #>.server
consentController = ConsentController() // ivar on e.g. the App Delegate
do {
let contract = try consentController.signContract(with: patient, date: Date())
patient._server = server
patient.update() { error in // cannot use `create`: patient already has an ID
if let error = error {
print("Error creating patient: \(error)")
}
else {
contract.create(server) { error in
if let error = error {
print("Error creating contract: \(error)")
}
}
}
}
}
catch let error {
c3_warn("Failed signing contract: \(error)")
}
For HIPAA-compliant (Safe Harbor) de-identified patient signing, including determining the (current) location, you can use deIdentifyAndSignConsentWithPatient()
.
Using the DeIdentifier
and Geocoder
included in this framework, a de-identified Patient resource will be created alongside the Contract that can be sent to the SMART/FHIR backend:
let server = <# SMART client #>.server
consentController = ConsentController() // ivar on e.g. the App Delegate
consentController!.deIdentifyAndSignContract(with: patient, date: Date()) { contract, patient, error in
patient._server = server
patient.update() { error in // cannot use `create`: patient already has an ID
if let error = error {
print("Error creating patient: \(error)")
}
else {
contract.create(server) { error in
if let error = error {
print("Error creating contract: \(error)")
}
}
}
}
}
If you don't want some of the automation, here are several points of entry that you can use to customize behavior.
Sample code that could work this way on the app delegate, showing override points in the eligibility-consenting setup.
You could call eligibilityStatusViewController()
and push the received view controller onto a navigation controller.
func eligibilityStatusViewController(withConfiguration config: StudyIntroConfiguration?) -> EligibilityStatusViewController {
return consentController.eligibilityStatusViewController(config) { controller in
let root = self.window!.rootViewController!
do {
let consentVC = try self.consentViewController()
root.presentViewController(consentVC, animated: true, completion: nil)
}
catch let error {
c3_warn("Failed to create consent view controller: \(error)")
}
}
}
func consentViewController() throws -> ORKTaskViewController {
return try consentController.consentViewController(
onUserDidConsent: { controller, result in
// look at the consent result for participant's name, signature and sharing choice
print("\(result.participantFriendlyName) DID CONSENT, START APP SETUP")
controller.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
},
onUserDidDecline: { controller in
controller.navigationController?.popToRootViewControllerAnimated(false)
controller.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
)
}
To create a consenting task from a bundled consent called Consent.json
and show it using an ORKTaskViewController
you can do the following.
let controller = try! try ConsentController(bundledContract: "Consent")
let task = controller.createConsentTask()
let vc = ORKTaskViewController(task: task, taskRunUUID: NSUUID())
vc.delegate = <# your ORKTaskViewControllerDelegate #>
// now present `vc` somewhere