def action_default(self):
        """Present currently unlocked set of results"""
        patientId = int(self.requestData["sim_patient_id"]);
        simTime = int(self.requestData["sim_time"]);
        
        manager = SimManager();

        # Load and format the results available so far
        results = manager.loadResults(patientId, simTime);
        lastGroupStrings = ['New']; # Sentinel value that will be different than first real data row
        lenLastGroupStrings = len(lastGroupStrings);
        htmlLines = list();
        for dataModel in results:
            self.formatDataModel(dataModel);
            for i, groupString in enumerate(dataModel["groupStrings"]):
                if i >= lenLastGroupStrings or groupString != lastGroupStrings[i]:
                    htmlLines.append( GROUP_HEADER_TEMPLATE % {"spacer": "   "*i, "groupString": groupString });
            htmlLines.append( LINE_TEMPLATE % dataModel );
            lastGroupStrings = dataModel["groupStrings"];
            lenLastGroupStrings = len(lastGroupStrings);
        self.requestData["detailTable"] = str.join("\n", htmlLines );

        # Load and format information on any pending result orders
        pendingResultOrders = manager.loadPendingResultOrders(patientId, simTime);
        htmlLines = list();
        for dataModel in pendingResultOrders:
            self.formatPendingResultOrderModel(dataModel);
            htmlLines.append( PENDING_ORDER_LINE_TEMPLATE % dataModel );
        self.requestData["pendingResultOrdersTable"] = str.join("\n", htmlLines );
        if len(pendingResultOrders) < 1: # Leave default "no results pending" message if don't find any
            self.requestData["pendingResultOrdersTable"] = EMPTY_RESULTS_LINE;
Example #2
0
    def action_signOrders(self):
        """Commit the pended orders into active orders in the database record."""
        userId = int(self.requestData["sim_user_id"])
        patientId = int(self.requestData["sim_patient_id"])
        simTime = int(self.requestData["sim_time"])

        if isinstance(self.requestData["newOrderItemId"], str):
            # Single item, represent as list of size 1
            self.requestData["newOrderItemId"] = [
                self.requestData["newOrderItemId"]
            ]
        # Convert to list of integers
        orderItemIds = [
            int(itemIdStr) for itemIdStr in self.requestData["newOrderItemId"]
        ]

        if isinstance(self.requestData["discontinuePatientOrderId"], str):
            # Single item, represent as list of size 1
            self.requestData["discontinuePatientOrderId"] = [
                self.requestData["discontinuePatientOrderId"]
            ]
        # Convert to list of integers
        discontinuePatientOrderIds = [
            int(itemIdStr)
            for itemIdStr in self.requestData["discontinuePatientOrderId"]
        ]

        manager = SimManager()
        manager.signOrders(userId, patientId, simTime, orderItemIds,
                           discontinuePatientOrderIds)
Example #3
0
 def action_copyPatientTemplate(self):
     manager = SimManager();
     patientData = \
         {   "name": self.requestData["sim_patient_name"],
         };
     templatePatientId = int(self.requestData["sim_patient_id"]);
     manager.copyPatientTemplate( patientData, templatePatientId );
Example #4
0
    def action_final(self): # Do as last step, so will capture any just completed creation steps
        """Load lists of users, patients, and states to select from"""
        manager = SimManager();
        
        userModels = manager.loadUserInfo();
        valueList = list();
        textList = list();
        for userModel in userModels:
            valueList.append(str(userModel["sim_user_id"]));
            textList.append(userModel["name"]);
        self.requestData["userOptions"] = self.optionTagsFromList(valueList, textList);

        patientModels = manager.loadPatientInfo();
        valueList = list();
        textList = list();
        for patientModel in patientModels:
            valueList.append(str(patientModel["sim_patient_id"]));
            textList.append("%(name)s" % patientModel);
        self.requestData["patientOptions"] = self.optionTagsFromList(valueList, textList);
        
        stateModels = manager.loadStateInfo();
        valueList = list();
        textList = list();
        for stateModel in stateModels:
            valueList.append(str(stateModel["sim_state_id"]));
            textList.append("%(name)s" % stateModel);
        self.requestData["stateOptions"] = self.optionTagsFromList(valueList, textList);
Example #5
0
    def action_default(self):
        """Present currently unlocked set of results"""
        patientId = int(self.requestData["sim_patient_id"])
        simTime = int(self.requestData["sim_time"])

        manager = SimManager()
        results = manager.loadResults(patientId, simTime)

        lastGroupStrings = ['New']
        # Sentinel value that will be different than first real data row
        lenLastGroupStrings = len(lastGroupStrings)
        htmlLines = list()
        for dataModel in results:
            self.formatDataModel(dataModel)
            for i, groupString in enumerate(dataModel["groupStrings"]):
                if i >= lenLastGroupStrings or groupString != lastGroupStrings[
                        i]:
                    htmlLines.append(GROUP_HEADER_TEMPLATE % {
                        "spacer": " &nbsp; " * i,
                        "groupString": groupString
                    })
            htmlLines.append(LINE_TEMPLATE % dataModel)
            lastGroupStrings = dataModel["groupStrings"]
            lenLastGroupStrings = len(lastGroupStrings)
        self.requestData["detailTable"] = str.join("\n", htmlLines)
Example #6
0
    def action_default(self):
        """Present currently selected set of pending clinical item orders"""
        patientId = int(self.requestData["sim_patient_id"])
        simTime = int(self.requestData["sim_time"])
        loadActive = isTrueStr(self.requestData["loadActive"])
        if not loadActive:
            self.requestData["activeCompleted"] = "Completed"
            self.requestData["activeOrderButtonClass"] = ""
            self.requestData["completedOrderButtonClass"] = "buttonSelected"
            self.requestData["historyTime"] = "Time"

        manager = SimManager()
        patientOrders = manager.loadPatientOrders(patientId,
                                                  simTime,
                                                  loadActive=loadActive)

        lastPatientOrder = None
        htmlLines = list()
        # Choose appropriate line template according to whether recommender enabled
        LINE_TEMPLATE = LINE_TEMPLATE_BY_ACTIVE if self.requestData[
            "enableRecommender"] == "True" else LINE_TEMPLATE_BY_ACTIVE_RECOMMENDER_DISABLED
        for patientOrder in patientOrders:
            self.formatPatientOrder(patientOrder, lastPatientOrder)
            htmlLines.append(LINE_TEMPLATE[loadActive] % patientOrder)
            lastPatientOrder = patientOrder
        self.requestData["detailTable"] = str.join("\n", htmlLines)
Example #7
0
    def action_default(self):
        """Render the details for the specified patient information, including controls to modify"""
        userId = int(self.requestData["sim_user_id"]);
        patientId = int(self.requestData["sim_patient_id"]);
        simTime = None;

        manager = SimManager();
        userModel = manager.loadUserInfo([userId])[0];  # Assume found good single match
        try:
            simTime = int(self.requestData["sim_time"]);
        except ValueError:
            # Unable to parse any explicit simulation time. 
            # Lookup the last activity (order) time for the patient and start just after that to resume the simulation
            simTime = manager.loadPatientLastEventTime(patientId) + 60;  # Advance by one minute to avoid confusing states where multiple events happening within zero time.
            self.requestData["sim_time"] = simTime; # So sub-pages have access

        patientModel = manager.loadPatientInfo([patientId], simTime)[0];
        #print >> sys.stderr, "Loaded %(sim_patient_id)s in state %(sim_state_id)s at %(relative_time_start)s" % patientModel

        for key, value in userModel.iteritems():
            self.requestData["sim_user."+key] = value;
        for key, value in patientModel.iteritems():
            self.requestData["sim_patient."+key] = value;
        self.requestData["sim_time.format"] = (BASE_TIME + timedelta(0,simTime)).strftime(TIME_FORMAT);

        subData = self.pageClassByName[self.requestData["currentDataPage"]]();
        subData.requestData["sim_user_id"] = self.requestData["sim_user_id"];
        subData.requestData["sim_patient_id"] = self.requestData["sim_patient_id"];
        subData.requestData["sim_time"] = self.requestData["sim_time"];
        subData.action_default();
        self.requestData["currentDataTable"] = subData.populatedTemplate();

        subData = NewOrders();
        subData.action_default();
        self.requestData["dataEntryTable"] = subData.populatedTemplate();
    def setUp(self):
        """Prepare state for test cases"""
        DBTestCase.setUp(self);

        self.manager = SimManager();  # Instance to test on

        from stride.clinical_item.ClinicalItemDataLoader import ClinicalItemDataLoader; 
        ClinicalItemDataLoader.build_clinical_item_psql_schemata();
        self.manager.buildCPOESimSchema();

        log.info("Populate the database with test data")

        #### Basically import a bunch of rigged CSV or TSV files that have realistic simulating case and grading data
        # Get that data into the test database

        dataTextStr = \
"""sim_result_id;name;description;group_string;priority
-10;Temp;Temperature (F);Flowsheet>Vitals;10
-20;Pulse;Pulse / Heart Rate (HR);Flowsheet>Vitals;20
-30;SBP;Blood Pressure, Systolic (SBP);Flowsheet>Vitals;30
-40;DBP;Blood Pressure, Diastolic (DBP);Flowsheet>Vitals;40
-50;Resp;Respirations (RR);Flowsheet>Vitals;50
-60;FiO2;Fraction Inspired Oxygen;Flowsheet>Vitals;60
-70;Urine;Urine Output (UOP);Flowsheet>Vitals;70
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_result", delim=";");
Example #9
0
 def action_createPatient(self):
     manager = SimManager();
     patientData = \
         {   "age_years": int(self.requestData["sim_patient_age_years"]),
             "gender": self.requestData["sim_patient_gender"],
             "name": self.requestData["sim_patient_name"],
         };
     initialStateId = int(self.requestData["sim_state_id"]);
     manager.createPatient( patientData, initialStateId );
class TestSimManagerGrading(DBTestCase):
    def setUp(self):
        """Prepare state for test cases"""
        DBTestCase.setUp(self);

        self.manager = SimManager();  # Instance to test on

        from stride.clinical_item.ClinicalItemDataLoader import ClinicalItemDataLoader; 
        ClinicalItemDataLoader.build_clinical_item_psql_schemata();
        self.manager.buildCPOESimSchema();

        log.info("Populate the database with test data")

        #### Basically import a bunch of rigged CSV or TSV files that have realistic simulating case and grading data
        # Get that data into the test database

        dataTextStr = \
"""sim_result_id;name;description;group_string;priority
-10;Temp;Temperature (F);Flowsheet>Vitals;10
-20;Pulse;Pulse / Heart Rate (HR);Flowsheet>Vitals;20
-30;SBP;Blood Pressure, Systolic (SBP);Flowsheet>Vitals;30
-40;DBP;Blood Pressure, Diastolic (DBP);Flowsheet>Vitals;40
-50;Resp;Respirations (RR);Flowsheet>Vitals;50
-60;FiO2;Fraction Inspired Oxygen;Flowsheet>Vitals;60
-70;Urine;Urine Output (UOP);Flowsheet>Vitals;70
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_result", delim=";");
        # Could change the is pandas insert_sql file...
        # Do same for sim_patient_order, sim_grading_key, sim_user, whatever else is just enough to get grading test to run
        # Do same for sim_patient_order, sim_grading_key, sim_user, whatever else is just enough to get grading test to run
        # Do same for sim_patient_order, sim_grading_key, sim_user, whatever else is just enough to get grading test to run
        # Do same for sim_patient_order, sim_grading_key, sim_user, whatever else is just enough to get grading test to run
        # Do same for sim_patient_order, sim_grading_key, sim_user, whatever else is just enough to get grading test to run

    def tearDown(self):
        """Restore state from any setUp or test steps"""
        #self.purgeTestRecords();
        DBTestCase.tearDown(self);

    def test_gradeCases(self):
        # Give the application ID of some simulated patient test cases and the name
        #   of a grading key and just verify that it produces the expected results

        simPatientIds = [1,2,3,4,5];
        simGradingKeyId = "JonCVersion";
        verifyGradesByPatientId = {1: 23, 2: 65, 3: 65, 4: 32, 5: 32};
        sampleGradesByPatientId = self.manager.gradeCases(simPatientIds, simGradingKeyId);
        self.assertEquals(verifyGradesByPatientId, sampleGradesByPatientId);
