fix: e2e ci misc fixes (#7785)
[openemr.git] / src / Services / FHIR / FhirValueSetService.php
blobd9659511b733cf79b923d85e25279f1f84d91846
1 <?php
3 /**
4 * FhirDeviceService.php
5 * @package openemr
6 * @link http://www.open-emr.org
7 * @author Robert Jones (Analog Informatics Corporation) <robert@analoginfo.com>, <robert@justjones.org>
8 * @copyright Copyright (c) 2023 Analog Informatics Corporation <https://analoginfo.com>
9 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
12 namespace OpenEMR\Services\FHIR;
14 use OpenEMR\FHIR\R4\FHIRDomainResource\FHIRValueSet;
15 use OpenEMR\FHIR\R4\FHIRResource\FHIRValueSet\FHIRValueSetCompose;
16 use OpenEMR\FHIR\R4\FHIRResource\FHIRValueSet\FHIRValueSetInclude;
17 use OpenEMR\FHIR\R4\FHIRResource\FHIRValueSet\FHIRValueSetConcept;
18 use OpenEMR\FHIR\R4\FHIRElement\FHIRCode;
19 use OpenEMR\FHIR\R4\FHIRElement\FHIRDateTime;
20 use OpenEMR\FHIR\R4\FHIRElement\FHIRId;
21 use OpenEMR\Services\AppointmentService;
22 use OpenEMR\Services\ListService;
23 use OpenEMR\Services\FHIR\Traits\BulkExportSupportAllOperationsTrait;
24 use OpenEMR\Services\FHIR\Traits\FhirBulkExportDomainResourceTrait;
25 use OpenEMR\Services\FHIR\Traits\FhirServiceBaseEmptyTrait;
26 use OpenEMR\Services\Search\FhirSearchParameterDefinition;
27 use OpenEMR\Services\Search\SearchFieldType;
28 use OpenEMR\Services\Search\ServiceField;
29 use OpenEMR\Validators\ProcessingResult;
31 class FhirValueSetService extends FhirServiceBase implements IResourceUSCIGProfileService, IFhirExportableResourceService
33 use FhirServiceBaseEmptyTrait;
34 use BulkExportSupportAllOperationsTrait;
35 use FhirBulkExportDomainResourceTrait;
37 /**
38 * @var AppointmentService
40 private $appointmentService;
42 /**
43 * @var ListService
45 private $listOptionService;
47 /**
48 * NB: started w Device as a base, but DocumentReference is what I needed... no underlying service either
49 * pc_catid => openemr_postcalendar_categories
50 * doc category => categories
51 * facility POS code
52 * customlists ??
53 * option_lists by list_id
55 * MariaDB [openemr]> select list_id , option_id , title , seq , is_default , option_value , mapping , notes, codes from list_options limit 40;
56 * +---------------------------------+-------------------+-----------------------+-----+------------+--------------+---------+-------+-------+
57 * | list_id | option_id | title | seq | is_default | option_value | mapping | notes | codes |
58 * +---------------------------------+-------------------+-----------------------+-----+------------+--------------+---------+-------+-------+
59 * | abook_type | bill_svc | Billing Service | 120 | 0 | 3 | | NULL | |
60 * | abook_type | ccda | Care Coordination | 35 | 0 | 2 | | NULL | |
61 * | abook_type | dist | Distributor | 30 | 0 | 3 | | NULL | |
62 * | abook_type | emr_direct | EMR Direct | 105 | 0 | 4 | | NULL | |
63 * | abook_type | external_org | External Organization | 120 | 0 | 1 | | NULL | |
64 * | abook_type | external_provider | External Provider | 110 | 0 | 1 | | NULL | |
65 * | abook_type | ord_img | Imaging Service | 5 | 0 | 3 | | NULL | |
66 * | abook_type | ord_imm | Immunization Service | 10 | 0 | 3 | | NULL | |
67 * | abook_type | ord_lab | Lab Service | 15 | 0 | 3 | | NULL | |
68 * | abook_type | oth | Other | 95 | 0 | 1 | | NULL | |
69 * | abook_type | spe | Specialist | 20 | 0 | 2 | | NULL | |
70 * | abook_type | vendor | Vendor | 25 | 0 | 3 | | NULL | |
71 * | address-types | both | Postal & Physical | 30 | 0 | 0 | | NULL | |
72 * | address-types | physical | Physical | 20 | 0 | 0 | | NULL | |
73 * | address-types | postal | Postal | 10 | 0 | 0 | | NULL | |
74 * | address-uses | billing | Billing | 50 | 0 | 0 | | NULL | |
75 * | address-uses | home | Home | 10 | 0 | 0 | | NULL | |
76 * | address-uses | old | Old/Incorrect | 40 | 0 | 0 | | NULL | |
77 * | address-uses | temp | Temporary | 30 | 0 | 0 | | NULL | |
78 * | address-uses | work | Work | 20 | 0 | 0 | | NULL | |
79 * | adjreason | Adm adjust | Adm adjust | 5 | 0 | 1 | | NULL | |
80 * | adjreason | After hrs calls | After hrs calls | 10 | 0 | 1 | | NULL | |
81 * | adjreason | Bad check | Bad check | 15 | 0 | 1 | | NULL | |
82 * | adjreason | Bad debt | Bad debt | 20 | 0 | 1 | | NULL | |
83 * | adjreason | Coll w/o | Coll w/o | 25 | 0 | 1 | | NULL | |
88 const USCGI_PROFILE_URI = 'http://hl7.org/fhir/StructureDefinition/shareablevalueset';
89 const APPOINTMENT_TYPE = 'appointment-type';
91 public function __construct()
93 parent::__construct();
94 // TODO: @adunsulag we need to look at adding a mapping service here in order to get our value sets out.
95 $this->appointmentService = new AppointmentService();
96 $this->listOptionService = new ListService();
99 /**
100 * Returns an array mapping FHIR Resource search parameters to OpenEMR search parameters
102 protected function loadSearchParameters()
104 return [
105 '_id' => new FhirSearchParameterDefinition('_id', SearchFieldType::TOKEN, [new ServiceField('id', ServiceField::TYPE_STRING)]),
106 '_lastUpdated' => $this->getLastModifiedSearchField(),
110 public function getLastModifiedSearchField(): ?FhirSearchParameterDefinition
112 return new FhirSearchParameterDefinition('_lastUpdated', SearchFieldType::DATETIME, ['sublist_updated_date', 'last_updated']);
115 private function getLastModifiedSearchFieldForAppointmentCategories()
117 return new FhirSearchParameterDefinition('_lastUpdated', SearchFieldType::DATETIME, ['pc_last_updated']);
121 * Retrieves all of the fhir observation resources mapped to the underlying openemr data elements.
122 * @param $fhirSearchParameters The FHIR resource search parameters
123 * @return processing result
125 public function getAll($fhirSearchParameters, $puuidBind = null): ProcessingResult
127 $fhirSearchResult = new ProcessingResult();
128 try {
129 // we don't really deal with provenance for ValueSet pieces so we will ignore this property
130 if (isset($fhirSearchParameters['_revinclude'])) {
131 unset($fhirSearchParameters['_revinclude']);
134 $this->addAppointmentCategoriesValueSetForSearch($fhirSearchResult, $fhirSearchParameters);
136 $this->addListOptionsValueSetsForSearch($fhirSearchResult, $fhirSearchParameters, $puuidBind);
137 } catch (SearchFieldException $exception) {
138 (new SystemLogger())->errorLogCaller("search exception thrown", ['message' => $exception->getMessage(),
139 'field' => $exception->getField()]);
140 // put our exception information here
141 $fhirSearchResult->setValidationMessages([$exception->getField() => $exception->getMessage()]);
143 return $fhirSearchResult;
148 * Returns the Canonical URIs for the FHIR resource for each of the US Core Implementation Guide Profiles that the
149 * resource implements. Most resources have only one profile, but several like DiagnosticReport and Observation
150 * has multiple profiles that must be conformed to.
151 * @see https://www.hl7.org/fhir/us/core/CapabilityStatement-us-core-server.html for the list of profiles
152 * @return string[]
154 function getProfileURIs(): array
156 return [self::USCGI_PROFILE_URI];
159 private function addAppointmentCategoriesValueSetForSearch(ProcessingResult $fhirSearchResult, array $fhirSearchParameters, string $puuidBind = null)
161 $this->getSearchFieldFactory()->setSearchFieldDefinition('_lastUpdated', $this->getLastModifiedSearchFieldForAppointmentCategories());
162 $oeSearchParameters = $this->createOpenEMRSearchParameters($fhirSearchParameters, $puuidBind);
163 if (
164 !isset($oeSearchParameters['_id'])
165 // could be array (AND) or comma-delimited string value (OR)
166 // check array first but should only be len 1 ("AND", becuase cannot be 2 simultaneous)
167 || $oeSearchParameters['_id']->hasCodeValue(self::APPOINTMENT_TYPE)
169 if (!isset($oeSearchParameters['_id'])) {
170 // if we have any match on categories we want to return everything... hate the double db call
171 // but rather than mess with a complex query we will just do it this way
172 $processingResult = $this->appointmentService->searchCalendarCategories($oeSearchParameters);
173 // nothing to do here as we have no categories matching so we return
174 if (!$processingResult->hasData()) {
175 return $fhirSearchResult;
178 $calendarCategories = $this->appointmentService->getCalendarCategories();
179 $valueSet = new FHIRValueSet();
180 $valueSet->setId(self::APPOINTMENT_TYPE);
181 $compose = new FHIRValueSetCompose();
182 $include = new FHIRValueSetInclude();
183 foreach ($calendarCategories as $category) {
184 if ($category["pc_cattype"] != 0) {
185 continue; // only cat_type==0
187 $concept = new FHIRValueSetConcept();
188 $code = new FHIRCode();
189 $code->setValue($category["pc_constant_id"]);
190 $concept->setCode($code);
191 $concept->setDisplay($category["pc_catname"]);
192 $include->addConcept($concept);
194 $compose->addInclude($include);
195 $valueSet->setCompose($compose);
196 $fhirSearchResult->addData($valueSet);
198 return $fhirSearchResult;
201 private function addListOptionsValueSetsForSearch(ProcessingResult $fhirSearchResult, array $fhirSearchParameters, ?string $puuidBind = null)
203 $this->getSearchFieldFactory()->setSearchFieldDefinition('_lastUpdated', $this->getLastModifiedSearchField());
204 $oeSearchParameters = $this->createOpenEMRSearchParameters($fhirSearchParameters, $puuidBind);
206 // Now the same for list_options selected in $listNames
207 $listsResult = $this->listOptionService->searchLists($oeSearchParameters);
208 if (!$listsResult->hasData()) {
209 $fhirSearchResult->addProcessingResult($listsResult);
210 return $fhirSearchResult;
212 foreach ($listsResult->getData() as $listRecord) {
213 $listName = $listRecord["option_id"];
214 $options = $this->listOptionService->getOptionsByListName($listName); // does not return title
215 if (count($options) == 0) {
216 continue;
218 $valueSet = new FHIRValueSet();
219 $valueSet->setId($listName);
220 $compose = new FHIRValueSetCompose();
221 $include = new FHIRValueSetInclude();
222 foreach ($options as $option) {
223 $concept = new FHIRValueSetConcept();
224 $code = new FHIRCode();
225 $code->setValue($option["option_id"]);
226 $concept->setCode($code);
227 $concept->setDisplay($option["title"]);
228 $include->addConcept($concept);
230 $compose->addInclude($include);
231 $valueSet->setCompose($compose);
232 $fhirSearchResult->addData($valueSet);
234 return $fhirSearchResult;