3 * Library and data structure to manage Code Types and code type lookups.
5 * The data structure is the $code_types array.
6 * The $code_types array is built from the code_types sql table and provides
7 * abstraction of diagnosis/billing code types. This is desirable
8 * because different countries or fields of practice use different methods for
9 * coding diagnoses, procedures and supplies. Fees will not be relevant where
10 * medical care is socialized. Attributes are:
11 * active - 1 if this code type is activated
12 * id - the numeric identifier of this code type in the codes table
13 * claim - 1 if this code type is used in claims
14 * fee - 1 if fees are used, else 0
15 * mod - the maximum length of a modifier, 0 if modifiers are not used
16 * just - the code type used for justification, empty if none
17 * rel - 1 if other billing codes may be "related" to this code type
18 * nofs - 1 if this code type should NOT appear in the Fee Sheet
19 * diag - 1 if this code type is for diagnosis
20 * proc - 1 if this code type is a procedure/service
21 * label - label used for code type
22 * external - 0 for storing codes in the code table
23 * 1 for storing codes in external ICD10 Diagnosis tables
24 * 2 for storing codes in external SNOMED (RF1) Diagnosis tables
25 * 3 for storing codes in external SNOMED (RF2) Diagnosis tables
26 * 4 for storing codes in external ICD9 Diagnosis tables
27 * 5 for storing codes in external ICD9 Procedure/Service tables
28 * 6 for storing codes in external ICD10 Procedure/Service tables
30 * Copyright (C) 2006-2010 Rod Roark <rod@sunsetsystems.com>
32 * LICENSE: This program is free software; you can redistribute it and/or
33 * modify it under the terms of the GNU General Public License
34 * as published by the Free Software Foundation; either version 2
35 * of the License, or (at your option) any later version.
36 * This program is distributed in the hope that it will be useful,
37 * but WITHOUT ANY WARRANTY; without even the implied warranty of
38 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 * GNU General Public License for more details.
40 * You should have received a copy of the GNU General Public License
41 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
44 * @author Rod Roark <rod@sunsetsystems.com>
45 * @author Brady Miller <brady@sparmy.com>
46 * @link http://www.open-emr.org
49 require_once("$srcdir/csv_like_join.php");
51 $code_types = array();
52 $default_search_type = '';
53 $ctres = sqlStatement("SELECT * FROM code_types WHERE ct_active=1 ORDER BY ct_seq, ct_key");
54 while ($ctrow = sqlFetchArray($ctres)) {
55 $code_types[$ctrow['ct_key']] = array(
56 'active' => $ctrow['ct_active' ],
57 'id' => $ctrow['ct_id' ],
58 'fee' => $ctrow['ct_fee' ],
59 'mod' => $ctrow['ct_mod' ],
60 'just' => $ctrow['ct_just'],
61 'rel' => $ctrow['ct_rel' ],
62 'nofs' => $ctrow['ct_nofs'],
63 'diag' => $ctrow['ct_diag'],
64 'mask' => $ctrow['ct_mask'],
65 'label'=> ( (empty($ctrow['ct_label'])) ?
$ctrow['ct_key'] : $ctrow['ct_label'] ),
66 'external'=> $ctrow['ct_external'],
67 'claim' => $ctrow['ct_claim'],
68 'proc' => $ctrow['ct_proc'],
70 if ($default_search_type === '') $default_search_type = $ctrow['ct_key'];
74 * This array stores the external table options. See above for $code_types array
75 * 'external' attribute for explanation of the option listings.
78 $cd_external_options = array(
80 '4' => xl('ICD9 Diagnosis'),
81 '5' => xl('ICD9 Procedure/Service'),
82 '1' => xl('ICD10 Diagnosis'),
83 '6' => xl('ICD10 Procedure/Service'),
84 '2' => xl('SNOMED (RF1) Diagnosis'),
85 '3' => xl('SNOMED (RF2) Diagnosis'),
89 * Checks is fee are applicable to any of the code types.
93 function fees_are_used() {
95 foreach ($code_types as $value) { if ($value['fee'] && $value['active']) return true; }
100 * Checks is modifiers are applicable to any of the code types.
101 * (If a code type is not set to show in the fee sheet, then is ignored)
103 * @param boolean $fee_sheet Will ignore code types that are not shown in the fee sheet
106 function modifiers_are_used($fee_sheet=false) {
108 foreach ($code_types as $value) {
109 if ($fee_sheet && !empty($value['nofs'])) continue;
110 if ($value['mod'] && $value['active']) return true;
116 * Checks if justifiers are applicable to any of the code types.
120 function justifiers_are_used() {
122 foreach ($code_types as $value) { if (!empty($value['just']) && $value['active']) return true; }
127 * Checks is related codes are applicable to any of the code types.
131 function related_codes_are_used() {
133 foreach ($code_types as $value) { if ($value['rel'] && $value['active']) return true; }
138 * Convert a code type id (ct_id) to the key string (ct_key)
143 function convert_type_id_to_key($id) {
145 foreach ($code_types as $key => $value) {
146 if ($value['id'] == $id) return $key;
151 * Return listing of pertinent and active code types.
153 * Function will return listing (ct_key) of pertinent
154 * active code types, such as diagnosis codes or procedure
155 * codes in a chosen format. Supported returned formats include
156 * as 1) an array and as 2) a comma-separated lists that has been
157 * process by urlencode() in order to place into URL address safely.
159 * @param string $category category of code types('diagnosis' or 'procedure')
160 * @param string $return_format format or returned code types ('array' or 'csv')
161 * @return string/array
163 function collect_codetypes($category,$return_format="array") {
168 foreach ($code_types as $ct_key => $ct_arr) {
169 if (!$ct_arr['active']) continue;
171 if ($category == "diagnosis") {
172 if ($ct_arr['diag']) {
173 array_push($return,$ct_key);
176 else if ($category == "procedure") {
177 if ($ct_arr['proc']) {
178 array_push($return,$ct_key);
182 //return nothing since no supported category was chosen
186 if ($return_format == "csv") {
187 //return it as a csv string
188 return csv_like_join($return);
190 else { //$return_format == "array"
197 * Main code set searching function.
199 * Function is able to search a variety of code sets. See the 'external' items in the comments at top
200 * of this page for a listing of the code sets supported. Also note that Products (using PROD as code type)
203 * @param string $form_code_type code set key (special keywords are PROD and --ALL--)
204 * @param string $search_term search term
205 * @param boolean $count if true, then will only return the number of entries
206 * @param boolean $active if true, then will only return active entries (not pertinent for PROD code sets)
207 * @param boolean $return_only_one if true, then will only return one perfect matching item
208 * @param integer $start Query start limit
209 * @param integer $number Query number returned
210 * @param array $filter_elements Array that contains elements to filter
213 function code_set_search($form_code_type,$search_term="",$count=false,$active=true,$return_only_one=false,$start=NULL,$number=NULL,$filter_elements=array()) {
217 if ( !is_null($start) && !is_null($number) ) {
218 $limit_query = " LIMIT $start, $number ";
220 if ($return_only_one) {
221 $limit_query = " LIMIT 1 ";
224 // build the filter_elements sql code
225 $query_filter_elements="";
226 if (!empty($filter_elements)) {
227 foreach ($filter_elements as $key => $element) {
228 $query_filter_elements .= " AND c." . add_escape_custom($key) . "=" . "'" . add_escape_custom($element) . "' ";
232 if ($form_code_type == 'PROD') { // Search for products/drugs
233 $query = "SELECT dt.drug_id, dt.selector, d.name " .
234 "FROM drug_templates AS dt, drugs AS d WHERE " .
235 "( d.name LIKE ? OR " .
236 "dt.selector LIKE ? ) " .
237 "AND d.drug_id = dt.drug_id " .
238 "ORDER BY d.name, dt.selector, dt.drug_id $limit_query";
239 $res = sqlStatement($query, array("%".$search_term."%", "%".$search_term."%") );
241 else if ($form_code_type == '--ALL--') { // Search all codes from the default codes table
242 // Note this will not search the external code sets
245 // Only filter for active codes
246 $active_query=" AND c.active = 1 ";
248 $query = "SELECT c.id, c.code_text, c.code_text_short, c.code, c.code_type, c.modifier, c.units, c.fee, " .
249 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, c.financial_reporting, " .
250 "ct.ct_key as code_type_name " .
251 "FROM `codes` as c " .
252 "LEFT OUTER JOIN `code_types` as ct " .
253 "ON c.code_type = ct.ct_id " .
254 "WHERE (c.code_text LIKE ? OR " .
255 "c.code LIKE ?) AND ct.ct_external = '0' " .
257 " $query_filter_elements " .
258 "ORDER BY code_type,code+0,code $limit_query";
259 $res = sqlStatement($query, array("%".$search_term."%", "%".$search_term."%") );
261 else if ( !($code_types[$form_code_type]['external']) ) { // Search from default codes table
264 // Only filter for active codes
265 $active_query=" AND c.active = 1 ";
267 $sql_bind_array = array();
268 $query = "SELECT c.id, c.code_text, c.code_text_short, c.code, c.code_type, c.modifier, c.units, c.fee, " .
269 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, c.financial_reporting, " .
270 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
271 "FROM `codes` as c ";
272 if ($return_only_one) {
273 $query .= "WHERE c.code = ? ";
274 array_push($sql_bind_array,$search_term);
277 $query .= "WHERE (c.code_text LIKE ? OR c.code LIKE ?) ";
278 array_push($sql_bind_array,"%".$search_term."%", "%".$search_term."%");
280 $query .= "AND c.code_type = ? $active_query $query_filter_elements " .
281 "ORDER BY c.code+0,c.code $limit_query";
282 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
283 $res = sqlStatement($query,$sql_bind_array);
285 else if ($code_types[$form_code_type]['external'] == 1 ) { // Search from ICD10 diagnosis codeset tables
288 // Only filter for active codes
289 // If there is no entry in codes sql table, then default to active
290 // (this is reason for including NULL below)
291 $active_query=" AND (c.active = 1 || c.active IS NULL) ";
293 // Ensure the icd10_dx_order_code sql table exists
294 $check_table = sqlQuery("SHOW TABLES LIKE 'icd10_dx_order_code'");
295 if ( !(empty($check_table)) ) {
296 $sql_bind_array = array();
297 $query = "SELECT ref.formatted_dx_code as code, ref.long_desc as code_text, " .
298 "c.id, c.code_type, c.modifier, c.units, c.fee, " .
299 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, c.financial_reporting, " .
300 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
301 "FROM `icd10_dx_order_code` as ref " .
302 "LEFT OUTER JOIN `codes` as c " .
303 "ON ref.formatted_dx_code = c.code AND c.code_type = ? ";
304 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
305 if ($return_only_one) {
306 $query .= "WHERE ref.formatted_dx_code = ? AND ref.valid_for_coding = '1' AND ref.active = '1' $active_query $query_filter_elements ";
307 array_push($sql_bind_array,$search_term);
310 $query .= "WHERE (ref.long_desc LIKE ? OR ref.formatted_dx_code LIKE ?) AND ref.valid_for_coding = '1' AND ref.active = '1' $active_query $query_filter_elements ";
311 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
313 $query .= "ORDER BY ref.formatted_dx_code+0, ref.formatted_dx_code $limit_query";
314 $res = sqlStatement($query,$sql_bind_array);
317 else if ($code_types[$form_code_type]['external'] == 2 ) { // Search from SNOMED (RF1) diagnosis codeset tables
319 // Only filter for active codes
320 // If there is no entry in codes sql table, then default to active
321 // (this is reason for including NULL below)
322 $active_query=" AND (c.active = 1 || c.active IS NULL) ";
324 // Ensure the sct_concepts sql table exists
325 $check_table = sqlQuery("SHOW TABLES LIKE 'sct_concepts'");
326 if ( !(empty($check_table)) ) {
327 $sql_bind_array = array();
328 $query = "SELECT ref.ConceptId as code, ref.FullySpecifiedName as code_text, " .
329 "c.id, c.code_type, c.modifier, c.units, c.fee, " .
330 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, c.financial_reporting, " .
331 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
332 "FROM `sct_concepts` as ref " .
333 "LEFT OUTER JOIN `codes` as c " .
334 "ON ref.ConceptId = c.code AND c.code_type = ? ";
335 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
336 if ($return_only_one) {
337 $query .= "WHERE (ref.ConceptId = ? AND ref.FullySpecifiedName LIKE '%(disorder)') $active_query $query_filter_elements ";
338 array_push($sql_bind_array,$search_term);
341 $query .= "WHERE ((ref.FullySpecifiedName LIKE ? OR ref.ConceptId LIKE ?) AND ref.FullySpecifiedName LIKE '%(disorder)') $active_query $query_filter_elements ";
342 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
344 $query .= "AND ref.ConceptStatus = 0 " .
345 "ORDER BY ref.ConceptId $limit_query";
346 $res = sqlStatement($query,$sql_bind_array);
349 else if ($code_types[$form_code_type]['external'] == 3 ) { // Search from SNOMED (RF2) diagnosis codeset tables
352 else if ($code_types[$form_code_type]['external'] == 4 ) { // Search from ICD9 diagnosis codeset tables
354 // Only filter for active codes
355 // If there is no entry in codes sql table, then default to active
356 // (this is reason for including NULL below)
357 $active_query=" AND (c.active = 1 || c.active IS NULL) ";
359 // Ensure the icd9_dx_code sql table exists
360 $check_table = sqlQuery("SHOW TABLES LIKE 'icd9_dx_code'");
361 if ( !(empty($check_table)) ) {
362 $sql_bind_array = array();
363 $query = "SELECT ref.formatted_dx_code as code, ref.long_desc as code_text, " .
364 "c.id, c.code_type, c.modifier, c.units, c.fee, " .
365 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, c.financial_reporting, " .
366 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
367 "FROM `icd9_dx_code` as ref " .
368 "LEFT OUTER JOIN `codes` as c " .
369 "ON ref.formatted_dx_code = c.code AND c.code_type = ? ";
370 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
371 if ($return_only_one) {
372 $query .= "WHERE ref.formatted_dx_code = ? AND ref.active = '1' $active_query $query_filter_elements ";
373 array_push($sql_bind_array,$search_term);
376 $query .= "WHERE (ref.long_desc LIKE ? OR ref.formatted_dx_code LIKE ?) AND ref.active = '1' $active_query $query_filter_elements ";
377 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
379 $query .= "ORDER BY ref.formatted_dx_code+0, ref.formatted_dx_code $limit_query";
380 $res = sqlStatement($query,$sql_bind_array);
383 else if ($code_types[$form_code_type]['external'] == 5 ) { // Search from ICD9 Procedure/Service codeset tables
385 // Only filter for active codes
386 // If there is no entry in codes sql table, then default to active
387 // (this is reason for including NULL below)
388 $active_query=" AND (c.active = 1 || c.active IS NULL) ";
390 // Ensure the icd9_sg_code sql table exists
391 $check_table = sqlQuery("SHOW TABLES LIKE 'icd9_sg_code'");
392 if ( !(empty($check_table)) ) {
393 $sql_bind_array = array();
394 $query = "SELECT ref.formatted_sg_code as code, ref.long_desc as code_text, " .
395 "c.id, c.code_type, c.modifier, c.units, c.fee, " .
396 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, " .
397 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
398 "FROM `icd9_sg_code` as ref " .
399 "LEFT OUTER JOIN `codes` as c " .
400 "ON ref.formatted_sg_code = c.code AND c.code_type = ? ";
401 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
402 if ($return_only_one) {
403 $query .= "WHERE ref.formatted_sg_code = ? AND ref.active = '1' $active_query ";
404 array_push($sql_bind_array,$search_term);
407 $query .= "WHERE (ref.long_desc LIKE ? OR ref.formatted_sg_code LIKE ?) AND ref.active = '1' $active_query ";
408 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
410 $query .= "ORDER BY ref.formatted_sg_code+0, ref.formatted_sg_code $limit_query";
411 $res = sqlStatement($query,$sql_bind_array);
414 else if ($code_types[$form_code_type]['external'] == 6 ) { // Search from ICD10 Procedure/Service codeset tables
417 // Only filter for active codes
418 // If there is no entry in codes sql table, then default to active
419 // (this is reason for including NULL below)
420 $active_query=" AND (c.active = 1 || c.active IS NULL) ";
422 // Ensure the icd10_dx_order_code sql table exists
423 $check_table = sqlQuery("SHOW TABLES LIKE 'icd10_pcs_order_code'");
424 if ( !(empty($check_table)) ) {
425 $sql_bind_array = array();
426 $query = "SELECT ref.pcs_code as code, ref.long_desc as code_text, " .
427 "c.id, c.code_type, c.modifier, c.units, c.fee, " .
428 "c.superbill, c.related_code, c.taxrates, c.cyp_factor, c.active, c.reportable, " .
429 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
430 "FROM `icd10_pcs_order_code` as ref " .
431 "LEFT OUTER JOIN `codes` as c " .
432 "ON ref.pcs_code = c.code AND c.code_type = ? ";
433 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
434 if ($return_only_one) {
435 $query .= "WHERE ref.pcs_code = ? AND ref.valid_for_coding = '1' AND ref.active = '1' $active_query ";
436 array_push($sql_bind_array,$search_term);
439 $query .= "WHERE (ref.long_desc LIKE ? OR ref.pcs_code LIKE ?) AND ref.valid_for_coding = '1' AND ref.active = '1' $active_query ";
440 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
442 $query .= "ORDER BY ref.pcs_code+0, ref.pcs_code $limit_query";
443 $res = sqlStatement($query,$sql_bind_array);
447 //using an external code that is not yet supported, so skip.
451 // just return the count
452 return sqlNumRows($res);
462 * Lookup Code Descriptions for one or more billing codes.
464 * Function is able to lookup code descriptions from a variety of code sets. See the 'external'
465 * items in the comments at top of this page for a listing of the code sets supported.
467 * @param string $codes Is of the form "type:code;type:code; etc.".
468 * @return string Is of the form "description;description; etc.".
470 function lookup_code_descriptions($codes) {
473 if (!empty($codes)) {
474 $relcodes = explode(';', $codes);
475 foreach ($relcodes as $codestring) {
476 if ($codestring === '') continue;
477 list($codetype, $code) = explode(':', $codestring);
478 if ( !($code_types[$codetype]['external']) ) { // Collect from default codes table
484 $wheretype = "code_type = ? AND ";
485 array_push($sqlArray,$code_types[$codetype]['id']);
487 $sql = "SELECT code_text FROM codes WHERE " .
488 "$wheretype code = ? ORDER BY id LIMIT 1";
489 array_push($sqlArray,$code);
490 $crow = sqlQuery($sql,$sqlArray);
491 if (!empty($crow['code_text'])) {
492 if ($code_text) $code_text .= '; ';
493 $code_text .= $crow['code_text'];
496 else if ($code_types[$codetype]['external'] == 1) { // Collect from ICD10 Diagnosis codeset tables
497 // Ensure the icd10_dx_order_code sql table exists
498 $check_table = sqlQuery("SHOW TABLES LIKE 'icd10_dx_order_code'");
499 if ( !(empty($check_table)) ) {
500 if ( !(empty($code)) ) {
501 // Will grab from previous inactive revisions if unable to find in current revision
502 $sql = "SELECT `long_desc` FROM `icd10_dx_order_code` " .
503 "WHERE `formatted_dx_code` = ? ORDER BY `revision` DESC 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'] == 2) { // Collect from SNOMED (RF1) Diagnosis codeset tables
513 // Ensure the sct_concepts sql table exists
514 $check_table = sqlQuery("SHOW TABLES LIKE 'sct_concepts'");
515 if ( !(empty($check_table)) ) {
516 if ( !(empty($code)) ) {
517 $sql = "SELECT `FullySpecifiedName` FROM `sct_concepts` " .
518 "WHERE `ConceptId` = ? AND `ConceptStatus` = 0 LIMIT 1";
519 $crow = sqlQuery($sql, array($code) );
520 if (!empty($crow['FullySpecifiedName'])) {
521 if ($code_text) $code_text .= '; ';
522 $code_text .= $crow['FullySpecifiedName'];
527 else if ($code_types[$codetype]['external'] == 3) { // Collect from SNOMED (RF2) Diagnosis codeset tables
530 else if ($code_types[$codetype]['external'] == 4) { // Collect from ICD9 Diagnosis codeset tables
531 // Ensure the icd9_dx_code sql table exists
532 $check_table = sqlQuery("SHOW TABLES LIKE 'icd9_dx_code'");
533 if ( !(empty($check_table)) ) {
534 if ( !(empty($code)) ) {
535 // Will grab from previous inactive revisions if unable to find in current revision
536 $sql = "SELECT `long_desc` FROM `icd9_dx_code` " .
537 "WHERE `formatted_dx_code` = ? ORDER BY `revision` DESC LIMIT 1";
538 $crow = sqlQuery($sql, array($code) );
539 if (!empty($crow['long_desc'])) {
540 if ($code_text) $code_text .= '; ';
541 $code_text .= $crow['long_desc'];
546 else if ($code_types[$codetype]['external'] == 5) { // Collect from ICD9 Procedure/Service codeset tables
547 // Ensure the icd9_dx_code sql table exists
548 $check_table = sqlQuery("SHOW TABLES LIKE 'icd9_sg_code'");
549 if ( !(empty($check_table)) ) {
550 if ( !(empty($code)) ) {
551 // Will grab from previous inactive revisions if unable to find in current revision
552 $sql = "SELECT `long_desc` FROM `icd9_sg_code` " .
553 "WHERE `formatted_sg_code` = ? ORDER BY `revision` DESC LIMIT 1";
554 $crow = sqlQuery($sql, array($code) );
555 if (!empty($crow['long_desc'])) {
556 if ($code_text) $code_text .= '; ';
557 $code_text .= $crow['long_desc'];
562 else if ($code_types[$codetype]['external'] == 6) { // Collect from ICD10 PRocedure/Service codeset tables
563 // Ensure the icd10_dx_order_code sql table exists
564 $check_table = sqlQuery("SHOW TABLES LIKE 'icd10_pcs_order_code'");
565 if ( !(empty($check_table)) ) {
566 if ( !(empty($code)) ) {
567 // Will grab from previous inactive revisions if unable to find in current revision
568 $sql = "SELECT `long_desc` FROM `icd10_pcs_order_code` " .
569 "WHERE `pcs_code` = ? ORDER BY `revision` DESC LIMIT 1";
570 $crow = sqlQuery($sql, array($code) );
571 if (!empty($crow['long_desc'])) {
572 if ($code_text) $code_text .= '; ';
573 $code_text .= $crow['long_desc'];
580 //using an external code that is not yet supported, so skip.