Example #11
0
    def action_searchByPatient(self):
        """Rather than explicit query items, specify a patient state context from which
        recent items can be extracted and used to query.
        """
        patientId = int(self.requestData["sim_patient_id"])
        simTime = int(self.requestData["sim_time"])

        # Track recent item IDs (orders, diagnoses, unlocked results, etc. that related order queries will be based off of)
        manager = SimManager()
        recentItemIds = manager.recentItemIds(patientId, simTime)
        recentItemIdStrs = list()
        for itemId in recentItemIds:
            recentItemIdStrs.append(str(itemId))

        self.requestData["queryItemIds"] = str.join(",", recentItemIdStrs)

        # Delegate to default action now
        self.action_default()
Example #12
0
    def action_default(self):
        """Present set of patient notes"""
        patientId = int(self.requestData["sim_patient_id"])
        simTime = int(self.requestData["sim_time"])

        # Load lookup table to translate note type IDs into description strings
        noteTypeById = DBUtil.loadTableAsDict("sim_note_type")

        manager = SimManager()
        results = manager.loadNotes(patientId, simTime)

        htmlLines = list()
        for dataModel in results:
            self.formatDataModel(dataModel, noteTypeById)
            htmlLines.append(LINE_TEMPLATE % dataModel)
        self.requestData["detailTable"] = str.join("\n", htmlLines)

        if len(results) > 0:
            self.requestData["initialNoteContent"] = results[-1]["content"]
Example #13
0
def add_grades_to(csv, graders):
    sim_manager = SimManager()
    grade_columns_ordered = []
    for i, grader in enumerate(sorted(graders)):
        grade_column = 'grade ({})'.format(grader)
        grades = sim_manager.grade_cases(csv['patient'].values.astype(int),
                                         grader)

        # validate most_active_user_id == most_graded_user_id
        inconsistent_users = [
            grade for grade in grades
            if grade['most_active_user_id'] != grade['most_graded_user_id']
        ]
        if inconsistent_users:
            for inconsistent_user in inconsistent_users:
                log.warn(
                    "Case {} grading issue: most_active_user_id <> most_graded_user_id ({} <> {})!"
                    .format(inconsistent_user['sim_patient_id'],
                            inconsistent_user['most_active_user_id'],
                            inconsistent_user['most_graded_user_id']))

        grade_results = pd.DataFrame(
            grades,
            columns=['sim_patient_id', 'most_active_user_id', 'total_score'])
        patient_id_column = 'grade{}_patient_id'.format(i)
        grade_results.rename(columns={'sim_patient_id': patient_id_column},
                             inplace=True)
        csv = pd.merge(csv,
                       grade_results,
                       left_on=['patient', 'user'],
                       right_on=[patient_id_column, 'most_active_user_id'])
        csv.rename(columns={'total_score': grade_column}, inplace=True)
        grade_columns_ordered.append(grade_column)

    # don't need grader name if there's only 1 grade
    # if len(grade_columns_ordered) == 1:
    #     csv.rename(columns={grade_columns_ordered[0]: 'grade'}, inplace=True)
    #     grade_columns_ordered = ['grade']

    return csv, grade_columns_ordered
Example #14
0
    def action_orderSetSearch(self):
        """Look for pre-defined order sets"""
        manager = SimManager()
        query = ClinicalItemQuery()
        query.parseParams(self.requestData)
        resultIter = manager.orderSetSearch(query)

        # Prepare data for HMTL formatting
        results = list()
        for dataModel in resultIter:
            if dataModel is not None:
                dataModel["nameFormat"] = NAME_TEMPLATE % dataModel
                dataModel["itemListJSON.quote"] = urllib.parse.quote(
                    json.dumps(dataModel["itemList"]))
                dataModel["controls"] = CONTROLS_TEMPLATE % dataModel
                results.append(dataModel)

        colNames = ["controls", "external_id", "nameFormat"]
        formatter = HtmlResultsFormatter(StringIO(), valign="top")
        formatter.formatResultDicts(results, colNames)

        self.requestData["dataRows"] = formatter.getOutFile().getvalue()
Example #15
0
    def action_orderSearch(self):
        """Search for orders by query string"""
        manager = SimManager()
        query = ClinicalItemQuery()
        query.parseParams(self.requestData)
        query.sourceTables = self.requestData["sourceTables"].split(",")
        results = manager.clinicalItemSearch(query)

        lastModel = None
        for dataModel in results:
            dataModel["controls"] = CONTROLS_TEMPLATE % dataModel
            dataModel["nPreCols"] = self.requestData["nPreCols"]
            dataModel["category_description.format"] = ""
            if lastModel is None or lastModel[
                    "category_description"] != dataModel[
                        "category_description"]:
                dataModel[
                    "category_description.format"] = "<b>%s</b>" % dataModel[
                        "category_description"]
                # Only show category if new
            lastModel = dataModel

        colNames = ["controls", "description"]
        # "name" for order code. ,"category_description.format"
        lastModel = None
        htmlLines = list()
        for dataModel in results:
            newCategory = (lastModel is None
                           or lastModel["category_description"] !=
                           dataModel["category_description"])
            showCategory = (self.requestData["groupByCategory"]
                            and newCategory)
            # Limit category display if many repeats
            if showCategory:
                htmlLines.append(CATEGORY_HEADER_TEMPLATE % dataModel)
            htmlLines.append(
                self.formatRowHTML(dataModel, colNames, showCategory))
            lastModel = dataModel
        self.requestData["dataRows"] = str.join("\n", htmlLines)
Example #16
0
    def action_default(self):
        """Present currently selected set of pending clinical item orders"""
        patientId = int(self.requestData["sim_patient_id"])
        simTime = int(self.requestData["sim_time"])
        loadActive = isTrueStr(self.requestData["loadActive"])
        if not loadActive:
            self.requestData["activeCompleted"] = "Completed"
            self.requestData["activeOrderButtonClass"] = ""
            self.requestData["completedOrderButtonClass"] = "buttonSelected"
            self.requestData["historyTime"] = "Time"

        manager = SimManager()
        patientOrders = manager.loadPatientOrders(patientId,
                                                  simTime,
                                                  loadActive=loadActive)

        lastPatientOrder = None
        htmlLines = list()
        for patientOrder in patientOrders:
            self.formatPatientOrder(patientOrder, lastPatientOrder)
            htmlLines.append(LINE_TEMPLATE_BY_ACTIVE[loadActive] %
                             patientOrder)
            lastPatientOrder = patientOrder
        self.requestData["detailTable"] = str.join("\n", htmlLines)
