def test_invalid_reading_not_created(reading_id, reading_referral_followup, patient_factory, api_post): patient_factory.create(patientId="123") # Removed bpSystolic to make the eading invalid del reading_referral_followup["bpSystolic"] response = api_post(endpoint="/api/readings", json=reading_referral_followup) assert response.status_code == 400 assert crud.read(Reading, readingId=reading_id) is None assert crud.read(Referral, readingId=reading_id) is None assert crud.read(FollowUp, readingId=reading_id) is None
def post(): # Get all patients for this user timestamp: int = request.args.get("since", None, type=int) if not timestamp: abort(400, message="'since' query parameter is required") # ~~~~~~~~~~~~~~~~~~~~~~ new Logic ~~~~~~~~~~~~~~~~~~~~~~~~~~ json = request.get_json(force=True) patients_on_server_chache = set() for r in json: if r.get("patientId") not in patients_on_server_chache: patient_on_server = crud.read(Patient, patientId=r.get("patientId")) if patient_on_server is None: continue else: patients_on_server_chache.add(patient_on_server.patientId) if crud.read(Reading, readingId=r.get("readingId")): continue else: error_message = readings.validate(r) if error_message is not None: abort(400, message=error_message) reading = marshal.unmarshal(Reading, r) invariant.resolve_reading_invariants(reading) crud.create(reading, refresh=True) user = util.current_user() # TODO: create custome DB calls for referral and followup all_patients = view.patient_view_for_user(user) new_readings = [] new_referral = [] new_followup = [] for p in all_patients: for r in p["readings"]: if r["dateTimeTaken"] > timestamp: new_readings.append(r) if (r["referral"] and r["referral"]["dateReferred"] > timestamp and r["dateTimeTaken"] < timestamp): new_referral.append(r["referral"]) if (r["followup"] and r["followup"]["dateAssessed"] > timestamp and r["dateTimeTaken"] < timestamp): new_followup.append(r["followup"]) return { "total": len(new_readings) + len(new_referral) + len(new_followup), "readings": new_readings, "newReferralsForOldReadings": new_referral, "newFollowupsForOldReadings": new_followup, }
def post(): json = request.get_json(force=True) error_message = patients.validate(json) if error_message is not None: abort(400, message=error_message) patient = marshal.unmarshal(Patient, json) if crud.read(Patient, patientId=patient.patientId): abort(409, message=f"A patient already exists with id: {patient.patientId}") # Resolve invariants and set the creation timestamp for the patient ensuring # that both the created and lastEdited fields have the exact same value. invariant.resolve_reading_invariants(patient) creation_time = get_current_time() patient.created = creation_time patient.lastEdited = creation_time crud.create(patient, refresh=True) # Associate the patient with the user who created them user = util.current_user() assoc.associate_by_user_role(patient, user) # If the patient has any readings, and those readings have referrals, we # associate the patient with the facilities they were referred to for reading in patient.readings: referral = reading.referral if referral and not assoc.has_association(patient, referral.healthFacility): assoc.associate(patient, facility=referral.healthFacility) # The associate function performs a database commit, since this will # wipe out the patient we want to return we must refresh it. data.db_session.refresh(patient) return marshal.marshal(patient), 201
def post(self): import api.util as util import data.crud as crud import service.assoc as assoc from models import Patient try: request_body = _get_request_body() except: return {"HTTP 400": decoding_error}, 400 if not request_body["patientId"]: return {"HTTP 400": "Patient Id is empty."}, 400 # Emulate old API functionality with new systems, use of /api/associations is # preferred over this method now patient = crud.read(Patient, patientId=request_body["patientId"]) if patient: user = util.current_user() facility = user.healthFacility if not assoc.has_association(patient, facility, user): assoc.associate(patient, facility, user) else: abort(409, message="Duplicate entry") return { "message": "patient has been added to facility successfully" }, 201 else: abort(404, message="This patient does not exist.")
def post(): json = request.get_json(force=True) # Populate the dateAssessed and healthCareWorkerId fields of the followup json["dateAssessed"] = get_current_time() user = util.current_user() json["healthcareWorkerId"] = user.id error_message = assessments.validate(json) if error_message is not None: abort(400, message=error_message) follow_up = marshal.unmarshal(FollowUp, json) # Check that reading id which doesn’t reference an existing reading in the database reading = crud.read(Reading, readingId=follow_up.readingId) if not reading: abort(404, message=f"No reading with id {follow_up.readingId}") crud.create(follow_up) # Creating an assessment also marks any referral attached to the associated # reading as "assessed" if follow_up.reading.referral: follow_up.reading.referral.isAssessed = True data.db_session.commit() return follow_up.id, 201
def test_update(patient_factory): patient_factory.create(patientId="abc", patientName="ABC") crud.update(Patient, {"patientName": "CDE"}, patientId="abc") query = crud.read(Patient, patientId="abc") assert query.patientName == "CDE"
def get(patient_id: str): patient = crud.read(Patient, patientId=patient_id) if not patient: abort(404, message=f"No patient with id {patient_id}") # getting all bpSystolic readings for each month bp_systolic = statsCalculation.get_stats_data("bpSystolic", patient.readings) # getting all bpDiastolic readings for each month bp_diastolic = statsCalculation.get_stats_data("bpDiastolic", patient.readings) # getting all heart rate readings for each month heart_rate = statsCalculation.get_stats_data("heartRateBPM", patient.readings) # getting all traffic lights from day 1 for this patient traffic_light_statuses = statsCalculation.get_stats_data( "trafficLightStatus", patient.readings ) # putting data into one object now data = { "bpSystolicReadingsMonthly": bp_systolic, "bpDiastolicReadingsMonthly": bp_diastolic, "heartRateReadingsMonthly": heart_rate, "trafficLightCountsFromDay1": { "green": traffic_light_statuses[0], # dont "yellowUp": traffic_light_statuses[1], # hate "yellowDown": traffic_light_statuses[2], # the "redUp": traffic_light_statuses[3], # magic "redDown": traffic_light_statuses[4], # numbers }, } return data
def current_user() -> User: """ Returns the the model for the user making the request. :return: """ identity = jwt.get_jwt_identity() return crud.read(User, id=identity["userId"])
def test_invalid_facility_not_created(facility_name, health_facility, api_post): # Invalid as healthFacilityName is not there del health_facility["healthFacilityName"] response = api_post(endpoint="/api/facilities", json=health_facility) assert response.status_code == 400 assert crud.read(HealthFacility, healthFacilityName=facility_name) is None
def test_update_patient_name(patient_factory, api_put): patient_id = "64164134514" patient_factory.create(patientId=patient_id, patientName="AB") response = api_put(endpoint=f"/api/patients/{patient_id}/info", json={"patientName": "CD"}) assert response.status_code == 200 assert crud.read(Patient, patientId=patient_id).patientName == "CD"
def test_post_facility(facility_name, health_facility, api_post): try: response = api_post(endpoint="/api/facilities", json=health_facility) assert response.status_code == 201 assert crud.read(HealthFacility, healthFacilityName=facility_name) is not None finally: crud.delete_by(HealthFacility, healthFacilityName=facility_name)
def post(): json: dict = request.get_json(force=True) error_message = associations.validate(json) if error_message is not None: abort(400, message=error_message) patient_id = json.get("patientId") facility_name = json.get("healthFacilityName") user_id = json.get("userId") patient = crud.read(Patient, patientId=patient_id) if not patient: abort(400, message=f"No patient exists with id: {patient_id}") if facility_name: facility = crud.read(HealthFacility, healthFacilityName=facility_name) if not facility: abort(400, message=f"No health facility with name: {facility_name}") else: facility = None if user_id: user = crud.read(User, id=user_id) if not user: abort(400, message=f"No user with id: {user_id}") # if user exists but no health facility then assign the patient to the user's health facility facility = user.healthFacility else: user = None if not facility_name and not user_id: # If neither facility_name or user_id are present in the request, create a # associate patient with the current user's health facility user = util.current_user() assoc.associate(patient, user.healthFacility, user) else: # Otherwise, simply associate the provided models together if not assoc.has_association(patient, facility, user): assoc.associate(patient, facility, user) return {}, 201
def test_create_patient_with_nested_readings(database, api_post): patient_id = "5390160146141" reading_ids = [ "65acfe28-b0d6-4a63-a484-eceb3277fb4e", "90293637-d763-494a-8cc7-85a88d023f3e", ] p = __make_patient(patient_id, reading_ids) response = api_post(endpoint="/api/patients", json=p) database.session.commit() try: assert response.status_code == 201 assert crud.read(Patient, patientId=patient_id) is not None for r in reading_ids: reading = crud.read(Reading, readingId=r) assert reading is not None assert reading.trafficLightStatus == TrafficLightEnum.GREEN finally: for r in reading_ids: crud.delete_by(Reading, readingId=r) crud.delete_by(Patient, patientId=patient_id)
def put(patient_id: str): json = request.get_json(force=True) error_message = patients.validate_put_request(json, patient_id) if error_message is not None: abort(400, message=error_message) # If the inbound JSON contains a `base` field then we need to check if it is the # same as the `lastEdited` field of the existing patient. If it is then that # means that the patient has not been edited on the server since this inbound # patient was last synced and we can apply the changes. If they are not equal, # then that means the patient has been edited on the server after it was last # synced with the client. In these cases, we reject the changes for the client. # # You can think of this like aborting a git merge due to conflicts. base = json.get("base") if base: last_edited = crud.read(Patient, patientId=patient_id).lastEdited if base != last_edited: abort(409, message="Unable to merge changes, conflict detected") # Delete the `base` field once we are done with it as to not confuse the # ORM as there is no "base" column in the database for patients. del json["base"] crud.update(Patient, json, patientId=patient_id) patient = crud.read(Patient, patientId=patient_id) # Update the patient's lastEdited timestamp only if there was no `base` field # in the request JSON. If there was then that means that this edit happened some # time in the past and is just being synced. In this case we want to keep the # `lastEdited` value which is present in the request. if not base: patient.lastEdited = get_current_time() data.db_session.commit() data.db_session.refresh( patient) # Need to refresh the patient after commit return marshal.marshal(patient)
def test_post_reading_with_referral_and_followup(reading_id, reading_referral_followup, patient_factory, api_post): patient_factory.create(patientId="123") try: response = api_post(endpoint="/api/readings", json=reading_referral_followup) assert response.status_code == 201 assert crud.read(Reading, readingId=reading_id) is not None assert crud.read(Referral, readingId=reading_id) is not None assert crud.read(FollowUp, readingId=reading_id) is not None # Reading should have it's traffic light computed reading = crud.read(Reading, readingId=reading_id) assert reading.trafficLightStatus.value == "GREEN" # Referral should be marked as assessed referral = crud.read(Referral, readingId=reading_id) assert referral.isAssessed finally: crud.delete_by(FollowUp, readingId=reading_id) crud.delete_by(Referral, readingId=reading_id) crud.delete_by(Reading, readingId=reading_id)
def test_update_patient_with_base(patient_factory, api_put): patient_id = "45642677524614" patient_factory.create(patientId=patient_id, patientName="AB", lastEdited=5) json = { "patientName": "CD", "lastEdited": 6, "base": 5, # base == lastEdited -> request is accepted } response = api_put(endpoint=f"/api/patients/{patient_id}/info", json=json) assert response.status_code == 200 patient = crud.read(Patient, patientId=patient_id) assert patient.patientName == "CD" assert patient.lastEdited == 6
def test_update_patient_abort_due_to_conflict(patient_factory, api_put): patient_id = "45642677524614" patient_factory.create(patientId=patient_id, patientName="AB", lastEdited=7) json = { "patientName": "CD", "lastEdited": 6, "base": 5, # base != lastEdited -> request is rejected } response = api_put(endpoint=f"/api/patients/{patient_id}/info", json=json) assert response.status_code == 409 patient = crud.read(Patient, patientId=patient_id) assert patient.patientName == "AB" assert patient.lastEdited == 7
def post(): # Get key-value pairs from parser and remove pairs with a None value data = Root.parser.parse_args() data = util.filterPairsWithNone(data) # Create a DB Model instance for the new facility and load into DB facility = marshal.unmarshal(HealthFacility, data) crud.create(facility) # Get back a dict for return facilityDict = marshal.marshal( crud.read(HealthFacility, healthFacilityName=data["healthFacilityName"])) return facilityDict, 201
def test_invalid_patient_not_created(patient_factory, api_post): patient_id = "48375354" # invalid as patientName is missing patient = { "patientId": patient_id, "patientSex": "FEMALE", "isPregnant": False, "zone": "37", "villageNumber": "37", "created": 1, "lastEdited": 5, "base": 5, "readings": [], } response = api_post(endpoint="/api/patients", json=patient) assert response.status_code == 400 assert crud.read(Patient, patientId=patient_id) is None
def post(): json = request.get_json(force=True) error_message = readings.validate(json) if error_message is not None: abort(400, message=error_message) reading = marshal.unmarshal(Reading, json) if crud.read(Reading, readingId=reading.readingId): abort( 409, message=f"A reading already exists with id: {reading.readingId}" ) invariant.resolve_reading_invariants(reading) crud.create(reading, refresh=True) return marshal.marshal(reading), 201
def _do_create(self, **kwargs) -> Any: import data from config import flask_bcrypt from models import User, Role d = dict(**kwargs) role_name = d["role"] del d["role"] # not an actual user field so delete if from the args # Hash the user's password so that they can login d["password"] = flask_bcrypt.generate_password_hash(d["password"]) user = marshal.unmarshal(User, d) crud.create(user) role = crud.read(Role, name=role_name) user.roleIds = [role] data.db_session.commit() return user
def post(assessment_id: int): if not assessment_id: abort(404, message=f"Assessment id is required") json = request.get_json(force=True) json["dateAssessed"] = get_current_time() # get current UserID user = util.current_user() json["healthcareWorkerId"] = user.id assessment = crud.read(FollowUp, id=assessment_id) if not assessment: abort(404, message=f"No assessment with id {assessment_id}") json["readingId"] = assessment.readingId error_message = assessments.validate(json) if error_message is not None: abort(400, message=error_message) crud.update(FollowUp, json, id=assessment.id) return assessment.id, 201
def get(patient_id: str): patient = crud.read(Patient, patientId=patient_id) return [marshal.marshal(r) for r in patient.readings]
def test_read(patient_factory): expected = patient_factory.create(patientId="abc", patientName="ABC") actual = crud.read(Patient, patientId="abc") assert actual == expected
def get(assessment_id: int): follow_up = crud.read(FollowUp, id=assessment_id) if not follow_up: abort(404, message=f"No assessment with id {id}") return marshal.marshal(follow_up)
def test_get_mobile_patient(database, api_post, api_get): patient_ids = [] reading_ids = [] try: p = __make_patient("222266667", ["893ddaad-1ebd-46fb-928b-ad0640115aa1"]) patient_ids.append("222266667") reading_ids.append("893ddaad-1ebd-46fb-928b-ad0640115aa1") response = api_post(endpoint="/api/patients", json=p) database.session.commit() assert response.status_code == 201 # Make the patient IDs so that they're on both sides of the patient IDs. p = __make_full_patient("9999", ["7f60bbb3-c49d-425f-825c-681c8330b61d"]) patient_ids.append("9999") reading_ids.append("7f60bbb3-c49d-425f-825c-681c8330b61d") response = api_post(endpoint="/api/patients", json=p) database.session.commit() assert response.status_code == 201 # Make the patient IDs so that they're on both sides of the patient IDs p = __make_full_patient("999955551", ["978e870e-c542-428a-a8bf-dabb0e52bff3"]) patient_ids.append("999955551") reading_ids.append("978e870e-c542-428a-a8bf-dabb0e52bff3") response = api_post(endpoint="/api/patients", json=p) database.session.commit() assert response.status_code == 201 for p in patient_ids: patient = crud.read(Patient, patientId=p) assert patient is not None # Add a more fleshed-out reading to the first patient. reading = __make_reading( reading_id="123dabdd-5des-7ufh-23fd-qd4308143651", patient_id=patient_ids[0]) reading_ids.append("123dabdd-5des-7ufh-23fd-qd4308143651") reading_response = api_post(endpoint="/api/readings", json=reading) database.session.commit() assert reading_response.status_code == 201 # Add a more minimal reading to the first patient. reading = __make_reading_no_extra_vitals( reading_id="526292b7-53d0-4e7e-8a96-f66f061477ff", patient_id=patient_ids[0]) reading_ids.append("526292b7-53d0-4e7e-8a96-f66f061477ff") reading_response = api_post(endpoint="/api/readings", json=reading) database.session.commit() assert reading_response.status_code == 201 # Add another fleshed-out reading to the first patient. reading = __make_reading( reading_id="2ab4f830-3cc0-4e98-bff3-174a9dcc630a", patient_id=patient_ids[0]) reading_ids.append("2ab4f830-3cc0-4e98-bff3-174a9dcc630a") reading_response = api_post(endpoint="/api/readings", json=reading) database.session.commit() assert reading_response.status_code == 201 for r in reading_ids: reading = crud.read(Reading, readingId=r) assert reading is not None # Get all the patients from /api/mobile/patients. responseMobile = api_get(endpoint="/api/mobile/patients") assert responseMobile.status_code == 200 # Setup an error message to return when an assert fails. # Note: Since this is usually tested with the test seed data, there will # be more than just 1 patient here. patient_number_info = ( f"There were {len(responseMobile.json())} patients " + "returned by api/mobile/patients. Dumping them all now:\n" + pformat(responseMobile.json(), width=48) + "\n" + "========================================================") # Loop through every single patient in the database. # For every patient in the database (as admin user, /api/mobile/patients returns # all the patients), get the patient info from the /api/patients/:id endpont # and then determine if they match. for patient_from_mobile_patients in responseMobile.json(): patient_id = patient_from_mobile_patients["patientId"] # Validate that the GET requests for /api/patients/{patient_id} and # /api/mobile/patients give back the same information. # We first validate that the patient info returned is consistent. # Get the patient from the /api/patients/:id endpoint. response_patients_get = api_get( endpoint=f"/api/patients/{patient_id}") assert response_patients_get.status_code == 200 patient_from_patients_api = response_patients_get.json() # Check that both results are basically equal. __assert_dicts_are_equal( patient_from_mobile_patients, patient_from_patients_api, f"patient {patient_id} from api/mobile/patients", f"patient {patient_id} from api/patients/:id", other_error_messages=patient_number_info, # Validate the readings later. ignored_keys=["readings"], ) # Validate the readings now. We check that they both have the same readings. # Loop through both of them in case one readings list is different from the other. for readingFromMobile in patient_from_mobile_patients["readings"]: # From the reading from the api/mobile/patients, find the corresponding reading # from the api/patients/:id endpoint current_reading_id = readingFromMobile["readingId"] readingFromNormalApi = [ r for r in patient_from_patients_api["readings"] if r["readingId"] == current_reading_id ][0] # Check that they give the exact same information. __assert_dicts_are_equal( readingFromMobile, readingFromNormalApi, f"reading {current_reading_id} from api/mobile/patients", f"reading {current_reading_id} from api/patients/:id", other_error_messages=patient_number_info, ignored_keys=["userId"], ) for readingFromNormalApi in patient_from_patients_api["readings"]: # From the reading from the api/patients/:id, find the corresponding reading # from the api/mobile/patients endpoint current_reading_id = readingFromNormalApi["readingId"] readingFromMobile = [ r for r in patient_from_mobile_patients["readings"] if r["readingId"] == current_reading_id ][0] # Check that they give the exact same information. __assert_dicts_are_equal( readingFromMobile, readingFromNormalApi, f"reading {current_reading_id} from api/mobile/patients", f"reading {current_reading_id} from api/patients/:id", other_error_messages=patient_number_info, ignored_keys=["userId"], ) finally: for r in reading_ids: crud.delete_by(Reading, readingId=r) for p in patient_ids: crud.delete_by(Patient, patientId=p)
def get(referral_id: int): referral = crud.read(Referral, id=referral_id) if not referral: abort(404, message=f"No referral with id {id}") return marshal.marshal(referral)
def get(reading_id: str): reading = crud.read(Reading, readingId=reading_id) if not reading: abort(404, message=f"No reading with id {reading_id}") return marshal.marshal(reading)
def get(patient_id: str): patient = crud.read(Patient, patientId=patient_id) if not patient: abort(404, message=f"No patient with id {patient_id}") return marshal.marshal(patient)
def post(): # Get all patients for this user user = util.current_user() timestamp: int = request.args.get("since", None, type=int) if not timestamp: abort(400, message="'since' query parameter is required") patients_to_be_added: [Patient] = [] # ~~~~~~~~~~~~~~~~~~~~~~ new Logic ~~~~~~~~~~~~~~~~~~~~~~~~~~ json = request.get_json(force=True) for p in json: patient_on_server = crud.read(Patient, patientId=p.get("patientId")) if patient_on_server is None: error_message = patients.validate(p) if error_message is not None: abort(400, message=error_message) patient = marshal.unmarshal(Patient, p) # # Resolve invariants and set the creation timestamp for the patient ensuring # # that both the created and lastEdited fields have the exact same value. invariant.resolve_reading_invariants_mobile(patient) creation_time = get_current_time() patient.created = creation_time patient.lastEdited = creation_time patients_to_be_added.append(patient) else: if (int(patient_on_server.lastEdited) < int( p.get("lastEdited")) < timestamp): if p.get("base"): if p.get("base") != p.get("lastEdited"): abort( 409, message= "Unable to merge changes, conflict detected", ) del p["base"] p["lastEdited"] = get_current_time() crud.update(Patient, p, patientId=p["patientId"]) # TODO: revisit association logic if not assoc.has_association(patient_on_server, user=user): assoc.associate(patient_on_server, user.healthFacility, user) # update association if patients_to_be_added: crud.create_all_patients(patients_to_be_added) for new_patient in patients_to_be_added: # TODO: revisit association logic if not assoc.has_association(new_patient, user=user): assoc.associate(new_patient, user.healthFacility, user) # read all the patients from the DB # TODO: optimize to get only patients all_patients = view.patient_view_for_user(user) all_patients_edited_or_new = [ p for p in all_patients if p["lastEdited"] > timestamp ] # ~~~~~~~~~~~~~~~~~ old logic ~~~~~~~~~~~~~~~~~~~~ # New patients are patients who are created after the timestamp # new_patients = [ # p["patientId"] for p in all_patients if p["created"] > timestamp # ] # Edited patients are patients who were created before the timestamp but # edited after it # edited_patients = [ # p["patientId"] # for p in all_patients # if p["created"] < p["lastEdited"] # and p["created"] <= timestamp < p["lastEdited"] # ] # New readings created after the timestamp for patients who where created before # the timestamp # readings = [] # New followups which were created after the timestamp for readings which were # created before the timestamp # followups = [] # # for p in all_patients: # for r in p["readings"]: # r_time = int(r["dateTimeTaken"]) # if p["created"] <= timestamp < r_time: # readings.append(r["readingId"]) # # if r["followup"] and r_time < timestamp < int( # r["followup"]["dateAssessed"] # ): # followups.append(r["followup"]["id"]) return { "total": len(all_patients_edited_or_new), "patients": all_patients_edited_or_new, }