Historical Expressions
Angular Form Engine scope. This page documents historical expressions in
@openmrs/ngx-formentry. Equivalent schema fields may behave differently in the React Form Engine. See About this documentation.
A historical expression is a JavaScript expression that can be evaluated to determine the value of a form field. The expression can reference historical encounter and observation data, as well as additional scope values. Historical expressions are optionally available for all form fields. They are useful for helping to determine the value of a form field based on historical data from a past encounter. If a historical value is available, a button gets shown next to the form labelled Use value. Clicking this button sets the value of the form field to the historical value.
Historical expressions are defined in the form schema as part of a question definition object.
{
"label": "Height (cm)",
"questionInfo": "",
"id": "height",
"historicalExpression": "HD.getObject('prevEnc').getValue('a8a6619c-1350-11df-a1f1-0026b9348838')",
"questionOptions": {
"rendering": "number",
"concept": "a8a6619c-1350-11df-a1f1-0026b9348838",
"max": "350",
"min": "0"
},
"type": "obs",
"validators": []
},In the example above, the historical expression is HD.getObject('prevEnc').getValue('a8a6619c-1350-11df-a1f1-0026b9348838'). This expression is evaluated to determine the value of the height question. The expression is evaluated using the HistoricalHelperService (opens in a new tab). HD represents an instance of the HistoricalHelperService class. The getObject function on the HistoricalHelperService class returns the previous encounter object (referred to here as prevEnc), whereas the getValue function on the HistoricalEncounter class returns the value of the question with the specified concept UUID. The HistoricalHelperService class is able to draw out the value of the question from the previous encounter because the question has the same backing concept UUID in the current encounter as it did in the previous encounter.
Defining a historical expression for a question is optional. If a question does not have a historical expression, it will be evaluated as normal. If it does have a historical expression, the expression will be evaluated and the result will be made available to use as the value of the question (see the GIF below for an example of this in practice).
Historical expressions are evaluated using the HistoricalHelperService (opens in a new tab) class, which provides a convenient way to evaluate expressions that depend on historical encounter and observation data.
Syntax
Historical expressions typically consist of boolean logic that evaluates to true or false. The following snippet shows the code for a question in an HIV Adult Return encounter form that answers whether a patient is covered by the national health insurance scheme, NHIF.
{
"label": "Is patient covered by NHIF",
"id": "nhif",
"historicalExpression": "_.isEmpty(HD.getObject('prevEnc').getValue('0b49e3e6-55df-4096-93ca-59edadb74b3f')) ? undefined : HD.getObject('prevEnc').getValue('0b49e3e6-55df-4096-93ca-59edadb74b3f')",
"questionOptions": {
"rendering": "select",
"concept": "0b49e3e6-55df-4096-93ca-59edadb74b3f",
"answers": [
{
"concept": "8b715fed-97f6-4e38-8f6a-c167a42f8923",
"label": "Yes"
},
{
"concept": "a899e0ac-1350-11df-a1f1-0026b9348838",
"label": "No"
}
]
},
"type": "obs",
"validators": []
}If we break down the historical expression, we can see that it consists of a ternary operator that checks whether the patient has a previous encounter. If the patient has a previous encounter, the expression returns the value of the NHIF question from the previous encounter. If the patient does not have a previous encounter, the expression returns undefined. It's using the isEmpty function from lodash and passing in the value of the NHIF question from the previous encounter. This value is obtained by calling the getValue function on the HistoricalEncounter object, which is obtained by calling the getObject function on the HistoricalHelperService class. The value passed to getValue is the backing concept UUID of the NHIF question. The HistoricalHelperService is able to draw out the value of the NHIF question from the previous encounter because the NHIF question has the same backing concept UUID in the current encounter as it did in the previous encounter.
Below is a GIF showing this in practice:

Prefilling the historical value
By default, the historical value is only offered through the Use value button. You can prefill the question with it instead, using one of these properties alongside historicalExpression:
historicalPrepopulateCondition: An expression that decides whether to prefill. The historical value is available to it ashistValue, an object withvalueandvalueDateproperties. When the expression returns a truthy value, the question is prefilled.historicalPrepopulate: Set this totrueto always prefill. AddallowedHistoricalValueAgeInDaysto only prefill values recorded within that many days. It must be a number, such as90, not a string. Otherwise it's ignored and the value is always prefilled.
If both are set, only historicalPrepopulateCondition is used. Neither has an effect when the historical expression doesn't return a value.
{
"label": "Height (cm)",
"id": "height",
"historicalExpression": "HD.getObject('prevEnc').getValue('a8a6619c-1350-11df-a1f1-0026b9348838')",
"historicalPrepopulateCondition": "!isEmpty(histValue.value)",
"questionOptions": {
"rendering": "number",
"concept": "a8a6619c-1350-11df-a1f1-0026b9348838"
},
"type": "obs",
"validators": []
}Here's a snippet (opens in a new tab) from O3's esm-form-entry-app (opens in a new tab) which shows how the encounter data source gets wired up:
private async wireDataSources(createFormParams: CreateFormParams, formSchema: FormSchema) {
const visitTypeUuid = this.singleSpaPropsService.getPropOrThrow('visitTypeUuid');
const patient = this.singleSpaPropsService.getPropOrThrow('patient');
// Clear any previously configured data sources.
// Reason: If a config value changes in between two invocations, that data source would otherwise stick
// until the page is refreshed (all other data sources would be overridden as expected).
for (const dataSourceKey of Object.keys(this.dataSources.dataSources)) {
this.dataSources.clearDataSource(dataSourceKey);
}
// Fixed data sources which are always required.
// We re-register them during each form creation flow because props like the logged-in user or patient
// can change in between.
const dataSources = this.formDataSourceService.getDataSources(formSchema);
this.dataSources.registerDataSource('location', dataSources.location);
this.dataSources.registerDataSource('provider', dataSources.provider);
this.dataSources.registerDataSource('drug', dataSources.drug);
this.dataSources.registerDataSource('problem', dataSources.problem);
this.dataSources.registerDataSource('personAttribute', dataSources.location);
this.dataSources.registerDataSource('conceptAnswers', dataSources.conceptAnswers);
this.dataSources.registerDataSource('diagnoses', dataSources.diagnoses);
this.dataSources.registerDataSource('patient', { visitTypeUuid }, true);
const patientObj = this.formDataSourceService.getPatientObject(patient);
this.dataSources.registerDataSource('patient', patientObj, true);
this.dataSources.registerDataSource('rawPrevEnc', createFormParams.previousEncounter, false);
const rawPrevObs = await dataSources.recentObs(patient.id);
this.dataSources.registerDataSource('rawPrevObs', rawPrevObs, false);
this.dataSources.registerDataSource('userLocation', createFormParams.session.sessionLocation);
}We're grabbing the previous encounter and registering it with the HistoricalEncounterDataService's (opens in a new tab) encounter store and making it accessible via a key called rawPrevEnc. This is what allows us to access the previous encounter data in the form schema. We're then registering the patient's most recent observations under the rawPrevObs key. These aren't limited to the previous encounter. The form entry app fetches the most recent observation from any encounter for the concept of every question marked with useMostRecentValue. If no question has that flag, or the patient has no matching observations, rawPrevObs is undefined. The engine registers these observations with the historical data service as prevObs, alongside prevEnc. Every data source is also available to other expressions by name, so hide expressions can read rawPrevObs directly. See Hiding fields based on a previous observation.