7 * @link http://www.open-emr.org
8 * @author Yash Bothra <yashrajbothra786@gmail.com>
9 * @author Stephen Waite <stephen.waite@cmsvt.com>
10 * @author Vishnu Yarmaneni <vardhanvishnu@gmail.com>
11 * @author Brady Miller <brady.g.miller@gmail.com>
12 * @author Stephen Nielson snielson@discoverandchange.com
13 * @copyright Copyright (c) 2020 Yash Bothra <yashrajbothra786@gmail.com>
14 * @copyright Copyright (c) 2020, 2022 Stephen Waite <stephen.waite@cmsvt.com>
15 * @copyright Copyright (c) 2020 Vishnu Yarmaneni <vardhanvishnu@gmail.com>
16 * @copyright Copyright (c) 2021 Brady Miller <brady.g.miller@gmail.com>
17 * @copyright Copyright (c) 2022 Stephen Nielson <snielson@discoverandchange.com>
18 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
21 namespace OpenEMR\Services\FHIR
;
24 use OpenEMR\FHIR\R4\FHIRElement\FHIRIdentifier
;
25 use OpenEMR\FHIR\R4\FHIRElement\FHIRMeta
;
26 use OpenEMR\FHIR\R4\FHIRResource\FHIREncounter\FHIREncounterHospitalization
;
27 use OpenEMR\FHIR\R4\FHIRResource\FHIREncounter\FHIREncounterLocation
;
28 use OpenEMR\Services\EncounterService
;
29 use OpenEMR\Services\FHIR\FhirServiceBase
;
30 use OpenEMR\FHIR\R4\FHIRDomainResource\FHIREncounter
;
31 use OpenEMR\FHIR\R4\FHIRElement\FHIRCode
;
32 use OpenEMR\FHIR\R4\FHIRElement\FHIRId
;
33 use OpenEMR\FHIR\R4\FHIRElement\FHIRCodeableConcept
;
34 use OpenEMR\FHIR\R4\FHIRElement\FHIRCoding
;
35 use OpenEMR\FHIR\R4\FHIRElement\FHIRPeriod
;
36 use OpenEMR\FHIR\R4\FHIRResource\FHIREncounter\FHIREncounterParticipant
;
37 use OpenEMR\Services\FHIR\Traits\BulkExportSupportAllOperationsTrait
;
38 use OpenEMR\Services\FHIR\Traits\FhirBulkExportDomainResourceTrait
;
39 use OpenEMR\Services\FHIR\Traits\FhirServiceBaseEmptyTrait
;
40 use OpenEMR\Services\FHIR\Traits\PatientSearchTrait
;
41 use OpenEMR\Services\Search\FhirSearchParameterDefinition
;
42 use OpenEMR\Services\Search\SearchFieldType
;
43 use OpenEMR\Services\Search\ServiceField
;
44 use OpenEMR\Validators\ProcessingResult
;
46 class FhirEncounterService
extends FhirServiceBase
implements
47 IFhirExportableResourceService
,
48 IPatientCompartmentResourceService
,
49 IResourceUSCIGProfileService
51 use PatientSearchTrait
;
52 use FhirServiceBaseEmptyTrait
;
53 use BulkExportSupportAllOperationsTrait
;
54 use FhirBulkExportDomainResourceTrait
;
56 public const ENCOUNTER_STATUS_FINISHED
= "finished";
58 public const ENCOUNTER_TYPE_CHECK_UP
= "185349003";
59 public const ENCOUNTER_TYPE_CHECK_UP_DESCRIPTION
= "Encounter for check up (procedure)";
61 public const ENCOUNTER_PARTICIPANT_TYPE_PRIMARY_PERFORMER
= "PPRF";
62 public const ENCOUNTER_PARTICIPANT_TYPE_PRIMARY_PERFORMER_TEXT
= "Primary Performer";
64 public const ENCOUNTER_PARTICIPANT_TYPE_REFERRER
= "REF";
65 public const ENCOUNTER_PARTICIPANT_TYPE_REFERRER_TEXT
= "Referrer";
69 * @var EncounterService
71 private $encounterService;
73 public function __construct()
75 parent
::__construct();
76 $this->encounterService
= new EncounterService();
80 * Returns an array mapping FHIR Encounter Resource search parameters to OpenEMR Encounter search parameters
81 * @return array The search parameters
83 protected function loadSearchParameters()
86 '_id' => new FhirSearchParameterDefinition(
88 SearchFieldType
::TOKEN
,
92 ServiceField
::TYPE_UUID
96 'patient' => $this->getPatientContextSearchField(),
97 'date' => new FhirSearchParameterDefinition('date', SearchFieldType
::DATETIME
, ['date']),
98 '_lastUpdated' => new FhirSearchParameterDefinition('_lastUpdated', SearchFieldType
::DATETIME
, ['last_update'])
103 * Parses an OpenEMR patient record, returning the equivalent FHIR Patient Resource
104 * https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-encounter-definitions.html
106 * @param array $dataRecord The source OpenEMR data record
107 * @param boolean $encode Indicates if the returned resource is encoded into a string. Defaults to false.
108 * @return FHIREncounter
110 public function parseOpenEMRRecord($dataRecord = array(), $encode = false)
112 $encounterResource = new FHIREncounter();
114 $meta = new FHIRMeta();
115 $meta->setVersionId('1');
116 $meta->setLastUpdated((new \
DateTime($dataRecord['last_update']) )->format(DATE_ATOM
)); // stored as utc
117 $encounterResource->setMeta($meta);
120 $id->setValue($dataRecord['euuid']);
121 $encounterResource->setId($id);
123 // identifier - required
124 $identifier = new FHIRIdentifier();
125 $identifier->setValue($dataRecord['euuid']);
126 // the system is a unique urn
127 $identifier->setSystem("urn:uuid:" . strtolower($dataRecord['euuid']));
128 $encounterResource->addIdentifier($identifier);
131 $status = new FHIRCode(self
::ENCOUNTER_STATUS_FINISHED
);
132 $encounterResource->setStatus($status);
135 if (!empty($dataRecord['class_code'])) {
136 $class = new FHIRCoding();
137 $class->setSystem(FhirCodeSystemConstants
::HL7_V3_ACT_CODE
);
138 $class->setCode($dataRecord['class_code']);
139 $class->setDisplay($dataRecord['class_title']);
140 $encounterResource->setClass($class);
142 $encounterResource->setClass(UtilsService
::createDataAbsentUnknownCodeableConcept());
145 // TODO: @adunsulag check with @brady.miller and find out if this really is the only possible encounter type
146 // ... it was here originally
147 $type = UtilsService
::createCodeableConcept(
148 [self
::ENCOUNTER_TYPE_CHECK_UP
=> [
149 'code' => self
::ENCOUNTER_TYPE_CHECK_UP
150 , "description" => self
::ENCOUNTER_TYPE_CHECK_UP_DESCRIPTION
151 , "system" => FhirCodeSystemConstants
::SNOMED_CT
154 $encounterResource->addType($type);
156 // subject - required
157 if (!empty($dataRecord['puuid'])) {
158 $encounterResource->setSubject(UtilsService
::createRelativeReference('Patient', $dataRecord['puuid']));
160 $encounterResource->setSubject(UtilsService
::createDataMissingExtension());
163 // participant - must support
164 if (!empty($dataRecord['provider_uuid'])) {
165 $participant = new FHIREncounterParticipant();
166 $participant->setIndividual(
167 UtilsService
::createRelativeReference(
169 $dataRecord['provider_uuid']
172 $period = new FHIRPeriod();
173 $period->setStart(UtilsService
::getLocalDateAsUTC($dataRecord['date']));
174 $participant->setPeriod($period);
176 $participantType = UtilsService
::createCodeableConcept([
177 self
::ENCOUNTER_PARTICIPANT_TYPE_PRIMARY_PERFORMER
=>
179 'code' => self
::ENCOUNTER_PARTICIPANT_TYPE_PRIMARY_PERFORMER
180 ,'description' => self
::ENCOUNTER_PARTICIPANT_TYPE_PRIMARY_PERFORMER_TEXT
181 ,'system' => FhirCodeSystemConstants
::HL7_PARTICIPATION_TYPE
184 $participant->addType($participantType);
185 $encounterResource->addParticipant($participant);
188 // referring provider
189 if (!empty($dataRecord['referrer_uuid'])) {
190 $participant = new FHIREncounterParticipant();
191 $participant->setIndividual(
192 UtilsService
::createRelativeReference(
194 $dataRecord['referrer_uuid']
197 $period = new FHIRPeriod();
198 $period->setStart(UtilsService
::getLocalDateAsUTC($dataRecord['date']));
199 $participant->setPeriod($period);
201 $participantType = UtilsService
::createCodeableConcept([
202 self
::ENCOUNTER_PARTICIPANT_TYPE_REFERRER
=>
204 'code' => self
::ENCOUNTER_PARTICIPANT_TYPE_REFERRER
205 ,'description' => self
::ENCOUNTER_PARTICIPANT_TYPE_REFERRER_TEXT
206 ,'system' => FhirCodeSystemConstants
::HL7_PARTICIPATION_TYPE
209 $participant->addType($participantType);
210 $encounterResource->addParticipant($participant);
213 // period - must support
214 if (!empty($dataRecord['date'])) {
215 $period = new FHIRPeriod();
216 $period->setStart(UtilsService
::getLocalDateAsUTC($dataRecord['date']));
217 $encounterResource->setPeriod($period);
220 // reasonCode - must support OR must support reasonReference
221 if (!empty($dataRecord['reason'])) {
222 // Note: that we use the encounter textual representation for the reason here which is just fine as ccda
223 // uses a textual representation of this. According to HL7 chat this is just fine as epoch and
224 // other systems do it this way
225 // @see https://chat.fhir.org/#narrow/stream/179175-argonaut/topic/Encounter.20Reason.20For.20Visit
226 // (beware of link rot)
227 $reason = new FHIRCodeableConcept();
228 $reasonText = $dataRecord['reason'] ??
"";
229 $reason->setText(trim($reasonText));
230 $encounterResource->addReasonCode($reason);
232 // hospitalization - must support
234 // hospitalization.dischargeDisposition - must support
235 if (!empty($dataRecord['discharge_disposition'])) {
236 $code = $dataRecord['discharge_disposition'];
237 $text = $dataRecord['discharge_disposition_text'];
239 $hospitalization = new FHIREncounterHospitalization();
240 $hospitalization->setDischargeDisposition(UtilsService
::createCodeableConcept(
244 'description' => $text,
245 'system' => FhirCodeSystemConstants
::HL7_DISCHARGE_DISPOSITION
249 $encounterResource->setHospitalization($hospitalization);
252 // SHALL support either location.location OR serviceProvider
253 // however ONC inferno requires both serviceProvider AND location.location
254 // location.location - must support
255 // serviceProvider - must support
256 if (!empty($dataRecord['facility_uuid'])) {
257 $encounterResource->setServiceProvider(
258 UtilsService
::createRelativeReference(
260 $dataRecord['facility_uuid']
264 // grab the facility location address
265 if (!empty($dataRecord['facility_location_uuid'])) {
266 $location = new FHIREncounterLocation();
267 $location->setLocation(
268 UtilsService
::createRelativeReference(
270 $dataRecord['facility_location_uuid']
273 $encounterResource->addLocation($location);
278 return json_encode($encounterResource);
280 return $encounterResource;
284 public function createProvenanceResource($dataRecord = array(), $encode = false)
286 if (!($dataRecord instanceof FHIREncounter
)) {
287 throw new \
BadMethodCallException("Data record should be correct instance class");
289 $provenanceService = new FhirProvenanceService();
291 if (!empty($dataRecord->getParticipant())) {
292 // grab the first one for author
293 $participant = reset($dataRecord->getParticipant());
294 $author = $participant->getIndividual() ??
null;
296 $provenance = $provenanceService->createProvenanceForDomainResource($dataRecord, $author);
301 * Searches for OpenEMR records using OpenEMR search parameters
303 * @param array openEMRSearchParameters OpenEMR search fields
304 * @param $puuidBind - Optional variable to only allow visibility of the patient with this puuid.
305 * @return ProcessingResult
307 protected function searchForOpenEMRRecords($searchParam, $puuidBind = null): ProcessingResult
309 return $this->encounterService
->search($searchParam, true, $puuidBind);
313 * Returns the Canonical URIs for the FHIR resource for each of the US Core Implementation Guide Profiles that the
314 * resource implements. Most resources have only one profile, but several like DiagnosticReport and Observation
315 * has multiple profiles that must be conformed to.
316 * @see https://www.hl7.org/fhir/us/core/CapabilityStatement-us-core-server.html for the list of profiles
319 function getProfileURIs(): array
322 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter'