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);
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)
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
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
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);