Example #17
0
    def setUp(self):
        """Prepare state for test cases"""
        DBTestCase.setUp(self)
        self.usage_reporter = SimManager()

        from stride.clinical_item.ClinicalItemDataLoader import ClinicalItemDataLoader
        ClinicalItemDataLoader.build_clinical_item_psql_schemata()
        self.usage_reporter.buildCPOESimSchema()

        log.info("Populate the database with test data")

        # Basically import a bunch of rigged CSV or TSV files that have realistic simulating case and grading data
        # Get that data into the test database
        clinical_item_category_str = \
            """clinical_item_category_id;source_table
            1;source_table
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_category_str), "clinical_item_category", delim=";")

        clinical_item_str = \
            """clinical_item_id;clinical_item_category_id;name
            1;1;Clinical item 1
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_str), "clinical_item", delim=";")

        clinical_item_str = \
            """sim_user_id;name
            1;Jonathan Chen
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_str), "sim_user", delim=";")

        sim_patient_str = \
            """sim_patient_id;name;age_years;gender
            1;Patient One;40;Male
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_patient_str), "sim_patient", delim=";")

        sim_state_str = \
            """sim_state_id;name
            1;Sim state 1
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_state_str), "sim_state", delim=";")

        sim_patient_order_str = \
            """sim_patient_order_id;sim_user_id;sim_patient_id;clinical_item_id;relative_time_start;sim_state_id
            1;1;1;1;1;1
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_patient_order_str), "sim_patient_order", delim=";")

        sim_grading_key_str = \
            """sim_grader_id;sim_state_id;clinical_item_id;score;group_name;sim_case_name
            Jonathan Chen;1;1;1;g1;case_name
            Andre Kumar;1;1;2;g1;case_name
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_grading_key_str), "sim_grading_key", delim=";")

        # Prepare survey file
        self.survey_csv = tempfile.NamedTemporaryFile(mode='w+', delete=False)
        self.survey_csv.write("Physician User Name,resident" + os.linesep +
                              "Jonathan Chen,1")
        self.survey_csv.flush()
Example #18
0
class TestMakeUsageReport(DBTestCase):
    def setUp(self):
        """Prepare state for test cases"""
        DBTestCase.setUp(self)
        self.usage_reporter = SimManager()

        from stride.clinical_item.ClinicalItemDataLoader import ClinicalItemDataLoader
        ClinicalItemDataLoader.build_clinical_item_psql_schemata()
        self.usage_reporter.buildCPOESimSchema()

        log.info("Populate the database with test data")

        # Basically import a bunch of rigged CSV or TSV files that have realistic simulating case and grading data
        # Get that data into the test database
        clinical_item_category_str = \
            """clinical_item_category_id;source_table
            1;source_table
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_category_str), "clinical_item_category", delim=";")

        clinical_item_str = \
            """clinical_item_id;clinical_item_category_id;name
            1;1;Clinical item 1
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_str), "clinical_item", delim=";")

        clinical_item_str = \
            """sim_user_id;name
            1;Jonathan Chen
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_str), "sim_user", delim=";")

        sim_patient_str = \
            """sim_patient_id;name;age_years;gender
            1;Patient One;40;Male
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_patient_str), "sim_patient", delim=";")

        sim_state_str = \
            """sim_state_id;name
            1;Sim state 1
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_state_str), "sim_state", delim=";")

        sim_patient_order_str = \
            """sim_patient_order_id;sim_user_id;sim_patient_id;clinical_item_id;relative_time_start;sim_state_id
            1;1;1;1;1;1
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_patient_order_str), "sim_patient_order", delim=";")

        sim_grading_key_str = \
            """sim_grader_id;sim_state_id;clinical_item_id;score;group_name;sim_case_name
            Jonathan Chen;1;1;1;g1;case_name
            Andre Kumar;1;1;2;g1;case_name
            """
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_grading_key_str), "sim_grading_key", delim=";")

        # Prepare survey file
        self.survey_csv = tempfile.NamedTemporaryFile(mode='w+', delete=False)
        self.survey_csv.write("Physician User Name,resident" + os.linesep +
                              "Jonathan Chen,1")
        self.survey_csv.flush()

    def tearDown(self):
        """Restore state from any setUp or test steps"""
        DBTestCase.tearDown(self)

        # delete temporary survey file
        self.survey_csv.close()
        os.remove(self.survey_csv.name)

        # delete output csv
        try:
            os.remove(tempfile.gettempdir() + os.pathsep + 'tmp.csv')
        except OSError:
            pass

    def test_not_enough_args(self, mock_aggregate_simulation_data):
        # setup
        argv = ['make_usage_report.py', '-g', tempfile.gettempdir() + '/tmp.csv']

        # test & verify
        self.verify_error_message_for_argv(argv, "Given parameters are not enough. Exiting.")

    def test_no_grader(self, mock_aggregate_simulation_data):
        # setup
        argv = ['make_usage_report.py', '../analysis/sim_data', tempfile.gettempdir() + '/tmp.csv']

        # test & verify
        self.verify_error_message_for_argv(argv, "No graders given. Cannot grade patient cases. Exiting.")

    def test_single_grader(self, mock_aggregate_simulation_data):
        # setup
        output_filename = tempfile.gettempdir() + '/tmp.csv'
        argv = ['make_usage_report.py', '../analysis/sim_data', '-g', 'Jonathan Chen', output_filename]

        # test
        make_usage_report.main(argv)

        # verify
        with open(output_filename) as output_file:
            output = csv.DictReader(output_file)
            self.assertTrue('grade ({})'.format(argv[3]) in output.fieldnames)
            # assert more columns than original + grade column
            self.assertGreater(len(output.fieldnames), len(header) + 1)

    def test_multiple_graders(self, mock_aggregate_simulation_data):
        # setup
        output_filename = tempfile.gettempdir() + '/tmp.csv'
        argv = ['make_usage_report.py', '../analysis/sim_data', '-g', 'Jonathan Chen,Andre Kumar', output_filename]

        # test
        make_usage_report.main(argv)

        # verify
        with open(output_filename) as output_file:
            output = csv.DictReader(output_file)
            self.assertTrue('grade ({})'.format(argv[3].split(',')[0]) in output.fieldnames)
            self.assertTrue('grade ({})'.format(argv[3].split(',')[1]) in output.fieldnames)
            # assert more columns than original + grade columns
            self.assertGreater(len(output.fieldnames), len(header) + 2)

    def test_resident_column_is_present(self, mock_aggregate_simulation_data):
        # setup
        output_filename = tempfile.gettempdir() + '/tmp.csv'
        argv = ['make_usage_report.py', '../analysis/sim_data', '-s', self.survey_csv.name, '-g', 'Jonathan Chen', output_filename]

        # test
        make_usage_report.main(argv)

        # verify
        with open(output_filename) as output_file:
            output = csv.DictReader(output_file)
            self.assertTrue('resident' in output.fieldnames)

    def verify_error_message_for_argv(self, argv, expected_error_message):
        with self.assertRaises(SystemExit) as cm:   # capture sys.exit() in the tested class
            with captured_output() as (out, err):
                make_usage_report.main(argv)

        actual_output = out.getvalue()
        self.assertEqual(expected_error_message, actual_output.split("\n", 1)[0])

        self.assertIsNone(cm.exception.code)    # we can verify exception code here
Example #19
0
    def action_default(self):
        """Look for related orders by association / recommender methods"""
        # If patient is specified then modify query and exclusion list based on items already ordered for patient
        recentItemIds = set()
        if self.requestData["sim_patient_id"]:
            patientId = int(self.requestData["sim_patient_id"])
            simTime = int(self.requestData["sim_time"])

            # Track recent item IDs (orders, diagnoses, unlocked results, etc. that related order queries will be based off of)
            manager = SimManager()
            recentItemIds = manager.recentItemIds(patientId, simTime)

        # Recommender Instance to test on
        self.recommender = ItemAssociationRecommender()
        self.recommender.dataManager.dataCache = webDataCache
        # Allow caching of data for rapid successive queries

        query = RecommenderQuery()
        if self.requestData["sortField"] == "":
            self.requestData["sortField"] = "P-YatesChi2-NegLog"
            # P-Fisher-NegLog should yield better results, but beware, much longer to calculate
        query.parseParams(self.requestData)
        if len(query.excludeItemIds) == 0:
            query.excludeItemIds = self.recommender.defaultExcludedClinicalItemIds(
            )
        if len(query.excludeCategoryIds) == 0:
            query.excludeCategoryIds = self.recommender.defaultExcludedClinicalItemCategoryIds(
            )
        #query.fieldList.extend( ["prevalence","PPV","RR"] );
        displayFields = list()
        if self.requestData["displayFields"] != "":
            displayFields = self.requestData["displayFields"].split(",")

        # Exclude items already ordered for the patient from any recommended list
        query.excludeItemIds.update(recentItemIds)
        if not query.queryItemIds:  # If no specific query items specified, then use the recent patient item IDs
            query.queryItemIds.update(recentItemIds)

        recommendedData = self.recommender(query)

        if len(recommendedData) > 0:
            # Denormalize results with links to clinical item descriptions
            self.recommender.formatRecommenderResults(recommendedData)

        # Display fields should append Format suffix to identify which version to display, but use original for header labels
        (self.requestData["fieldHeaders"], displayFieldsFormatSuffixed
         ) = self.prepareDisplayHeaders(displayFields)

        # Format for HTML and add a control field for interaction with the data
        for dataModel in recommendedData:
            self.prepareResultRow(dataModel, displayFields)

        # Try organize by category
        if self.requestData["groupByCategory"]:
            recommendedData = self.recommender.organizeByCategory(
                recommendedData)

        colNames = ["controls"]
        # "name" for code. ,"category_description"
        colNames.extend(displayFieldsFormatSuffixed)
        colNames.extend(["description"])

        lastModel = None
        htmlLines = list()
        for dataModel in recommendedData:
            newCategory = (lastModel is None
                           or lastModel["category_description"] !=
                           dataModel["category_description"])
            showCategory = (self.requestData["groupByCategory"]
                            and newCategory)
            # Limit category display if many repeats
            if showCategory:
                htmlLines.append(CATEGORY_HEADER_TEMPLATE % dataModel)
            htmlLines.append(
                self.formatRowHTML(dataModel, colNames, showCategory))
            lastModel = dataModel
        self.requestData["dataRows"] = str.join("\n", htmlLines)
Example #20
0
class TestSimManagerGrading(DBTestCase):
    def setUp(self):
        """Prepare state for test cases"""
        DBTestCase.setUp(self)

        self.manager = SimManager()  # Instance to test on

        from stride.clinical_item.ClinicalItemDataLoader import ClinicalItemDataLoader
        ClinicalItemDataLoader.build_clinical_item_psql_schemata()
        self.manager.buildCPOESimSchema()

        log.info("Populate the database with test data")

        # Basically import a bunch of rigged CSV or TSV files that have realistic simulating case and grading data
        # Get that data into the test database
        clinical_item_category_str = \
"""clinical_item_category_id;source_table
1;source_table
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_category_str),
                          "clinical_item_category",
                          delim=";")

        clinical_item_str = \
"""clinical_item_id;clinical_item_category_id;name
1;1;Clinical item 1
2;1;Clinical item 2
3;1;Clinical item 3
4;1;Clinical item 4
5;1;Clinical item 5
6;1;Clinical item 6
7;1;Clinical item 7
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_str),
                          "clinical_item",
                          delim=";")

        clinical_item_str = \
"""sim_user_id;name
0;Default user
1;Jonathan Chen
2;User 2
3;User 3
4;User 4
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_str), "sim_user", delim=";")

        sim_patient_str = \
"""sim_patient_id;name;age_years;gender
1;Patient One;40;Male
2;Patient Two;50;Female
3;Patient Three;60;Male
4;Patient Four;70;Female
5;Patient Five;80;Male
6;Patient Six;90;Female
7;Patient Seven;100;Male
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_patient_str), "sim_patient", delim=";")

        sim_state_str = \
"""sim_state_id;name
1;Sim state 1
2;Sim state 2
3;Sim state 3
4;Sim state 4
5;Sim state 5
6;Sim state 6
7;Sim state 7
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_state_str), "sim_state", delim=";")

        sim_patient_order_str = \
