Grouper in Action
The most important part in the clinical module is the grouper and sometimes it is also regarded as the heart of casemix. A working grouper is essential to the success of the application. In this section, I will show and explain how the grouper works in code. Once you understand the basic mechanism of the grouper additional rules or modification can be made to suit your need.
Back to casemix main page
Requirement
- Python3
- Jupyter Notebook (optional)
Source Code
Download source at GitHub
First and foremost you need to download and restore the database included in the repository. Just run the opencasemix.sql and start your notebook.
Let’s first establish connection to the database:
import sqlalchemy as db
engine = db.create_engine('mysql+mysqlconnector://username:password@ipaddress:3306/opencasemix')
con = engine.connect()
con
Notes
- Before running make sure you change the connection paramters (username, password and ipaddress).
Output:
<sqlalchemy.engine.base.Connection at 0x7ff8f8c66e20>
Below are functions required by the grouper.
def getDRG(code):
sql = "select * from drg where drg_code='%s'"%(code)
result = engine.execute(sql, code=code)
drg = result.fetchone()
return drg
def getPrincipalDiagnosis(diagnoses):
code = diagnoses[0] if diagnoses is not None else None
return code
def getMedicalDRG(cast_code, gender, age):
gender_code = gender if gender is not None else ""
sql_age = ""
if (age>28):
sql_age = "and mdc_code<>'15'"
sql = """
select base_name, a.base_code
from drg_cast a
left join drg_bases b on (a.base_code = b.base_code)
where cast_code = '%s'
and (b.gender_code= '%s' or b.gender_code is null)
%s
""" %(cast_code, gender_code, sql_age)
drg_bases = engine.execute(sql).fetchall()
if drg_bases:
return drg_bases[0]['base_code']
else:
return None
def getPrincipalProcedure(procedures):
code = procedures[0] if procedures is not None else None
return code
def getSurgicalDRG(cast_code, gender,age):
gender_code = gender if gender is not None else ""
sql_age = ""
if (age>28):
sql_age = "and mdc_code<>'15'"
sql = """
select base_name, a.base_code
from drg_cast a
left join drg_bases b on (a.base_code = b.base_code)
where cast_code = '%s'
and (b.gender_code= '%s' or b.gender_code is null)
%s
""" %(cast_code, gender_code, sql_age)
drg_bases = engine.execute(sql).fetchall()
if drg_bases:
return drg_bases[0]['base_code']
else:
return None
def getSeverity(diagnoses):
# Complication or comorbidities (cc)
cc = ['H33.3']
# Major complication or comorbidities (ccm)
mcc = ['A10.1']
drg_severity = 1
# Has diagnosis in CC or MCC ?
# Remove principal diagnosis from the list
diagnoses.pop(0)
for diagnosis in diagnoses:
if diagnosis in cc:
drg_severity = 2
if diagnosis in mcc:
drg_severity = 3
return drg_severity
Notes
- Inside the code you may notice the exclusion of MDC15 when certain condition are met. This is simply because MDC15 is limited only to newborn and neonates and cannot be applied to cases outside the age limit. Hence the age required to be in days.
- cc and mcc array inside getSeverity function is a list of ICD10 codes to determine the severity of the case. ICD10 codes inside CC will set the outcome to SOI2 while MCC will set to SOI3. If the ICD10 code does not fall into any one of them it will be considered as SOI1. You may add ICD10 codes to the arrays accordingly.
- ICD10 codes in MCC is dominant than in CC.
- Some of the DRG are age and gender sensitive.
Finally the grouper in action:
base_code = None
# Gender = male|female
patient_gender = "male"
# Age in days
patient_age = 20*365
# diagnoses is an array where the first item is principal diagnosis
# followed by co-morbidities
diagnoses = ['P39.1','H33.3','A11.1']
#diagnoses = [None,'H33.4','A11.1']
#diagnoses = ['XXXXXXX']
# procedures is an array where the first item is principal procedure
# followed by secondary procedures
procedures = ['16.89','11.12']
#procedures = [None,'B']
#procedures = None
# Start
principal_diagnosis = getPrincipalDiagnosis(diagnoses)
# Has principal procedure ?
if principal_diagnosis:
base_code = getMedicalDRG(principal_diagnosis, patient_gender, patient_age)
if base_code is not None:
# Has OT procedure ?
if (procedures is not None):
principal_procedure = getPrincipalProcedure(procedures)
if principal_procedure is not None:
surgical_drg = getSurgicalDRG(principal_procedure, patient_gender, patient_age)
if surgical_drg is not None:
# Override base code
base_code = surgical_drg
# Set drg severity
drg_severity = getSeverity(diagnoses)
drg_code = "%s%s" %(base_code, drg_severity)
drg = getDRG(drg_code)
print("DRG: %s %s" %(drg['drg_code'], drg['drg_name']))
else:
# This is where the ICD10 diagnosis is not located anywhere in casting
# and required to be assigned to a DRG
print("Ungroupable: %s"%(principal_diagnosis))
else:
# End if no principal diagnosis
print("No principal diagnosis")
Output:
DRG: 02012 ORBITAL PROCEDURES (W CC)
Notes
- Age must be in days
- Ungroupable is a bin where ICD10 codes is not defined anywhere in the drg castings. As a result the case cannot be assigned to any DRG. The ICD10 codes require to be cast into a suitable DRG group.
It’s alive
Once you have a working grouper the next step would be to merge the output with the costing information. However in order to have a complete grouper you need to build up a working dataset for the grouper to consume. They are:
- List of DRG (medical and surgical)
- Cast ICD10 and ICD9CM to DRGs
- Decide your CC and MCC list
The next step is quite simple and if you understand the calculations in the Costing Module it is just a matter of translating them into codes.