4 * FhirObservationVitalsService.php
6 * @link http://www.open-emr.org
7 * @author Stephen Nielson <stephen@nielson.org>
8 * @copyright Copyright (c) 2021 Stephen Nielson <stephen@nielson.org>
9 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
12 namespace OpenEMR\Services\FHIR\Observation
;
14 use OpenEMR\Common\Logging\SystemLogger
;
15 use OpenEMR\Common\Uuid\UuidMapping
;
16 use OpenEMR\Common\Uuid\UuidRegistry
;
17 use OpenEMR\FHIR\R4\FHIRDomainResource\FHIRObservation
;
18 use OpenEMR\FHIR\R4\FHIRElement\FHIRCodeableConcept
;
19 use OpenEMR\FHIR\R4\FHIRElement\FHIRCoding
;
20 use OpenEMR\FHIR\R4\FHIRElement\FHIRId
;
21 use OpenEMR\FHIR\R4\FHIRElement\FHIRMeta
;
22 use OpenEMR\FHIR\R4\FHIRElement\FHIRQuantity
;
23 use OpenEMR\FHIR\R4\FHIRResource\FHIRObservation\FHIRObservationComponent
;
24 use OpenEMR\Services\FHIR\FhirCodeSystemConstants
;
25 use OpenEMR\Services\FHIR\FhirProvenanceService
;
26 use OpenEMR\Services\FHIR\FhirServiceBase
;
27 use OpenEMR\Services\FHIR\IPatientCompartmentResourceService
;
28 use OpenEMR\Services\FHIR\UtilsService
;
29 use OpenEMR\Services\Search\FhirSearchParameterDefinition
;
30 use OpenEMR\Services\Search\SearchFieldException
;
31 use OpenEMR\Services\Search\SearchFieldType
;
32 use OpenEMR\Services\Search\ServiceField
;
33 use OpenEMR\Services\Search\TokenSearchField
;
34 use OpenEMR\Services\VitalsService
;
35 use OpenEMR\Validators\ProcessingResult
;
37 class FhirObservationVitalsService
extends FhirServiceBase
implements IPatientCompartmentResourceService
39 // we set this to be 'Final' which has the follow interpretation
40 // 'The observation is complete and there are no further actions needed.'
41 // @see http://hl7.org/fhir/R4/valueset-observation-status.html
42 const VITALS_DEFAULT_OBSERVATION_STATUS
= "final";
44 const VITALS_PANEL_LOINC_CODE
= '85353-1';
50 const CATEGORY
= "vital-signs";
52 const COLUMN_MAPPINGS
= [
53 // @see http://hl7.org/fhir/R4/observation-vitalsigns.html
54 self
::VITALS_PANEL_LOINC_CODE
=> [
55 // this code contains a lot of the other vital sign codes and is treated specially in this service.
56 'fullcode' => 'LOINC:' . self
::VITALS_PANEL_LOINC_CODE
57 ,'code' => self
::VITALS_PANEL_LOINC_CODE
58 ,'description' => 'Vital signs, weight, height, head circumference, oxygen saturation and BMI panel'
60 ,'in_vitals_panel' => false
63 'fullcode' => 'LOINC:9279-1'
65 ,'description' => 'Respiratory Rate'
66 ,'column' => ['respiration', 'respiration_unit', 'respiration_interpretation_code', 'respiration_interpretation_title']
67 ,'in_vitals_panel' => true
70 'fullcode' => 'LOINC:8867-4'
72 ,'description' => 'Heart rate'
73 ,'column' => ['pulse', 'pulse_unit', 'pulse_interpretation_code', 'pulse_interpretation_title']
74 ,'in_vitals_panel' => true
77 'fullcode' => 'LOINC:2708-6'
79 ,'description' => 'Oxygen saturation in Arterial blood'
80 ,'column' => ['oxygen_saturation', 'oxygen_saturation_unit', 'oxygen_saturation_interpretation_code', 'oxygen_saturation_interpretation_title']
81 ,'in_vitals_panel' => true
84 'fullcode' => 'LOINC:59408-5',
86 'description' => 'Oxygen saturation in Arterial blood by Pulse oximetry',
87 'column' => ['oxygen_saturation', 'oxygen_saturation_unit', 'oxygen_flow_rate', 'oxygen_flow_rate_unit', '_interpretation_code', '_interpretation_title'],
88 'in_vitals_panel' => true
91 'fullcode' => 'LOINC:3151-8'
93 'description' => 'Inhaled oxygen flow rate',
94 'column' => ['oxygen_flow_rate', 'oxygen_flow_rate_unit', 'oxygen_flow_rate_interpretation_code', 'oxygen_flow_rate_interpretation_title'],
95 'in_vitals_panel' => true
98 'fullcode' => 'LOINC:8310-5',
100 'description' => 'Body Temperature',
101 'column' => ['temperature', 'temperature_unit', 'temperature_interpretation_code', 'temperature_interpretation_title'],
102 'in_vitals_panel' => true
105 'fullcode' => 'LOINC:8327-9',
107 'description' => 'Temperature Location',
108 'column' => 'temp_method',
109 'in_vitals_panel' => true
112 'fullcode' => 'LOINC:8302-2',
114 'description' => 'Body height',
115 'column' => ['height', 'height_unit', 'height_interpretation_code', 'height_interpretation_title'],
116 'in_vitals_panel' => true
119 'fullcode' => 'LOINC:9843-4'
121 ,'description' => 'Head Occipital-frontal circumference'
122 ,'column' => ['head_circ', 'head_circ_unit', 'head_circ_interpretation_code', 'head_circ_interpretation_title']
123 ,'in_vitals_panel' => true
126 'fullcode' => 'LOINC:29463-7'
128 ,'description' => 'Body weight'
129 ,'column' => ['weight', 'weight_unit', 'weight_interpretation_code', 'weight_interpretation_title']
130 ,'in_vitals_panel' => true
133 'fullcode' => 'LOINC:39156-5'
135 ,'description' => 'Body mass index (BMI) [Ratio]'
136 ,'column' => ['BMI', 'BMI_status', 'BMI_unit', 'BMI_interpretation_code', 'BMI_interpretation_title']
137 ,'in_vitals_panel' => true
140 'fullcode' => 'LOINC:85354-9'
142 ,'description' => 'Blood pressure systolic and diastolic'
143 // we hack this a bit to make it work by having our systolic and diastolic together
144 ,'column' => ['bps', 'bps_unit', 'bpd', 'bpd_unit', 'bps_interpretation_code'
145 , 'bps_interpretation_title', 'bpd_interpretation_code', 'bpd_interpretation_title']
146 ,'in_vitals_panel' => true
149 'fullcode' => 'LOINC:8480-6'
151 ,'description' => 'Systolic blood pressure'
152 // we hack this a bit to make it work by having our systolic and diastolic together
153 ,'column' => ['bps', 'bps_unit', 'bps_interpretation_code', 'bps_interpretation_title']
154 ,'in_vitals_panel' => true
157 'fullcode' => 'LOINC:8462-4'
159 ,'description' => 'Diastolic blood pressure'
160 // we hack this a bit to make it work by having our systolic and diastolic together
161 ,'column' => ['bpd', 'bpd_unit', 'bpd_interpretation_code', 'bpd_interpretation_title']
162 ,'in_vitals_panel' => true
167 // pediatric profiles are different...
168 // need pediatric BMI
169 // need pediatric head-occipetal
170 // TODO: @adunsulag figure out where these values come from...
172 // Birth - 36 months @see https://www.cdc.gov/growthcharts/html_charts/hcageinf.htm
175 'fullcode' => 'LOINC:8289-1'
177 ,'description' => 'Head Occipital-frontal circumference Percentile'
178 ,'column' => ['ped_head_circ', 'ped_head_circ_unit', 'ped_head_circ_interpretation_code', 'ped_head_circ_interpretation_title']
179 ,'in_vitals_panel' => false
181 // 2-20yr @see https://www.cdc.gov/growthcharts/html_charts/bmiagerev.htm
183 'fullcode' => 'LOINC:59576-9'
185 ,'description' => 'Body mass index (BMI) [Percentile] Per age and sex'
186 ,'column' => ['ped_bmi', 'ped_bmi_unit', 'ped_bmi_interpretation_code', 'ped_bmi_interpretation_title']
187 ,'in_vitals_panel' => false
189 // @see https://www.cdc.gov/growthcharts/html_charts/wtstat.htm
190 // grab min(height) and find where weight <= 50
191 // could do this with 3 columns representing height, weight & %
193 // select % WHERE height <= usrheight & weight <= usrweight ORDER BY height DESC, weight DESC LIMIT 1
195 'fullcode' => 'LOINC:77606-2'
197 ,'description' => 'Weight-for-length Per age and sex'
198 ,'column' => ['ped_weight_height', 'ped_weight_height_unit', 'ped_weight_height_interpretation_code', 'ped_weight_height_interpretation_title']
199 ,'in_vitals_panel' => false
201 // need pediatric weight for height observations...
204 public function __construct($fhirApiURL = null)
206 parent
::__construct($fhirApiURL);
207 $this->service
= new VitalsService();
208 $this->populateResourceMappingUuidsForAllVitals();
211 public function getResourcePathForCode($code)
213 return "category=" . self
::CATEGORY
. "&code=" . $code;
215 public function getCodeFromResourcePath($resourcePath)
218 parse_str($resourcePath, $query_vars);
219 return $query_vars['code'] ??
null;
222 public function populateResourceMappingUuidsForAllVitals()
224 $resourcePathList = [];
225 foreach (self
::COLUMN_MAPPINGS
as $column => $mapping) {
226 // TODO: @adunsulag make this a single function call so we can be more effecient
227 // $resourcePathList[] = "category=vital-signs&code=" . $mapping['code'];
228 $resourcePath = $this->getResourcePathForCode($mapping['code']);
229 UuidMapping
::createMissingResourceUuids('Observation', 'form_vitals', $resourcePath);
233 public function supportsCategory($category)
235 return ($category === self
::CATEGORY
);
238 public function supportsCode($code)
240 return array_search($code, array_keys(self
::COLUMN_MAPPINGS
)) !== false;
245 * Returns an array mapping FHIR Resource search parameters to OpenEMR search parameters
247 protected function loadSearchParameters()
250 'patient' => $this->getPatientContextSearchField(),
251 'code' => new FhirSearchParameterDefinition('code', SearchFieldType
::TOKEN
, ['code']),
252 'category' => new FhirSearchParameterDefinition('category', SearchFieldType
::TOKEN
, ['category']),
253 'date' => new FhirSearchParameterDefinition('date', SearchFieldType
::DATETIME
, ['date']),
254 '_id' => new FhirSearchParameterDefinition('_id', SearchFieldType
::TOKEN
, [new ServiceField('uuid', ServiceField
::TYPE_UUID
)]),
260 * Inserts an OpenEMR record into the sytem.
261 * @return The OpenEMR processing result.
263 protected function insertOpenEMRRecord($openEmrRecord)
265 // TODO: Implement insertOpenEMRRecord() method.
269 * Updates an existing OpenEMR record.
270 * @param $fhirResourceId The OpenEMR record's FHIR Resource ID.
271 * @param $updatedOpenEMRRecord The "updated" OpenEMR record.
272 * @return The OpenEMR Service Result
274 protected function updateOpenEMRRecord($fhirResourceId, $updatedOpenEMRRecord)
276 // TODO: Implement updateOpenEMRRecord() method.
280 * Searches for OpenEMR records using OpenEMR search parameters
281 * @param openEMRSearchParameters OpenEMR search fields
282 * @param $puuidBind - Optional variable to only allow visibility of the patient with this puuid.
283 * @return OpenEMR records
285 protected function searchForOpenEMRRecords($openEMRSearchParameters): ProcessingResult
287 $processingResult = new ProcessingResult();
290 $observationCodesToReturn = [];
292 if (isset($openEMRSearchParameters['category']) && $openEMRSearchParameters['category'] instanceof TokenSearchField
) {
293 if (!$openEMRSearchParameters['category']->hasCodeValue(self
::CATEGORY
)) {
294 throw new SearchFieldException("category", "invalid value");
296 // we only support one category and then we remove it.
297 unset($openEMRSearchParameters['category']);
300 if (isset($openEMRSearchParameters['code'])) {
302 * @var TokenSearchField
304 $code = $openEMRSearchParameters['code'];
305 if (!($code instanceof TokenSearchField
)) {
306 throw new SearchFieldException('code', "Invalid code");
308 foreach ($code->getValues() as $value) {
309 $code = $value->getCode();
310 $observationCodesToReturn[$code] = $code;
312 unset($openEMRSearchParameters['code']);
316 if (empty($observationCodesToReturn)) {
318 $observationCodesToReturn = array_keys(self
::COLUMN_MAPPINGS
);
319 $observationCodesToReturn = array_combine($observationCodesToReturn, $observationCodesToReturn);
322 // convert vital sign records from 1:many
324 $result = $this->service
->search($openEMRSearchParameters, true);
325 $data = $result->getData() ??
[];
327 // need to transform these into something we can consume
328 foreach ($data as $record) {
329 // each vital record becomes a 1 -> many record for our observations
330 $this->parseVitalsIntoObservationRecords($processingResult, $record, $observationCodesToReturn);
332 } catch (SearchFieldException
$exception) {
333 $processingResult->setValidationMessages([$exception->getField() => $exception->getMessage()]);
336 return $processingResult;
339 private function parseVitalsIntoObservationRecords(ProcessingResult
$processingResult, $record, $observationCodesToReturn)
341 $uuidMappings = $this->getVitalSignsUuidMappings(UuidRegistry
::uuidToBytes($record['uuid']));
342 // convert each record into it's own openEMR record array
344 if (!empty($observationCodesToReturn[self
::VITALS_PANEL_LOINC_CODE
])) {
345 if (!empty($uuidMappings[self
::VITALS_PANEL_LOINC_CODE
])) {
347 "code" => self
::VITALS_PANEL_LOINC_CODE
348 , "description" => $this->getDescriptionForCode(self
::VITALS_PANEL_LOINC_CODE
)
349 , "category" => self
::CATEGORY
350 , "puuid" => $record['puuid']
351 , "euuid" => $record['euuid']
353 , "uuid" => UuidRegistry
::uuidToString($uuidMappings[self
::VITALS_PANEL_LOINC_CODE
])
354 , "user_uuid" => $record['user_uuid']
355 , "date" => $record['date']
357 foreach ($uuidMappings as $code => $uuid) {
358 if (!$this->isVitalSignPanelCodes($code)) { // we will skip over our vital signs code, and any pediatric stuff
361 $vitalsRecord["members"][$code] = UuidRegistry
::uuidToString($uuid);
363 $processingResult->addData($vitalsRecord);
364 unset($observationCodesToReturn[self
::VITALS_PANEL_LOINC_CODE
]);
366 (new SystemLogger())->error("FhirVitalsService->parseVitalsIntoObservationRecords() Cannot return vitals panel as mapping uuid is missing for code " . self
::VITALS_PANEL_LOINC_CODE
);
369 foreach ($observationCodesToReturn as $code) {
372 ,"description" => $this->getDescriptionForCode($code)
373 ,"category" => self
::CATEGORY
374 , "puuid" => $record['puuid']
375 , "euuid" => $record['euuid']
376 , "user_uuid" => $record['user_uuid']
377 ,"uuid" => UuidRegistry
::uuidToString($uuidMappings[$code])
378 ,"date" => $record['date']
381 $columns = $this->getColumnsForCode($code);
382 $columns[] = 'details'; // make sure to grab our detail columns
383 // if any value of the column is populated we will return that the record has a value.
384 foreach ($columns as $column) {
385 if (isset($record[$column]) && $record[$column] != "") {
386 $vitalsRecord[$column] = $record[$column];
389 $processingResult->addData($vitalsRecord);
393 private function getVitalSignsUuidMappings($uuid)
395 $mappedRecords = UuidMapping
::getMappedRecordsForTableUUID($uuid);
397 if (!empty($mappedRecords)) {
398 foreach ($mappedRecords as $record) {
399 $resourcePath = $record['resource_path'] ??
'';
400 $code = $this->getCodeFromResourcePath($resourcePath);
402 // TODO: @adunsulag handle this exception
405 $codeMappings[$code] = $record['uuid'];
408 return $codeMappings;
413 * Parses a FHIR Resource, returning the equivalent OpenEMR record.
415 * @param $fhirResource The source FHIR resource
416 * @return a mapped OpenEMR data record (array)
418 public function parseFhirResource($fhirResource = array())
424 * Parses an OpenEMR data record, returning the equivalent FHIR Resource
426 * @param $dataRecord The source OpenEMR data record
427 * @param $encode Indicates if the returned resource is encoded into a string. Defaults to True.
428 * @return the FHIR Resource. Returned format is defined using $encode parameter.
430 public function parseOpenEMRRecord($dataRecord = array(), $encode = false)
432 $observation = new FHIRObservation();
433 $meta = new FHIRMeta();
434 $meta->setVersionId('1');
435 $meta->setLastUpdated(gmdate('c'));
436 $observation->setMeta($meta);
439 $id->setValue($dataRecord['uuid']);
440 $observation->setId($id);
442 if (!empty($dataRecord['date'])) {
443 $observation->setEffectiveDateTime(gmdate('c', strtotime($dataRecord['date'])));
445 $observation->setEffectiveDateTime(UtilsService
::createDataMissingExtension());
448 $code = $dataRecord['code'];
449 $description = $this->getDescriptionForCode($code);
451 $categoryCoding = new FHIRCoding();
452 $categoryCode = new FHIRCodeableConcept();
453 if (!empty($dataRecord['code'])) {
454 $categoryCoding->setCode($dataRecord['code']);
455 $categoryCoding->setDisplay($description);
456 $categoryCoding->setSystem(FhirCodeSystemConstants
::LOINC
);
457 $categoryCode->addCoding($categoryCoding);
458 $observation->setCode($categoryCode);
462 $observation->setStatus(self
::VITALS_DEFAULT_OBSERVATION_STATUS
);
464 // TODO: @adunsulag if our provenance needs to be more detailed we can use performer to set the user
466 $obsConcept = new FHIRCodeableConcept();
467 $obsCategoryCoding = new FhirCoding();
468 $obsCategoryCoding->setSystem(FhirCodeSystemConstants
::HL7_OBSERVATION_CATEGORY
);
469 $obsCategoryCoding->setCode($dataRecord['category']);
470 $obsConcept->addCoding($obsCategoryCoding);
471 $observation->addCategory($obsConcept);
473 $observation->setSubject(UtilsService
::createRelativeReference("Patient", $dataRecord['puuid']));
475 if (!empty($dataRecord['notes'])) {
476 $observation->addNote(['text' => $dataRecord['notes']]);
480 "9279-1" => 'respiration', "8867-4" => 'pulse', '2708-6' => 'oxygen_saturation', '8310-5' => 'temperature'
481 ,'8302-2' => 'height', '9843-4' => 'head_circ', '29463-7' => 'weight', '39156-5' => 'BMI'
482 ,'59576-9' => 'ped_bmi', '8289-1' => 'ped_head_circ', '77606-2' => 'ped_weight_height'
485 if (isset($basic_codes[$code])) {
486 $this->populateBasicQuantityObservation($basic_codes[$code], $observation, $dataRecord);
488 // more complicated codes
490 case self
::VITALS_PANEL_LOINC_CODE
: // vital-signs panel
491 $this->populateVitalSignsPanelObservation($observation, $dataRecord);
494 $this->populateBodyTemperatureLocation($observation, $dataRecord);
496 case '85354-9': // blood pressure panel that includes systolic & diastolic pressure
497 $this->populateBloodPressurePanel($observation, $dataRecord);
500 $this->populateComponentColumn(
505 $this->getDescriptionForCode('8480-6')
509 $this->populateComponentColumn(
514 $this->getDescriptionForCode('8462-4')
518 $this->populateCoding($observation, '59408-5');
521 $this->populatePulseOximetryObservation($observation, $dataRecord);
527 private function getColumnsForCode($code)
529 $codeMapping = self
::COLUMN_MAPPINGS
[$code] ??
null;
530 if (isset($codeMapping)) {
531 return is_array($codeMapping['column']) ?
$codeMapping['column'] : [$codeMapping['column']];
536 private function getDescriptionForCode($code)
538 $codeMapping = self
::COLUMN_MAPPINGS
[$code] ??
null;
539 if (isset($codeMapping)) {
540 return $codeMapping['description'];
545 private function populateCoding(FHIRObservation
$observation, $code)
547 // add additional oxygen-saturation coding
548 $oxSaturation = new FHIRCoding();
549 $oxSaturation->setCode($code);
550 $oxSaturation->setDisplay($this->getDescriptionForCode($code));
551 $oxSaturation->setSystem(FhirCodeSystemConstants
::LOINC
);
553 $observation->getCode()->addCoding($oxSaturation);
556 private function populatePulseOximetryObservation(FHIRObservation
$observation, $dataRecord)
558 $this->populateCoding($observation, '2708-6');
560 $this->columnHasPositiveFloatValue('oxygen_flow_rate', $dataRecord)
561 ||
$this->columnHasPositiveFloatValue('oxygen_saturation', $dataRecord)
563 $this->populateComponentColumn(
568 $this->getDescriptionForCode('3151-8')
570 $this->populateComponentColumn(
575 // only place this is used.
576 'Oxygen saturation in Arterial blood'
579 $observation->setDataAbsentReason(UtilsService
::createDataAbsentUnknownCodeableConcept());
583 private function populateVitalSignsPanelObservation(FHIRObservation
$observation, $record)
585 if (!empty($record['members'])) {
586 foreach ($record['members'] as $code => $uuid) {
587 $reference = UtilsService
::createRelativeReference("Observation", $uuid);
588 $reference->setDisplay($this->getDescriptionForCode($code));
589 $observation->addHasMember($reference);
594 private function isVitalSignPanelCodes($code)
596 $codeMapping = self
::COLUMN_MAPPINGS
[$code] ??
null;
597 if (isset($codeMapping)) {
598 return $codeMapping['in_vitals_panel'];
603 private function populateBasicQuantityObservation($column, FHIRObservation
$observation, $record)
605 $quantity = $this->getFHIRQuantityForColumn($column, $record);
606 if ($quantity != null) {
607 $observation->setValueQuantity($quantity);
609 $observation->setDataAbsentReason(UtilsService
::createDataAbsentUnknownCodeableConcept());
612 if (isset($record['details'][$column])) {
613 $observation->addInterpretation($this->getInterpretationForColumn($record, $column));
617 private function getInterpretationForColumn($record, $column): ?FHIRCodeableConcept
619 if (isset($record['details'][$column])) {
620 $code = $record['details'][$column]['interpretation_codes'];
621 $text = $record['details'][$column]['interpretation_title'];
622 return UtilsService
::createCodeableConcept([$code => $text], FhirCodeSystemConstants
::HL7_V3_OBSERVATION_INTERPRETATION
);
627 private function getFHIRQuantityForColumn($column, $record)
629 if ($this->columnHasPositiveFloatValue($column, $record)) {
630 $quantity = new FHIRQuantity();
631 $quantity->setValue(floatval($record[$column]));
632 $quantity->setSystem(FhirCodeSystemConstants
::UNITS_OF_MEASURE
);
633 $unit = $record[$column . '_unit'] ??
null;
635 // @see http://hl7.org/fhir/R4/observation-vitalsigns.html for the codes on this
636 if ($unit === 'in') {
638 $code = "[" . $unit . "]";
639 } else if ($unit === 'lb') {
641 $code = "[" . $unit . "]";
642 } else if ($unit === 'degF') {
643 $code = "[" . $unit . "]";
645 $quantity->setCode($code);
646 $quantity->setUnit($unit);
652 private function columnHasPositiveFloatValue($column, $record)
654 return (isset($record[$column]) && floatval($record[$column]) > 0.00);
657 private function populateBloodPressurePanel(FHIRObservation
$observation, $dataRecord)
659 $this->populateComponentColumn(
664 $this->getDescriptionForCode('8480-6')
666 $this->populateComponentColumn(
671 $this->getDescriptionForCode('8462-4')
675 private function populateComponentColumn(FHIRObservation
$observation, $dataRecord, $column, $code, $description)
677 $component = new FHIRObservationComponent();
678 $coding = UtilsService
::createCodeableConcept([$code => xlt($description)], FhirCodeSystemConstants
::LOINC
);
679 $component->setCode($coding);
680 $quantity = $this->getFHIRQuantityForColumn($column, $dataRecord);
681 if ($quantity != null) {
682 $component->setValueQuantity($quantity);
684 $component->setDataAbsentReason(UtilsService
::createDataAbsentUnknownCodeableConcept());
686 if (isset($dataRecord['details'][$column])) {
687 $component->addInterpretation($this->getInterpretationForColumn($dataRecord, $column));
689 $observation->addComponent($component);
692 private function populateBodyTemperatureLocation(FHIRObservation
$observation, $record)
694 // no guidance on how to pass this on, so we are using the value string to pass this on.
695 $observation->setValueString($record['temp_method']);
699 * Creates the Provenance resource for the equivalent FHIR Resource
701 * @param $dataRecord The source OpenEMR data record
702 * @param $encode Indicates if the returned resource is encoded into a string. Defaults to True.
703 * @return the FHIR Resource. Returned format is defined using $encode parameter.
705 public function createProvenanceResource($dataRecord, $encode = false)
707 if (!($dataRecord instanceof FHIRObservation
)) {
708 throw new \
BadMethodCallException("Data record should be correct instance class");
710 $fhirProvenanceService = new FhirProvenanceService();
711 $fhirProvenance = $fhirProvenanceService->createProvenanceForDomainResource($dataRecord);
713 return json_encode($fhirProvenance);
715 return $fhirProvenance;
719 public function getPatientContextSearchField(): FhirSearchParameterDefinition
721 return new FhirSearchParameterDefinition('patient', SearchFieldType
::REFERENCE
, [new ServiceField('puuid', ServiceField
::TYPE_UUID
)]);