"""sim_patient_order_id;sim_user_id;sim_patient_id;clinical_item_id;relative_time_start;sim_state_id
1;0;1;1;1;1
2;1;1;2;2;2
3;1;1;3;3;3
4;1;1;4;4;4
5;1;1;5;5;5
6;1;1;6;6;6
7;1;1;7;7;7
8;2;2;1;1;1
9;3;2;2;2;1
10;3;2;3;3;2
11;2;3;1;1;1
12;3;3;2;2;2
13;3;3;3;3;3
14;1;4;1;1;2
15;1;4;2;2;3
16;2;5;1;1;3
17;2;5;2;2;4
18;3;6;4;1;1
19;3;6;4;2;2
20;3;6;4;3;3
21;4;7;5;1;1
22;4;7;5;2;2
23;4;7;5;3;3
24;4;7;5;4;4
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_patient_order_str),
                          "sim_patient_order",
                          delim=";")

        sim_grading_key_str = \
"""sim_grader_id;sim_state_id;clinical_item_id;score;group_name
Jonathan Chen;1;1;1;g1
Jonathan Chen;2;2;1;g2
Jonathan Chen;3;3;1;g3
Jonathan Chen;4;4;1;
Jonathan Chen;5;5;1;g5
Jonathan Chen;6;6;1;
Jonathan Chen;7;7;1;g7
Jonathan Chen;3;1;1;g8
Jonathan Chen;4;2;1;g8
Jonathan Chen;1;4;-1000;
Jonathan Chen;2;4;10;
Jonathan Chen;3;4;2000;
Jonathan Chen;1;5;-1000;g9
Jonathan Chen;2;5;-1;
Jonathan Chen;3;5;0;g10
Jonathan Chen;3;5;-500;
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_grading_key_str),
                          "sim_grading_key",
                          delim=";")

        self.expected_grades_by_patient_id = [
            {
                "total_score":
                6,  # Default user (id = 0) is ignored and NULL group_names are counted separately
                "sim_patient_id": 1,
                "most_graded_user_id": 1,
                "most_active_user_id": 1,
                "sim_grader_id": "Jonathan Chen"
            },
            {
                "total_score":
                1,  # Ungraded (clinical_item_id, sim_state_id) keys are omitted from summation
                "sim_patient_id": 2,
                "most_graded_user_id":
                2,  # Most graded user is User 2 (even though most active is User 3)
                "most_active_user_id": 3,
                "sim_grader_id": "Jonathan Chen"
            },
            {
                "total_score": 3,
                "sim_patient_id": 3,
                "most_graded_user_id":
                3,  # Most graded user is the most active one
                "most_active_user_id": 3,
                "sim_grader_id": "Jonathan Chen"
            },
            # 4: No grading available for the existing case
            {
                "total_score":
                1,  # Scores in the same group g8 are counted only once
                "sim_patient_id": 5,
                "most_graded_user_id": 2,
                "most_active_user_id": 2,
                "sim_grader_id": "Jonathan Chen"
            },
            {
                "total_score":
                1010,  # Non-uniform scores (i.e., not all scores = 1)
                "sim_patient_id": 6,
                "most_graded_user_id": 3,
                "most_active_user_id": 3,
                "sim_grader_id": "Jonathan Chen"
            },
            {
                "total_score":
                -1501,  # All negative and one 0 score results in negative score
                "sim_patient_id": 7,
                "most_graded_user_id": 4,
                "most_active_user_id": 4,
                "sim_grader_id": "Jonathan Chen"
            }
            # 8: Case doesn't exist
        ]

    def tearDown(self):
        """Restore state from any setUp or test steps"""
        DBTestCase.tearDown(self)

    def test_gradeCases(self):
        # Give the application ID of some simulated patient test cases and the name
        #   of a grading key and just verify that it produces the expected results
        sim_patient_ids = [1, 2, 3, 4, 5, 6, 7, 8]
        sim_grading_key_id = "Jonathan Chen"
        actual_grades_by_patient_id = self.manager.grade_cases(
            sim_patient_ids, sim_grading_key_id)
        self.assertEqual(self.expected_grades_by_patient_id,
                         actual_grades_by_patient_id)

    def test_commandLine(self):
        argv = ["SimManager.py", "-p", "1,2,3,4,5,6", "-g", "Jonathan Chen"]
        with captured_output() as (out, err):
            self.manager.main(argv)

        actual_output = out.getvalue()

        actual_comment_line, output_csv = actual_output.split("\n", 1)

        # verify comment line
        expected_comment_line = COMMENT_TAG + " " + json.dumps({"argv": argv})
        self.assertEqual(expected_comment_line, actual_comment_line)

        # verify csv
        actual_output_csv = StringIO(output_csv)
        reader = csv.DictReader(actual_output_csv)
        # verify header
        self.assertEqualList(
            sorted(self.expected_grades_by_patient_id[0].keys()),
            sorted(reader.fieldnames))

        # verify lines
        for line_num, actual_grade in enumerate(reader):
            expected_grade_str_values = {
                k: str(v)
                for k, v in
                self.expected_grades_by_patient_id[line_num].items()
            }
            self.assertEqual(expected_grade_str_values, actual_grade)

    def test_commandLine_no_patientIds(self):
        argv = ["SimManager.py", "-g", "Jonathan Chen"]
        self.verify_error_message_for_argv(
            argv, "No patient cases given. Nothing to grade. Exiting.")

    def test_commandLine_no_graderIds(self):
        argv = ["SimManager.py", "-p", "1"]
        self.verify_error_message_for_argv(
            argv, "No graders given. Cannot grade patient cases. Exiting.")

    def verify_error_message_for_argv(self, argv, expected_error_message):
        with self.assertRaises(
                SystemExit) as cm:  # capture sys.exit() in the tested class
            with captured_output() as (out, err):
                self.manager.main(argv)

        actual_output = out.getvalue()
        self.assertEqual(expected_error_message,
                         actual_output.split("\n", 1)[0])

        self.assertIsNone(
            cm.exception.code)  # we can verify exception code here
Example #21
0
    def setUp(self):
        """Prepare state for test cases"""
        DBTestCase.setUp(self);

        self.manager = SimManager();  # Instance to test on

        from stride.clinical_item.ClinicalItemDataLoader import ClinicalItemDataLoader; 
        ClinicalItemDataLoader.build_clinical_item_psql_schemata();
        self.manager.buildCPOESimSchema();

        self.testPatientId = None;

        self.purgeTestRecords();

        log.info("Populate the database with test data")

        self.clinicalItemCategoryIdStrList = list();
        headers = ["clinical_item_category_id","source_table"];
        dataModels = \
            [
                RowItemModel( [-1, "Labs"], headers ),
                RowItemModel( [-2, "Imaging"], headers ),
                RowItemModel( [-3, "Meds"], headers ),
                RowItemModel( [-4, "Nursing"], headers ),
                RowItemModel( [-5, "Problems"], headers ),
                RowItemModel( [-6, "Lab Results"], headers ),
            ];
        for dataModel in dataModels:
            (dataItemId, isNew) = DBUtil.findOrInsertItem("clinical_item_category", dataModel );
            self.clinicalItemCategoryIdStrList.append( str(dataItemId) );

        headers = ["clinical_item_id","clinical_item_category_id","name","analysis_status"];
        dataModels = \
            [
                RowItemModel( [-1, -1, "CBC",1], headers ),
                RowItemModel( [-2, -1, "BMP",1], headers ),
                RowItemModel( [-3, -1, "Hepatic Panel",1], headers ),
                RowItemModel( [-4, -1, "Cardiac Enzymes",1], headers ),
                RowItemModel( [-5, -2, "CXR",1], headers ),
                RowItemModel( [-6, -2, "RUQ Ultrasound",1], headers ),
                RowItemModel( [-7, -2, "CT Abdomen/Pelvis",1], headers ),
                RowItemModel( [-8, -2, "CT PE Thorax",1], headers ),
                RowItemModel( [-9, -3, "Acetaminophen",1], headers ),
                RowItemModel( [-10, -3, "Carvedilol",1], headers ),
                RowItemModel( [-11, -3, "Enoxaparin",1], headers ),
                RowItemModel( [-12, -3, "Warfarin",1], headers ),
                RowItemModel( [-13, -3, "Ceftriaxone",1], headers ),
                RowItemModel( [-14, -4, "Foley Catheter",1], headers ),
                RowItemModel( [-15, -4, "Vital Signs",1], headers ),
                RowItemModel( [-16, -4, "Fall Precautions",1], headers ),
            ];
        for dataModel in dataModels:
            (dataItemId, isNew) = DBUtil.findOrInsertItem("clinical_item", dataModel );


        dataTextStr = \
"""sim_user_id;name
-1;Test User
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_user", delim=";");

        dataTextStr = \
"""sim_patient_id;age_years;gender;name
-1;60;Female;Test Female Patient
-2;55;Male;Test Male Patient
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_patient", delim=";");

        dataTextStr = \
"""sim_result_id;name;description;group_string;priority
-10;Temp;Temperature (F);Flowsheet>Vitals;10
-20;Pulse;Pulse / Heart Rate (HR);Flowsheet>Vitals;20
-30;SBP;Blood Pressure, Systolic (SBP);Flowsheet>Vitals;30
-40;DBP;Blood Pressure, Diastolic (DBP);Flowsheet>Vitals;40
-50;Resp;Respirations (RR);Flowsheet>Vitals;50
-60;FiO2;Fraction Inspired Oxygen;Flowsheet>Vitals;60
-70;Urine;Urine Output (UOP);Flowsheet>Vitals;70
-11000;WBC;WBC;LAB BLOOD ORDERABLES>Hematology>Automated Blood Count;11000
-11010;HGB;HEMOGLOBIN;LAB BLOOD ORDERABLES>Hematology>Automated Blood Count;11010
-11020;HCT;HEMATOCRIT;LAB BLOOD ORDERABLES>Hematology>Automated Blood Count;11020
-11030;PLT;PLATELET COUNT;LAB BLOOD ORDERABLES>Hematology>Automated Blood Count;11030
-13010;NA;SODIUM, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13010
-13020;K;POTASSIUM, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13020
-13030;CL;CHLORIDE, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13030
-13040;CO2;CO2, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13040
-13050;BUN;UREA NITROGEN,SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13050
-13060;CR;CREATININE, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13060
-13070;GLU;GLUCOSE, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13070
-13090;CA;CALCIUM, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13090
-13110;MG;MAGNESIUM, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13110
-13120;PHOS;PHOSPHORUS, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13120
-13210;TBIL;TOTAL BILIRUBIN;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13210
-13220;DBIL;CONJUGATED BILI;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13220
-13230;IBIL;UNCONJUGATED BILIRUBIN;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13230
-13240;AST;AST (SGOT), SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13240
-13250;ALT;ALT (SGPT), SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13250
-13260;ALKP;ALK P'TASE, TOTAL, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13260
-13270;ALB;ALBUMIN, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13270
-13280;TP;PROTEIN, TOTAL, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13280
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_result", delim=";");

        # Map orders to expected results.
        # Simplify expect vital signs to result in 5 minutes. Basic chemistry labs in 10 minutes, CBC in 15 minutes
        dataTextStr = \
"""sim_order_result_map_id;clinical_item_id;sim_result_id;turnaround_time
-1;-15;-10;300
-2;-15;-20;300
-3;-15;-30;300
-4;-15;-40;300
-5;-15;-50;300
-6;-1;-11000;900
-7;-1;-11010;900
-8;-1;-11020;900
-9;-1;-11030;900
-10;-2;-13010;600
-11;-2;-13020;600
-12;-2;-13030;600
-13;-2;-13040;600
-14;-2;-13050;600
-15;-2;-13060;600
-16;-2;-13070;600
-17;-2;-13090;600
-18;-3;-13210;600
-19;-3;-13240;600
-20;-3;-13250;600
-21;-3;-13260;600
-22;-3;-13270;600
-23;-3;-13280;600
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_order_result_map", delim=";");


        dataTextStr = \
"""sim_state_id;name;description
0;Test 0; Test State 0
-1;Test 1;Test State 1
-2;Test 2;Test State 2
-3;Test 3;Test State 3
-4;Test 4;Test State 4
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_state", delim=";");

        dataTextStr = \
"""sim_state_transition_id;pre_state_id;post_state_id;clinical_item_id;time_trigger;description
-1;-1;-2;None;9000;Passive time from 1 to 2
-2;-2;-3;-11;None;Transition 2 to 3 if order for 11 (Enoxaparin)
-3;-2;-3;-12;None;Transition 2 to 3 if order for 12 (Warfarin) (don't need both anti-coagulants. One adequate to trigger transition)
-4;-2;-4;-13;None;Transition 2 to 4 if order for 13 (Ceftriaxone)
-5;-3;-1;-10;9000;Transition 3 back to 1 if order for 10 (Carvedilol) OR 9000 seconds pass
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_state_transition", delim=";");

        dataTextStr = \
"""sim_patient_state_id;sim_patient_id;sim_state_id;relative_time_start;relative_time_end
-1;-1;-1;-7200;0
-3;-1;-1;0;1800
-2;-1;-2;1800;None
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_patient_state", delim=";");

        # Order Vital Signs at time 0, then basic labs (CBC, BMP, LFTs) at 10 minutes (600 seconds)
        dataTextStr = \
"""sim_patient_order_id;sim_user_id;sim_patient_id;sim_state_id;clinical_item_id;relative_time_start;relative_time_end
-1;-1;-1;-1;-15;0;None
-2;-1;-1;-1;-1;600;None
-3;-1;-1;-1;-2;600;None
-4;-1;-1;-1;-3;600;None
-5;-1;-1;-2;-15;1800;None
-6;-1;-1;-2;-1;1800;None
-7;-1;-1;-2;-2;1800;None
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_patient_order", delim=";");

        dataTextStr = \
"""sim_state_result_id;sim_result_id;sim_state_id;num_value;num_value_noise;text_value;result_flag
-1;-10;0;98.7;0.2;;
-2;-20;0;75;3;;
-3;-30;0;130;4;;
-4;-40;0;85;2;;
-5;-50;0;12;1;;
-6;-60;0;0.21;0;;
-7;-70;0;500;100;;
-8;-11000;0;7;1;;
-9;-11010;0;13;0.5;;
-10;-11020;0;40;1;;
-11;-11030;0;300;25;;
-12;-13010;0;140;4;;
-13;-13020;0;4.5;0.4;;
-14;-13030;0;95;3;;
-15;-13040;0;24;1;;
-16;-13050;0;12;3;;
-17;-13060;0;0.7;0.2;;
-18;-13070;0;140;12;;
-19;-13090;0;9;0.4;;
-20;-13110;0;2;0.3;;
-21;-13120;0;3;0.5;;
-22;-13210;0;0.2;0.1;;
-23;-13240;0;29;5;;
-24;-13250;0;20;4;;
-25;-13260;0;85;8;;
-26;-13270;0;4;0.4;;
-27;-13280;0;6;0.5;;
-28;-10;-1;101.4;0.4;Fever;H
-29;-20;-1;115;4;Tachycardia;H
-30;-30;-1;92;5;Hypotension;L
-31;-40;-1;55;3;Hypotension;L
-32;-70;-1;50;10;Low UOP;L
-33;-11000;-1;12;1;Leukocytosis;H
-34;-13060;-1;2.4;0.3;AKI;H
-35;-20;-2;105;4;Tachycardia;H
-36;-11000;-2;10;1;;
-37;-13060;-2;1.9;0.3;AKI;H
-38;-13070;-2;250;13;;H
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_state_result", delim=";");

        dataTextStr = \
"""sim_note_id;sim_state_id;relative_state_time;content
-1;-1;7200;Initial Note
-2;-2;0;Later Note
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_note", delim=";");
Example #22
0
class TestSimManager(DBTestCase):
    def setUp(self):
        """Prepare state for test cases"""
        DBTestCase.setUp(self);

        self.manager = SimManager();  # Instance to test on

        from stride.clinical_item.ClinicalItemDataLoader import ClinicalItemDataLoader; 
        ClinicalItemDataLoader.build_clinical_item_psql_schemata();
        self.manager.buildCPOESimSchema();

        self.testPatientId = None;

        self.purgeTestRecords();

        log.info("Populate the database with test data")

        self.clinicalItemCategoryIdStrList = list();
        headers = ["clinical_item_category_id","source_table"];
        dataModels = \
            [
                RowItemModel( [-1, "Labs"], headers ),
                RowItemModel( [-2, "Imaging"], headers ),
                RowItemModel( [-3, "Meds"], headers ),
                RowItemModel( [-4, "Nursing"], headers ),
                RowItemModel( [-5, "Problems"], headers ),
                RowItemModel( [-6, "Lab Results"], headers ),
            ];
        for dataModel in dataModels:
            (dataItemId, isNew) = DBUtil.findOrInsertItem("clinical_item_category", dataModel );
            self.clinicalItemCategoryIdStrList.append( str(dataItemId) );

        headers = ["clinical_item_id","clinical_item_category_id","name","analysis_status"];
        dataModels = \
            [
                RowItemModel( [-1, -1, "CBC",1], headers ),
                RowItemModel( [-2, -1, "BMP",1], headers ),
                RowItemModel( [-3, -1, "Hepatic Panel",1], headers ),
                RowItemModel( [-4, -1, "Cardiac Enzymes",1], headers ),
                RowItemModel( [-5, -2, "CXR",1], headers ),
                RowItemModel( [-6, -2, "RUQ Ultrasound",1], headers ),
                RowItemModel( [-7, -2, "CT Abdomen/Pelvis",1], headers ),
                RowItemModel( [-8, -2, "CT PE Thorax",1], headers ),
                RowItemModel( [-9, -3, "Acetaminophen",1], headers ),
                RowItemModel( [-10, -3, "Carvedilol",1], headers ),
                RowItemModel( [-11, -3, "Enoxaparin",1], headers ),
                RowItemModel( [-12, -3, "Warfarin",1], headers ),
                RowItemModel( [-13, -3, "Ceftriaxone",1], headers ),
                RowItemModel( [-14, -4, "Foley Catheter",1], headers ),
                RowItemModel( [-15, -4, "Vital Signs",1], headers ),
                RowItemModel( [-16, -4, "Fall Precautions",1], headers ),
            ];
        for dataModel in dataModels:
            (dataItemId, isNew) = DBUtil.findOrInsertItem("clinical_item", dataModel );


        dataTextStr = \
"""sim_user_id;name
-1;Test User
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_user", delim=";");

        dataTextStr = \
"""sim_patient_id;age_years;gender;name
-1;60;Female;Test Female Patient
-2;55;Male;Test Male Patient
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_patient", delim=";");

        dataTextStr = \
"""sim_result_id;name;description;group_string;priority
-10;Temp;Temperature (F);Flowsheet>Vitals;10
-20;Pulse;Pulse / Heart Rate (HR);Flowsheet>Vitals;20
-30;SBP;Blood Pressure, Systolic (SBP);Flowsheet>Vitals;30
-40;DBP;Blood Pressure, Diastolic (DBP);Flowsheet>Vitals;40
-50;Resp;Respirations (RR);Flowsheet>Vitals;50
-60;FiO2;Fraction Inspired Oxygen;Flowsheet>Vitals;60
-70;Urine;Urine Output (UOP);Flowsheet>Vitals;70
-11000;WBC;WBC;LAB BLOOD ORDERABLES>Hematology>Automated Blood Count;11000
-11010;HGB;HEMOGLOBIN;LAB BLOOD ORDERABLES>Hematology>Automated Blood Count;11010
-11020;HCT;HEMATOCRIT;LAB BLOOD ORDERABLES>Hematology>Automated Blood Count;11020
-11030;PLT;PLATELET COUNT;LAB BLOOD ORDERABLES>Hematology>Automated Blood Count;11030
-13010;NA;SODIUM, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13010
-13020;K;POTASSIUM, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13020
-13030;CL;CHLORIDE, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13030
-13040;CO2;CO2, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13040
-13050;BUN;UREA NITROGEN,SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13050
-13060;CR;CREATININE, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13060
-13070;GLU;GLUCOSE, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13070
-13090;CA;CALCIUM, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13090
-13110;MG;MAGNESIUM, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13110
-13120;PHOS;PHOSPHORUS, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13120
-13210;TBIL;TOTAL BILIRUBIN;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13210
-13220;DBIL;CONJUGATED BILI;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13220
-13230;IBIL;UNCONJUGATED BILIRUBIN;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13230
-13240;AST;AST (SGOT), SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13240
-13250;ALT;ALT (SGPT), SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13250
-13260;ALKP;ALK P'TASE, TOTAL, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13260
-13270;ALB;ALBUMIN, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13270
-13280;TP;PROTEIN, TOTAL, SER/PLAS;LAB BLOOD ORDERABLES>Chemistry>General Chemistry;13280
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_result", delim=";");

        # Map orders to expected results.
        # Simplify expect vital signs to result in 5 minutes. Basic chemistry labs in 10 minutes, CBC in 15 minutes
        dataTextStr = \
"""sim_order_result_map_id;clinical_item_id;sim_result_id;turnaround_time
-1;-15;-10;300
-2;-15;-20;300
-3;-15;-30;300
-4;-15;-40;300
-5;-15;-50;300
-6;-1;-11000;900
-7;-1;-11010;900
-8;-1;-11020;900
-9;-1;-11030;900
-10;-2;-13010;600
-11;-2;-13020;600
-12;-2;-13030;600
-13;-2;-13040;600
-14;-2;-13050;600
-15;-2;-13060;600
-16;-2;-13070;600
-17;-2;-13090;600
-18;-3;-13210;600
-19;-3;-13240;600
-20;-3;-13250;600
-21;-3;-13260;600
-22;-3;-13270;600
-23;-3;-13280;600
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_order_result_map", delim=";");


        dataTextStr = \
"""sim_state_id;name;description
0;Test 0; Test State 0
-1;Test 1;Test State 1
-2;Test 2;Test State 2
-3;Test 3;Test State 3
-4;Test 4;Test State 4
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_state", delim=";");

        dataTextStr = \
"""sim_state_transition_id;pre_state_id;post_state_id;clinical_item_id;time_trigger;description
-1;-1;-2;None;9000;Passive time from 1 to 2
-2;-2;-3;-11;None;Transition 2 to 3 if order for 11 (Enoxaparin)
-3;-2;-3;-12;None;Transition 2 to 3 if order for 12 (Warfarin) (don't need both anti-coagulants. One adequate to trigger transition)
-4;-2;-4;-13;None;Transition 2 to 4 if order for 13 (Ceftriaxone)
-5;-3;-1;-10;9000;Transition 3 back to 1 if order for 10 (Carvedilol) OR 9000 seconds pass
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_state_transition", delim=";");

        dataTextStr = \
"""sim_patient_state_id;sim_patient_id;sim_state_id;relative_time_start;relative_time_end
-1;-1;-1;-7200;0
-3;-1;-1;0;1800
-2;-1;-2;1800;None
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_patient_state", delim=";");

        # Order Vital Signs at time 0, then basic labs (CBC, BMP, LFTs) at 10 minutes (600 seconds)
        dataTextStr = \
"""sim_patient_order_id;sim_user_id;sim_patient_id;sim_state_id;clinical_item_id;relative_time_start;relative_time_end
-1;-1;-1;-1;-15;0;None
-2;-1;-1;-1;-1;600;None
-3;-1;-1;-1;-2;600;None
-4;-1;-1;-1;-3;600;None
-5;-1;-1;-2;-15;1800;None
-6;-1;-1;-2;-1;1800;None
-7;-1;-1;-2;-2;1800;None
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_patient_order", delim=";");

        dataTextStr = \
"""sim_state_result_id;sim_result_id;sim_state_id;num_value;num_value_noise;text_value;result_flag
-1;-10;0;98.7;0.2;;
-2;-20;0;75;3;;
-3;-30;0;130;4;;
-4;-40;0;85;2;;
-5;-50;0;12;1;;
-6;-60;0;0.21;0;;
-7;-70;0;500;100;;
-8;-11000;0;7;1;;
-9;-11010;0;13;0.5;;
-10;-11020;0;40;1;;
-11;-11030;0;300;25;;
-12;-13010;0;140;4;;
-13;-13020;0;4.5;0.4;;
-14;-13030;0;95;3;;
-15;-13040;0;24;1;;
-16;-13050;0;12;3;;
-17;-13060;0;0.7;0.2;;
-18;-13070;0;140;12;;
-19;-13090;0;9;0.4;;
-20;-13110;0;2;0.3;;
-21;-13120;0;3;0.5;;
-22;-13210;0;0.2;0.1;;
-23;-13240;0;29;5;;
-24;-13250;0;20;4;;
-25;-13260;0;85;8;;
-26;-13270;0;4;0.4;;
-27;-13280;0;6;0.5;;
-28;-10;-1;101.4;0.4;Fever;H
-29;-20;-1;115;4;Tachycardia;H
-30;-30;-1;92;5;Hypotension;L
-31;-40;-1;55;3;Hypotension;L
-32;-70;-1;50;10;Low UOP;L
-33;-11000;-1;12;1;Leukocytosis;H
-34;-13060;-1;2.4;0.3;AKI;H
-35;-20;-2;105;4;Tachycardia;H
-36;-11000;-2;10;1;;
-37;-13060;-2;1.9;0.3;AKI;H
-38;-13070;-2;250;13;;H
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_state_result", delim=";");

        dataTextStr = \
"""sim_note_id;sim_state_id;relative_state_time;content
-1;-1;7200;Initial Note
-2;-2;0;Later Note
"""     # Parse into DB insertion object
        DBUtil.insertFile( StringIO(dataTextStr), "sim_note", delim=";");

    def purgeTestRecords(self):
        log.info("Purge test records from the database")
        if self.testPatientId is not None:
            # Delete test generated data
            DBUtil.execute("delete from sim_patient_order where sim_patient_id = %s", (self.testPatientId,) );
            DBUtil.execute("delete from sim_patient_state where sim_patient_id = %s", (self.testPatientId,) );
            DBUtil.execute("delete from sim_patient where sim_patient_id = %s", (self.testPatientId,) );

        DBUtil.execute("delete from sim_note where sim_note_id < 0");
        DBUtil.execute("delete from sim_state_result where sim_state_result_id < 0");
        DBUtil.execute("delete from sim_patient_order where sim_state_id < 0 or sim_patient_order_id < 0");
        DBUtil.execute("delete from sim_order_result_map where sim_order_result_map_id < 0");
        DBUtil.execute("delete from sim_result where sim_result_id < 0");
        DBUtil.execute("delete from sim_patient_state where sim_state_id < 0 or sim_patient_state_id < 0");
        DBUtil.execute("delete from sim_state_transition where pre_state_id < 0");
        DBUtil.execute("delete from sim_state where sim_state_id <= 0");
        DBUtil.execute("delete from sim_user where sim_user_id < 0");
        DBUtil.execute("delete from sim_patient where sim_patient_id < 0");
        DBUtil.execute("delete from clinical_item where clinical_item_id < 0");

    def tearDown(self):
        """Restore state from any setUp or test steps"""
        self.purgeTestRecords();
        DBTestCase.tearDown(self);

    def test_copyPatientTemplate(self):
        # Copy a patient template, including deep copy of notes, orders, states, but only up to relative time zero
        newPatientData = {"name":"Template Copy"};
        templatePatientId = -1;
        self.testPatientId = self.manager.copyPatientTemplate( newPatientData, templatePatientId );
        futureTime = 1000000;   # Far future time to test that we still only copied the results up to time zero

        # Verify basic patient information
        patientCols = ["name","age_years","gender","sim_state_id"];
        patientModel = self.manager.loadPatientInfo([self.testPatientId])[0];
        expectedPatientModel = RowItemModel(["Template Copy",60,"Female",-1], patientCols);
        self.assertEqualDict(expectedPatientModel, patientModel, patientCols);

        # Verify notes
        dataCols = ["sim_patient_id","content"];
        sampleData = self.manager.loadNotes(self.testPatientId, futureTime);
        verifyData = \
            [   RowItemModel([self.testPatientId,"Initial Note"], dataCols),
                RowItemModel([self.testPatientId,"Initial Note"], dataCols),    # Second copy because another state initiation at time zero and negative onset time
            ];
        self.assertEqualDictList(verifyData, sampleData, dataCols);

        # Verify orders
        dataCols = ["sim_user_id","sim_patient_id","sim_state_id","clinical_item_id","relative_time_start","relative_time_end"];
        sampleData = self.manager.loadPatientOrders(self.testPatientId, futureTime, loadActive=None);
        verifyData = \
            [   RowItemModel([-1,self.testPatientId,-1,-15,0,None], dataCols),
            ];
        self.assertEqualDictList(verifyData, sampleData, dataCols);

        # Verify states
        dataCols = ["sim_patient_id","sim_state_id","relative_time_start","relative_time_end"];
        query = SQLQuery();
        for dataCol in dataCols:
            query.addSelect(dataCol);
        query.addFrom("sim_patient_state");
        query.addWhereEqual("sim_patient_id", self.testPatientId );
        query.addOrderBy("relative_time_start");
        sampleDataTable = DBUtil.execute(query,includeColumnNames=True);
        sampleData = modelListFromTable(sampleDataTable);
        verifyData = \
            [   RowItemModel([self.testPatientId,-1,-7200,0], dataCols),
                RowItemModel([self.testPatientId,-1,0,None], dataCols),
            ];
        self.assertEqualDictList(verifyData, sampleData, dataCols);

    def test_loadResults(self):
        # Query for results based on simulated turnaround times, including fallback to default normal values
        #   if no explicit (abnormal) values specified for simulated state

        colNames = ["name", "num_value", "result_relative_time"];

        # Time zero, no orders for diagnostics, so no results should exist
        patientId = -1;
        relativeTime = 0;
        sampleResults = self.manager.loadResults(patientId, relativeTime);
        verifyResults = \
            [
            ];
        self.assertEqualDictList(verifyResults, sampleResults, colNames);

        # Time 2 minutes, orders done, but not long-enough to get results back, so no results should exist
        relativeTime = 120;
        sampleResults = self.manager.loadResults(patientId, relativeTime);
        verifyResults = \
            [
            ];
        self.assertEqualDictList(verifyResults, sampleResults, colNames);

        # Time 5 minutes, vital signs should result now
        relativeTime = 300;
        sampleResults = self.manager.loadResults(patientId, relativeTime);
        verifyResults = \
            [
                RowItemModel(["Temp", 101.4, 300], colNames),
                RowItemModel(["Pulse", 115, 300], colNames),
                RowItemModel(["SBP", 92, 300], colNames),
                RowItemModel(["DBP", 55, 300], colNames),
                RowItemModel(["Resp", 12, 300], colNames),  # Normal result retrieve from default state 0
            ];
        self.assertEqualDictList(verifyResults, sampleResults, colNames);

        # Time 10 minutes, labs ordered, but not results expected yet. Prior vital signs should still be available.
        relativeTime = 600;
        sampleResults = self.manager.loadResults(patientId, relativeTime);
        verifyResults = \
            [
                RowItemModel(["Temp", 101.4, 300], colNames),
                RowItemModel(["Pulse", 115, 300], colNames),
                RowItemModel(["SBP", 92, 300], colNames),
                RowItemModel(["DBP", 55, 300], colNames),
                RowItemModel(["Resp", 12, 300], colNames),  # Normal result retrieve from default state 0
            ];
        self.assertEqualDictList(verifyResults, sampleResults, colNames);

        # Time 20 minutes, labs ordered 10 minutes ago, should have chemistry results
        relativeTime = 1200;
        sampleResults = self.manager.loadResults(patientId, relativeTime);
        verifyResults = \
            [
                RowItemModel(["Temp", 101.4, 300], colNames),
                RowItemModel(["Pulse", 115, 300], colNames),
                RowItemModel(["SBP", 92, 300], colNames),
                RowItemModel(["DBP", 55, 300], colNames),
                RowItemModel(["Resp", 12, 300], colNames),  # Normal result retrieve from default state 0

                RowItemModel(["NA", 140, 1200], colNames),
                RowItemModel(["K", 4.5, 1200], colNames),
                RowItemModel(["CL", 95, 1200], colNames),
                RowItemModel(["CO2", 24, 1200], colNames),
                RowItemModel(["BUN", 12, 1200], colNames),
                RowItemModel(["CR", 2.4, 1200], colNames),  # Abnormal value in state 1, all others default state 0
                RowItemModel(["GLU", 140, 1200], colNames),
                RowItemModel(["CA", 9, 1200], colNames),
                RowItemModel(["TBIL", 0.2, 1200], colNames),
                RowItemModel(["AST", 29, 1200], colNames),
                RowItemModel(["ALT", 20, 1200], colNames),
                RowItemModel(["ALKP", 85, 1200], colNames),
                RowItemModel(["ALB", 4, 1200], colNames),
                RowItemModel(["TP", 6, 1200], colNames),
            ];
        self.assertEqualDictList(verifyResults, sampleResults, colNames);

        # Time 25 minutes, CBC results should now be available as well
        relativeTime = 1500;
        sampleResults = self.manager.loadResults(patientId, relativeTime);
        verifyResults = \
            [
                RowItemModel(["Temp", 101.4, 300], colNames),
                RowItemModel(["Pulse", 115, 300], colNames),
                RowItemModel(["SBP", 92, 300], colNames),
                RowItemModel(["DBP", 55, 300], colNames),
                RowItemModel(["Resp", 12, 300], colNames),  # Normal result retrieve from default state 0

                RowItemModel(["NA", 140, 1200], colNames),
                RowItemModel(["K", 4.5, 1200], colNames),
                RowItemModel(["CL", 95, 1200], colNames),
                RowItemModel(["CO2", 24, 1200], colNames),
                RowItemModel(["BUN", 12, 1200], colNames),
                RowItemModel(["CR", 2.4, 1200], colNames),  # Abnormal value in state 1, all others default state 0
                RowItemModel(["GLU", 140, 1200], colNames),
                RowItemModel(["CA", 9, 1200], colNames),
                RowItemModel(["TBIL", 0.2, 1200], colNames),
                RowItemModel(["AST", 29, 1200], colNames),
                RowItemModel(["ALT", 20, 1200], colNames),
                RowItemModel(["ALKP", 85, 1200], colNames),
                RowItemModel(["ALB", 4, 1200], colNames),
                RowItemModel(["TP", 6, 1200], colNames),

                RowItemModel(["WBC", 12, 1500], colNames),  # Abnormal state value
                RowItemModel(["HGB", 13, 1500], colNames),
                RowItemModel(["HCT", 40, 1500], colNames),
                RowItemModel(["PLT", 300, 1500], colNames),

            ];
        self.assertEqualDictList(verifyResults, sampleResults, colNames);

        # Time 30 minutes, patient entered new state (-2), and repeat vitals + labs ordered, but no new results yet due to turnaround time
        relativeTime = 1800;
        sampleResults = self.manager.loadResults(patientId, relativeTime);
        verifyResults = \
            [
                RowItemModel(["Temp", 101.4, 300], colNames),
                RowItemModel(["Pulse", 115, 300], colNames),
                RowItemModel(["SBP", 92, 300], colNames),
                RowItemModel(["DBP", 55, 300], colNames),
                RowItemModel(["Resp", 12, 300], colNames),  # Normal result retrieve from default state 0

                RowItemModel(["NA", 140, 1200], colNames),
                RowItemModel(["K", 4.5, 1200], colNames),
                RowItemModel(["CL", 95, 1200], colNames),
                RowItemModel(["CO2", 24, 1200], colNames),
                RowItemModel(["BUN", 12, 1200], colNames),
                RowItemModel(["CR", 2.4, 1200], colNames),  # Abnormal value in state 1, all others default state 0
                RowItemModel(["GLU", 140, 1200], colNames),
                RowItemModel(["CA", 9, 1200], colNames),
                RowItemModel(["TBIL", 0.2, 1200], colNames),
                RowItemModel(["AST", 29, 1200], colNames),
                RowItemModel(["ALT", 20, 1200], colNames),
                RowItemModel(["ALKP", 85, 1200], colNames),
                RowItemModel(["ALB", 4, 1200], colNames),
                RowItemModel(["TP", 6, 1200], colNames),

                RowItemModel(["WBC", 12, 1500], colNames),  # Abnormal state value
                RowItemModel(["HGB", 13, 1500], colNames),
                RowItemModel(["HCT", 40, 1500], colNames),
                RowItemModel(["PLT", 300, 1500], colNames),

            ];
        self.assertEqualDictList(verifyResults, sampleResults, colNames);

        # Time 45 minutes, patient second state and repeat labs, should retrieve both new and old results (with relative timestamps)
        relativeTime = 2700;
        sampleResults = self.manager.loadResults(patientId, relativeTime);
        verifyResults = \
            [
                RowItemModel(["Temp", 101.4, 300], colNames),
                RowItemModel(["Pulse", 115, 300], colNames),
                RowItemModel(["SBP", 92, 300], colNames),
                RowItemModel(["DBP", 55, 300], colNames),
                RowItemModel(["Resp", 12, 300], colNames),  # Normal result retrieve from default state 0

                RowItemModel(["NA", 140, 1200], colNames),
                RowItemModel(["K", 4.5, 1200], colNames),
                RowItemModel(["CL", 95, 1200], colNames),
                RowItemModel(["CO2", 24, 1200], colNames),
                RowItemModel(["BUN", 12, 1200], colNames),
                RowItemModel(["CR", 2.4, 1200], colNames),  # Abnormal value in state 1, all others default state 0
                RowItemModel(["GLU", 140, 1200], colNames),
                RowItemModel(["CA", 9, 1200], colNames),
                RowItemModel(["TBIL", 0.2, 1200], colNames),
                RowItemModel(["AST", 29, 1200], colNames),
                RowItemModel(["ALT", 20, 1200], colNames),
                RowItemModel(["ALKP", 85, 1200], colNames),
                RowItemModel(["ALB", 4, 1200], colNames),
                RowItemModel(["TP", 6, 1200], colNames),

                RowItemModel(["WBC", 12, 1500], colNames),  # Abnormal state value
                RowItemModel(["HGB", 13, 1500], colNames),
                RowItemModel(["HCT", 40, 1500], colNames),
                RowItemModel(["PLT", 300, 1500], colNames),

                # State 2 Results
                RowItemModel(["Temp", 98.7, 2100], colNames),
                RowItemModel(["Pulse", 105, 2100], colNames),   # Still abnormal, other vitals revert to default
                RowItemModel(["SBP", 130, 2100], colNames),
                RowItemModel(["DBP", 85, 2100], colNames),
                RowItemModel(["Resp", 12, 2100], colNames),

                RowItemModel(["NA", 140, 2400], colNames),
                RowItemModel(["K", 4.5, 2400], colNames),
                RowItemModel(["CL", 95, 2400], colNames),
                RowItemModel(["CO2", 24, 2400], colNames),
                RowItemModel(["BUN", 12, 2400], colNames),
                RowItemModel(["CR", 1.9, 2400], colNames),  # Abnormal value in state 1, all others default state 0
                RowItemModel(["GLU", 250, 2400], colNames), # Newly abnormal value in state 2
                RowItemModel(["CA", 9, 2400], colNames),

                RowItemModel(["WBC", 10, 2700], colNames),  # Abnormal state value, but not as bad as prior state
                RowItemModel(["HGB", 13, 2700], colNames),
                RowItemModel(["HCT", 40, 2700], colNames),
                RowItemModel(["PLT", 300, 2700], colNames),
            ];
        self.assertEqualDictList(verifyResults, sampleResults, colNames);


    def test_discontinueOrders(self):
        # Query for results based on simulated turnaround times, including fallback to default normal values
        #   if no explicit (abnormal) values specified for simulated state

        colNames = ["name", "num_value", "result_relative_time"];

        # See setUp for test data construction
        userId = -1;
        patientId = -1;

        # Time zero, vital sign orders check entered (clinical_item_id = -15, sim_patient_order_id = -1)
        # Time 2 minutes, orders done, but not long-enough to get results back, so no results should exist
        relativeTime = 120;
        sampleResults = self.manager.loadResults(patientId, relativeTime);
        verifyResults = \
            [
            ];
        self.assertEqualDictList(verifyResults, sampleResults, colNames);

        # Time 5 minutes, vital signs should result now
        relativeTime = 300;
        sampleResults = self.manager.loadResults(patientId, relativeTime);
        verifyResults = \
            [
                RowItemModel(["Temp", 101.4, 300], colNames),
                RowItemModel(["Pulse", 115, 300], colNames),
                RowItemModel(["SBP", 92, 300], colNames),
                RowItemModel(["DBP", 55, 300], colNames),
                RowItemModel(["Resp", 12, 300], colNames),  # Normal result retrieve from default state 0
            ];
        self.assertEqualDictList(verifyResults, sampleResults, colNames);

        # Go back and simulate the vitals check order being discontinued before results came back
        discontinueTime = 120;
        newOrderItemIds = [];   # No new orders
        discontinuePatientOrderIds = [-1];  # See setUp data for the ID of the order to simulate canceling
        self.manager.signOrders(userId, patientId, discontinueTime, newOrderItemIds, discontinuePatientOrderIds);

        # Redo simulation of Time 5 minutes, vital signs should not appear now, since order was cancelled
        relativeTime = 300;
        sampleResults = self.manager.loadResults(patientId, relativeTime);
        verifyResults = \
            [
            ];
        self.assertEqualDictList(verifyResults, sampleResults, colNames);

        # Check that there is still a record of orders, including the cancelled one
        orderCols = ["name","relative_time_start","relative_time_end"];
        sampleOrders = self.manager.loadPatientOrders(patientId, 300, loadActive=None);
        verifyOrders = \
            [
                RowItemModel(["Vital Signs", 0, 120], orderCols),
            ];
        self.assertEqualDictList(verifyOrders, sampleOrders, orderCols);

        # Go back and simulate the vitals check order being discontinued immediately (same time as order),
        #   then don't even keep a record of it to clean up data entry error
        discontinueTime = 0;
        newOrderItemIds = [];   # No new orders
        discontinuePatientOrderIds = [-1];  # See setUp data for the ID of the order to simulate canceling
        self.manager.signOrders(userId, patientId, discontinueTime, newOrderItemIds, discontinuePatientOrderIds);

        # Check that there is no record of the order anymore, even including the cancelled one
        orderCols = ["name","relative_time_start","relative_time_end"];
        sampleOrders = self.manager.loadPatientOrders(patientId, 300, loadActive=None);
        verifyOrders = \
            [
            ];
        self.assertEqualDictList(verifyOrders, sampleOrders, orderCols);

    def test_stateTransition(self):
        # Query for results based on simulated turnaround times, including fallback to default normal values
        #   if no explicit (abnormal) values specified for simulated state

        colNames = ["sim_state_id"];

        userId = -1;
        patientId = -1;

        # Time zero, initial state expected
        relativeTime = 0;
        samplePatient = self.manager.loadPatientInfo([patientId], relativeTime)[0];
        verifyPatient = RowItemModel([-1], colNames);
        self.assertEqualDict(samplePatient, verifyPatient, colNames);

        # After previously recorded second state
        relativeTime = 2000;
        samplePatient = self.manager.loadPatientInfo([patientId], relativeTime)[0];
        verifyPatient = RowItemModel([-2], colNames);
        self.assertEqualDict(samplePatient, verifyPatient, colNames);

        # Sign orders that do not affect this state
        orderItemIds = [-4,-5];
        self.manager.signOrders(userId, patientId, relativeTime, orderItemIds);

        # Expect to stay in same state
        relativeTime = 2100;
        samplePatient = self.manager.loadPatientInfo([patientId], relativeTime)[0];
        verifyPatient = RowItemModel([-2], colNames);
        self.assertEqualDict(samplePatient, verifyPatient, colNames);

        # Sign orders that should trigger state transition
        relativeTime = 2100;
        orderItemIds = [-11,-6];
        self.manager.signOrders(userId, patientId, relativeTime, orderItemIds);

        # Expect transition to new state
        relativeTime = 2100;
        samplePatient = self.manager.loadPatientInfo([patientId], relativeTime)[0];
        verifyPatient = RowItemModel([-3], colNames);
        self.assertEqualDict(samplePatient, verifyPatient, colNames);

        # Expect transition to original state 1 then further to state 2 with enough time passage
        relativeTime = 22000;
        samplePatient = self.manager.loadPatientInfo([patientId], relativeTime)[0];
        verifyPatient = RowItemModel([-2], colNames);
        self.assertEqualDict(samplePatient, verifyPatient, colNames);

        # Retroactive query to verify state 1 intermediate transition state
        relativeTime = 12000;
        samplePatient = self.manager.loadPatientInfo([patientId], relativeTime)[0];
        verifyPatient = RowItemModel([-1], colNames);
        self.assertEqualDict(samplePatient, verifyPatient, colNames);

        # Sign alternative order to trigger state 2 to 3 transition as well as order to immediately trigger state 3 to 1
        relativeTime = 22000;
        orderItemIds = [-12,-10];
        self.manager.signOrders(userId, patientId, relativeTime, orderItemIds);

        # Expect transition to new state 3 them 1 immediately
        relativeTime = 22100;
        samplePatient = self.manager.loadPatientInfo([patientId], relativeTime)[0];
        verifyPatient = RowItemModel([-1], colNames);
        self.assertEqualDict(samplePatient, verifyPatient, colNames);

        # Expect transition back to state 2 with enough time
        relativeTime = 32200;
        samplePatient = self.manager.loadPatientInfo([patientId], relativeTime)[0];
        verifyPatient = RowItemModel([-2], colNames);
        self.assertEqualDict(samplePatient, verifyPatient, colNames);

        # Sign orders simultaneously that could trigger two different branching state transition paths
        #   Arbitrarily go based on the first action provided / encountered
        relativeTime = 32200;
        orderItemIds = [-13,-11,-12];
        self.manager.signOrders(userId, patientId, relativeTime, orderItemIds);

        # Expect transition back to branched state 4
        relativeTime = 32300;
        samplePatient = self.manager.loadPatientInfo([patientId], relativeTime)[0];
        verifyPatient = RowItemModel([-4], colNames);
        self.assertEqualDict(samplePatient, verifyPatient, colNames);

    def test_loadPatientLastEventTime(self):
        # Query for last time have a record of a patient order start or cancellation
        #   as natural point to resume a simulated case

        # See setUp for test data construction
        userId = -1;
        patientId = -1;

        # Initial test data already loaded with example orders
        sampleValue = self.manager.loadPatientLastEventTime(patientId);
        verifyValue = 1800;
        self.assertEqual(verifyValue, sampleValue);

        # Sign an additional order
        relativeTime = 2000;
        newOrderItemIds = [-1];   # Any additional order
        self.manager.signOrders(userId, patientId, relativeTime, newOrderItemIds);

        # Verify update of last order time
        sampleValue = self.manager.loadPatientLastEventTime(patientId);
        verifyValue = 2000;
        self.assertEqual(verifyValue, sampleValue);

        # Find and cancel the last order at a later time
        relativeTime = 2200;
        patientOrders = self.manager.loadPatientOrders(patientId, relativeTime);
        lastOrder = patientOrders[0];
        for patientOrder in patientOrders:
            if lastOrder["relative_time_start"] < patientOrder["relative_time_start"]:
                lastOrder = patientOrder;

        newOrderItemIds = [];   # No new orders
        discontinuePatientOrderIds = [lastOrder["sim_patient_order_id"]];
        self.manager.signOrders(userId, patientId, relativeTime, newOrderItemIds, discontinuePatientOrderIds);

        # Verify update of last order time includes discontinue times
        sampleValue = self.manager.loadPatientLastEventTime(patientId);
        verifyValue = 2200;
        self.assertEqual(verifyValue, sampleValue);

        # Lookup patient with no prior orders recorded, then should default to time 0
        patientId = -2;
        sampleValue = self.manager.loadPatientLastEventTime(patientId);
        verifyValue = 0;
        self.assertEqual(verifyValue, sampleValue);
Example #23
0
class TestSimManagerGrading(DBTestCase):
    def setUp(self):
        """Prepare state for test cases"""
        DBTestCase.setUp(self)

        self.manager = SimManager()  # Instance to test on

        from stride.clinical_item.ClinicalItemDataLoader import ClinicalItemDataLoader
        ClinicalItemDataLoader.build_clinical_item_psql_schemata()
        self.manager.buildCPOESimSchema()

        log.info("Populate the database with test data")

        # Basically import a bunch of rigged CSV or TSV files that have realistic simulating case and grading data
        # Get that data into the test database
        clinical_item_category_str = \
"""clinical_item_category_id;source_table
1;source_table
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_category_str), "clinical_item_category", delim=";")

        clinical_item_str = \
