Support flexiblility in code types utilized in claims
[openemr.git] / custom / code_types.inc.php
blob07d230c4f54476ab2734fcc1f31008f29429c47f
1 <?php
2 // Copyright (C) 2006-2010 Rod Roark <rod@sunsetsystems.com>
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
9 // This array provides abstraction of billing code types. This is desirable
10 // because different countries or fields of practice use different methods for
11 // coding diagnoses, procedures and supplies. Fees will not be relevant where
12 // medical care is socialized. Attribues are:
14 // id - the numeric identifier of this code type in the codes table
15 // fee - 1 if fees are used, else 0
16 // mod - the maximum length of a modifier, 0 if modifiers are not used
17 // just - the code type used for justification, empty if none
18 // rel - 1 if other billing codes may be "related" to this code type
19 // nofs - 1 if this code type should NOT appear in the Fee Sheet
20 // diag - 1 if this code type is for diagnosis
21 // active - 1 if this code type is activated
22 // label - label used for code type
23 // external - 0 for storing codes in the code table
24 // 1 for storing codes in external ICD10 tables
25 // 2 for storing codes in external SNOMED (RF1) tables
26 // 3 for storing codes in external SNOMED (RF2) tables
27 // 4 for storing codes in external ICD9 tables
30 /*********************************************************************
31 if ($GLOBALS['ippf_specific']) {
32 // IPPF:
33 $code_types = array(
34 'ICD9' => array('id' => 2, 'fee' => 0, 'mod' => 0, 'just' => '', 'rel' => 0, 'nofs' => 0, 'diag' => TRUE),
35 'MA' => array('id' => 12, 'fee' => 1, 'mod' => 0, 'just' => '', 'rel' => 1, 'nofs' => 0),
36 'IPPF' => array('id' => 11, 'fee' => 0, 'mod' => 0, 'just' => '', 'rel' => 0, 'nofs' => 1),
37 'ACCT' => array('id' => 13, 'fee' => 0, 'mod' => 0, 'just' => '', 'rel' => 0, 'nofs' => 1),
39 $default_search_type = 'MA';
41 else if ($GLOBALS['athletic_team']) {
42 // UK Sports Medicine:
43 $code_types = array(
44 'OSICS10' => array('id' => 9, 'fee' => 0, 'mod' => 4, 'just' => '', 'rel' => 0, 'nofs' => 0, 'diag' => TRUE),
45 'OPCS' => array('id' => 6, 'fee' => 0, 'mod' => 0, 'just' => '', 'rel' => 0, 'nofs' => 0),
46 'PTCJ' => array('id' => 7, 'fee' => 0, 'mod' => 0, 'just' => '', 'rel' => 0, 'nofs' => 0),
47 'CPT4' => array('id' => 1, 'fee' => 0, 'mod' => 0, 'just' => '', 'rel' => 0, 'nofs' => 0),
48 'SMPC' => array('id' => 10, 'fee' => 0, 'mod' => 0, 'just' => '', 'rel' => 0, 'nofs' => 0),
50 $default_search_type = 'OSICS10';
52 else {
53 // USA Clinics:
54 $code_types = array(
55 'ICD9' => array('id' => 2, 'fee' => 0, 'mod' => 2, 'just' => '' , 'rel' => 0, 'nofs' => 0, 'diag' => TRUE),
56 'CPT4' => array('id' => 1, 'fee' => 1, 'mod' => 2, 'just' => 'ICD9', 'rel' => 0, 'nofs' => 0),
57 'HCPCS' => array('id' => 3, 'fee' => 1, 'mod' => 2, 'just' => 'ICD9', 'rel' => 0, 'nofs' => 0),
59 $default_search_type = 'ICD9';
61 *********************************************************************/
63 $cd_external_options = array(
64 '0' => xl('No'),
65 '1' => xl('ICD10'),
66 '2' => xl('SNOMED (RF1)'),
67 '3' => xl('SNOMED (RF2)'),
68 '4' => xl('ICD9')
71 // Code types are now stored in the database.
73 $code_types = array();
74 $default_search_type = '';
75 $ctres = sqlStatement("SELECT * FROM code_types WHERE ct_active=1 ORDER BY ct_seq, ct_key");
76 while ($ctrow = sqlFetchArray($ctres)) {
77 $code_types[$ctrow['ct_key']] = array(
78 'id' => $ctrow['ct_id' ],
79 'fee' => $ctrow['ct_fee' ],
80 'mod' => $ctrow['ct_mod' ],
81 'just' => $ctrow['ct_just'],
82 'rel' => $ctrow['ct_rel' ],
83 'nofs' => $ctrow['ct_nofs'],
84 'diag' => $ctrow['ct_diag'],
85 'mask' => $ctrow['ct_mask'],
86 'label'=> ( (empty($ctrow['ct_label'])) ? $ctrow['ct_key'] : $ctrow['ct_label'] ),
87 'external'=> $ctrow['ct_external']
89 if ($default_search_type === '') $default_search_type = $ctrow['ct_key'];
92 /********************************************************************/
94 function fees_are_used() {
95 global $code_types;
96 foreach ($code_types as $value) { if ($value['fee']) return true; }
97 return false;
100 function modifiers_are_used($fee_sheet=false) {
101 global $code_types;
102 foreach ($code_types as $value) {
103 if ($fee_sheet && !empty($value['nofs'])) continue;
104 if ($value['mod']) return true;
106 return false;
109 function related_codes_are_used() {
110 global $code_types;
111 foreach ($code_types as $value) { if ($value['rel']) return true; }
112 return false;
115 // Convert a code type id to a key
116 function convert_type_id_to_key($id) {
117 global $code_types;
118 foreach ($code_types as $key => $value) {
119 if ($value['id'] == $id) return $key;
123 // Main code set searching function
124 // $form_code_type - code set key (special keywords are PROD and --ALL--)
125 // $search_term - search term
126 // $count - if true, then will only return the number of entries
127 // $active - if true, then will only return active entries (not pertinent for PROD or external code sets)
128 // $return_only_one - if true, then will only return one perfect matching item
129 // $start - Query start limit
130 // $number - Query number returned
131 // $filter_elements - Array that contains elements to filter
132 function code_set_search($form_code_type,$search_term="",$count=false,$active=true,$return_only_one=false,$start=NULL,$number=NULL,$filter_elements=array()) {
133 global $code_types;
135 $limit_query = '';
136 if ( !is_null($start) && !is_null($number) ) {
137 $limit_query = " LIMIT $start, $number ";
139 if ($return_only_one) {
140 $limit_query = " LIMIT 1 ";
143 // build the filter_elements sql code
144 $query_filter_elements="";
145 if (!empty($filter_elements)) {
146 foreach ($filter_elements as $key => $element) {
147 $query_filter_elements .= " AND codes." . add_escape_custom($key) . "=" . "'" . add_escape_custom($element) . "' ";
151 if ($form_code_type == 'PROD') { // Search for products/drugs
152 $query = "SELECT dt.drug_id, dt.selector, d.name " .
153 "FROM drug_templates AS dt, drugs AS d WHERE " .
154 "( d.name LIKE ? OR " .
155 "dt.selector LIKE ? ) " .
156 "AND d.drug_id = dt.drug_id " .
157 "ORDER BY d.name, dt.selector, dt.drug_id $limit_query";
158 $res = sqlStatement($query, array("%".$search_term."%", "%".$search_term."%") );
160 else if ($form_code_type == '--ALL--') { // Search all codes from the default codes table
161 // Note this will not search the external code sets
162 $active_query = '';
163 if ($active) {
164 // Only filter for active codes
165 $active_query=" AND codes.active = 1 ";
167 $query = "SELECT `id`, `code_text`, `code_text_short`, `code`, `code_type`, `modifier`, `units`, `fee`, " .
168 "`superbill`, `related_code`, `taxrates`, `cyp_factor`, `active`, `reportable`, `financial_reporting`, " .
169 "code_types.ct_key as code_type_name " .
170 "FROM `codes` " .
171 "LEFT OUTER JOIN `code_types` " .
172 "ON codes.code_type = code_types.ct_id " .
173 "WHERE (codes.code_text LIKE ? OR " .
174 "codes.code LIKE ?) AND code_types.ct_external = '0' " .
175 " $active_query " .
176 " $query_filter_elements " .
177 "ORDER BY code_type,code+0,code $limit_query";
178 $res = sqlStatement($query, array("%".$search_term."%", "%".$search_term."%") );
180 else if ( !($code_types[$form_code_type]['external']) ) { // Search from default codes table
181 $active_query = '';
182 if ($active) {
183 // Only filter for active codes
184 $active_query=" AND codes.active = 1 ";
186 $sql_bind_array = array();
187 $query = "SELECT `id`, `code_text`, `code_text_short`, `code`, `code_type`, `modifier`, `units`, `fee`, " .
188 "`superbill`, `related_code`, `taxrates`, `cyp_factor`, `active`, `reportable`, `financial_reporting`, " .
189 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
190 "FROM `codes` ";
191 if ($return_only_one) {
192 $query .= "WHERE codes.code = ? ";
193 array_push($sql_bind_array,$search_term);
195 else {
196 $query .= "WHERE (codes.code_text LIKE ? OR codes.code LIKE ?) ";
197 array_push($sql_bind_array,"%".$search_term."%", "%".$search_term."%");
199 $query .= "AND code_type = ? $active_query $query_filter_elements " .
200 "ORDER BY code+0,code $limit_query";
201 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
202 $res = sqlStatement($query,$sql_bind_array);
204 else if ($code_types[$form_code_type]['external'] == 1 ) { // Search from ICD10 codeset tables
205 $active_query = '';
206 if ($active) {
207 // Only filter for active codes
208 // If there is no entry in codes sql table, then default to active
209 // (this is reason for including NULL below)
210 $active_query=" AND (codes.active = 1 || codes.active IS NULL) ";
212 // Ensure the icd10_dx_order_code sql table exists
213 $check_table = sqlQuery("SHOW TABLES LIKE 'icd10_dx_order_code'");
214 if ( !(empty($check_table)) ) {
215 $sql_bind_array = array();
216 $query = "SELECT icd10_dx_order_code.formatted_dx_code as code, icd10_dx_order_code.long_desc as code_text, " .
217 "codes.id, codes.code_type, codes.modifier, codes.units, codes.fee, " .
218 "codes.superbill, codes.related_code, codes.taxrates, codes.cyp_factor, codes.active, codes.reportable, codes.financial_reporting, " .
219 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
220 "FROM `icd10_dx_order_code` " .
221 "LEFT OUTER JOIN `codes` " .
222 "ON icd10_dx_order_code.formatted_dx_code = codes.code AND codes.code_type = ? ";
223 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
224 if ($return_only_one) {
225 $query .= "WHERE icd10_dx_order_code.formatted_dx_code = ? AND icd10_dx_order_code.valid_for_coding = '1' $active_query $query_filter_elements ";
226 array_push($sql_bind_array,$search_term);
228 else {
229 $query .= "WHERE (icd10_dx_order_code.long_desc LIKE ? OR icd10_dx_order_code.formatted_dx_code LIKE ?) AND icd10_dx_order_code.valid_for_coding = '1' $active_query $query_filter_elements";
230 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
232 $query .= "ORDER BY `formatted_dx_code`+0, `formatted_dx_code` $limit_query";
233 $res = sqlStatement($query,$sql_bind_array);
236 else if ($code_types[$form_code_type]['external'] == 2 ) { // Search from SNOMED (RF1) codeset tables
237 if ($active) {
238 // Only filter for active codes
239 // If there is no entry in codes sql table, then default to active
240 // (this is reason for including NULL below)
241 $active_query=" AND (codes.active = 1 || codes.active IS NULL) ";
243 // Ensure the sct_concepts sql table exists
244 $check_table = sqlQuery("SHOW TABLES LIKE 'sct_concepts'");
245 if ( !(empty($check_table)) ) {
246 $sql_bind_array = array();
247 $query = "SELECT `ConceptId` as code, `FullySpecifiedName` as code_text, " .
248 "codes.id, codes.code_type, codes.modifier, codes.units, codes.fee, " .
249 "codes.superbill, codes.related_code, codes.taxrates, codes.cyp_factor, codes.active, codes.reportable, codes.financial_reporting, " .
250 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
251 "FROM `sct_concepts` " .
252 "LEFT OUTER JOIN `codes` " .
253 "ON sct_concepts.ConceptId = codes.code AND codes.code_type = ? ";
254 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
255 if ($return_only_one) {
256 $query .= "WHERE (`ConceptId` = ? AND `FullySpecifiedName` LIKE '%(disorder)') $active_query $query_filter_elements ";
257 array_push($sql_bind_array,$search_term);
259 else {
260 $query .= "WHERE ((`FullySpecifiedName` LIKE ? OR `ConceptId` LIKE ?) AND `FullySpecifiedName` LIKE '%(disorder)') $active_query $query_filter_elements ";
261 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
263 $query .= "AND `ConceptStatus` = 0 " .
264 "ORDER BY `ConceptId` $limit_query";
265 $res = sqlStatement($query,$sql_bind_array);
268 else if ($code_types[$form_code_type]['external'] == 3 ) { // Search from SNOMED (RF2) codeset tables
269 //placeholder
271 else if ($code_types[$form_code_type]['external'] == 4 ) { // Search from ICD9 codeset tables
272 if ($active) {
273 // Only filter for active codes
274 // If there is no entry in codes sql table, then default to active
275 // (this is reason for including NULL below)
276 $active_query=" AND (codes.active = 1 || codes.active IS NULL) ";
278 // Ensure the icd9_dx_code sql table exists
279 $check_table = sqlQuery("SHOW TABLES LIKE 'icd9_dx_code'");
280 if ( !(empty($check_table)) ) {
281 $sql_bind_array = array();
282 $query = "SELECT icd9_dx_code.formatted_dx_code as code, icd9_dx_code.long_desc as code_text, " .
283 "codes.id, codes.code_type, codes.modifier, codes.units, codes.fee, " .
284 "codes.superbill, codes.related_code, codes.taxrates, codes.cyp_factor, codes.active, codes.reportable, codes.financial_reporting, " .
285 "'" . add_escape_custom($form_code_type) . "' as code_type_name " .
286 "FROM `icd9_dx_code` " .
287 "LEFT OUTER JOIN `codes` " .
288 "ON icd9_dx_code.formatted_dx_code = codes.code AND codes.code_type = ? ";
289 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
290 if ($return_only_one) {
291 $query .= "WHERE icd9_dx_code.formatted_dx_code = ? $active_query $query_filter_elements ";
292 array_push($sql_bind_array,$search_term);
294 else {
295 $query .= "WHERE (icd9_dx_code.long_desc LIKE ? OR icd9_dx_code.formatted_dx_code LIKE ?) $active_query $query_filter_elements ";
296 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
298 $query .= "ORDER BY `formatted_dx_code`+0, `formatted_dx_code` $limit_query";
299 $res = sqlStatement($query,$sql_bind_array);
302 else {
303 //using an external code that is not yet supported, so skip.
305 if (isset($res)) {
306 if ($count) {
307 // just return the count
308 return sqlNumRows($res);
310 else {
311 // return the data
312 return $res;
317 // Look up descriptions for one or more billing codes. Input is of the
318 // form "type:code;type:code; etc.".
320 function lookup_code_descriptions($codes) {
321 global $code_types;
322 $code_text = '';
323 if (!empty($codes)) {
324 $relcodes = explode(';', $codes);
325 foreach ($relcodes as $codestring) {
326 if ($codestring === '') continue;
327 list($codetype, $code) = explode(':', $codestring);
328 if ( !($code_types[$codetype]['external']) ) { // Collect from default codes table
329 $wheretype = "";
330 $sqlArray = array();
331 if (empty($code)) {
332 $code = $codetype;
333 } else {
334 $wheretype = "code_type = ? AND ";
335 array_push($sqlArray,$code_types[$codetype]['id']);
337 $sql = "SELECT code_text FROM codes WHERE " .
338 "$wheretype code = ? ORDER BY id LIMIT 1";
339 array_push($sqlArray,$code);
340 $crow = sqlQuery($sql,$sqlArray);
341 if (!empty($crow['code_text'])) {
342 if ($code_text) $code_text .= '; ';
343 $code_text .= $crow['code_text'];
346 else if ($code_types[$codetype]['external'] == 1) { // Collect from ICD10 codeset tables
347 // Ensure the icd10_dx_order_code sql table exists
348 $check_table = sqlQuery("SHOW TABLES LIKE 'icd10_dx_order_code'");
349 if ( !(empty($check_table)) ) {
350 if ( !(empty($code)) ) {
351 $sql = "SELECT `long_desc` FROM `icd10_dx_order_code` " .
352 "WHERE `formatted_dx_code` = ? LIMIT 1";
353 $crow = sqlQuery($sql, array($code) );
354 if (!empty($crow['long_desc'])) {
355 if ($code_text) $code_text .= '; ';
356 $code_text .= $crow['long_desc'];
361 else if ($code_types[$codetype]['external'] == 2) { // Collect from SNOMED (RF1) codeset tables
362 // Ensure the sct_concepts sql table exists
363 $check_table = sqlQuery("SHOW TABLES LIKE 'sct_concepts'");
364 if ( !(empty($check_table)) ) {
365 if ( !(empty($code)) ) {
366 $sql = "SELECT `FullySpecifiedName` FROM `sct_concepts` " .
367 "WHERE `ConceptId` = ? AND `ConceptStatus` = 0 LIMIT 1";
368 $crow = sqlQuery($sql, array($code) );
369 if (!empty($crow['FullySpecifiedName'])) {
370 if ($code_text) $code_text .= '; ';
371 $code_text .= $crow['FullySpecifiedName'];
376 else if ($code_types[$codetype]['external'] == 3) { // Collect from SNOMED (RF2) codeset tables
377 //placeholder
379 else if ($code_types[$codetype]['external'] == 4) { // Collect from ICD9 codeset tables
380 // Ensure the icd9_dx_code sql table exists
381 $check_table = sqlQuery("SHOW TABLES LIKE 'icd9_dx_code'");
382 if ( !(empty($check_table)) ) {
383 if ( !(empty($code)) ) {
384 $sql = "SELECT `long_desc` FROM `icd9_dx_code` " .
385 "WHERE `formatted_dx_code` = ? LIMIT 1";
386 $crow = sqlQuery($sql, array($code) );
387 if (!empty($crow['long_desc'])) {
388 if ($code_text) $code_text .= '; ';
389 $code_text .= $crow['long_desc'];
394 else {
395 //using an external code that is not yet supported, so skip.
399 return $code_text;