Improve flexibility of which code types are used in claims and other misc.
[openemr.git] / custom / code_types.inc.php
blobbc01315fbcc95865dfc21ab49b3bf74420353d18
1 <?php
2 /**
3 * Library to manage Code Types and code type lookups.
5 * The $code_types array is built from the code_types sql table and provides
6 * abstraction of diagnosis/billing code types. This is desirable
7 * because different countries or fields of practice use different methods for
8 * coding diagnoses, procedures and supplies. Fees will not be relevant where
9 * medical care is socialized. Attribues are:
10 * active - 1 if this code type is activated
11 * id - the numeric identifier of this code type in the codes table
12 * fee - 1 if fees are used, else 0
13 * mod - the maximum length of a modifier, 0 if modifiers are not used
14 * just - the code type used for justification, empty if none
15 * rel - 1 if other billing codes may be "related" to this code type
16 * nofs - 1 if this code type should NOT appear in the Fee Sheet
17 * diag - 1 if this code type is for diagnosis
18 * label - label used for code type
19 * external - 0 for storing codes in the code table
20 * 1 for storing codes in external ICD10 Diagnosis tables
21 * 2 for storing codes in external SNOMED (RF1) Diagnosis tables
22 * 3 for storing codes in external SNOMED (RF2) Diagnosis tables
23 * 4 for storing codes in external ICD9 Diagnosis tables
24 * 5 for storing codes in external ICD9 Procedure/Service tables
25 * 6 for storing codes in external ICD10 Procedure/Service tables
26 * claim - 1 if this code type is used in claims
28 * Copyright (C) 2006-2010 Rod Roark <rod@sunsetsystems.com>
30 * LICENSE: This program is free software; you can redistribute it and/or
31 * modify it under the terms of the GNU General Public License
32 * as published by the Free Software Foundation; either version 2
33 * of the License, or (at your option) any later version.
34 * This program is distributed in the hope that it will be useful,
35 * but WITHOUT ANY WARRANTY; without even the implied warranty of
36 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 * GNU General Public License for more details.
38 * You should have received a copy of the GNU General Public License
39 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
41 * @package OpenEMR
42 * @author Rod Roark <rod@sunsetsystems.com>
43 * @author Brady Miller <brady@sparmy.com>
44 * @link http://www.open-emr.org
47 /**
48 * This array stores the external table options. See above for option listings.
49 * @var array
51 $cd_external_options = array(
52 '0' => xl('No'),
53 '4' => xl('ICD9 Diagnosis'),
54 '5' => xl('ICD9 Procedure/Service'),
55 '1' => xl('ICD10 Diagnosis'),
56 '6' => xl('ICD10 Procedure/Service'),
57 '2' => xl('SNOMED (RF1) Diagnosis'),
58 '3' => xl('SNOMED (RF2) Diagnosis'),
61 /**
62 * This array is built from the code_types sql table and provides
63 * abstraction of the diagnosis/billing code types.
64 * @var array
66 $code_types = array();
67 $default_search_type = '';
68 $ctres = sqlStatement("SELECT * FROM code_types WHERE ct_active=1 ORDER BY ct_seq, ct_key");
69 while ($ctrow = sqlFetchArray($ctres)) {
70 $code_types[$ctrow['ct_key']] = array(
71 'active' => $ctrow['ct_active' ],
72 'id' => $ctrow['ct_id' ],
73 'fee' => $ctrow['ct_fee' ],
74 'mod' => $ctrow['ct_mod' ],
75 'just' => $ctrow['ct_just'],
76 'rel' => $ctrow['ct_rel' ],
77 'nofs' => $ctrow['ct_nofs'],
78 'diag' => $ctrow['ct_diag'],
79 'mask' => $ctrow['ct_mask'],
80 'label'=> ( (empty($ctrow['ct_label'])) ? $ctrow['ct_key'] : $ctrow['ct_label'] ),
81 'external'=> $ctrow['ct_external'],
82 'claim' => $ctrow['ct_claim']
84 if ($default_search_type === '') $default_search_type = $ctrow['ct_key'];
87 /**
88 * Checks is fee are applicable to any of the code types.
90 * @return boolean
92 function fees_are_used() {
93 global $code_types;
94 foreach ($code_types as $value) { if ($value['fee'] && $value['active']) return true; }
95 return false;
98 /**
99 * Checks is modifiers are applicable to any of the code types.
100 * (If a code type is not set to show in the fee sheet, then is ignored)
102 * @param boolean $fee_sheet Will ignore code types that are not shown in the fee sheet
103 * @return boolean
105 function modifiers_are_used($fee_sheet=false) {
106 global $code_types;
107 foreach ($code_types as $value) {
108 if ($fee_sheet && !empty($value['nofs'])) continue;
109 if ($value['mod'] && $value['active']) return true;
111 return false;
115 * Checks if justifiers are applicable to any of the code types.
117 * @return boolean
119 function justifiers_are_used() {
120 global $code_types;
121 foreach ($code_types as $value) { if (!empty($value['just']) && $value['active']) return true; }
122 return false;
126 * Checks is related codes are applicable to any of the code types.
128 * @return boolean
130 function related_codes_are_used() {
131 global $code_types;
132 foreach ($code_types as $value) { if ($value['rel'] && $value['active']) return true; }
133 return false;
137 * Convert a code type id (ct_id) to the key string (ct_key)
139 * @param integer $id
140 * @return string
142 function convert_type_id_to_key($id) {
143 global $code_types;
144 foreach ($code_types as $key => $value) {
145 if ($value['id'] == $id) return $key;
150 * Main code set searching function.
152 * Function is able to search a variety of code sets. See the 'external' items in the comments at top
153 * of this page for a listing of the code sets supported. Also note that Products (using PROD as code type)
154 * is also supported.
156 * @param string $form_code_type code set key (special keywords are PROD and --ALL--)
157 * @param string $search_term search term
158 * @param boolean $count if true, then will only return the number of entries
159 * @param boolean $active if true, then will only return active entries (not pertinent for PROD code sets)
160 * @param boolean $return_only_one if true, then will only return one perfect matching item
161 * @param integer $start Query start limit
162 * @param integer $number Query number returned
163 * @param array $filter_elements Array that contains elements to filter
164 * @return recordset
166 function code_set_search($form_code_type,$search_term="",$count=false,$active=true,$return_only_one=false,$start=NULL,$number=NULL,$filter_elements=array()) {
167 global $code_types;
169 $limit_query = '';
170 if ( !is_null($start) && !is_null($number) ) {
171 $limit_query = " LIMIT $start, $number ";
173 if ($return_only_one) {
174 $limit_query = " LIMIT 1 ";
177 // build the filter_elements sql code
178 $query_filter_elements="";
179 if (!empty($filter_elements)) {
180 foreach ($filter_elements as $key => $element) {
181 $query_filter_elements .= " AND c." . add_escape_custom($key) . "=" . "'" . add_escape_custom($element) . "' ";
185 if ($form_code_type == 'PROD') { // Search for products/drugs
186 $query = "SELECT dt.drug_id, dt.selector, d.name " .
187 "FROM drug_templates AS dt, drugs AS d WHERE " .
188 "( d.name LIKE ? OR " .
189 "dt.selector LIKE ? ) " .
190 "AND d.drug_id = dt.drug_id " .
191 "ORDER BY d.name, dt.selector, dt.drug_id $limit_query";
192 $res = sqlStatement($query, array("%".$search_term."%", "%".$search_term."%") );
194 else if ($form_code_type == '--ALL--') { // Search all codes from the default codes table
195 // Note this will not search the external code sets
196 $active_query = '';
197 if ($active) {
198 // Only filter for active codes
199 $active_query=" AND c.active = 1 ";
201 $query = "SELECT c.id, c.code_text, c.code_text_short, c.code, c.code_type, c.modifier, c.units, c.fee, " .
202 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, c.financial_reporting, " .
203 "ct.ct_key as code_type_name " .
204 "FROM `codes` as c " .
205 "LEFT OUTER JOIN `code_types` as ct " .
206 "ON c.code_type = ct.ct_id " .
207 "WHERE (c.code_text LIKE ? OR " .
208 "c.code LIKE ?) AND ct.ct_external = '0' " .
209 " $active_query " .
210 " $query_filter_elements " .
211 "ORDER BY code_type,code+0,code $limit_query";
212 $res = sqlStatement($query, array("%".$search_term."%", "%".$search_term."%") );
214 else if ( !($code_types[$form_code_type]['external']) ) { // Search from default codes table
215 $active_query = '';
216 if ($active) {
217 // Only filter for active codes
218 $active_query=" AND c.active = 1 ";
220 $sql_bind_array = array();
221 $query = "SELECT c.id, c.code_text, c.code_text_short, c.code, c.code_type, c.modifier, c.units, c.fee, " .
222 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, c.financial_reporting, " .
223 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
224 "FROM `codes` as c ";
225 if ($return_only_one) {
226 $query .= "WHERE c.code = ? ";
227 array_push($sql_bind_array,$search_term);
229 else {
230 $query .= "WHERE (c.code_text LIKE ? OR c.code LIKE ?) ";
231 array_push($sql_bind_array,"%".$search_term."%", "%".$search_term."%");
233 $query .= "AND c.code_type = ? $active_query $query_filter_elements " .
234 "ORDER BY c.code+0,c.code $limit_query";
235 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
236 $res = sqlStatement($query,$sql_bind_array);
238 else if ($code_types[$form_code_type]['external'] == 1 ) { // Search from ICD10 diagnosis codeset tables
239 $active_query = '';
240 if ($active) {
241 // Only filter for active codes
242 // If there is no entry in codes sql table, then default to active
243 // (this is reason for including NULL below)
244 $active_query=" AND (c.active = 1 || c.active IS NULL) ";
246 // Ensure the icd10_dx_order_code sql table exists
247 $check_table = sqlQuery("SHOW TABLES LIKE 'icd10_dx_order_code'");
248 if ( !(empty($check_table)) ) {
249 $sql_bind_array = array();
250 $query = "SELECT ref.formatted_dx_code as code, ref.long_desc as code_text, " .
251 "c.id, c.code_type, c.modifier, c.units, c.fee, " .
252 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, c.financial_reporting, " .
253 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
254 "FROM `icd10_dx_order_code` as ref " .
255 "LEFT OUTER JOIN `codes` as c " .
256 "ON ref.formatted_dx_code = c.code AND c.code_type = ? ";
257 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
258 if ($return_only_one) {
259 $query .= "WHERE ref.formatted_dx_code = ? AND ref.valid_for_coding = '1' $active_query $query_filter_elements ";
260 array_push($sql_bind_array,$search_term);
262 else {
263 $query .= "WHERE (ref.long_desc LIKE ? OR ref.formatted_dx_code LIKE ?) AND ref.valid_for_coding = '1' $active_query $query_filter_elements";
264 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
266 $query .= "ORDER BY ref.formatted_dx_code+0, ref.formatted_dx_code $limit_query";
267 $res = sqlStatement($query,$sql_bind_array);
270 else if ($code_types[$form_code_type]['external'] == 2 ) { // Search from SNOMED (RF1) diagnosis codeset tables
271 if ($active) {
272 // Only filter for active codes
273 // If there is no entry in codes sql table, then default to active
274 // (this is reason for including NULL below)
275 $active_query=" AND (c.active = 1 || c.active IS NULL) ";
277 // Ensure the sct_concepts sql table exists
278 $check_table = sqlQuery("SHOW TABLES LIKE 'sct_concepts'");
279 if ( !(empty($check_table)) ) {
280 $sql_bind_array = array();
281 $query = "SELECT ref.ConceptId as code, ref.FullySpecifiedName as code_text, " .
282 "c.id, c.code_type, c.modifier, c.units, c.fee, " .
283 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, c.financial_reporting, " .
284 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
285 "FROM `sct_concepts` as ref " .
286 "LEFT OUTER JOIN `codes` as c " .
287 "ON ref.ConceptId = c.code AND c.code_type = ? ";
288 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
289 if ($return_only_one) {
290 $query .= "WHERE (ref.ConceptId = ? AND ref.FullySpecifiedName LIKE '%(disorder)') $active_query $query_filter_elements ";
291 array_push($sql_bind_array,$search_term);
293 else {
294 $query .= "WHERE ((ref.FullySpecifiedName LIKE ? OR ref.ConceptId LIKE ?) AND ref.FullySpecifiedName LIKE '%(disorder)') $active_query $query_filter_elements ";
295 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
297 $query .= "AND ref.ConceptStatus = 0 " .
298 "ORDER BY ref.ConceptId $limit_query";
299 $res = sqlStatement($query,$sql_bind_array);
302 else if ($code_types[$form_code_type]['external'] == 3 ) { // Search from SNOMED (RF2) diagnosis codeset tables
303 //placeholder
305 else if ($code_types[$form_code_type]['external'] == 4 ) { // Search from ICD9 diagnosis codeset tables
306 if ($active) {
307 // Only filter for active codes
308 // If there is no entry in codes sql table, then default to active
309 // (this is reason for including NULL below)
310 $active_query=" AND (c.active = 1 || c.active IS NULL) ";
312 // Ensure the icd9_dx_code sql table exists
313 $check_table = sqlQuery("SHOW TABLES LIKE 'icd9_dx_code'");
314 if ( !(empty($check_table)) ) {
315 $sql_bind_array = array();
316 $query = "SELECT ref.formatted_dx_code as code, ref.long_desc as code_text, " .
317 "c.id, c.code_type, c.modifier, c.units, c.fee, " .
318 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, c.financial_reporting, " .
319 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
320 "FROM `icd9_dx_code` as ref " .
321 "LEFT OUTER JOIN `codes` as c " .
322 "ON ref.formatted_dx_code = c.code AND c.code_type = ? ";
323 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
324 if ($return_only_one) {
325 $query .= "WHERE ref.formatted_dx_code = ? $active_query $query_filter_elements ";
326 array_push($sql_bind_array,$search_term);
328 else {
329 $query .= "WHERE (ref.long_desc LIKE ? OR ref.formatted_dx_code LIKE ?) $active_query $query_filter_elements ";
330 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
332 $query .= "ORDER BY ref.formatted_dx_code+0, ref.formatted_dx_code $limit_query";
333 $res = sqlStatement($query,$sql_bind_array);
336 else if ($code_types[$form_code_type]['external'] == 5 ) { // Search from ICD9 Procedure/Service codeset tables
337 if ($active) {
338 // Only filter for active codes
339 // If there is no entry in codes sql table, then default to active
340 // (this is reason for including NULL below)
341 $active_query=" AND (c.active = 1 || c.active IS NULL) ";
343 // Ensure the icd9_sg_code sql table exists
344 $check_table = sqlQuery("SHOW TABLES LIKE 'icd9_sg_code'");
345 if ( !(empty($check_table)) ) {
346 $sql_bind_array = array();
347 $query = "SELECT ref.formatted_sg_code as code, ref.long_desc as code_text, " .
348 "c.id, c.code_type, c.modifier, c.units, c.fee, " .
349 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, " .
350 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
351 "FROM `icd9_sg_code` as ref " .
352 "LEFT OUTER JOIN `codes` as c " .
353 "ON ref.formatted_sg_code = c.code AND c.code_type = ? ";
354 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
355 if ($return_only_one) {
356 $query .= "WHERE ref.formatted_sg_code = ? $active_query ";
357 array_push($sql_bind_array,$search_term);
359 else {
360 $query .= "WHERE (ref.long_desc LIKE ? OR ref.formatted_sg_code LIKE ?) $active_query ";
361 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
363 $query .= "ORDER BY ref.formatted_sg_code+0, ref.formatted_sg_code $limit_query";
364 $res = sqlStatement($query,$sql_bind_array);
367 else if ($code_types[$form_code_type]['external'] == 6 ) { // Search from ICD10 Procedure/Service codeset tables
368 $active_query = '';
369 if ($active) {
370 // Only filter for active codes
371 // If there is no entry in codes sql table, then default to active
372 // (this is reason for including NULL below)
373 $active_query=" AND (c.active = 1 || c.active IS NULL) ";
375 // Ensure the icd10_dx_order_code sql table exists
376 $check_table = sqlQuery("SHOW TABLES LIKE 'icd10_pcs_order_code'");
377 if ( !(empty($check_table)) ) {
378 $sql_bind_array = array();
379 $query = "SELECT ref.pcs_code as code, ref.long_desc as code_text, " .
380 "c.id, c.code_type, c.modifier, c.units, c.fee, " .
381 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, " .
382 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
383 "FROM `icd10_pcs_order_code` as ref " .
384 "LEFT OUTER JOIN `codes` as c " .
385 "ON ref.pcs_code = c.code AND c.code_type = ? ";
386 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
387 if ($return_only_one) {
388 $query .= "WHERE ref.pcs_code = ? AND ref.valid_for_coding = '1' $active_query ";
389 array_push($sql_bind_array,$search_term);
391 else {
392 $query .= "WHERE (ref.long_desc LIKE ? OR ref.pcs_code LIKE ?) AND ref.valid_for_coding = '1' $active_query ";
393 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
395 $query .= "ORDER BY ref.pcs_code+0, ref.pcs_code $limit_query";
396 $res = sqlStatement($query,$sql_bind_array);
399 else {
400 //using an external code that is not yet supported, so skip.
402 if (isset($res)) {
403 if ($count) {
404 // just return the count
405 return sqlNumRows($res);
407 else {
408 // return the data
409 return $res;
415 * Lookup Code Descriptions for one or more billing codes.
417 * Function is able to lookup code descriptions from a variety of code sets. See the 'external'
418 * items in the comments at top of this page for a listing of the code sets supported.
420 * @param string $codes Is of the form "type:code;type:code; etc.".
421 * @return string Is of the form "description;description; etc.".
423 function lookup_code_descriptions($codes) {
424 global $code_types;
425 $code_text = '';
426 if (!empty($codes)) {
427 $relcodes = explode(';', $codes);
428 foreach ($relcodes as $codestring) {
429 if ($codestring === '') continue;
430 list($codetype, $code) = explode(':', $codestring);
431 if ( !($code_types[$codetype]['external']) ) { // Collect from default codes table
432 $wheretype = "";
433 $sqlArray = array();
434 if (empty($code)) {
435 $code = $codetype;
436 } else {
437 $wheretype = "code_type = ? AND ";
438 array_push($sqlArray,$code_types[$codetype]['id']);
440 $sql = "SELECT code_text FROM codes WHERE " .
441 "$wheretype code = ? ORDER BY id LIMIT 1";
442 array_push($sqlArray,$code);
443 $crow = sqlQuery($sql,$sqlArray);
444 if (!empty($crow['code_text'])) {
445 if ($code_text) $code_text .= '; ';
446 $code_text .= $crow['code_text'];
449 else if ($code_types[$codetype]['external'] == 1) { // Collect from ICD10 Diagnosis codeset tables
450 // Ensure the icd10_dx_order_code sql table exists
451 $check_table = sqlQuery("SHOW TABLES LIKE 'icd10_dx_order_code'");
452 if ( !(empty($check_table)) ) {
453 if ( !(empty($code)) ) {
454 $sql = "SELECT `long_desc` FROM `icd10_dx_order_code` " .
455 "WHERE `formatted_dx_code` = ? LIMIT 1";
456 $crow = sqlQuery($sql, array($code) );
457 if (!empty($crow['long_desc'])) {
458 if ($code_text) $code_text .= '; ';
459 $code_text .= $crow['long_desc'];
464 else if ($code_types[$codetype]['external'] == 2) { // Collect from SNOMED (RF1) Diagnosis codeset tables
465 // Ensure the sct_concepts sql table exists
466 $check_table = sqlQuery("SHOW TABLES LIKE 'sct_concepts'");
467 if ( !(empty($check_table)) ) {
468 if ( !(empty($code)) ) {
469 $sql = "SELECT `FullySpecifiedName` FROM `sct_concepts` " .
470 "WHERE `ConceptId` = ? AND `ConceptStatus` = 0 LIMIT 1";
471 $crow = sqlQuery($sql, array($code) );
472 if (!empty($crow['FullySpecifiedName'])) {
473 if ($code_text) $code_text .= '; ';
474 $code_text .= $crow['FullySpecifiedName'];
479 else if ($code_types[$codetype]['external'] == 3) { // Collect from SNOMED (RF2) Diagnosis codeset tables
480 //placeholder
482 else if ($code_types[$codetype]['external'] == 4) { // Collect from ICD9 Diagnosis codeset tables
483 // Ensure the icd9_dx_code sql table exists
484 $check_table = sqlQuery("SHOW TABLES LIKE 'icd9_dx_code'");
485 if ( !(empty($check_table)) ) {
486 if ( !(empty($code)) ) {
487 $sql = "SELECT `long_desc` FROM `icd9_dx_code` " .
488 "WHERE `formatted_dx_code` = ? LIMIT 1";
489 $crow = sqlQuery($sql, array($code) );
490 if (!empty($crow['long_desc'])) {
491 if ($code_text) $code_text .= '; ';
492 $code_text .= $crow['long_desc'];
497 else if ($code_types[$codetype]['external'] == 5) { // Collect from ICD9 Procedure/Service codeset tables
498 // Ensure the icd9_dx_code sql table exists
499 $check_table = sqlQuery("SHOW TABLES LIKE 'icd9_sg_code'");
500 if ( !(empty($check_table)) ) {
501 if ( !(empty($code)) ) {
502 $sql = "SELECT `long_desc` FROM `icd9_sg_code` " .
503 "WHERE `formatted_sg_code` = ? LIMIT 1";
504 $crow = sqlQuery($sql, array($code) );
505 if (!empty($crow['long_desc'])) {
506 if ($code_text) $code_text .= '; ';
507 $code_text .= $crow['long_desc'];
512 else if ($code_types[$codetype]['external'] == 6) { // Collect from ICD10 PRocedure/Service codeset tables
513 // Ensure the icd10_dx_order_code sql table exists
514 $check_table = sqlQuery("SHOW TABLES LIKE 'icd10_pcs_order_code'");
515 if ( !(empty($check_table)) ) {
516 if ( !(empty($code)) ) {
517 $sql = "SELECT `long_desc` FROM `icd10_dx_order_code` " .
518 "WHERE `pcs_code` = ? LIMIT 1";
519 $crow = sqlQuery($sql, array($code) );
520 if (!empty($crow['long_desc'])) {
521 if ($code_text) $code_text .= '; ';
522 $code_text .= $crow['long_desc'];
528 else {
529 //using an external code that is not yet supported, so skip.
533 return $code_text;