def _patient_wrapper(row): """ The wrapper bolts the patient object onto the case, if we find it, otherwise does what the view would have done in the first place and adds an empty patient property """ from bhoma.apps.patient.models import CPatient data = row.get('value') docid = row.get('id') doc = row.get('doc') if not data or data is None: return row if not isinstance(data, dict) or not docid: return row else: if 'rev' in data: data['_rev'] = data.pop('rev') case = cls.wrap(data) case.patient = None if doc == None: # there's (I think) a bug in couchdb causing these to come back empty try: doc = CPatient.get_db().get(docid) except Exception, e: pass if doc and doc.get("doc_type") == "CPatient": case.patient = CPatient.wrap(doc) return case
def patient_excel(request): # we have to make sure to update any patients without export tags # before redirecting to the export view. for pat in CPatient.view("patient/missing_export_tag", include_docs=True): try: pat.save() except ResourceConflict: # workaround potential conflicts by trying twice pat = CPatient.get(pat.get_id) pat.save() return HttpResponseRedirect("%s?export_tag=CPatient&filename=Patients" % reverse("export_data_async"))
def create_patients(count, clinic_id): from bhoma.utils.data import random_person, random_clinic_id from bhoma.apps.patient.models import CPatient if clinic_id is None: print "no clinic specified, will randomly assign ids" CPatient.get_db() for i in range(count): p = random_person() this_clinic_id = clinic_id if clinic_id else random_clinic_id() p.clinic_ids = [this_clinic_id,] p.save() print "successfully generated %s new patients" % count
def testRegeneratePatient(self): folder_name = os.path.join(os.path.dirname(__file__), "data", "chw") patient = export.import_patient_json_file(os.path.join(folder_name, "patient.json")) self.assertEqual(0, len(patient.encounters)) with open(os.path.join(folder_name, "non_life_threatening_referral.xml"), "r") as f: formbody = f.read() formdoc = post_xform_to_couch(formbody) new_form_workflow(formdoc, SENDER_PHONE, None) patient = CPatient.get(patient.get_id) self.assertEqual(1, len(patient.encounters)) self.assertTrue(reprocess(patient.get_id)) patient = CPatient.get(patient.get_id) self.assertEqual(1, len(patient.encounters))
def reprocess(patient_id): """ Reprocess a patient's data from xforms, by playing them back in the order they are found. Returns true if successfully regenerated, otherwise false. """ # you can't call the loader because the loader calls this patient = CPatient.get(patient_id) # first create a backup in case anything goes wrong backup_id = CPatient.copy(patient) try: # have to change types, otherwise we get conflicts with our cases backup = CPatient.get(backup_id) backup.doc_type = "PatientBackup" backup.save() # reload the original and blank out encounters/cases/version patient = CPatient.get(patient_id) patient.encounters = [] patient.cases = [] # don't blank out pregnancies. we need them in order to preserve ids # patient.pregnancies = [] patient.backup_id = backup_id patient.app_version = None # blanking out the version marks it "upgraded" to the current version patient.save() for form in patient.unique_xforms(): add_form_to_patient(patient_id, form) # save to stick the new version on so we know we've upgraded this form if form.requires_upgrade(): form.save() # only send the updated signal when all the dust has settled patient_updated.send(sender="reprocessing", patient_id=patient_id) get_db().delete_doc(backup_id) return True except Exception, e: logging.exception("problem regenerating patient case data (patient: %s)" % patient_id) current_rev = get_db().get_rev(patient_id) patient = get_db().get(backup_id) patient["_rev"] = current_rev patient["_id"] = patient_id patient["doc_type"] = "CPatient" get_db().save_doc(patient) get_db().delete_doc(backup_id) return False
def random_person(): gender = random.choice([gen[0] for gen in GENDERS]) first_name, last_name = random_male_name() if gender == GENDER_MALE else random_female_name() clinic = random_clinic_id() patient_id = "%s%06d" % (clinic, random.randint(0, 999999)) patient = CPatient(first_name=first_name, middle_name="", patient_id=patient_id, last_name=last_name, birthdate=random_dob(), birthdate_estimated = False, gender = gender, created_on = datetime.datetime.utcnow()) patient.address = CAddress() patient.address.clinic_id = clinic return patient
def pi_details(request): year = int(request.GET["year"]) month = int(request.GET["month"]) clinic = request.GET["clinic"] report_slug = request.GET["report"] col_slug = request.GET["col"] results = get_db().view(const.get_view_name(report_slug), reduce=False, key=[year, month -1, clinic, col_slug], include_docs=True) forms = [] for row in results: num, denom = row["value"] # only count forms for now, and make sure they have a patient id # and contributed to the report denominator if row["doc"]["doc_type"] == "CXFormInstance" and denom > 0: form = CXFormInstance.wrap(row["doc"]) try: form.patient_id = form.xpath("case/patient_id") form.bhoma_patient_id = CPatient.get(form.patient_id).formatted_id except ResourceNotFound: form.patient = form.patient_id = form.bhoma_patient_id = None form.num = num form.denom = denom form.good = num == denom forms.append(form) elif row["doc"]["doc_type"] == "PregnancyReportRecord" and denom > 0: # for the pregnancy PI force the aggregated pregnancy docs # to look like forms preg = PregnancyReportRecord.wrap(row["doc"]) try: preg.bhoma_patient_id = CPatient.get(preg.patient_id).formatted_id except ResourceNotFound: form.patient = form.patient_id = form.bhoma_patient_id = None preg.num = num preg.denom = denom preg.good = num == denom preg.encounter_date = preg.first_visit_date forms.append(preg) title = "PI Details - %s: %s (%s, %s)" % (const.get_name(report_slug), const.get_display_name(report_slug, col_slug), datetime(year, month, 1).strftime("%B %Y"), clinic_display_name(clinic)) return render_to_response(request, "reports/pi_details.html", {"report": {"name": title}, "forms": forms})
def get_patient(patient_id): """ Loads a patient from the database. If any conflicts are detected this will run through all the xforms and regenerate the patient, deleting all other revisions. """ resolve_conflicts(patient_id) return CPatient.get(patient_id)
def wrapper_func(row): """ Given a row of the view, get out a json representation of a patient row """ patient = CPatient.wrap(row["doc"]) return [patient.get_id, patient.formatted_id, patient.gender, patient.birthdate.strftime("%Y-%m-%d") if patient.birthdate else "", patient.current_clinic_display]
def patient_case(request, patient_id, case_id): pat = CPatient.get(patient_id) found_case = None for case in pat.cases: if case.get_id == case_id: found_case = case break return render_to_response(request, "case/single_case.html", {"patient": pat, "case": found_case, "options": TouchscreenOptions.default()})
def search_results(request): query = request.GET.get('q', '') if not query: return HttpResponseRedirect(reverse("patient_search")) patients = CPatient.view(VIEW_PATIENT_SEARCH, key=query.lower(), include_docs=True) minus_duplicates = SortedDict() for patient in patients: if not patient.get_id in minus_duplicates: minus_duplicates[patient.get_id] = patient return render_to_response(request, "patient/search_results.html", {"patients": minus_duplicates.values(), "query": query} )
def lookup_by_id(request): """ Get a patient by ID, returning the json representation of the patient """ pat_id = request.GET.get('id') if pat_id != None: patients = CPatient.view(VIEW_PATIENT_BY_BHOMA_ID, key=pat_id, reduce=False, include_docs=True).all() return _patient_list_response(patients) else: pat_uuid = request.GET.get('uuid') patient = loader.get_patient(pat_uuid) return HttpResponse(json.dumps(patient.to_json()), mimetype='text/json')
def export_patient_download(request, patient_id): """ Export a patient's forms to a zip file. """ # this may not perform with huge amounts of data, but for a single # patient should be fine patient = CPatient.get(patient_id) temp_file = export.export_patient(patient) data = temp_file.read() response = HttpResponse(data, content_type='application/zip') response['Content-Disposition'] = 'attachment; filename=%s' % export.get_zip_filename(patient) response['Content-Length'] = len(data) return response
def export_patient(request, patient_id): patient = CPatient.get(patient_id) count = 1 form_filenames = [] for form in patient.xforms(): form_filenames.append(export.get_form_filename(count, form)) count += 1 return render_to_response(request, "patient/export_instructions.html", {"patient": patient, "zip_filename": export.get_zip_filename(patient), "foldername": export.get_zip_filename(patient).split(".")[0], "pat_filename": export.get_patient_filename(patient), "forms": form_filenames})
def testUpdate(self): patient = random_person() case = bootstrap_case_from_xml(self, "create_update.xml") patient.cases=[case,] patient.save() # make sure we can get it back from our shared view case_back = CommCareCase.get_with_patient("case/all_and_patient", case.case_id) self.assertEqual(case.case_id, case_back.case_id) self.assertEqual(patient.first_name, case_back.patient.first_name) self.assertEqual(patient.last_name, case_back.patient.last_name) self.assertEqual(patient.get_id, case_back.patient.get_id) self.assertEqual(1, len(patient.cases)) self.assertEqual(case._id, patient.cases[0]._id) # update case = bootstrap_case_from_xml(self, "update.xml", case.case_id) self.assertEqual(patient.get_id, case.patient.get_id) case.save() patient = CPatient.get(patient.get_id) self.assertEqual(1, len(patient.cases)) case_in_patient = patient.cases[0] self.assertEqual(case._id, case_in_patient._id) self.assertEqual(False, case_in_patient.closed) self.assertEqual(3, len(case_in_patient.actions)) new_update_action = case_in_patient.actions[2] self.assertEqual(const.CASE_ACTION_UPDATE, new_update_action["action_type"]) # some properties didn't change self.assertEqual("123", str(case["someotherprop"])) # but some should have self.assertEqual("abcd", case_in_patient["someprop"]) self.assertEqual("abcd", new_update_action["someprop"]) # and there are new ones self.assertEqual("efgh", case_in_patient["somenewprop"]) self.assertEqual("efgh", new_update_action["somenewprop"]) # we also changed everything originally in the case self.assertEqual("a_new_type", case_in_patient.type) self.assertEqual("a_new_type", new_update_action["type"]) self.assertEqual("a new name", case_in_patient.name) self.assertEqual("a new name", new_update_action["name"]) self.assertEqual(UPDATE_DATE, case_in_patient.opened_on) self.assertEqual(UPDATE_DATE, new_update_action["opened_on"]) # case should have a new modified date self.assertEqual(MODIFY_DATE, case.modified_on)
def fuzzy_match(request): # TODO this currently always returns nothing fname = request.POST.get('fname') lname = request.POST.get('lname') gender = request.POST.get('sex') dob = request.POST.get('dob') def prep(val): return val.strip().lower() if val and isinstance(val, basestring) else val startkey = map(prep, [gender, lname, fname, dob]) endkey = map(prep, [gender, lname, fname, dob, {}]) pats = CPatient.view(VIEW_PATIENT_FUZZY_SEARCH, startkey=startkey, endkey=endkey, reduce=False, include_docs=True).all() return _patient_list_response(pats)
def insert_zscores(sender, form, **kwargs): """For Under5, hook up zscore calculated""" from bhoma.apps.zscore.models import Zscore from bhoma.apps.patient.models import CPatient patient_id = form.xpath("case/patient_id") if not patient_id or not get_db().doc_exist(patient_id): return patient = CPatient.get(patient_id) if form["#type"] == "underfive" and patient.age_in_months <= 60: form.zscore_calc_good = [] try: zscore = Zscore.objects.get(gender=patient.gender, age=patient.age_in_months) except Zscore.DoesNotExist: # how is this possible? zscore = None if zscore and form.xpath("nutrition/weight_for_age") and form.xpath("vitals/weight"): def calculate_zscore(l_value,m_value,s_value,x_value): #for L != 0, Z = (((X/M)^L)-1)/(L*S) eval_power = pow((float(x_value) / m_value),l_value) return ((eval_power - 1) / (l_value * s_value)) def compare_sd(zscore_value): if zscore_value >= 0: return "0" elif 0 > zscore_value and zscore_value >= -2: return "-2" elif -2 > zscore_value and zscore_value >= -3: return "-3" elif -3 > zscore_value: return "below -3" zscore_num = calculate_zscore(zscore.l_value, zscore.m_value, zscore.s_value, form["vitals"]["weight"]) sd_num = compare_sd(zscore_num) if form.xpath("nutrition/weight_for_age") == sd_num: form.zscore_calc_good = "true" else: form.zscore_calc_good = "false" else: form.zscore_calc_good = "unable_to_calc" form.save()
def close_ltfu_cases(sender, patient_id, **kwargs): """ Checks if any open cases in the patient are passed the LTFU date and if they are closes them with status lost to followup. """ from bhoma.apps.patient.models import CPatient from bhoma.apps.case.bhomacaselogic.ltfu import close_as_lost patient = CPatient.get(patient_id) save_pat = False for case in patient.cases: today = datetime.utcnow().date() if not case.closed and case.ltfu_date and case.ltfu_date < today: close_as_lost(case) save_pat = True if save_pat: patient.save()
def update_pregnancy_report_data(sender, patient_id, **kwargs): """ Update pregnancies of a patient. """ from bhoma.apps.reports.calc.pregnancy import PregnancyReportData from bhoma.apps.reports.models import PregnancyReportRecord from bhoma.apps.patient.models import CPatient patient = CPatient.get(patient_id) # manually remove old pregnancies, since all pregnancy data is dynamically generated for old_preg in PregnancyReportRecord.view("reports/pregnancies_for_patient", key=patient_id, include_docs=True).all(): old_preg.delete() for preg in patient.pregnancies: preg_report_data = PregnancyReportData(patient, preg) couch_pregnancy = preg_report_data.to_couch_object() couch_pregnancy.save()
def new_form_received(patient_id, form): """ A new form was received for a patient. This usually just adds the form to the patient object, but will fully reprocess the patient data if the form is from the past, so that previously-entered but later-occurring changes can be applied to the data """ patient = CPatient.get(patient_id) encounter_date = Encounter.get_visit_date(form) full_reprocess = False for encounter in patient.encounters: if encounter.visit_date > encounter_date: full_reprocess = True break if full_reprocess: reprocess(patient_id) else: add_form_to_patient(patient_id, form)
def update_patient_deceased_status(sender, patient_id, **kwargs): """ Check if any of the cases are resolved with type death and if so, mark the patient as deceased. """ from bhoma.apps.patient.models import CPatient patient = CPatient.get(patient_id) if not patient.is_deceased: for case in patient.cases: if case.outcome == Outcome.PATIENT_DIED: patient.handle_died() patient.save() else: save_pat = False for case in patient.cases: if not case.closed: case.manual_close(Outcome.PATIENT_DIED, datetime.utcnow()) save_pat = True if save_pat: patient.save()
def testLifeThreatening(self): folder_name = os.path.join(os.path.dirname(__file__), "data", "chw") patient = export.import_patient_json_file(os.path.join(folder_name, "patient.json")) self.assertEqual(0, len(patient.encounters)) self.assertEqual(0, len(patient.cases)) add_form_with_date_offset\ (None, os.path.join(folder_name, "life_threatening_referral.xml"), days_from_today=0) patient = CPatient.get(patient.get_id) self.assertEqual(1, len(patient.encounters)) [case] = patient.cases [ccase] = case.commcare_cases self.assertFalse(ccase.closed) visit_date = datetime.utcnow().date() self.assertEqual("referral_no_show", ccase.followup_type) self.assertFalse(ccase.closed) self.assertEqual(visit_date + timedelta(days=3), ccase.start_date) self.assertEqual(visit_date + timedelta(days=3), ccase.activation_date) self.assertEqual(visit_date + timedelta(days=3), ccase.due_date) self.assertEqual(visit_date + timedelta(days=42), case.ltfu_date) self.assertEqual("new_clinic_referral|fever headache", ccase.name)
def add_form_to_patient(patient_id, form): """ Adds a clinic form to a patient, including all processing necessary. """ patient = CPatient.get(patient_id) new_encounter = Encounter.from_xform(form) patient.encounters.append(new_encounter) encounter_info = ENCOUNTERS_BY_XMLNS.get(form.namespace) if not encounter_info: raise Exception("Attempt to add unknown form type: %s to patient %s!" % \ (form.namespace, patient_id)) if encounter_info.classification == CLASSIFICATION_CLINIC: case = get_or_update_bhoma_case(form, new_encounter) if case: patient.cases.append(case) if is_pregnancy_encounter(new_encounter): update_pregnancies(patient, new_encounter) if is_delivery_encounter(new_encounter): update_deliveries(patient, new_encounter) elif encounter_info.classification == CLASSIFICATION_PHONE: # process phone form process_phone_form(patient, new_encounter) else: logging.error("Unknown classification %s for encounter: %s" % \ (encounter_info.classification, form.get_id)) # finally close any previous cases we had open, according # to the complicated rules close_previous_cases_from_new_form(patient, form, new_encounter) patient.save()
def new_encounter(request, patient_id, encounter_slug): """A new encounter for a patient""" encounter_info = CLINIC_ENCOUNTERS[encounter_slug] def callback(xform, doc): if doc: new_form_workflow(doc, SENDER_CLINIC, patient_id) return HttpResponseRedirect(reverse("single_patient", args=(patient_id,))) patient = CPatient.get(patient_id) xform = encounter_info.get_xform() # TODO: generalize this better preloader_tags = {"case": {"patient_id" : patient_id, "age_years" : str(patient.age) if patient.age != None else '', "dob": patient.birthdate.strftime('%Y-%m-%d') if patient.birthdate else '', "gender" : patient.gender, "bhoma_case_id" : "<uid>", "case_id" : "<uid>"}, "meta": {"clinic_id": settings.BHOMA_CLINIC_ID, "user_id": request.user.get_profile()._id, "username": request.user.username}} return xforms_views.play(request, xform.id, callback, preloader_tags)
def delete_all_patients(): for pat in CPatient.view(const.VIEW_PATIENT_BY_BHOMA_ID, reduce=False, include_docs=True).all(): pat.delete()