"""clinical_item_id;clinical_item_category_id;name
1;1;Clinical item 1
2;1;Clinical item 2
3;1;Clinical item 3
4;1;Clinical item 4
5;1;Clinical item 5
6;1;Clinical item 6
7;1;Clinical item 7
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_str), "clinical_item", delim=";")

        clinical_item_str = \
"""sim_user_id;name
0;Default user
1;Jonathan Chen
2;User 2
3;User 3
4;User 4
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_str), "sim_user", delim=";")

        sim_patient_str = \
"""sim_patient_id;name;age_years;gender
1;Patient One;40;Male
2;Patient Two;50;Female
3;Patient Three;60;Male
4;Patient Four;70;Female
5;Patient Five;80;Male
6;Patient Six;90;Female
7;Patient Seven;100;Male
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_patient_str), "sim_patient", delim=";")

        sim_state_str = \
"""sim_state_id;name
1;Sim state 1
2;Sim state 2
3;Sim state 3
4;Sim state 4
5;Sim state 5
6;Sim state 6
7;Sim state 7
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_state_str), "sim_state", delim=";")

        sim_patient_order_str = \
"""sim_patient_order_id;sim_user_id;sim_patient_id;clinical_item_id;relative_time_start;sim_state_id
1;0;1;1;1;1
2;1;1;2;2;2
3;1;1;3;3;3
4;1;1;4;4;4
5;1;1;5;5;5
6;1;1;6;6;6
7;1;1;7;7;7
8;2;2;1;1;1
9;3;2;2;2;1
10;3;2;3;3;2
11;2;3;1;1;1
12;3;3;2;2;2
13;3;3;3;3;3
14;1;4;1;1;2
15;1;4;2;2;3
16;2;5;1;1;3
17;2;5;2;2;4
18;3;6;4;1;1
19;3;6;4;2;2
20;3;6;4;3;3
21;4;7;5;1;1
22;4;7;5;2;2
23;4;7;5;3;3
24;4;7;5;4;4
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_patient_order_str), "sim_patient_order", delim=";")

        sim_grading_key_str = \
"""sim_grader_id;sim_state_id;clinical_item_id;score;group_name
Jonathan Chen;1;1;1;g1
Jonathan Chen;2;2;1;g2
Jonathan Chen;3;3;1;g3
Jonathan Chen;4;4;1;
Jonathan Chen;5;5;1;g5
Jonathan Chen;6;6;1;
Jonathan Chen;7;7;1;g7
Jonathan Chen;3;1;1;g8
Jonathan Chen;4;2;1;g8
Jonathan Chen;1;4;-1000;
Jonathan Chen;2;4;10;
Jonathan Chen;3;4;2000;
Jonathan Chen;1;5;-1000;g9
Jonathan Chen;2;5;-1;
Jonathan Chen;3;5;0;g10
Jonathan Chen;3;5;-500;
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_grading_key_str), "sim_grading_key", delim=";")

    def tearDown(self):
        """Restore state from any setUp or test steps"""
        DBTestCase.tearDown(self)

    def test_gradeCases(self):
        # Give the application ID of some simulated patient test cases and the name
        #   of a grading key and just verify that it produces the expected results
        sim_patient_ids = [1, 2, 3, 4, 5, 6, 7, 8]
        sim_grading_key_id = "Jonathan Chen"
        expected_grades_by_patient_id = {
            1: {
                "total_score": 6,   # Default user (id = 0) is ignored and NULL group_names are counted separately
                "sim_patient_id": 1,
                "most_graded_user_id": 1,
                "most_active_user_id": 1
            },
            2: {
                "total_score": 1,   # Ungraded (clinical_item_id, sim_state_id) keys are omitted from summation
                "sim_patient_id": 2,
                "most_graded_user_id": 2,   # Most graded user is User 2 (even though most active is User 3)
                "most_active_user_id": 3
            },
            3: {
                "total_score": 3,
                "sim_patient_id": 3,
                "most_graded_user_id": 3,   # Most graded user is the most active one
                "most_active_user_id": 3
            },
            # 4: No grading available for the existing case
            5: {
                "total_score": 1,   # Scores in the same group g8 are counted only once
                "sim_patient_id": 5,
                "most_graded_user_id": 2,
                "most_active_user_id": 2
            },
            6: {
                "total_score": 1010,    # Non-uniform scores (i.e., not all scores = 1)
                "sim_patient_id": 6,
                "most_graded_user_id": 3,
                "most_active_user_id": 3
            },
            7: {
                "total_score": -1501,   # All negative and one 0 score results in negative score
                "sim_patient_id": 7,
                "most_graded_user_id": 4,
                "most_active_user_id": 4
            }
            # 8: Case doesn't exist
        }
        actual_grades_by_patient_id = self.manager.grade_cases(sim_patient_ids, sim_grading_key_id)
        self.assertEquals(expected_grades_by_patient_id, actual_grades_by_patient_id)
