class patient(object): ''' this class pulls information from the database into a python object. ''' def __init__(self, sno): ''' initiate the class with default variables, then load from database ''' self.serialno = sno self.dbstate = None self.load_warnings = [] # patient table atts self.courseno0 = None self.money0 = 0 self.money1 = 0 self.money2 = 0 self.money3 = 0 self.money4 = 0 self.money5 = 0 self.money6 = 0 self.money7 = 0 self.money8 = 0 self.money9 = 0 self.money10 = 0 self.pd0 = None self.pd1 = None self.pd2 = None self.pd3 = None self.pd4 = None # this field is no longer used (last treatment date) self.pd5 = None self.pd6 = None self.pd7 = None self.pd8 = None self.pd9 = None self.pd10 = None self.pd11 = None self.pd12 = None self.pd13 = None self.pd14 = None self.sname = '' self.fname = '' self.title = '' self.sex = '' self.dob = None self.addr1 = '' self.addr2 = '' self.addr3 = '' self.pcde = '' self.tel1 = '' self.tel2 = '' self.occup = '' self.nhsno = '' self.cnfd = None self.cset = '' self.dnt1 = 0 self.dnt2 = 0 self.ur8st = '' self.ur7st = '' self.ur6st = '' self.ur5st = '' self.ur4st = '' self.ur3st = '' self.ur2st = '' self.ur1st = '' self.ul1st = '' self.ul2st = '' self.ul3st = '' self.ul4st = '' self.ul5st = '' self.ul6st = '' self.ul7st = '' self.ul8st = '' self.ll8st = '' self.ll7st = '' self.ll6st = '' self.ll5st = '' self.ll4st = '' self.ll3st = '' self.ll2st = '' self.ll1st = '' self.lr1st = '' self.lr2st = '' self.lr3st = '' self.lr4st = '' self.lr5st = '' self.lr6st = '' self.lr7st = '' self.lr8st = '' self.dent0 = 0 self.dent1 = 0 self.dent2 = 0 self.dent3 = 0 self.billdate = None self.billct = 0 self.billtype = None self.money11 = 0 self.familyno = localsettings.last_family_no self.memo = '' self.town = '' self.county = '' self.mobile = '' self.fax = '' self.email1 = '' self.email2 = '' self.status = '' self.initaccept = 0 self.lastreaccept = None self.lastclaim = None self.expiry = None self.cstatus = None self.transfer = 0 self.pstatus = None self.estimates = [] # from userdata self.plandata = PlanData(self.serialno) # NEIL'S STUFF#### self.exemption = "" self.exempttext = "" self.bpe = [] self.bpedate = nullDate self.chartdate = nullDate self.notes_dict = {} self.MEDALERT = False self.mh_chkdate = None self.mh_form_date = None self.HIDDENNOTES = [] self.chartgrid = {} self._fee_table = None self.synopsis = "" self._n_family_members = None self._dayBookHistory = None self.treatment_course = None self.est_logger = None self._most_recent_daybook_entry = None self._first_note_date = None self._has_exam_booked = None self._previous_surnames = None self.monies_reset = False self._n_hyg_visits = None if self.serialno == 0: return # # now load stuff from the database ## # db = connect.connect() cursor = db.cursor() self.getSynopsis() cursor.execute(PATIENT_QUERY, (self.serialno, )) values = cursor.fetchall() if values == (): raise localsettings.PatientNotFoundError for i, att_ in enumerate(patient_query_atts): value = values[0][i] if value is not None: self.__dict__[att_] = value elif att_ == "familyno": self.familyno = 0 query = '''select exemption, exempttext from exemptions where serialno=%s''' cursor.execute(query, (self.serialno, )) values = cursor.fetchall() for value in values: self.exemption, self.exempttext = value query = '''select bpedate, bpe from bpe where serialno=%s order by bpedate''' cursor.execute(query, (self.serialno, )) values = cursor.fetchall() for value in values: self.bpe.append(value) if self.courseno0 != 0: self.getEsts() self.treatment_course = TreatmentCourse(self.serialno, self.courseno0) self.getNotesTuple() cursor.execute(QUICK_MED_QUERY, (self.serialno, )) try: self.MEDALERT, self.mh_chkdate = cursor.fetchone() except TypeError: pass cursor.execute(MED_FORM_QUERY + " limit 1", (self.serialno, )) try: self.mh_form_date = cursor.fetchone()[0] except TypeError: pass cursor.close() # - load from plandata self.plandata.getFromDB() self.appt_prefs = ApptPrefs(self.serialno) self.updateChartgrid() self.take_snapshot() @property def appt_memo(self): return self.appt_prefs.note def set_appt_memo(self, memo): self.appt_prefs.note = memo @property def recall_active(self): return self.appt_prefs.recall_active @property def exam_due(self): return self.recall_active and self.recd < localsettings.currentDay() @property def recd(self): return self.appt_prefs.recdent @property def dayBookHistory(self): if self._dayBookHistory is None: db = connect.connect() cursor = db.cursor() query = 'select date, trtid, chart from daybook where serialno=%s' cursor.execute(query, (self.serialno, )) self._dayBookHistory = cursor.fetchall() cursor.close() return self._dayBookHistory @property def last_treatment_date(self): max_date = localsettings.currentDay() if self.treatment_course.cmp_txs != \ self.dbstate.treatment_course.cmp_txs: return max_date if self._most_recent_daybook_entry is None: db = connect.connect() cursor = db.cursor() query = 'select max(date) from daybook where serialno=%s' if cursor.execute(query, (self.serialno, )): max_date = cursor.fetchone()[0] cursor.close() self._most_recent_daybook_entry = max_date return self._most_recent_daybook_entry @property def first_note_date(self): ''' returns teh first date found in the patient notes ''' if self._first_note_date is None: min_date = localsettings.currentDay() db = connect.connect() cursor = db.cursor() query = 'select min(ndate) from formatted_notes where serialno=%s' if cursor.execute(query, (self.serialno, )): min_date = cursor.fetchone()[0] cursor.close() self._first_note_date = min_date \ if min_date else localsettings.currentDay() return self._first_note_date @property def n_hyg_visits(self): if self._n_hyg_visits is not None: pass elif not localsettings.hyg_ixs: self._n_hyg_visits = 0 else: if len(localsettings.hyg_ixs) == 1: conditional = "=" values = (self.serialno, localsettings.hyg_ixs[0]) else: conditional = "in" values = (self.serialno, localsettings.hyg_ixs) self._n_hyg_visits = 0 db = connect.connect() cursor = db.cursor() query = '''select count(*) from (select date from daybook where serialno=%%s and trtid %s %%s group by date) as t''' % conditional if cursor.execute(query, values): self._n_hyg_visits = cursor.fetchone()[0] cursor.close() return self._n_hyg_visits def forget_exam_booked(self): self._has_exam_booked = None @property def has_exam_booked(self): if self._has_exam_booked is None: db = connect.connect() cursor = db.cursor() cursor.execute(FUTURE_EXAM_QUERY, (self.serialno, )) self._has_exam_booked = bool(cursor.fetchone()[0]) cursor.close() return self._has_exam_booked def __repr__(self): return "'Patient_class instance - serialno %d'" % self.serialno @property def address(self): ''' a printable address ''' address = "" for line in (self.addr1, self.addr2, self.addr3, self.town, self.county, self.pcde): if line.strip(" ") != "": address += "%s\n" % line.strip(" ") return address def getAge(self, on_date=None): ''' return the age in form (year(int), months(int), isToday(bool)) ''' if on_date is None: # use today on_date = localsettings.currentDay() try: nextbirthday = datetime.date(on_date.year, self.dob.month, self.dob.day) except ValueError: # catch leap years!! nextbirthday = datetime.date(on_date.year, self.dob.month, self.dob.day - 1) ageYears = on_date.year - self.dob.year if nextbirthday > on_date: ageYears -= 1 months = (12 - self.dob.month) + on_date.month else: months = on_date.month - self.dob.month if self.dob.day > on_date.day: months -= 1 isToday = nextbirthday == localsettings.currentDay() return (ageYears, months, isToday) @property def ageYears(self): return self.getAge()[0] @property def age_course_start(self): ''' returns a tuple (year, months) for the patient at accd ''' return self.getAge(self.treatment_course.accd)[:2] @property def under_6(self): ''' returns a bool "is patient under 6?". ''' return self.ageYears < 6 @property def under_18(self): ''' returns a bool "is patient under 18?". ''' return self.ageYears < 18 def forget_fee_table(self): self._fee_table = None @property def fee_table(self): ''' logic to determine which feeTable should be used for standard items ''' if self._fee_table is None: if self.treatment_course.accd is None: cse_accd = localsettings.currentDay() else: cse_accd = self.treatment_course.accd for table in reversed(localsettings.FEETABLES.tables.values()): LOGGER.debug( "checking feescale %s to see if suitable a feetable", table) start, end = table.startDate, table.endDate LOGGER.debug("categories, start, end = %s, %s, %s", table.categories, start, end) if end is None: end = localsettings.currentDay() if self.cset in table.categories and start <= cse_accd <= end: self._fee_table = table if self._fee_table is None: # - no matching table found, use the default. LOGGER.warning("NO SUITABLE FEETABLE FOUND, RETURNING DEFAULT") self._fee_table = localsettings.FEETABLES.default_table return self._fee_table def getEsts(self): ''' get estimate data ''' self.estimates = db_estimates.get_ests(self.serialno, self.courseno0) self.est_logger = EstLogger(self.courseno0) def getSynopsis(self): ''' the synopsis line is displayed on the clinical summary page ''' db = connect.connect() cursor = db.cursor() try: if cursor.execute(SYNOPSIS_QUERY, (self.serialno, )): self.synopsis = cursor.fetchall()[-1][0] except connect.OperationalError: # - necessary because the column is missing is db schema 1.4 LOGGER.warning("invalid schema for getSynopsis") @property def underTreatment(self): ''' a boolean value stating whether the patient has a continuing treatment plan ''' return (self.treatment_course is not None and self.treatment_course.underTreatment) @property def max_tx_courseno(self): ''' a patient who has had many courses of treatment, this gets the latest ''' return self.treatment_course.max_tx_courseno @property def newer_course_found(self): ''' check for a newer course in the currtrtmt2 table than the one loaded at startup. ''' return self.treatment_course.newer_course_found def getNotesTuple(self): ''' connect and poll the formatted_notes table ''' self.notes_dict = formatted_notes.get_notes_dict(self.serialno) def flipDec_Perm(self, tooth): ''' switches a deciduous tooth to a permanent one, and viceVersa pass a variable like "ur5" ''' quadrant = tooth[:2] pos = int(tooth[2]) - 1 # will be 0-7 if quadrant == "ul": var = self.dent1 pos = 7 - pos elif quadrant == "ur": var = self.dent0 elif quadrant == "ll": var = self.dent2 else: # lr var = self.dent3 pos = 7 - pos existing = dec_perm.fromSignedByte(var) if existing[pos] == "1": existing = existing[:pos] + "0" + existing[pos + 1:] else: existing = existing[:pos] + "1" + existing[pos + 1:] if quadrant == "ul": self.dent1 = dec_perm.toSignedByte(existing) elif quadrant == "ur": self.dent0 = dec_perm.toSignedByte(existing) elif quadrant == "ll": self.dent2 = dec_perm.toSignedByte(existing) else: # lr self.dent3 = dec_perm.toSignedByte(existing) self.updateChartgrid() def updateChartgrid(self): ''' a legacy issue with openmolar is the way teeth are saved as present is as 4 bytes (32 bits = 32 teeth). very frugal storage, but requires a fair deal of client computation :( ''' grid = "" for quad in (self.dent1, self.dent0, self.dent3, self.dent2): grid += dec_perm.fromSignedByte(quad) for pos in mouth: if grid[mouth.index(pos)] == "0": self.chartgrid[pos] = pos else: self.chartgrid[pos] = decidmouth[mouth.index(pos)] def apply_fees(self): ''' update the money owed. ''' LOGGER.debug("Applying Fees") if "N" in self.cset: self.money0 = self.dbstate.money0 + self.fees_accrued else: self.money1 = self.dbstate.money1 + self.fees_accrued @property def fees(self): ''' calculate what money is due. ''' return int(self.money0 + self.money1 + self.money9 + self.money10 + self.money11 - self.money2 - self.money3 - self.money8) @property def fees_accrued(self): ''' what fees have changed since load. ''' old_estimate_charges = 0 if self.courseno0 == self.dbstate.courseno0: old_estimate_charges = self.dbstate.estimate_charges accrued_fees = self.estimate_charges - old_estimate_charges LOGGER.debug("fees_accrued = (new-existing) = %d - %d = %d", self.estimate_charges, old_estimate_charges, accrued_fees) return accrued_fees @property def estimate_charges(self): ''' charges for all completed treatments. ''' charges = 0 for est in self.estimates: if est.completed == 2: charges += est.ptfee elif est.completed == 1: charges += est.interim_pt_fee return charges @property def est_logger_text(self): ''' a summary of the estimate for use in the est_logger_table est_logger is unconcerned whether treatment is completed etc.. ''' text = "" total, p_total = 0, 0 for estimate in sorted(self.estimates): text += estimate.log_text total += estimate.fee p_total += estimate.ptfee text += "TOTAL || || || || || || %s || %s" % (total, p_total) return text def resetAllMonies(self): ''' gets money1 and money 0 from apply_fees, then equalises money3 and money2 accordingly. zero's everything else money11 (bad debt) is left unaltered. ''' self.dbstate.money0 = 0 self.dbstate.money1 = 0 self.monies_reset = True self.money0 = 0 self.money1 = 0 self.apply_fees() self.money9 = 0 self.money10 = 0 self.money2 = self.money0 self.money3 = self.money1 self.money8 = 0 def nhs_claims(self, completed_only=True): ''' nhs items from the estimates. if completed_only is False, then include planned items. ''' claims = [] for est in self.estimates: if est.csetype.startswith("N") and \ (not completed_only or est.completed == 2): claims.append(est) return claims def addHiddenNote(self, ntype, note="", attempt_delete=False, one_only=False): ''' re-written for schema 1.9 ''' LOGGER.info( "addHiddenNote - ntype='%s',note='%s', attempt_delete='%s'", ntype, note, attempt_delete) HN = () if ntype == "payment": HN = ("RECEIVED: ", note) elif ntype == "printed": HN = ("PRINTED: ", note) elif ntype == "exam": HN = ("TC: EXAM", note) elif ntype == "chart_treatment": HN = ("TC:", note) elif ntype == "perio_treatment": HN = ("TC: PERIO", note) elif ntype == "xray_treatment": HN = ("TC: XRAY", note) elif ntype == "treatment": HN = ("TC: OTHER", note) elif ntype == "mednotes": # other treatment HN = ("UPDATED:Medical Notes", note) elif ntype == "close_course": HN = ("COURSE CLOSED", "=" * 10) elif ntype == "open_course": HN = ("COURSE OPENED", "= " * 5) elif ntype == "resume_course": HN = ("COURSE RE-OPENED", "= " * 5) elif ntype == "fee": HN = ("INTERIM: ", note) if not HN: LOGGER.warning("unable to add Hidden Note notetype '%s' not found", ntype) return reversing_note = ("UNCOMPLETED", "{%s}" % note) if attempt_delete: try: self.HIDDENNOTES.remove(HN) except ValueError: LOGGER.debug("'%s' not in hiddenotes", HN) LOGGER.debug(self.HIDDENNOTES) self.HIDDENNOTES.append(reversing_note) else: try: self.HIDDENNOTES.remove(reversing_note) except ValueError: self.HIDDENNOTES.append(HN) if one_only: while self.HIDDENNOTES.count(HN) > 1: self.HIDDENNOTES.remove(HN) def clearHiddenNotes(self): ''' reset self.HIDDENNOTES ''' self.HIDDENNOTES = [] def updateBilling(self, tone): ''' update the last billdate and tone of invoice ''' self.billdate = localsettings.currentDay() self.billct += 1 self.billtype = tone def reset_billing(self): ''' if patients account is now is order, reset all billing params ''' if self.fees == 0: self.billdate = None self.billct = None self.billtype = None def treatmentOutstanding(self): ''' does the patient have treatmentOutstanding? returns a boolean ''' return (self.treatment_course and self.treatment_course.has_treatment_outstanding) def checkExemption(self): ''' see if the patient's exemption requires removal. ''' if (self.exemption == "S" and self.getAge(self.treatment_course.accd)[0] > 19): self.exemption = "" self.load_warnings.append(_("Student Exemption removed")) elif (self.exemption == "A" and self.getAge(self.treatment_course.accd)[0] > 18): self.exemption = "" self.load_warnings.append(_("Age Exemption removed")) else: return True @property def name_id(self): ''' name and serialno ''' return u"%s - %s" % (self.name, self.serialno) @property def name(self): ''' patients name in a readable form ''' return u"%s %s %s" % (self.title, self.fname, self.sname) @property def psn(self): ''' previous surname ''' try: return self.previous_surnames[0] except IndexError: return "" @property def previous_surnames(self): ''' previous surnames are stored. ## TODO - check this is used. ''' if self._previous_surnames is None: db = connect.connect() cursor = db.cursor() cursor.execute(PSN_QUERY, (self.serialno, )) self._previous_surnames = [s[0] for s in cursor.fetchall()] cursor.close() return self._previous_surnames @property def n_family_members(self): ''' how many members are linked to this patient's familyno ''' if self._n_family_members is None: db = connect.connect() cursor = db.cursor() cursor.execute(FAMILY_COUNT_QUERY, (self.familyno, )) self._n_family_members = cursor.fetchone()[0] return self._n_family_members @property def under_capitation(self): ''' under capitation if regular NHS patient and under 18. ''' if self.cset != "N": return False years, months = self.age_course_start return years < 17 or (years == 17 and months < 11) def new_tx_course(self, new_courseno): ''' start a new treatment course ''' self.courseno0 = new_courseno self.treatment_course = TreatmentCourse(self.serialno, new_courseno) @property def COPIED_ATTRIBUTES(self): ''' these are what is copied over into pt.dbstate ''' return (patient_query_atts + exemptionTableAtts + bpeTableAtts + clinical_memos + ("fees", "estimate_charges", "serialno", "estimates", "appt_prefs", "treatment_course", "chartgrid")) @property def USER_CHANGEABLE_ATTRIBUTES(self): ''' the attributes, common to pt and the object copy pt.db_state which is generated during take_snapshot used to determine whether the patient has been edited. ''' for att_ in self.COPIED_ATTRIBUTES: # if att_ not in ("treatment_course", "estimates", "chartgrid"): yield att_ @property def changes(self): ''' what has changed since the patient was loaded ''' changes = [] for att_ in self.USER_CHANGEABLE_ATTRIBUTES: new_value = self.__dict__.get(att_, "") db_value = self.dbstate.__dict__.get(att_, "") if new_value != db_value: message = "Altered pt.%s" % att_.ljust(20) if att_ not in ("treatment_course", "estimates"): message += (" ORIG = '%s' NEW = '%s'" % (db_value, new_value)) LOGGER.debug(message) changes.append(att_) return changes @property def has_changes(self): is_changed = self.changes != [] self.lock_record_in_use() if is_changed else self.clear_lock() return is_changed def take_snapshot(self): ''' create a snapshot of this class, copying all attributes that the user can change ''' memo = {} cls = self.__class__ snapshot = cls.__new__(cls) memo[id(self)] = snapshot for att_, val_ in self.__dict__.items(): if att_ in self.COPIED_ATTRIBUTES: setattr(snapshot, att_, deepcopy(val_, memo)) self.dbstate = snapshot LOGGER.debug("snapshot of %s taken" % self) @property def course_dentist(self): ''' returns the course dentist for NHS and private courses, but the contracted dentist otherwise. this is used in the daybook for "work done for lists". ''' if self.cset == "I": return self.dnt1 if self.dnt2 not in (0, None): return self.dnt2 return self.dnt1 @property def has_new_course(self): ''' if the initial state has no course, or a lower course number, this is true. ''' if self.treatment_course and self.dbstate.treatment_course is None: return True return (self.treatment_course.courseno != self.dbstate.treatment_course.courseno) @property def tx_hash_tups(self): ''' a list of unique hashes of all treatment on the current treatment plan returns a tuple (unique hash, attribute, treatment) ''' for hash_, att_, tx in self.treatment_course._get_tx_hashes(): if re.match("[ul][lr][1-8]", att_): att_ = self.chartgrid.get(att_) yield hash_, att_, tx @property def completed_tx_hash_tups(self): for hash_, att_, tx in self.treatment_course.completed_tx_hash_tups: if re.match("[ul][lr][1-8]", att_): att_ = self.chartgrid.get(att_) yield hash_, att_, tx @property def completed_tx_hashes(self): return list(self.treatment_course.completed_tx_hashes) @property def planned_tx_hash_tups(self): return self.treatment_course.planned_tx_hash_tups @property def has_planned_perio_txs(self): for hash_, att_, tx in self.planned_tx_hash_tups: if att_ == "perio": return True return False def get_tx_from_hash(self, hash_): return self.treatment_course.get_tx_from_hash(hash_) def ests_from_hash(self, hash_): ''' return all estimate items associated with a unique tx_hash ''' for est in self.estimates: for tx_hash in est.tx_hashes: if tx_hash == hash_: yield est @property def address_tuple(self): return (self.sname, self.addr1, self.addr2, self.addr3, self.town, self.county, self.pcde, self.tel1) def reload_mh_form_date(self): db = connect.connect() cursor = db.cursor() cursor.execute(MED_FORM_QUERY + " limit 1", (self.serialno, )) try: self.mh_form_date = cursor.fetchone()[0] except TypeError: self.mh_form_date = None cursor.close() def mh_form_dates(self): ''' the dates that the mh form has been signed off by the patient. ''' db = connect.connect() cursor = db.cursor() cursor.execute(MED_FORM_QUERY, (self.serialno, )) for row in cursor.fetchall(): yield row[0] cursor.close() def set_record_in_use(self): records_in_use.set_in_use(self.serialno) def lock_record_in_use(self): records_in_use.set_locked(self.serialno) def clear_lock(self): records_in_use.clear_lock(self.serialno)
class patient(object): ''' this class pulls information from the database into a python object. ''' def __init__(self, sno): ''' initiate the class with default variables, then load from database ''' self.serialno = sno self.dbstate = None self.load_warnings = [] # patient table atts self.courseno0 = None self.money0 = 0 self.money1 = 0 self.money2 = 0 self.money3 = 0 self.money4 = 0 self.money5 = 0 self.money6 = 0 self.money7 = 0 self.money8 = 0 self.money9 = 0 self.money10 = 0 self.pd0 = None self.pd1 = None self.pd2 = None self.pd3 = None self.pd4 = None # this field is no longer used (last treatment date) self.pd5 = None self.pd6 = None self.pd7 = None self.pd8 = None self.pd9 = None self.pd10 = None self.pd11 = None self.pd12 = None self.pd13 = None self.pd14 = None self.sname = '' self.fname = '' self.title = '' self.sex = '' self.dob = None self.addr1 = '' self.addr2 = '' self.addr3 = '' self.pcde = '' self.tel1 = '' self.tel2 = '' self.occup = '' self.nhsno = '' self.cnfd = None self.cset = '' self.dnt1 = 0 self.dnt2 = 0 self.ur8st = '' self.ur7st = '' self.ur6st = '' self.ur5st = '' self.ur4st = '' self.ur3st = '' self.ur2st = '' self.ur1st = '' self.ul1st = '' self.ul2st = '' self.ul3st = '' self.ul4st = '' self.ul5st = '' self.ul6st = '' self.ul7st = '' self.ul8st = '' self.ll8st = '' self.ll7st = '' self.ll6st = '' self.ll5st = '' self.ll4st = '' self.ll3st = '' self.ll2st = '' self.ll1st = '' self.lr1st = '' self.lr2st = '' self.lr3st = '' self.lr4st = '' self.lr5st = '' self.lr6st = '' self.lr7st = '' self.lr8st = '' self.dent0 = 0 self.dent1 = 0 self.dent2 = 0 self.dent3 = 0 self.billdate = None self.billct = 0 self.billtype = None self.money11 = 0 self.familyno = localsettings.last_family_no self.memo = '' self.town = '' self.county = '' self.mobile = '' self.fax = '' self.email1 = '' self.email2 = '' self.status = '' self.initaccept = 0 self.lastreaccept = None self.lastclaim = None self.expiry = None self.cstatus = None self.transfer = 0 self.pstatus = None self.estimates = [] # from userdata self.plandata = PlanData(self.serialno) # NEIL'S STUFF#### self.exemption = "" self.exempttext = "" self.bpe = [] self.bpedate = nullDate self.chartdate = nullDate self.notes_dict = {} self.MEDALERT = False self.mh_chkdate = None self.mh_form_date = None self.HIDDENNOTES = [] self.chartgrid = {} self._fee_table = None self.synopsis = "" self._n_family_members = None self._dayBookHistory = None self.treatment_course = None self.est_logger = None self._most_recent_daybook_entry = None self._first_note_date = None self._has_exam_booked = None self._previous_surnames = None self.monies_reset = False self._n_hyg_visits = None if self.serialno == 0: return # # now load stuff from the database ## # db = connect.connect() cursor = db.cursor() self.getSynopsis() cursor.execute(PATIENT_QUERY, (self.serialno,)) values = cursor.fetchall() if values == (): raise localsettings.PatientNotFoundError for i, att_ in enumerate(patient_query_atts): value = values[0][i] if value is not None: self.__dict__[att_] = value elif att_ == "familyno": self.familyno = 0 query = '''select exemption, exempttext from exemptions where serialno=%s''' cursor.execute(query, (self.serialno,)) values = cursor.fetchall() for value in values: self.exemption, self.exempttext = value query = '''select bpedate, bpe from bpe where serialno=%s order by bpedate''' cursor.execute(query, (self.serialno,)) values = cursor.fetchall() for value in values: self.bpe.append(value) if self.courseno0 != 0: self.getEsts() self.treatment_course = TreatmentCourse( self.serialno, self.courseno0) self.getNotesTuple() cursor.execute(QUICK_MED_QUERY, (self.serialno,)) try: self.MEDALERT, self.mh_chkdate = cursor.fetchone() except TypeError: pass cursor.execute(MED_FORM_QUERY + " limit 1", (self.serialno,)) try: self.mh_form_date = cursor.fetchone()[0] except TypeError: pass cursor.close() # - load from plandata self.plandata.getFromDB() self.appt_prefs = ApptPrefs(self.serialno) self.updateChartgrid() self.take_snapshot() @property def appt_memo(self): return self.appt_prefs.note def set_appt_memo(self, memo): self.appt_prefs.note = memo @property def recall_active(self): return self.appt_prefs.recall_active @property def exam_due(self): return self.recall_active and self.recd < localsettings.currentDay() @property def recd(self): return self.appt_prefs.recdent @property def dayBookHistory(self): if self._dayBookHistory is None: db = connect.connect() cursor = db.cursor() query = 'select date, trtid, chart from daybook where serialno=%s' cursor.execute(query, (self.serialno,)) self._dayBookHistory = cursor.fetchall() cursor.close() return self._dayBookHistory @property def last_treatment_date(self): max_date = localsettings.currentDay() if self.treatment_course.cmp_txs != \ self.dbstate.treatment_course.cmp_txs: return max_date if self._most_recent_daybook_entry is None: db = connect.connect() cursor = db.cursor() query = 'select max(date) from daybook where serialno=%s' if cursor.execute(query, (self.serialno,)): max_date = cursor.fetchone()[0] cursor.close() self._most_recent_daybook_entry = max_date return self._most_recent_daybook_entry @property def first_note_date(self): ''' returns teh first date found in the patient notes ''' if self._first_note_date is None: min_date = localsettings.currentDay() db = connect.connect() cursor = db.cursor() query = 'select min(ndate) from formatted_notes where serialno=%s' if cursor.execute(query, (self.serialno,)): min_date = cursor.fetchone()[0] cursor.close() self._first_note_date = min_date \ if min_date else localsettings.currentDay() return self._first_note_date @property def n_hyg_visits(self): if self._n_hyg_visits is not None: pass elif not localsettings.hyg_ixs: self._n_hyg_visits = 0 else: if len(localsettings.hyg_ixs) == 1: conditional = "=" values = (self.serialno, localsettings.hyg_ixs[0]) else: conditional = "in" values = (self.serialno, localsettings.hyg_ixs) self._n_hyg_visits = 0 db = connect.connect() cursor = db.cursor() query = '''select count(*) from (select date from daybook where serialno=%%s and trtid %s %%s group by date) as t''' % conditional if cursor.execute(query, values): self._n_hyg_visits = cursor.fetchone()[0] cursor.close() return self._n_hyg_visits def forget_exam_booked(self): self._has_exam_booked = None @property def has_exam_booked(self): if self._has_exam_booked is None: db = connect.connect() cursor = db.cursor() cursor.execute(FUTURE_EXAM_QUERY, (self.serialno,)) self._has_exam_booked = bool(cursor.fetchone()[0]) cursor.close() return self._has_exam_booked def __repr__(self): return "'Patient_class instance - serialno %d'" % self.serialno @property def address(self): ''' a printable address ''' address = "" for line in (self.addr1, self.addr2, self.addr3, self.town, self.county, self.pcde): if line.strip(" ") != "": address += "%s\n" % line.strip(" ") return address def getAge(self, on_date=None): ''' return the age in form (year(int), months(int), isToday(bool)) ''' if on_date is None: # use today on_date = localsettings.currentDay() try: nextbirthday = datetime.date(on_date.year, self.dob.month, self.dob.day) except ValueError: # catch leap years!! nextbirthday = datetime.date(on_date.year, self.dob.month, self.dob.day - 1) ageYears = on_date.year - self.dob.year if nextbirthday > on_date: ageYears -= 1 months = (12 - self.dob.month) + on_date.month else: months = on_date.month - self.dob.month if self.dob.day > on_date.day: months -= 1 isToday = nextbirthday == localsettings.currentDay() return (ageYears, months, isToday) @property def ageYears(self): return self.getAge()[0] @property def age_course_start(self): ''' returns a tuple (year, months) for the patient at accd ''' return self.getAge(self.treatment_course.accd)[:2] @property def under_6(self): ''' returns a bool "is patient under 6?". ''' return self.ageYears < 6 @property def under_18(self): ''' returns a bool "is patient under 18?". ''' return self.ageYears < 18 def forget_fee_table(self): self._fee_table = None @property def fee_table(self): ''' logic to determine which feeTable should be used for standard items ''' if self._fee_table is None: if self.treatment_course.accd is None: cse_accd = localsettings.currentDay() else: cse_accd = self.treatment_course.accd for table in reversed(localsettings.FEETABLES.tables.values()): LOGGER.debug( "checking feescale %s to see if suitable a feetable", table) start, end = table.startDate, table.endDate LOGGER.debug("categories, start, end = %s, %s, %s", table.categories, start, end) if end is None: end = localsettings.currentDay() if self.cset in table.categories and start <= cse_accd <= end: self._fee_table = table if self._fee_table is None: # - no matching table found, use the default. LOGGER.warning("NO SUITABLE FEETABLE FOUND, RETURNING DEFAULT") self._fee_table = localsettings.FEETABLES.default_table return self._fee_table def getEsts(self): ''' get estimate data ''' self.estimates = db_estimates.get_ests(self.serialno, self.courseno0) self.est_logger = EstLogger(self.courseno0) def getSynopsis(self): ''' the synopsis line is displayed on the clinical summary page ''' db = connect.connect() cursor = db.cursor() try: if cursor.execute(SYNOPSIS_QUERY, (self.serialno,)): self.synopsis = cursor.fetchall()[-1][0] except connect.OperationalError: # - necessary because the column is missing is db schema 1.4 LOGGER.warning("invalid schema for getSynopsis") @property def underTreatment(self): ''' a boolean value stating whether the patient has a continuing treatment plan ''' return (self.treatment_course is not None and self.treatment_course.underTreatment) @property def max_tx_courseno(self): ''' a patient who has had many courses of treatment, this gets the latest ''' return self.treatment_course.max_tx_courseno @property def newer_course_found(self): ''' check for a newer course in the currtrtmt2 table than the one loaded at startup. ''' return self.treatment_course.newer_course_found def getNotesTuple(self): ''' connect and poll the formatted_notes table ''' self.notes_dict = formatted_notes.get_notes_dict(self.serialno) def flipDec_Perm(self, tooth): ''' switches a deciduous tooth to a permanent one, and viceVersa pass a variable like "ur5" ''' quadrant = tooth[:2] pos = int(tooth[2]) - 1 # will be 0-7 if quadrant == "ul": var = self.dent1 pos = 7 - pos elif quadrant == "ur": var = self.dent0 elif quadrant == "ll": var = self.dent2 else: # lr var = self.dent3 pos = 7 - pos existing = dec_perm.fromSignedByte(var) if existing[pos] == "1": existing = existing[:pos] + "0" + existing[pos + 1:] else: existing = existing[:pos] + "1" + existing[pos + 1:] if quadrant == "ul": self.dent1 = dec_perm.toSignedByte(existing) elif quadrant == "ur": self.dent0 = dec_perm.toSignedByte(existing) elif quadrant == "ll": self.dent2 = dec_perm.toSignedByte(existing) else: # lr self.dent3 = dec_perm.toSignedByte(existing) self.updateChartgrid() def updateChartgrid(self): ''' a legacy issue with openmolar is the way teeth are saved as present is as 4 bytes (32 bits = 32 teeth). very frugal storage, but requires a fair deal of client computation :( ''' grid = "" for quad in (self.dent1, self.dent0, self.dent3, self.dent2): grid += dec_perm.fromSignedByte(quad) for pos in mouth: if grid[mouth.index(pos)] == "0": self.chartgrid[pos] = pos else: self.chartgrid[pos] = decidmouth[mouth.index(pos)] def apply_fees(self): ''' update the money owed. ''' LOGGER.debug("Applying Fees") if "N" in self.cset: self.money0 = self.dbstate.money0 + self.fees_accrued else: self.money1 = self.dbstate.money1 + self.fees_accrued @property def fees(self): ''' calculate what money is due. ''' return int(self.money0 + self.money1 + self.money9 + self.money10 + self.money11 - self.money2 - self.money3 - self.money8) @property def fees_accrued(self): ''' what fees have changed since load. ''' old_estimate_charges = 0 if self.courseno0 == self.dbstate.courseno0: old_estimate_charges = self.dbstate.estimate_charges accrued_fees = self.estimate_charges - old_estimate_charges LOGGER.debug("fees_accrued = (new-existing) = %d - %d = %d", self.estimate_charges, old_estimate_charges, accrued_fees) return accrued_fees @property def estimate_charges(self): ''' charges for all completed treatments. ''' charges = 0 for est in self.estimates: if est.completed == 2: charges += est.ptfee elif est.completed == 1: charges += est.interim_pt_fee return charges @property def est_logger_text(self): ''' a summary of the estimate for use in the est_logger_table est_logger is unconcerned whether treatment is completed etc.. ''' text = "" total, p_total = 0, 0 for estimate in sorted(self.estimates): text += estimate.log_text total += estimate.fee p_total += estimate.ptfee text += "TOTAL || || || || || || %s || %s" % (total, p_total) return text def resetAllMonies(self): ''' gets money1 and money 0 from apply_fees, then equalises money3 and money2 accordingly. zero's everything else money11 (bad debt) is left unaltered. ''' self.dbstate.money0 = 0 self.dbstate.money1 = 0 self.monies_reset = True self.money0 = 0 self.money1 = 0 self.apply_fees() self.money9 = 0 self.money10 = 0 self.money2 = self.money0 self.money3 = self.money1 self.money8 = 0 def nhs_claims(self, completed_only=True): ''' nhs items from the estimates. if completed_only is False, then include planned items. ''' claims = [] for est in self.estimates: if est.csetype.startswith("N") and \ (not completed_only or est.completed == 2): claims.append(est) return claims def addHiddenNote(self, ntype, note="", attempt_delete=False, one_only=False): ''' re-written for schema 1.9 ''' LOGGER.info( "addHiddenNote - ntype='%s',note='%s', attempt_delete='%s'", ntype, note, attempt_delete ) HN = () if ntype == "payment": HN = ("RECEIVED: ", note) elif ntype == "printed": HN = ("PRINTED: ", note) elif ntype == "exam": HN = ("TC: EXAM", note) elif ntype == "chart_treatment": HN = ("TC:", note) elif ntype == "perio_treatment": HN = ("TC: PERIO", note) elif ntype == "xray_treatment": HN = ("TC: XRAY", note) elif ntype == "treatment": HN = ("TC: OTHER", note) elif ntype == "mednotes": # other treatment HN = ("UPDATED:Medical Notes", note) elif ntype == "close_course": HN = ("COURSE CLOSED", "=" * 10) elif ntype == "open_course": HN = ("COURSE OPENED", "= " * 5) elif ntype == "resume_course": HN = ("COURSE RE-OPENED", "= " * 5) elif ntype == "fee": HN = ("INTERIM: ", note) if not HN: LOGGER.warning( "unable to add Hidden Note notetype '%s' not found", ntype) return reversing_note = ("UNCOMPLETED", "{%s}" % note) if attempt_delete: try: self.HIDDENNOTES.remove(HN) except ValueError: LOGGER.debug("'%s' not in hiddenotes", HN) LOGGER.debug(self.HIDDENNOTES) self.HIDDENNOTES.append(reversing_note) else: try: self.HIDDENNOTES.remove(reversing_note) except ValueError: self.HIDDENNOTES.append(HN) if one_only: while self.HIDDENNOTES.count(HN) > 1: self.HIDDENNOTES.remove(HN) def clearHiddenNotes(self): ''' reset self.HIDDENNOTES ''' self.HIDDENNOTES = [] def updateBilling(self, tone): ''' update the last billdate and tone of invoice ''' self.billdate = localsettings.currentDay() self.billct += 1 self.billtype = tone def reset_billing(self): ''' if patients account is now is order, reset all billing params ''' if self.fees == 0: self.billdate = None self.billct = None self.billtype = None def treatmentOutstanding(self): ''' does the patient have treatmentOutstanding? returns a boolean ''' return (self.treatment_course and self.treatment_course.has_treatment_outstanding) def checkExemption(self): ''' see if the patient's exemption requires removal. ''' if (self.exemption == "S" and self.getAge(self.treatment_course.accd)[0] > 19): self.exemption = "" self.load_warnings.append(_("Student Exemption removed")) elif (self.exemption == "A" and self.getAge(self.treatment_course.accd)[0] > 18): self.exemption = "" self.load_warnings.append(_("Age Exemption removed")) else: return True @property def name_id(self): ''' name and serialno ''' return u"%s - %s" % (self.name, self.serialno) @property def name(self): ''' patients name in a readable form ''' return u"%s %s %s" % (self.title, self.fname, self.sname) @property def psn(self): ''' previous surname ''' try: return self.previous_surnames[0] except IndexError: return "" @property def previous_surnames(self): ''' previous surnames are stored. ## TODO - check this is used. ''' if self._previous_surnames is None: db = connect.connect() cursor = db.cursor() cursor.execute(PSN_QUERY, (self.serialno,)) self._previous_surnames = [s[0] for s in cursor.fetchall()] cursor.close() return self._previous_surnames @property def n_family_members(self): ''' how many members are linked to this patient's familyno ''' if self._n_family_members is None: db = connect.connect() cursor = db.cursor() cursor.execute(FAMILY_COUNT_QUERY, (self.familyno,)) self._n_family_members = cursor.fetchone()[0] return self._n_family_members @property def under_capitation(self): ''' under capitation if regular NHS patient and under 18. ''' if self.cset != "N": return False years, months = self.age_course_start return years < 17 or (years == 17 and months < 11) def new_tx_course(self, new_courseno): ''' start a new treatment course ''' self.courseno0 = new_courseno self.treatment_course = TreatmentCourse(self.serialno, new_courseno) @property def COPIED_ATTRIBUTES(self): ''' these are what is copied over into pt.dbstate ''' return (patient_query_atts + exemptionTableAtts + bpeTableAtts + clinical_memos + ("fees", "estimate_charges", "serialno", "estimates", "appt_prefs", "treatment_course", "chartgrid")) @property def USER_CHANGEABLE_ATTRIBUTES(self): ''' the attributes, common to pt and the object copy pt.db_state which is generated during take_snapshot used to determine whether the patient has been edited. ''' for att_ in self.COPIED_ATTRIBUTES: # if att_ not in ("treatment_course", "estimates", "chartgrid"): yield att_ @property def changes(self): ''' what has changed since the patient was loaded ''' changes = [] for att_ in self.USER_CHANGEABLE_ATTRIBUTES: new_value = self.__dict__.get(att_, "") db_value = self.dbstate.__dict__.get(att_, "") if new_value != db_value: message = "Altered pt.%s" % att_.ljust(20) if att_ not in ("treatment_course", "estimates"): message += ( " ORIG = '%s' NEW = '%s'" % (db_value, new_value)) LOGGER.debug(message) changes.append(att_) return changes @property def has_changes(self): is_changed = self.changes != [] self.lock_record_in_use() if is_changed else self.clear_lock() return is_changed def take_snapshot(self): ''' create a snapshot of this class, copying all attributes that the user can change ''' memo = {} cls = self.__class__ snapshot = cls.__new__(cls) memo[id(self)] = snapshot for att_, val_ in self.__dict__.items(): if att_ in self.COPIED_ATTRIBUTES: setattr(snapshot, att_, deepcopy(val_, memo)) self.dbstate = snapshot LOGGER.debug("snapshot of %s taken" % self) @property def course_dentist(self): ''' returns the course dentist for NHS and private courses, but the contracted dentist otherwise. this is used in the daybook for "work done for lists". ''' if self.cset == "I": return self.dnt1 if self.dnt2 not in (0, None): return self.dnt2 return self.dnt1 @property def has_new_course(self): ''' if the initial state has no course, or a lower course number, this is true. ''' if self.treatment_course and self.dbstate.treatment_course is None: return True return (self.treatment_course.courseno != self.dbstate.treatment_course.courseno) @property def tx_hash_tups(self): ''' a list of unique hashes of all treatment on the current treatment plan returns a tuple (unique hash, attribute, treatment) ''' for hash_, att_, tx in self.treatment_course._get_tx_hashes(): if re.match("[ul][lr][1-8]", att_): att_ = self.chartgrid.get(att_) yield hash_, att_, tx @property def completed_tx_hash_tups(self): for hash_, att_, tx in self.treatment_course.completed_tx_hash_tups: if re.match("[ul][lr][1-8]", att_): att_ = self.chartgrid.get(att_) yield hash_, att_, tx @property def completed_tx_hashes(self): return list(self.treatment_course.completed_tx_hashes) @property def planned_tx_hash_tups(self): return self.treatment_course.planned_tx_hash_tups @property def has_planned_perio_txs(self): for hash_, att_, tx in self.planned_tx_hash_tups: if att_ == "perio": return True return False def get_tx_from_hash(self, hash_): return self.treatment_course.get_tx_from_hash(hash_) def ests_from_hash(self, hash_): ''' return all estimate items associated with a unique tx_hash ''' for est in self.estimates: for tx_hash in est.tx_hashes: if tx_hash == hash_: yield est @property def address_tuple(self): return (self.sname, self.addr1, self.addr2, self.addr3, self.town, self.county, self.pcde, self.tel1) def reload_mh_form_date(self): db = connect.connect() cursor = db.cursor() cursor.execute(MED_FORM_QUERY + " limit 1", (self.serialno,)) try: self.mh_form_date = cursor.fetchone()[0] except TypeError: self.mh_form_date = None cursor.close() def mh_form_dates(self): ''' the dates that the mh form has been signed off by the patient. ''' db = connect.connect() cursor = db.cursor() cursor.execute(MED_FORM_QUERY, (self.serialno,)) for row in cursor.fetchall(): yield row[0] cursor.close() def set_record_in_use(self): records_in_use.set_in_use(self.serialno) def lock_record_in_use(self): records_in_use.set_locked(self.serialno) def clear_lock(self): records_in_use.clear_lock(self.serialno)
class patient(object): def __init__(self, sno): ''' initiate the class with default variables, then load from database ''' self.serialno = sno self.dbstate = None self.load_warnings = [] # patient table atts self.courseno0 = None self.pf0 = 0 self.pf1 = 0 self.pf2 = 0 self.pf3 = 0 self.pf4 = 0 self.pf5 = 0 self.pf6 = 0 self.pf7 = 0 self.pf8 = 0 self.pf9 = 0 self.pf10 = 0 self.pf11 = 0 self.pf12 = 0 self.pf14 = 0 self.pf15 = 0 self.pf16 = 0 self.pf17 = 0 self.pf18 = 0 self.pf19 = 0 self.money0 = 0 self.money1 = 0 self.money2 = 0 self.money3 = 0 self.money4 = 0 self.money5 = 0 self.money6 = 0 self.money7 = 0 self.money8 = 0 self.money9 = 0 self.money10 = 0 self.pd0 = None self.pd1 = None self.pd2 = None self.pd3 = None self.pd4 = None self.pd5 = None self.pd6 = None self.pd7 = None self.pd8 = None self.pd9 = None self.pd10 = None self.pd11 = None self.pd12 = None self.pd13 = None self.pd14 = None self.sname = '' self.fname = '' self.title = '' self.sex = '' self.dob = None self.addr1 = '' self.addr2 = '' self.addr3 = '' self.pcde = '' self.tel1 = '' self.tel2 = '' self.occup = '' self.nhsno = '' self.cnfd = None self.psn = '' self.cset = '' self.dnt1 = 0 self.dnt2 = 0 self.courseno1 = 0 self.ur8st = '' self.ur7st = '' self.ur6st = '' self.ur5st = '' self.ur4st = '' self.ur3st = '' self.ur2st = '' self.ur1st = '' self.ul1st = '' self.ul2st = '' self.ul3st = '' self.ul4st = '' self.ul5st = '' self.ul6st = '' self.ul7st = '' self.ul8st = '' self.ll8st = '' self.ll7st = '' self.ll6st = '' self.ll5st = '' self.ll4st = '' self.ll3st = '' self.ll2st = '' self.ll1st = '' self.lr1st = '' self.lr2st = '' self.lr3st = '' self.lr4st = '' self.lr5st = '' self.lr6st = '' self.lr7st = '' self.lr8st = '' self.dent0 = 0 self.dent1 = 0 self.dent2 = 0 self.dent3 = 0 self.dmask = "YYYYYYY" self.minstart = 0 self.maxend = 0 self.billdate = None self.billct = 0 self.billtype = None self.pf20 = 0 self.money11 = 0 self.pf13 = 0 self.familyno = localsettings.last_family_no self.memo = '' self.town = '' self.county = '' self.mobile = '' self.fax = '' self.email1 = '' self.email2 = '' self.status = '' self.source = '' self.enrolled = '' self.archived = None self.initaccept = 0 self.lastreaccept = None self.lastclaim = None self.expiry = None self.cstatus = None self.transfer = 0 self.pstatus = None self.courseno2 = 0 # TABLE 'mnhist'####### self.chgdate = nullDate # date YES None self.ix = 0 # tinyint(3) unsigned YES None self.note = '' # varchar(60) YES None self.estimates = [] # from userdata self.plandata = PlanData(self.serialno) # NEIL'S STUFF#### self.exemption = "" self.exempttext = "" self.perioData = {} self.bpe = [] self.bpedate = nullDate self.chartdate = nullDate self.notes_dict = {} self.MH = () self.MEDALERT = False self.HIDDENNOTES = [] self.chartgrid = {} self._fee_table = None self.synopsis = "" self._n_family_members = None self._dayBookHistory = None self.treatment_course = None self.est_logger = None if self.serialno == 0: return # # now load stuff from the database ## # db = connect.connect() cursor = db.cursor() self.getSynopsis() cursor.execute(PATIENT_QUERY, (self.serialno,)) values = cursor.fetchall() if values == (): raise localsettings.PatientNotFoundError for i, att in enumerate(patientTableAtts): value = values[0][i] if value is not None: self.__dict__[att] = value elif att == "familyno": self.familyno = 0 query = '''select exemption, exempttext from exemptions where serialno=%s''' cursor.execute(query, self.serialno) values = cursor.fetchall() for value in values: self.exemption, self.exempttext = value query = '''select bpedate, bpe from bpe where serialno=%s order by bpedate''' cursor.execute(query, self.serialno) values = cursor.fetchall() for value in values: self.bpe.append(value) if self.courseno0 != 0: self.getEsts() self.treatment_course = TreatmentCourse( self.serialno, self.courseno0) self.getNotesTuple() query = 'select chartdate,chartdata from perio where serialno=%s' cursor.execute(query, self.serialno) perioData = cursor.fetchall() for data in perioData: self.perioData[localsettings.formatDate(data[0])] = ( perio.get_perioData(data[1])) #--perioData is #--a dictionary (keys=dates) of dictionaries with keys #--like "ur8" and containing 7 tuples of data query = 'select drnm,adrtel,curmed,oldmed,allerg,heart,lungs,' +\ 'liver,kidney,bleed,anaes,other,alert,chkdate from mednotes' +\ ' where serialno=%s' cursor.execute(query, (self.serialno,)) self.MH = cursor.fetchone() if self.MH is not None: self.MEDALERT = self.MH[12] cursor.close() # db.close() #-- load from plandata self.plandata.getFromDB() self.appt_prefs = ApptPrefs(self.serialno) self.updateChartgrid() self.take_snapshot() @property def appt_memo(self): return self.appt_prefs.note def set_appt_memo(self, memo): self.appt_prefs.note = memo @property def recall_active(self): return self.appt_prefs.recall_active @property def recd(self): return self.appt_prefs.recdent @property def dayBookHistory(self): if self._dayBookHistory is None: db = connect.connect() cursor = db.cursor() query = 'select date, trtid, chart from daybook where serialno=%s' cursor.execute(query, self.serialno) self._dayBookHistory = cursor.fetchall() cursor.close() return self._dayBookHistory def __repr__(self): return "'Patient_class instance - serialno %d'" % self.serialno @property def address(self): ''' a printable address ''' address = "" for line in (self.addr1, self.addr2, self.addr3, self.town, self.county, self.pcde): if line.strip(" ") != "": address += "%s\n" % line.strip(" ") return address def getAge(self, on_date=None): ''' return the age in form (year(int), months(int), isToday(bool)) ''' if on_date is None: # use today on_date = localsettings.currentDay() day = self.dob.day try: nextbirthday = datetime.date(on_date.year, self.dob.month, self.dob.day) except ValueError: # catch leap years!! nextbirthday = datetime.date(on_date.year, self.dob.month, self.dob.day - 1) ageYears = on_date.year - self.dob.year if nextbirthday > on_date: ageYears -= 1 months = (12 - self.dob.month) + on_date.month else: months = on_date.month - self.dob.month if self.dob.day > on_date.day: months -= 1 isToday = nextbirthday == localsettings.currentDay() return (ageYears, months, isToday) @property def ageYears(self): return self.getAge()[0] @property def age_course_start(self): ''' returns a tuple (year, months) for the patient at accd ''' return self.getAge(self.treatment_course.accd)[:2] @property def under_6(self): ''' returns a bool "is patient under 6?". ''' return self.ageYears < 6 @property def under_18(self): ''' returns a bool "is patient under 18?". ''' return self.ageYears < 18 def forget_fee_table(self): self._fee_table = None @property def fee_table(self): ''' logic to determine which feeTable should be used for standard items ''' if self._fee_table is None: if self.treatment_course.accd is None: cse_accd = localsettings.currentDay() else: cse_accd = self.treatment_course.accd for table in localsettings.FEETABLES.tables.values(): LOGGER.debug( "checking feescale %s to see if suitable a feetable" % ( table)) start, end = table.startDate, table.endDate LOGGER.debug("categories, start, end = %s, %s, %s" % ( table.categories, start, end)) if end is None: end = localsettings.currentDay() if self.cset in table.categories and start <= cse_accd <= end: self._fee_table = table if self._fee_table is None: #-- no matching table found, use the default. LOGGER.warning("NO SUITABLE FEETABLE FOUND, RETURNING DEFAULT") self._fee_table = localsettings.FEETABLES.default_table return self._fee_table def getEsts(self): ''' get estimate data ''' db = connect.connect() cursor = db.cursor() cursor.execute(ESTS_QUERY, (self.serialno, self.courseno0)) rows = cursor.fetchall() self.estimates = [] for row in rows: hash_ = row[10] completed = bool(row[9]) tx_hash = estimates.TXHash(hash_, completed) ix = row[0] found = False # use existing est if one relates to multiple treatments for existing_est in self.estimates: if existing_est.ix == ix: existing_est.tx_hashes.append(tx_hash) found = True break if found: continue # initiate a custom data class est = estimates.Estimate() est.ix = ix est.courseno = row[11] est.number = row[1] est.itemcode = row[2] est.description = row[3] est.fee = row[4] est.ptfee = row[5] est.feescale = row[6] est.csetype = row[7] est.dent = row[8] est.tx_hashes = [tx_hash] self.estimates.append(est) cursor.close() self.est_logger = EstLogger(self.courseno0) def getSynopsis(self): db = connect.connect() cursor = db.cursor() fields = clinical_memos query = "" for field in fields: query += field + "," query = query.strip(",") try: if cursor.execute( 'SELECT %s from clinical_memos where serialno=%d' % (query, self.serialno)): self.synopsis = cursor.fetchall()[-1][0] except connect.OperationalError as e: 'necessary because the column is missing is db schema 1.4' print "WARNING -", e @property def underTreatment(self): return (self.treatment_course is not None and self.treatment_course.underTreatment) @property def max_tx_courseno(self): return self.treatment_course.max_tx_courseno @property def newer_course_found(self): return self.treatment_course.newer_course_found def getNotesTuple(self): ''' connect and poll the formatted_notes table ''' self.notes_dict = formatted_notes.get_notes_dict(self.serialno) def flipDec_Perm(self, tooth): ''' switches a deciduous tooth to a permanent one, and viceVersa pass a variable like "ur5" ''' quadrant = tooth[:2] pos = int(tooth[2]) - 1 # will be 0-7 if quadrant == "ul": var = self.dent1 pos = 7 - pos elif quadrant == "ur": var = self.dent0 elif quadrant == "ll": var = self.dent2 else: # lr var = self.dent3 pos = 7 - pos existing = dec_perm.fromSignedByte(var) if existing[pos] == "1": existing = existing[:pos] + "0" + existing[pos + 1:] else: existing = existing[:pos] + "1" + existing[pos + 1:] if quadrant == "ul": self.dent1 = dec_perm.toSignedByte(existing) elif quadrant == "ur": self.dent0 = dec_perm.toSignedByte(existing) elif quadrant == "ll": self.dent2 = dec_perm.toSignedByte(existing) else: # lr self.dent3 = dec_perm.toSignedByte(existing) self.updateChartgrid() def updateChartgrid(self): grid = "" for quad in (self.dent1, self.dent0, self.dent3, self.dent2): grid += dec_perm.fromSignedByte(quad) for pos in mouth: if grid[mouth.index(pos)] == "0": self.chartgrid[pos] = pos else: self.chartgrid[pos] = decidmouth[mouth.index(pos)] def apply_fees(self): if "N" in self.cset: self.money0 = self.dbstate.money0 + self.fees_accrued else: self.money1 = self.dbstate.money1 + self.fees_accrued @property def fees(self): return int(self.money0 + self.money1 + self.money9 + self.money10 + self.money11 - self.money2 - self.money3 - self.money8) @property def fees_accrued(self): old_estimate_charges = 0 if self.courseno0 == self.dbstate.courseno0: old_estimate_charges = self.dbstate.estimate_charges accrued_fees = self.estimate_charges - old_estimate_charges LOGGER.debug("fees_accrued = (new-existing) = %d - %d = %d" % ( self.estimate_charges, old_estimate_charges, accrued_fees) ) return accrued_fees @property def estimate_charges(self): charges = 0 for est in self.estimates: if est.completed == 2: charges += est.ptfee elif est.completed == 1: charges += est.interim_pt_fee return charges @property def est_logger_text(self): ''' a summary of the estimate for use in the est_logger_table est_logger is unconcerned whether treatment is completed etc.. ''' text = "" total, p_total = 0, 0 for estimate in sorted(self.estimates): text += estimate.log_text total += estimate.fee p_total += estimate.ptfee text += "TOTAL || || || || || || %s || %s" % (total, p_total) return text def resetAllMonies(self): ''' zero's everything except money11 (bad debt) ''' self.money0 = 0 self.money1 = 0 self.money9 = 0 self.money10 = 0 self.money2 = 0 self.money3 = 0 self.money8 = 0 self.dbstate.money0 = 0 self.dbstate.money1 = 0 def nhs_claims(self, completed_only=True): claims = [] for est in self.estimates: if (est.csetype.startswith("N") and (not completed_only or est.completed == 2) ): # yield est claims.append(est) return claims def addHiddenNote(self, ntype, note="", attempt_delete=False): ''' re-written for schema 1.9 ''' LOGGER.info( "patient.addHiddenNote(ntype='%s',note='%s', attempt_delete='%s'" % ( ntype, note, attempt_delete)) HN = () if ntype == "payment": HN = ("RECEIVED: ", note) elif ntype == "printed": HN = ("PRINTED: ", note) elif ntype == "exam": HN = ("TC: EXAM", note) elif ntype == "chart_treatment": HN = ("TC:", note) elif ntype == "perio_treatment": HN = ("TC: PERIO", note) elif ntype == "xray_treatment": HN = ("TC: XRAY", note) elif ntype == "treatment": HN = ("TC: OTHER", note) elif ntype == "mednotes": # other treatment HN = ("UPDATED:Medical Notes", note) elif ntype == "close_course": HN = ("COURSE CLOSED", "=" * 10) elif ntype == "open_course": HN = ("COURSE OPENED", "= " * 5) elif ntype == "resume_course": HN = ("COURSE RE-OPENED", "= " * 5) elif ntype == "fee": HN = ("INTERIM: ", note) if not HN: print "unable to add Hidden Note notetype '%s' not found" % ntype return reversing_note = ("UNCOMPLETED", "{%s}" % note) if attempt_delete: try: self.HIDDENNOTES.remove(HN) except ValueError: self.HIDDENNOTES.append(reversing_note) else: try: self.HIDDENNOTES.remove(reversing_note) except ValueError: self.HIDDENNOTES.append(HN) def clearHiddenNotes(self): self.HIDDENNOTES = [] def updateBilling(self, tone): self.billdate = localsettings.currentDay() self.billct += 1 self.billtype = tone def treatmentOutstanding(self): return (self.treatment_course and self.treatment_course.has_treatment_outstanding) def checkExemption(self): if (self.exemption == "S" and self.getAge(self.treatment_course.accd)[0] > 19): self.exemption = "" self.load_warnings.append(_("Student Exemption removed")) else: return True @property def name_id(self): return u"%s - %s" % ( self.name, self.serialno) @property def name(self): return u"%s %s %s" % ( self.title, self.fname, self.sname) @property def n_family_members(self): if self._n_family_members is None: db = connect.connect() cursor = db.cursor() cursor.execute("select count(*) from patients where familyno=%s", (self.familyno,)) self._n_family_members = cursor.fetchone()[0] return self._n_family_members @property def under_capitation(self): if self.cset != "N": return False years, months = self.age_course_start return years < 17 or (years == 17 and months < 11) def new_tx_course(self, new_courseno): self.courseno0 = new_courseno self.treatment_course = TreatmentCourse(self.serialno, new_courseno) @property def COPIED_ATTRIBUTES(self): ''' these are what is copied over into pt.dbstate ''' return (patientTableAtts + exemptionTableAtts + bpeTableAtts + mnhistTableAtts + perioTableAtts + clinical_memos + ( "fees", "estimate_charges", "serialno", "estimates", "appt_prefs", "treatment_course", "chartgrid")) @property def USER_CHANGEABLE_ATTRIBUTES(self): ''' the attributes, common to pt and the object copy pt.db_state which is generated during take_snapshot used to determine whether the patient has been edited. ''' for att in self.COPIED_ATTRIBUTES: # if att not in ("treatment_course", "estimates", "chartgrid"): yield att @property def changes(self): changes = [] for att in self.USER_CHANGEABLE_ATTRIBUTES: new_value = self.__dict__.get(att, "") db_value = self.dbstate.__dict__.get(att, "") if new_value != db_value: message = "Altered pt.%s" % att.ljust(20) if att not in ("treatment_course", "estimates"): message += ( " ORIG = '%s' NEW = '%s'" % (db_value, new_value)) LOGGER.debug(message) changes.append(att) return changes def take_snapshot(self): # create a snapshot of this class, copying all attributes that the # user can change memo = {} cls = self.__class__ snapshot = cls.__new__(cls) memo[id(self)] = snapshot for att, v in self.__dict__.items(): if att in self.COPIED_ATTRIBUTES: setattr(snapshot, att, deepcopy(v, memo)) self.dbstate = snapshot LOGGER.debug("snapshot of %s taken" % self) @property def course_dentist(self): ''' returns the course dentist for NHS and private courses, but the contracted dentist otherwise. this is used in the daybook for "work done for lists". ''' if self.cset == "I": return self.dnt1 if self.dnt2 not in (0, None): return self.dnt2 return self.dnt1 @property def has_new_course(self): if self.treatment_course and self.dbstate.treatment_course is None: return True return (self.treatment_course.courseno != self.dbstate.treatment_course.courseno) @property def tx_hash_tups(self): ''' a list of unique hashes of all treatment on the current treatment plan returns a tuple (unique hash, attribute, treatment) ''' for hash_, att, tx in self.treatment_course._get_tx_hashes(): if re.match("[ul][lr][1-8]", att): att = self.chartgrid.get(att) yield hash_, att, tx @property def completed_tx_hash_tups(self): for hash_, att, tx in self.treatment_course.completed_tx_hash_tups: if re.match("[ul][lr][1-8]", att): att = self.chartgrid.get(att) yield hash_, att, tx @property def completed_tx_hashes(self): return list(self.treatment_course.completed_tx_hashes) @property def planned_tx_hash_tups(self): return self.treatment_course.planned_tx_hash_tups @property def has_planned_perio_txs(self): for hash_, att, tx in self.planned_tx_hash_tups: if att == "perio": return True return False def get_tx_from_hash(self, hash_): return self.treatment_course.get_tx_from_hash(hash_) def ests_from_hash(self, hash_): ''' return all estimate items associated with a unique tx_hash ''' for est in self.estimates: for tx_hash in est.tx_hashes: if tx_hash == hash_: yield est