Data bindings are a powerful feature that enables seamless synchronization of patient information across different forms and services. By using data bindings, you can ensure that patient data is correctly identified, securely stored, and easily accessible.
This document explains how to use them effectively, and code examples to understand how to debug them, and create new ones.
The data binding feature allows you to prepopulate form fields with patient data and send new data to external medical records systems.
The Workflow is as follows:
linkId
properties to identify the fields.parser.get
function to read data from the ClaimPower API and store it in our database.setInitialFormValues
function to prepopulate the form fields.questionnaire_response_to_patient
function to read the answers provided by the user.When a unique form is requested for a patient, we call the ClaimPower API to fetch the patient data and populate our Medplum store. When the patient opens the form, we show our copy of the data.
When a new form is requested for a patient, we call the Clampower API again to refresh the data.
The Claimpower API returns a Patient
FHIR object, and additional objects like
Coverage
, CareTeam
, Organization
, DocumentReference
,
MedicationAdministration
, Condition
, Observation
, and
ActivityDefinition
.
A set of methods in the file [medplum] src/medplum/patient.ts
are used to call the
parser methods to attempt to update the patient and related objects.
The file that manages connecting with the Claimpower API is src/utils/claimpower/api.ts
.
It uses the claimpower_patient_id
and the db_name
to make a request to the Claimpower API.
const GetPatient = async (claimpower_patient_id: string, db_name: string): GetPatientData => {};
The Claimpower API responses and the lotus data model implement FHIR objects, they have very similar formats but there are some differences in the spelling of properties, and the location for some of the data.
When the data is retrieved we store as much as possible in the lotus store.
When the Claimpower API changes, the parser.get
file must be updated to reflect the new data model, and
additional methods might be required to parse and store the data correctly.
When creating new methods in the Claimpower API, or updating the existing ones, follow the FHIR recommendations and consider the medplum documentation to use similar data in both systems.
When a patient receives a unique form link, we display the questionnaire and replace each field with the data we have in our database.
The file responsible is src/utils/setInitialFormValues.ts
and it includes a method that
receives a reference to a questionnaire
and it changes it in place.
A questionnaire
has an item property, which is an array of QuestionnaireItem
objects.
Each item has a linkId
property, the dictionary of methods in the file uses the linkId
to
call a unique method to retrieve the associated data.
When creating a new binding, ensure that the setInitialFormValues
file is updated to include the new method.
Use the @medplum/fhir-types
to document the FHIR objects and their properties and make sure
that the Initial
value being created matches the expected data type.
For user convenience, when a patient opens the form, we create a record in the localStorage
that can be retrieved later.
Every time a patient changes an answer we update the localStorage
record, and
when the patient refreshes the page, we use the localStorage
record to
prepopulate the form fields.
The values in the localStorage
will take precedence over the ones in our database, as we assume that
the patient has the most recent information.
When the patient submits the form, we read the answers provided by the user and
use the linkId
in each item to identify the fields.
The file that reads the answers has methods like this:
if (i.linkId?.startsWith('Patient.Coverage')) {
answer = getInitialFromCoverage(i.linkId, Coverage);
}
Each binding that is created as a linkId needs a custom function to read it.
The answers can have different data type represented on their value like:
const answer = {
valueString: '',
valueBoolean: true,
valueNumber: 1,
}
We have a questionnaire that contains an array of items, each identified by a
linkId
. We use the file src/medplum/translators/questionnaire_response_to_patient.ts
to read the answers provided by the user and update the patient and related objects.
After a form is submmited, the front end sends a POST
request to the API in this file
src/pages/api/claimpower/forms/pdf/index.ts
.
The file reads the answers to generate a PDF, update the lotus medplum store, and make a request to the Claimpower API that includes the FHIR objects and the PDF.
To send the data to Claimpower, we combine the data we have in our database with the new answers provided by the user.
The parser.set
file helps us remove ambiguity and ensure that the data is correctly stored.
The FHIR specification allows each implementation to have its own interpretation, the Claimpower API enforces different rules and validations that we need to follow.
Each binding must have its own method to guarantee that we're translating the data correctly, and avoid known pitfalls.
The Claimpower data model and the lotus store use very similar formats, but there are some differences in the
spelling of properties, and the location for some of the data. Parsing the data proactively ensures that every binding
is well documented, and allows for faster integrations when we use the fhir-types
and the available parsers.
When creating a new binding, ensure that the parser.set
file is updated to include the new method. And
add the name of the linkId
to the docs/yaml file to keep the documentation up to date.
Additionally, some screenings require a CPT code to be sent to the Claimpower API.
CPT codes are used to describe medical, surgical, and diagnostic services and are used to bill insurance companies for services provided.
To create a new binding, you need to modify multiple files, as described below. Is important to complete all the steps to create the binding in both directions. Recommended to develop and test each step individually for better modularity and organization.
src/pages/api/claimpower/patient/get_form_url.ts
The get_form_url
api is used to create a unique form link for the patient.
When the link is called with a claimpower_patient_id
and a db_name
parameter, the function will
make a request to the ClaimPower API to fetch the patient data and store it in our database.
It then uses the method replacePatient
and createRelatedResources
to store the data in our database.
The get_form_url is currently supporting multiple FHIR objects, the Claimpower API might return additional ones
that could require a new parser function. To call the parser might require changes in the get_form_url
file
or the src/medplum/patient
file.
src/utils/claimpower/parser.get.ts
This file includes parser functions for multiple FHIR objects. The file uses @medplum/fhir-types
to document
the FHIR objects and their properties.
For example Patient is a FHIR object that includes
demographic information about a patient. We use the Patient.extension
property to store additional information.
The FHIR specification is vague about the implementation, and there are some differences between the Claimpower API response and the Medplum store. We use the parser to remove ambiguity, run validations and ensure that the data is correctly stored.
Extend one of the following functions or create a new one to parse the data from the ClaimPower API:
export const parseCareTeam = (patient: Patient, Organization: Organization): CareTeam => {};
export const parseOrganization = (patient: Patient, response: PatientResponse): Organization[] => {};
export const parseCoverage = (patient: Patient, response: PatientResponse): Coverage[] => {};
export const parseDocumentReference = (patient: Patient, response: PatientResponse): DocumentReference[] => {};
export const parseMedicationAdministration = (patient: Patient, response: PatientResponse): MedicationAdministration[] => {};
export const parseCondition = (patient: Patient, response: PatientResponse): Condition[] => {};
export const parseObservation = (patient: Patient, observations: Observation[]): Observation[] => {};
export const parseActivityDefinition = (patient: Patient, response: PatientResponse): ActivityDefinition[] => {};
src/utils/setInitialFormValues.ts
The medplum utility file src/medplum/patient
includes a method to retrieve all the data we have about
a patient.
The method is called in the page that renders the form src/pages/forms/[slug]/[form_id]/[patient_id].tsx
.
The method const patientEverything = async (id: string, access_token: string) => {}
returns a Bundle
with all the resources related to a patient. We use the parseFhirPath
utility to easily access the data and
retrieve the different FHIR objects and their properties.
Every binding has a method on this file to prepopulate the form fields.
Example for a binding to get data:
Consult the medplum documentation and use @medplum/fhir-types
to get insights in your IDE.
Extend one of the following methods, or create a new one:
const getInitialFromCoverage = (key: string, coverage?: Coverage[]): Initial => {};
const getInitialFromOrganization = (key: string, organization: Organization): Initial => {};
const getInitialFromCareTeam = (key: string, patient: Patient, everything?: Bundle): Initial => {};
const getInitialFromDocumentReference = (key: string, patient: Patient): Initial => {};
const formatToPhoneNumber = (phone: string): string => {};
const getInitialFromPatient = (key: string, patient: Patient): Initial => {};
const getInitials = (patient: Patient): Initial => {};
const buildInitialAnswers = (item: QuestionnaireItem[], patient: Patient, everything?: Bundle, resources?: any): QuestionnaireItem[] => {};
const setInitialFormValues = async (};
setInitialFormValues
receives the questionnaire as a reference and changes it in place.
We use a dictionary pattern to find the method that corresponds to the linkId
of the item, so that is easier to
extend every time we need to add a new binding. Create a new method in the dictionary to add a new binding.
Review the capitalization and the naming conventions to keep the code consistent.
/**
* This function receives a linkId and a list of items and returns the item that matches the linkId, traversing the items recursively
* @param key {string} - The linkId to search for
* @param coverage {Coverage[]} - The list of coverages to read from
*/
const getInitialFromCoverage = (key: string, coverage?: Coverage[]): Initial => {
const initial: Initial = { valueString: '' };
const methods: { [key: string]: () => Initial } = {
'Coverage.Payor': (): Initial => ({
valueString: coverage?.[0]?.payor?.[0]?.display || '',
}),
}
if (methods[key]) {
return methods[key]() || '';
}
return initial;
};
Using the correct data types in your project offers several advantages: