7 * @link http://www.open-emr.org
8 * @author Stephen Nielson <stephen@nielson.org>
9 * @copyright Copyright (c) 2021 Stephen Nielson <stephen@nielson.org>
10 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
13 namespace OpenEMR\Services
;
15 use OpenEMR\Services\FHIR\FhirCodeSystemConstants
;
18 * Service for code type
20 class CodeTypesService
26 const CODE_TYPE_SNOMED_CT
= "SNOMED-CT";
27 const CODE_TYPE_SNOMED
= "SNOMED";
28 const CODE_TYPE_CPT4
= "CPT4";
29 const CODE_TYPE_LOINC
= "LOINC";
30 const CODE_TYPE_NUCC
= "NUCC";
31 const CODE_TYPE_RXNORM
= "RXNORM";
32 const CODE_TYPE_RXCUI
= "RXCUI";
33 const CODE_TYPE_ICD10
= 'ICD10';
34 const CODE_TYPE_OID
= array(
35 '2.16.840.1.113883.6.96' => self
::CODE_TYPE_SNOMED_CT
,
36 '2.16.840.1.113883.6.12' => self
::CODE_TYPE_CPT4
,
37 '2.16.840.1.113883.6.1' => self
::CODE_TYPE_LOINC
,
38 '2.16.840.1.113883.6.101' => self
::CODE_TYPE_NUCC
,
39 '2.16.840.1.113883.6.88' => self
::CODE_TYPE_RXNORM
,
40 '2.16.840.1.113883.6.90' => self
::CODE_TYPE_ICD10
,
45 public $installedCodeTypes;
49 private $snomedInstalled;
50 private $cpt4Installed;
51 private $rxnormInstalled;
53 public function __construct()
55 // currently, our installed code types are
57 $this->installedCodeTypes
= $code_types;
59 $this->snomedInstalled
= isset($code_types[self
::CODE_TYPE_SNOMED_CT
]);
60 $this->cpt4Installed
= isset($code_types[self
::CODE_TYPE_CPT4
]);
61 $this->rxnormInstalled
= isset($code_types[self
::CODE_TYPE_RXNORM
]);
65 * Lookup the description for a series of codes in the database. Eventually we would want to
66 * remove the global lookup_code_descriptions and use this so we can mock these codes and improve our unit test speed.
68 * @param string $codes - Is of the form "type:code;type:code; etc.".
69 * @param string $desc_detail - Can choose either the normal description('code_text') or the brief description('code_text_short').
70 * @return string - Is of the form "description;description; etc.".
71 * @see code_types.inc.php
73 public function lookup_code_description($codes, $desc_detail = "code_text"): string
78 $code_text = lookup_code_descriptions($codes, $desc_detail);
79 if (empty($code_text)) {
80 // let's return a description of code if available.
81 // sometimes depending on code, long and/or short description may not be available.
82 $desc_detail = $desc_detail == "code_text" ?
"code_text_short" : "code_text";
83 $code_text = lookup_code_descriptions($codes, $desc_detail);
90 * @return bool - Returns true if the snomed-ct codes are installed
92 public function isSnomedCodesInstalled(): bool
94 return $this->snomedInstalled
;
99 * @return bool - Returns whether the system has the cpt4 codes installed
101 public function isCPT4Installed(): bool
103 return $this->cpt4Installed
;
108 * @return bool - Returns whether the system has the rxnorm codes installed
110 public function isRXNORMInstalled(): bool
112 return $this->rxnormInstalled
;
115 public function isInstalledCodeType($codeType): bool
117 return isset($this->installedCodeTypes
[$codeType]);
122 * @param false $useOid
123 * @return string|null
125 public function getSystemForCode($code, $useOid = false)
127 $codeType = $this->getCodeTypeForCode($code);
128 if (!empty($codeType)) {
129 return $this->getSystemForCodeType($codeType);
138 public function parseCode($code)
142 if (is_string($code) && strpos($code, ":") !== false) {
143 $parts = explode(":", $code);
144 $parsedCode = $parts[1];
145 $parsedType = $parts[0];
147 return ['code' => $parsedCode, 'code_type' => $parsedType];
151 * Returns a code with the code type prefixed
153 * @param $code string The value for the code that exists in the given code_type datadatabse
154 * @param $type string The code_type that the code belongs to (SNOMED, RXCUI, ICD10, etc).
155 * @return string The fully typed code (TYPE:CODE)
157 public function getCodeWithType($code, $type, $oe_format = false)
159 if (empty($type) ||
empty($code)) {
163 $type = $this->formatCodeType($type ??
"");
165 return ($type ??
"") . ":" . ($code ??
"");
172 public function getCodeTypeForCode($code)
174 $parsedCode = $this->parseCode($code);
175 return $parsedCode['code_type'];
179 * @param string $codeType
180 * @param false $useOid
181 * @return string|null
183 public function getSystemForCodeType($codeType, $useOid = false)
188 if (self
::CODE_TYPE_SNOMED_CT
== $codeType) {
189 $system = '2.16.840.1.113883.6.96';
190 } elseif (self
::CODE_TYPE_SNOMED
== $codeType) {
191 $system = '2.16.840.1.113883.6.96';
192 } elseif (self
::CODE_TYPE_CPT4
== $codeType) {
193 $system = '2.16.840.1.113883.6.12';
194 } elseif (self
::CODE_TYPE_LOINC
== $codeType) {
195 $system = '2.16.840.1.113883.6.1';
196 } elseif (self
::CODE_TYPE_ICD10
== $codeType) {
197 $system = '2.16.840.1.113883.6.90';
198 } elseif (self
::CODE_TYPE_RXCUI
== $codeType || self
::CODE_TYPE_RXNORM
== $codeType) {
199 $system = '2.16.840.1.113883.6.88';
202 if (self
::CODE_TYPE_SNOMED_CT
== $codeType) {
203 $system = FhirCodeSystemConstants
::SNOMED_CT
;
204 } elseif (self
::CODE_TYPE_SNOMED
== $codeType) {
205 $system = FhirCodeSystemConstants
::SNOMED_CT
;
206 } elseif (self
::CODE_TYPE_NUCC
== $codeType) {
207 $system = FhirCodeSystemConstants
::NUCC_PROVIDER
;
208 } elseif (self
::CODE_TYPE_LOINC
== $codeType) {
209 $system = FhirCodeSystemConstants
::LOINC
;
210 } elseif (self
::CODE_TYPE_RXNORM
== $codeType || self
::CODE_TYPE_RXCUI
== $codeType) {
211 $system = FhirCodeSystemConstants
::RXNORM
;
218 * @param string $type
221 public function formatCodeType($type)
223 switch (strtoupper(trim($type))) {
234 if (!$this->isSnomedCodesInstalled()) {
235 $type = 'SNOMED CT'; // for valueset table lookups
241 if (!$this->isRXNORMInstalled() && $this->isInstalledCodeType('RXCUI')) {
242 $type = 'RXCUI'; // then let's use RxCUI for lookups
252 if (strpos($type, '2.16.840.1.113883.') !== false) {
253 $type = $this->getCodeTypeFromSystem($type);
259 public function getCodeTypeFromSystem($oid): string
261 return self
::CODE_TYPE_OID
[$oid] ??
'';
265 * Returns a resolved code set including using valueset table to try and get a code set.
269 * @param string $currentCodeText
270 * @param string $codeDescriptionType
273 public function resolveCode($code, $codeType, $currentCodeText = '', $codeDescriptionType = 'code_text')
278 'code' => $code ??
'',
279 'formatted_code' => $code . ':' . $codeType,
280 'formatted_code_type' => $codeType ??
'',
281 'code_text' => $currentCodeText,
284 'valueset_name' => ''
287 $default['formatted_code'] = '';
291 $formatted_type = $this->formatCodeType($codeType ?
: '');
292 $oid = $this->getSystemForCodeType($formatted_type, true);
293 $formatted_code = $this->getCodeWithType($code ??
'', $formatted_type);
294 if (empty($currentCodeText) && $this->isInstalledCodeType($formatted_type)) {
295 $currentCodeText = $this->lookup_code_description($formatted_code, $codeDescriptionType);
298 // use valueset table if code description not found.
299 if (empty($currentCodeText)) {
300 if (strpos($codeType, '2.16.840.1.113883.') !== false) {
301 $oid = trim($codeType);
304 $value = $this->lookupFromValueset($code, $formatted_type, $oid);
305 $formatted_type = $value['code_type'] ?
: $formatted_type;
306 if (!empty($code) && !empty($formatted_type)) {
307 $formatted_code = $formatted_type . ':' . $code;
309 $oid = $value['code_system'];
310 $currentCodeText = $value['description'];
311 $valueset_name = $value['valueset_name'];
312 $valueset = $value['valueset'];
316 'code' => $code ??
"",
317 'formatted_code' => $formatted_code ?
: $code,
318 'formatted_code_type' => $formatted_type ?
: $codeType,
319 'code_text' => trim($currentCodeText),
320 'system_oid' => $oid ??
"",
321 'valueset' => $valueset ??
"",
322 'valueset_name' => $valueset_name ??
""
326 public function getInstalledCodeTypes()
328 return $this->installedCodeTypes ??
null;
331 public function lookupFromValueset($code, $codeType, $codeSystem)
334 "Select * From valueset Where code = ? And (code_type = ? Or code_type LIKE ? Or code_system = ?)",
335 array($code, $codeType, "$codeType%", $codeSystem)