Example #24
0
    def setUp(self):
        """Prepare state for test cases"""
        DBTestCase.setUp(self)

        self.manager = SimManager()  # Instance to test on

        from stride.clinical_item.ClinicalItemDataLoader import ClinicalItemDataLoader
        ClinicalItemDataLoader.build_clinical_item_psql_schemata()
        self.manager.buildCPOESimSchema()

        log.info("Populate the database with test data")

        # Basically import a bunch of rigged CSV or TSV files that have realistic simulating case and grading data
        # Get that data into the test database
        clinical_item_category_str = \
"""clinical_item_category_id;source_table
1;source_table
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_category_str), "clinical_item_category", delim=";")

        clinical_item_str = \
"""clinical_item_id;clinical_item_category_id;name
1;1;Clinical item 1
2;1;Clinical item 2
3;1;Clinical item 3
4;1;Clinical item 4
5;1;Clinical item 5
6;1;Clinical item 6
7;1;Clinical item 7
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_str), "clinical_item", delim=";")

        clinical_item_str = \
"""sim_user_id;name
0;Default user
1;Jonathan Chen
2;User 2
3;User 3
4;User 4
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(clinical_item_str), "sim_user", delim=";")

        sim_patient_str = \
"""sim_patient_id;name;age_years;gender
1;Patient One;40;Male
2;Patient Two;50;Female
3;Patient Three;60;Male
4;Patient Four;70;Female
5;Patient Five;80;Male
6;Patient Six;90;Female
7;Patient Seven;100;Male
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_patient_str), "sim_patient", delim=";")

        sim_state_str = \
