def cancel_case(self, message, ref_id): '''Cancel a case Format: cancel [ref_id] ref_id: case reference number cancels a case only if that case has no other report associated ''' case = self.find_case(ref_id) if case.reportmalnutrition_set.count(): raise HandlerFailed(_("Cannot cancel +%(ref_id)s: "\ "case has malnutrition reports.") % {'ref_id': ref_id}) if case.reportmalaria_set.count(): raise HandlerFailed(_("Cannot cancel +%(ref_id)s: "\ "case has malaria reports.") % {'ref_id': ref_id}) if case.reportdiagnosis_set.count(): raise HandlerFailed(_("Cannot cancel +%(ref_id)s: "\ "case has diagnosis reports.") % {'ref_id': ref_id}) case.delete() message.respond(_("Case +%(ref_id)s cancelled.") % {'ref_id': ref_id}) log(message.persistant_connection.reporter, 'case_cancelled') return True
def transfer_case(self, message, ref_id, target): '''Transfer case from one reporter to another Format: transfer [ref_id] [target] ref_id: case reference number target: alias of the CHW/Reporter to transfer to ''' reporter = message.persistant_connection.reporter case = self.find_case(ref_id) new_provider = self.find_provider(message, target) case.reporter = new_provider case.save() info = { 'username': new_provider.alias, 'name': new_provider.full_name(), 'location': new_provider.location} info['ref_id'] = case.ref_id message.respond(_("Case +%(ref_id)s transferred to @%(username)s " \ "(%(name)s - %(location)s).") % info) message.forward(new_provider.connection().identity, _("Case +%(ref_id)s transferred to you from "\ "@%(username)s (%(name)s - %(location)s).") % \ info) log(case, 'case_transferred') return True
def report_diarrhea(self, message, ref_id, ors, days, complications): ''' Diarrhea reporting Format: ors [ref_id] [ors - y/n] [days] [observations] ref_id: case ID ors: Oral Rehidratation Salt given? days: number of diarrhea days observations: codes of observations Replies Diarrhea informations ''' case = self.find_case(ref_id) ors = True if ors == "y" else False days = int(days) observed, choices = self.get_diarrheaobservations(complications) self.delete_similar(case.reportdiarrhea_set) reporter = message.persistant_connection.reporter report = ReportDiarrhea(case=case, reporter=reporter, \ ors=ors, days=days) report.save() for obs in observed: report.observed.add(obs) report.diagnose() report.save() choice_term = dict(choices) info = case.get_dictionary() info.update(report.get_dictionary()) msg = _("%(diagnosis_msg)s. +%(ref_id)s %(last_name)s, " \ "%(first_name_short)s, %(gender)s/%(months)s (%(guardian)s)." \ " %(days)s, %(ors)s") % info if observed: msg += ", " + info["observed"] message.respond("DIARRHEA> " + msg) ''' if report.status in (report.MODERATE_STATUS, report.SEVERE_STATUS, report.DANGER_STATUS): alert = _("@%(username)s reports %(msg)s") % \ {"username":provider.user.username, "msg":msg} recipients = [provider] for query in (Provider.objects.filter(alerts=True), Provider.objects.filter(clinic=provider.clinic)): for recipient in query: if recipient in recipients: continue recipients.append(recipient) message.forward(recipient.mobile, alert) ''' log(case, "diarrhea_taken") return True
def note_case(self, message, ref_id, note): ''' Annotate Case Format: note [ref_id] [note] ref_id: case reference number note: free text note ''' reporter = message.persistant_connection.reporter case = self.find_case(ref_id) CaseNote(case=case, created_by=reporter, text=note).save() message.respond(_("Note added to case +%(ref_id)s.") % \ {'ref_id': ref_id}) log(case, 'note_added') return True
def followup_diarrhea(self, message, ref_id, is_ok): ''' Follow-up on diarrhea reporting Format: ors [ref_id] [is_ok - y/n] ref_id: case ID is_ok: patient new health condition Replies Diarrhea informations ''' case = self.find_case(ref_id) is_ok = True if is_ok == "y" else False reporter = message.persistant_connection.reporter report = ReportDiarrhea.objects.get(case=case) if is_ok: report.status = ReportDiarrhea.HEALTHY_STATUS report.save() else: report.status = ReportDiarrhea.SEVERE_STATUS report.save() info = report.case.get_dictionary() info.update(report.get_dictionary()) msg = _("%(diagnosis_msg)s. +%(ref_id)s %(last_name)s, " \ "%(first_name_short)s, %(gender)s/%(months)s " \ "(%(guardian)s). %(days)s, %(ors)s") % info if report.observed.all().count() > 0: msg += ", " + info["observed"] message.respond("DIARRHEA> " + msg) ''' if report.status in (report.MODERATE_STATUS, report.SEVERE_STATUS, report.DANGER_STATUS): alert = _("@%(username)s reports %(msg)s") % \ {"username":reporter.alias, "msg":msg} recipients = [reporter] for query in (Provider.objects.filter(alerts=True), Provider.objects.filter(clinic=provider.clinic)): for recipient in query: if recipient in recipients: continue recipients.append(recipient) message.forward(recipient.mobile, alert) ''' log(case, "diarrhea_fu_taken") return True
def confirm_join(self, message, username): ''' check if a username is registered Format: confirm [username]''' try: user = Reporter.objects.get(alias__iexact=username) except object.DoesNotExist: self.respond_not_registered(message, username) user.registered_self = True user.save() info = {'mobile': user.connection().identity, 'last_name': user.last_name.title(), 'first_name': user.first_name.title(), 'alias': user.alias, 'location': user.location} self.respond_to_join(message, info) log(user, 'confirmed_join') return True
def report_malaria(self, message, ref_id, result, bednet, observed): '''Record malaria rdt test results Format: mrdt +[patient_ID\] malaria[y/n] bednet[y/n] symptoms separated by spaces[D CG A F V NR UF B CV CF] ''' case = self.find_case(ref_id) observed, choices = self.get_observations(observed) self.delete_similar(case.reportmalaria_set) reporter = message.persistant_connection.reporter self.log('debug', bednet) result = result.lower() == 'y' bednet = bednet.lower() == 'y' alert = None report = ReportMalaria(case=case, reporter=reporter, result=result, \ bednet=bednet) report.save() for obs in observed: report.observed.add(obs) report.save() # build up an information dictionary info = case.get_dictionary() info.update(report.get_dictionary()) info.update({ 'reporter_name': reporter.full_name(), 'reporter_alias': reporter.alias, 'reporter_identity': reporter.connection().identity}) # this could all really do with cleaning up # note that there is always an alert that goes out if not result: if observed: info['observed'] = ', (%s)' % info['observed'] msg = _("MRDT> Child +%(ref_id)s, %(last_name)s, "\ "%(first_name)s, %(gender)s/%(age)s (%(guardian)s), "\ "%(location)s. RDT=%(result_text)s,"\ " Bednet=%(bednet_text)s%(observed)s. "\ "Please refer patient IMMEDIATELY "\ "for clinical evaluation" % info) # alerts to health team alert = _("MRDT> Negative MRDT with Fever. +%(ref_id)s, "\ "%(last_name)s, %(first_name)s, %(gender)s/%(age)s"\ " %(location)s. Patient requires IMMEDIATE "\ "referral. Reported by CHW %(reporter_name)s "\ "@%(reporter_alias)s m:%(reporter_identity)s." % info) else: msg = _("MRDT> Child +%(ref_id)s, "\ "%(last_name)s, %(first_name)s, "\ "%(gender)s/%(age)s (%(guardian)s), "\ "%(location)s. RDT=%(result_text)s,"\ " Bednet=%(bednet_text)s." % info) else: # this is all for if child has tested postive # and is really just abut years, months = case.years_months() tabs, yage = None, None # just reformatted to make it look like less ugh if years < 1: if months < 2: tabs, yage = None, None else: tabs, yage = 1, _("less than 3") elif years < 3: tabs, yage = 1, _("less than 3") elif years < 9: tabs, yage = 2, years elif years < 15: tabs, yage = 3, years else: tabs, yage = 4, years # messages change depending upon age and dangers dangers = report.observed.filter(uid__in=('vomiting', 'appetite', \ 'breathing', 'confusion', 'fits')) # no tabs means too young if not tabs: info['instructions'] = _("Child is too young for treatment. "\ "Please refer IMMEDIATELY to clinic") else: # old enough to take tabs, but lets format msg if dangers: info['danger'] = _(" and danger signs (") + \ ','.join([u.name for u in dangers]) + ')' info['instructions'] = \ _("Refer to clinic immediately after %(tabs)s "\ "tab%(plural)s of Coartem is given") % {'tabs': tabs, \ 'plural': (tabs > 1) and 's' or ''} else: info['danger'] = '' info['instructions'] = \ _("Child is %(age)s. Please provide %(tabs)s"\ " tab%(plural)s of Coartem (ACT) twice a day"\ " for 3 days") % {'age': yage, \ 'tabs': tabs, \ 'plural': (tabs > 1) and 's' or ''} # finally build out the messages msg = _("MRDT> Child +%(ref_id)s, %(last_name)s, %(first_name)s, "\ "%(gender)s/%(age)s has MALARIA%(danger)s. %(instructions)s"\ % info) alert = \ _("MRDT> Child +%(ref_id)s, %(last_name)s, %(first_name)s, "\ "%(gender)s/%(months)s (%(location)s) has MALARIA%(danger)s. "\ "CHW: @%(reporter_alias)s %(reporter_identity)s" % info) if len(msg) > self.MAX_MSG_LEN: '''FIXME: Either make this an intelligent breakup of the message or let the backend handle that.''' message.respond(msg[:msg.rfind('. ') + 1]) message.respond(msg[msg.rfind('. ') + 1:]) else: message.respond(msg) if alert: recipients = report.get_alert_recipients() for recipient in recipients: if len(alert) > self.MAX_MSG_LEN: message.forward(recipient.connection().identity, \ alert[:alert.rfind('. ') + 1]) message.forward(recipient.connection().identity, \ alert[alert.rfind('. ') + 1:]) else: message.forward(recipient.connection().identity, alert) log(case, 'mrdt_taken') return True
def report_malaria(self, message, ref_id, result, bednet, observed): '''Processes incoming mrdt reports. Expected format is as above. Can process inputs without spaces, but symptoms must have spaces between them. '+' and 'y' register as positive for malaria, all other characters register as negative. 'y' registers as yes for a bednet, all other characters register as no.''' case = self.find_case(ref_id) observed, choices = self.get_observations(observed) self.delete_similar(case.reportmalaria_set) reporter = message.persistant_connection.reporter result = (result == '+' or result.lower() == 'y') bednet = (bednet.lower() == 'y') report = ReportMalaria(case=case, reporter=reporter, \ result=result, bednet=bednet) report.save() for obs in observed: report.observed.add(obs) report.save() # build up an information dictionary info = case.get_dictionary() info.update(report.get_dictionary()) info.update({ 'reporter_name': reporter.full_name(), 'reporter_alias': reporter.alias, 'reporter_identity': reporter.connection().identity}) # this could all really do with cleaning up # note that there is always an alert that goes out if not result: if observed: info['observed'] = ', (%s)' % info['observed'] msg = _("MRDT> Child +%(ref_id)s, %(last_name)s, %(first_name)s, "\ "%(gender)s/%(age)s (%(guardian)s), "\ "%(location)s. RDT=%(result_text)s,"\ " Bednet=%(bednet_text)s%(observed)s. "\ "Please refer patient IMMEDIATELY "\ "for clinical evaluation" % info) # alerts to health team alert = _("MRDT> Negative MRDT with Fever. +%(ref_id)s, "\ "%(last_name)s,%(first_name)s, %(gender)s/%(age)s "\ "%(location)s. Patient "\ "requires IMMEDIATE referral. Reported by CHW "\ "%(reporter_name)s "\ "@%(reporter_alias)s m:%(reporter_identity)s." % info) else: # this is all for if child has tested postive # and is really just abut years, months = case.years_months() tabs, yage = None, None # just reformatted to make it look like less ugh if years < 1: if months < 5: tabs, yage = None, None else: tabs, yage = _("one quarter"), _("under one year (5-10kg)") elif years < 7: tabs, yage = _("one half"), _("1-6 years (11-24kg)") elif years < 14: tabs, yage = _("one "), _("7-13 years (25-50kg)") elif years < 18: tabs, yage = _("one and a half"), _("14 - 17 years (50-70kg)") else: tabs, yage = _("two"), _("18+ years (70+ kg)") # messages change depending upon age and dangers dangers = report.observed.filter(uid__in=('vomiting', \ 'appetite', 'breathing', 'confusion', 'fits')) # no tabs means too young if not tabs: info['instructions'] = _("Child is too young for treatment. "\ "Please refer IMMEDIATELY to clinic") else: # old enough to take tabs, but lets format msg if dangers: info['danger'] = _(" and danger signs (") + \ ','.join([u.name for u in dangers]) + ')' info['instructions'] = _("Refer to clinic after %s "\ " each of Artesunate 50mg and Amodiaquine 150mg "\ "is given" % (tabs)) else: info['danger'] = '' info['instructions'] = \ _("Child is %(age)s. %(tabs)s each of "\ "Artesunate 50mg and Amodiaquine 150mg morning and "\ "evening for 3 days") % {'age': yage, 'tabs': tabs} # finally build out the messages msg = _("Patient +%(ref_id)s, %(first_name)s %(last_name)s, "\ "%(gender)s/%(age)s (%(guardian)s). Bednet=%(bednet_text)s "\ "%(observed)s. Patient has MALARIA%(danger)s." % (info)) alert = \ _("MRDT> Child +%(ref_id)s, %(last_name)s, %(first_name)s, "\ "%(gender)s/%(months)s (%(location)s) has MALARIA%(danger)s. "\ "CHW: @%(reporter_alias)s %(reporter_identity)s" % info) message.respond(msg) message.respond(_(info['instructions'])) ''' @todo: enable alerts ''' ''' recipients = report.get_alert_recipients() for recipient in recipients: message.forward(recipient.mobile, alert) ''' log(case, 'mrdt_taken') return True
info.update({ 'id': case.ref_id, 'last_name': last.upper(), 'age': case.age()}) #set up the languages msg = {} msg['en'] = "New +%(id)s: %(last_name)s, %(first_name)s " \ "%(gender)s/%(age)s (%(guardian)s) %(location)s" % info msg['fr'] = "Nouv +%(id)s: %(last_name)s, %(first_name)s " \ "%(gender)s/%(age)s (%(guardian)s) %(location)s" % info message.respond(_("%s") % msg[lang]) log(case, 'patient_created') return True new_case.format = "[new/nouv] [patient last name] [patient first name] " \ "gender[m/f] [dob ddmmyy] [guardian] (contact #)" def find_case(self, ref_id): '''look up a patient id return case ''' try: return Case.objects.get(ref_id=int(ref_id)) except Case.DoesNotExist: raise HandlerFailed(_("Case +%(ref_id)s not found.") % \ {'ref_id': ref_id})
def report_case(self, message, ref_id, muac=None, weight=None, height=None, complications=''): '''Record muac, weight, height, complications if any Format: muac +[patient_ID\] muac[measurement] edema[e/n] symptoms separated by spaces[CG D A F V NR UF] reply with diagnostic message ''' # TODO use gettext instead of this dodgy dictionary _i = { 'units': {'MUAC': 'mm', 'weight': 'kg', 'height': 'cm'}, 'en': {'error': "Can't understand %s (%s): %s"}, 'fr': {'error': "Ne peux pas comprendre %s (%s): %s"}} def guess_language(msg): if msg.upper().startswith('MUAC'): return 'en' if msg.upper().startswith('PB'): return 'fr' # use reporter's preferred language, if possible if message.reporter: if message.reporter.language is not None: lang = message.reporter.language else: # otherwise make a crude guess lang = guess_language(message.text) message.reporter.language = lang else: lang = 'fr' # if there is no height, ASSUME that muac is the (newly) optional # field that has been omitted. swap weight and height to their # ASSUMED values. TODO change the order of the fields in the form if height is None: wt = weight mu = muac weight = mu height = wt case = self.find_case(ref_id) try: muac = float(muac) if muac < 30: # muac is in cm? muac *= 10 muac = int(muac) except ValueError: raise HandlerFailed((_i[lang] % ('MUAC', _i['units']['MUAC'], \ muac))) #_("Can't understand MUAC (mm): %s") % muac) if weight is not None: try: weight = float(weight) if weight > 100: # weight is in g? weight /= 1000.0 except ValueError: #raise HandlerFailed("Can't understand weight (kg): #%s" % weight) raise HandlerFailed((_i[lang] % ('weight', \ _i['units']['weight'], weight))) if height is not None: try: height = float(height) if height < 3: # weight height in m? height *= 100 height = int(height) except ValueError: #raise HandlerFailed("Can't understand height (cm): # %s" % height) raise HandlerFailed((_i[lang] % ('height', \ _i['units']['height'], height))) observed, choices = self.get_observations(complications) self.delete_similar(case.reportmalnutrition_set) reporter = message.persistant_connection.reporter report = ReportMalnutrition(case=case, reporter=reporter, muac=muac, weight=weight, height=height) report.save() for obs in observed: report.observed.add(obs) report.diagnose() report.save() #choice_term = dict(choices) info = case.get_dictionary() info.update(report.get_dictionary()) msg = _("%(diagnosis_msg)s. +%(ref_id)s %(last_name)s, "\ "%(first_name_short)s, %(gender)s/%(months)s (%(guardian)s). "\ "MUAC %(muac)s") % info if weight: msg += ", %.1f kg" % weight if height: msg += ", %.1d cm" % height if observed: msg += ', ' + info['observed'] #get the last reported muac b4 this one last_muac = report.get_last_muac() if last_muac is not None: psign = "%" #take care for cases when testing using httptester, % #sign prevents feedback. if PersistantBackend.from_message(message).title == "http": psign = "%" last_muac.update({'psign': psign}) msg += _(". Last MUAC (%(reported_date)s): %(muac)s "\ "(%(percentage)s%(psign)s)") % last_muac msg = "MUAC> " + msg if len(msg) > self.MAX_MSG_LEN: message.respond(msg[:msg.rfind('. ') + 1]) message.respond(msg[msg.rfind('. ') + 1:]) else: message.respond(msg) if report.status in (report.MODERATE_STATUS, report.SEVERE_STATUS, report.SEVERE_COMP_STATUS): alert = _("@%(username)s reports %(msg)s [%(mobile)s]")\ % {'username': report.reporter.alias, 'msg': msg, \ 'mobile': reporter.connection().identity} recipients = report.get_alert_recipients() for recipient in recipients: if len(alert) > self.MAX_MSG_LEN: message.forward(recipient.connection().identity, \ alert[:alert.rfind('. ') + 1]) message.forward(recipient.connection().identity, \ alert[alert.rfind('. ') + 1:]) else: message.forward(recipient.connection().identity, alert) log(case, 'muac_taken') return True
def diagnosis(self, message, ref_id, text): '''Diagnosis of a case''' case = self.find_case(ref_id) reporter = message.persistant_connection.reporter diags = [] labs = [] hits = find_diagnostic_re.findall(message.text) for hit in hits: code = hit[2:] try: diags.append(Diagnosis.objects.get(code__iexact=code)) except Diagnosis.DoesNotExist: raise HandlerFailed("Unknown diagnosis code: %(code)s" % \ {'code': code}) hits = find_lab_re.findall(text) for hit in hits: code, sign, number = hit try: # the code starts with / labs.append([Lab.objects.get(code__iexact=code[1:]), \ sign, number]) except Lab.DoesNotExist: raise HandlerFailed("Unknown lab code: %(code)s" % \ {'code': code}) self.delete_similar(case.reportdiagnosis_set) report = ReportDiagnosis(case=case, reporter=reporter, \ text=message.text) report.save() for diag in diags: report.diagnosis.add(diag) for lab, sign, number in labs: ld = LabDiagnosis() ld.lab = lab ld.result = int(sign == "+") if number: ld.amount = number ld.diagnosis = report ld.save() report.save() info = case.get_dictionary() info.update(report.get_dictionary()) if info['labs_text']: info['labs_text'] = "%sLabs: %s" % (info['diagnosis'] and " " \ or "", info['labs_text']) message.respond(_("D> +%(ref_id)s %(first_name_short)s.%(last_name)s "\ "%(diagnosis)s%(labs_text)s") % info) # add in the forward of instructions to the case provider # if that it is not the reporter of the issue instructions = [] for diagnosis in report.diagnosis.all(): if diagnosis.instructions: instructions.append(diagnosis.instructions) if instructions: if reporter != case.reporter: # there's a different provider info = {'ref_id': ref_id, 'instructions': \ (", ".join(instructions))} message.forward(case.reporter.connection().identity, \ "D> +%(ref_id)s %(instructions)s" % info) log(case, 'diagnosis_taken') return True
def report_birth(self, message, last, first, gender, dob, weight, where, \ guardian, complications=''): '''Record births and register the child as a new case format: birth [last name] [first name] [gender m/f] [dob ddmmyy] [weight in kg] location[H/C/T/O] [guardian] (complications) response: a case reference no. for the new born ''' if len(dob) != 6: # There have been cases where a date like 30903 have been sent and # when saved it gives some date that is way off raise HandlerFailed(_("Date format is ddmmyy: %(dob)s")\ % {'dob': dob}) else: dob = re.sub(r'\D', '', dob) try: dob = time.strptime(dob, "%d%m%y") except ValueError: try: dob = time.strptime(dob, "%d%m%Y") except ValueError: raise HandlerFailed(_("Couldn't understand date: %(dob)s")\ % {'dob': dob}) dob = datetime.date(*dob[:3]) reporter = message.persistant_connection.reporter location = None if not location: if reporter.location: location = reporter.location info = { 'first_name': first.title(), 'last_name': last.title(), 'gender': gender.upper(), 'dob': dob, 'guardian': guardian.title(), 'mobile': '', 'reporter': reporter, 'location': location} abirth = ReportBirth(where=where.upper()) #Perform Location checks if abirth.get_where() is None: raise HandlerFailed(_("Location `%(location)s` is not known. " \ "Please try again with a known location")\ % {'location': where}) iscase = Case.objects.filter(first_name=info['first_name'], \ last_name=info['last_name'], \ reporter=info['reporter'], dob=info['dob']) if iscase: info['PID'] = iscase[0].ref_id # TODO: log this message case = iscase[0] else: case = Case(**info) case.save() log(case, 'patient_created') info.update({ 'id': case.ref_id, 'last_name': last.upper(), 'age': case.age(), 'dob': dob.strftime("%d/%m/%y")}) info2 = { 'case': case, 'weight': weight, 'where': where, 'reporter': reporter, 'complications': complications} #check if birth has already been reported rbirth = ReportBirth.objects.filter(case=case) if rbirth: raise HandlerFailed(_("The birth of %(last_name)s, "\ "%(first_name)s (+%(PID)s) " \ "has already been reported by %(reporter)s.") \ % info) abirth = ReportBirth(**info2) abirth.save() info.update({'where': abirth.get_where()}) message.respond(_( "Birth +%(id)s: %(last_name)s,"\ " %(first_name)s %(gender)s/%(dob)s "\ "(%(guardian)s) %(location)s at %(where)s") % info) return True