"""sim_state_id;name
1;Sim state 1
2;Sim state 2
3;Sim state 3
4;Sim state 4
5;Sim state 5
6;Sim state 6
7;Sim state 7
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_state_str), "sim_state", delim=";")

        sim_patient_order_str = \
"""sim_patient_order_id;sim_user_id;sim_patient_id;clinical_item_id;relative_time_start;sim_state_id
1;0;1;1;1;1
2;1;1;2;2;2
3;1;1;3;3;3
4;1;1;4;4;4
5;1;1;5;5;5
6;1;1;6;6;6
7;1;1;7;7;7
8;2;2;1;1;1
9;3;2;2;2;1
10;3;2;3;3;2
11;2;3;1;1;1
12;3;3;2;2;2
13;3;3;3;3;3
14;1;4;1;1;2
15;1;4;2;2;3
16;2;5;1;1;3
17;2;5;2;2;4
18;3;6;4;1;1
19;3;6;4;2;2
20;3;6;4;3;3
21;4;7;5;1;1
22;4;7;5;2;2
23;4;7;5;3;3
24;4;7;5;4;4
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_patient_order_str), "sim_patient_order", delim=";")

        sim_grading_key_str = \
"""sim_grader_id;sim_state_id;clinical_item_id;score;group_name
Jonathan Chen;1;1;1;g1
Jonathan Chen;2;2;1;g2
Jonathan Chen;3;3;1;g3
Jonathan Chen;4;4;1;
Jonathan Chen;5;5;1;g5
Jonathan Chen;6;6;1;
Jonathan Chen;7;7;1;g7
Jonathan Chen;3;1;1;g8
Jonathan Chen;4;2;1;g8
Jonathan Chen;1;4;-1000;
Jonathan Chen;2;4;10;
Jonathan Chen;3;4;2000;
Jonathan Chen;1;5;-1000;g9
Jonathan Chen;2;5;-1;
Jonathan Chen;3;5;0;g10
Jonathan Chen;3;5;-500;
"""
        # Parse into DB insertion object
        DBUtil.insertFile(StringIO(sim_grading_key_str), "sim_grading_key", delim=";")
Example #25
0
 def action_createUser(self):
     manager = SimManager();
     userData = {"name": self.requestData["sim_user_name"] };
     manager.createUser(userData);