#! /usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
## ##
## Copyright 2010, Neil Wallace <rowinggolfer@googlemail.com> ##
## ##
## This program is free software: you can redistribute it and/or modify ##
## it under the terms of the GNU General Public License as published by ##
## the Free Software Foundation, either version 3 of the License, or ##
## (at your option) any later version. ##
## ##
## This program is distributed in the hope that it will be useful, ##
## but WITHOUT ANY WARRANTY; without even the implied warranty of ##
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ##
## GNU General Public License for more details. ##
## ##
## You should have received a copy of the GNU General Public License ##
## along with this program. If not, see <http://www.gnu.org/licenses/>. ##
## ##
###############################################################################
'''
Provides the TreatmentItem Class
'''
import logging
import re
import types
from PyQt4 import QtSql, QtCore
from lib_openmolar.common.datatypes import proc_codes
from insertable_record import InsertableRecord
PROCEDURE_CODES = proc_codes.ProcedureCodesInstance()
[docs]class TreatmentItemException(Exception):
'''
a custom exception raised by treatment item errors
'''
[docs] def __init__(self, value="unknown"):
self.value = value
def __str__(self):
return repr(self.value)
[docs]class TreatmentItem(object):
'''
.. note::
this custom data object represents an item of treatment.
the underlying procedure code can be accessed with TreatmentItem.code
raises a :doc:`TreatmentItemException` if errors are encountered
'''
#:
SIMPLE = proc_codes.ProcCode.SIMPLE
#:
TOOTH = proc_codes.ProcCode.TOOTH
#:
TEETH = proc_codes.ProcCode.TEETH
#:
ROOT = proc_codes.ProcCode.ROOT
#:
FILL = proc_codes.ProcCode.FILL
#:
CROWN = proc_codes.ProcCode.CROWN
#:
BRIDGE = proc_codes.ProcCode.BRIDGE
#:
PROSTHETICS = proc_codes.ProcCode.PROSTHETICS
#:
OTHER = proc_codes.ProcCode.OTHER
[docs] def __init__(self, param):
'''
*overloaded function*
:param: QSql.QSqlrecord
will load values from the Record
:param: string
string should be of the form "A01"
ie. uniquely identify a proc code
:param: :class:`ProcCode`
pass in a :doc:`ProcCode` object directly
'''
if type(param) == QtSql.QSqlRecord:
self.qsql_record = param
param = str(param.value("om_code").toString())
else:
#:
self.qsql_record = None
if type(param) == types.StringType:
#:
self.code = PROCEDURE_CODES[param]
else:
self.code = param
self._px_clinician = None
self._tx_clinician = None
self._cmp_date = None
self._metadata = None
#:
self._comment = ""
#:
self.is_completed = False
if self.in_database:
self._from_record()
else:
if SETTINGS.current_practitioner:
self.set_px_clinician(SETTINGS.current_practitioner.id)
def _from_record(self):
'''
An extension of __init__, loading data from the database
when initiated by a QSqlRecord.
'''
LOGGER.debug("converting QsqlRecord to TreatmentItem")
self.set_px_clinician(self.qsql_record.value("px_clinician").toInt()[0])
tx_clinician, valid = self.qsql_record.value("tx_clinician").toInt()
self.set_comment(unicode(self.qsql_record.value("comment").toString()))
self.set_completed(self.qsql_record.value("completed").toBool())
if valid and tx_clinician !=0 :
self.set_tx_clinician(tx_clinician)
tx_date = self.qsql_record.value("tx_date").toDate()
if tx_date:
self.set_cmp_date(tx_date)
def _get_metadata(self):
'''
poll the database to get metadata associated with this item
'''
self._metadata = []
query = '''select
tooth, tx_type, surfaces, material, type, technition from treatment_teeth
left join treatment_fills on treatment_fills.tooth_tx_id = treatment_teeth.ix
left join treatment_crowns on treatment_crowns.tooth_tx_id = treatment_teeth.ix
where treatment_teeth.treatment_id = ?
'''
q_query = QtSql.QSqlQuery(SETTINGS.psql_conn)
q_query.prepare(query)
q_query.addBindValue(self.id)
q_query.exec_()
while q_query.next():
record = q_query.record()
treatment_item_metadata = TreatmentItemMetadata(self, record)
self._metadata.append(treatment_item_metadata)
@property
@property
[docs] def id(self):
'''
returns the primary key of the treatments table for this item,
or None if the item is not in the database
'''
return None if not self.in_database else self.qsql_record.value("ix")
[docs] def set_cmp_date(self, date=QtCore.QDate.currentDate()):
'''
:param: date
sets the item as completed, on date date
'''
self.set_completed()
self._cmp_date = date
@property
[docs] def in_database(self):
'''
returns true if the item is in the database
if it is, then it will have a valid :attr:`qsql_record` .
'''
return self.qsql_record != None
@property
[docs] def cmp_date(self):
'''
date the item was completed (returns None if tx incomplete)
'''
return self._cmp_date
@property
[docs] def tooth_required(self):
'''
returns True if this code needs tooth metadata (some don't)
'''
return self.code.tooth_required and self.metadata == []
@property
[docs] def type(self):
return self.code.type
@property
[docs] def is_chartable(self):
'''
a bool indicating whether this treatment item can be represented
on a dental chart.
example, an examination is not, but a filling in the UR5 is
'''
for data in self.metadata:
if data.is_chartable:
return True
return False
@property
[docs] def is_bridge(self):
return self.code.is_bridge
@property
[docs] def is_prosthetics(self):
return self.code.is_prosthetics
@property
[docs] def category(self):
return self.code.category
@property
[docs] def description(self):
return self.code.description
@property
@property
[docs] def pontics(self):
'''
a list of all metadata items which are pontics
'''
pontics = []
for data in self.metadata:
if data.is_pontic:
pontics.append(data)
return pontics
@property
[docs] def abutments(self):
'''
a list of all metadata items which are bridge abutments
'''
abutments = []
for data in self.metadata:
if data.is_abutment:
abutments.append(data)
return abutments
@property
[docs] def allowed_pontics(self):
'''
which teeth are acceptable as pontics
eg upper partial pontics should be in range(1,18)
'''
return self.code.allowed_pontics
@property
[docs] def further_info_needed(self):
return self.code.further_info_needed
@property
[docs] def pontics_required(self):
return self.code.pontics_required
@property
[docs] def total_span(self):
return self.code.total_span
@property
[docs] def surfaces_required(self):
for data in self.metadata:
if data.surfaces != "":
return False
return self.code.surfaces_required
@property
[docs] def set_teeth(self, teeth):
'''
:param: [int, int, int...]
ints should comply with :doc:`../../misc/tooth_notation`
'''
#print "setting teeth", teeth
for tooth in teeth:
metadata = self.add_metadata()
metadata.set_tooth(tooth)
[docs] def set_abutments(self, teeth):
'''
:param: list of pontics [int, int, int...]
ints should comply with :doc:`../../misc/tooth_notation`
'''
for tooth in teeth:
metadata = self.add_metadata()
metadata.set_tooth(tooth)
metadata.set_tx_type("abutment")
[docs] def set_pontics(self, pontics):
'''
:param: list of pontics [int, int, int...]
ints should comply with :doc:`../../misc/tooth_notation`
'''
#print "setting pontics %s"% pontics
if self.pontics_required:
for pontic in pontics:
metadata = self.add_metadata()
metadata.set_tooth(pontic)
metadata.set_tx_type("pontic")
[docs] def set_surfaces(self, surfaces):
for data in self.metadata:
data.set_surfaces(surfaces)
[docs] def set_completed(self, completed=True):
'''
:kword: completed=bool
'''
self.is_completed = completed
[docs] def set_px_clinician(self, clinician_id):
'''
:param: clinician_id (int)
.. note::
who prescribed this treatment
int should be the unique id of a clinician
'''
assert (clinician_id is None or
type(clinician_id) == types.IntType), "error setting_px_clinician"
self._px_clinician = clinician_id
[docs] def set_tx_clinician(self, clinician_id):
'''
:param: clinician_id (int)
.. note::
who prescribed this treatment
int should be the unique id of a clinician
'''
assert (clinician_id is None or
type(clinician_id) == types.IntType), "error setting_tx_clinician"
self._tx_clinician = clinician_id
@property
[docs] def px_clinician(self):
'''
the unique id of a clinician who prescribed the treatment
'''
return self._px_clinician
@property
[docs] def tx_clinician(self):
'''
the unique id of a clinician who performed the treatment
'''
return self._tx_clinician
@property
[docs] def allow_multiple_teeth(self):
'''
True if this treatment can related to multiple teeth
.. note::
an example would be a periodontal splint
'''
return self.is_bridge or self.is_prosthetics or self.type == self.TEETH
@property
[docs] def required_span(self):
'''
how many units should this be if it is to be a bridge?
returns an integer or None
'''
expected_span = str(self.total_span)
n = re.match("\d+", expected_span)
if n:
return int(n.group())
@property
[docs] def entered_span(self):
'''
returns the entered total span of a bridge (if this is a bridge)
'''
return len(self.pontics) + len(self.abutments)
@property
[docs] def is_valid(self):
return self.check_valid()[0]
@property
[docs] def errors(self):
'''
convenience function to get the errors for this item (if any)
'''
return self.check_valid()[1]
[docs] def check_valid(self):
'''
a tuple (valid, errors),
where valid is a boolean, and errors a list of errors
check to see that the item has all the attributes required by the
underlying procedure code
.. note::
our 3 surface filling will need to know tooth and surfaces
returns a tuple (valid, errors),
where valid is a boolean, and errors a list of errors
'''
errors = []
if self.tooth_required and not self.metadata:
errors.append(_("No Tooth Selected"))
if self.pontics_required and self.pontics == []:
errors.append(_("No Pontics Selected"))
if self.px_clinician is None:
errors.append(_("Unknown Prescribing Clinician"))
if self.is_completed and self.tx_clinician is None:
errors.append(_("Unknown Treating Clinician"))
if self.is_completed and self.cmp_date is None:
errors.append(_("Unknown Completion Date"))
if self.is_bridge:
entered, required = self.entered_span, self.required_span
if not ( entered == required or
("+" in self.total_span and entered > required)):
errors.append(u"%s (%s %s - %s %s)"% (
_("Incorrect Bridge Span"),
required, _("units required"),
entered, _("entered")))
for pontic in self.pontics:
if not pontic.tooth in self.allowed_pontics:
errors.append("invalid pontic (%s)"% (
SETTINGS.TOOTHGRID_LONGNAMES[pontic.tooth]))
for data in self.metadata:
errors += data.errors()
if self.comment_required and self.comment == "":
errors.append(_("A comment is required"))
return (errors==[], errors)
[docs] def commit_to_db(self, database):
'''
:param: database
write this item to the database
will raise an exception if item is not :func:`is_valid`
'''
valid, reason = self.check_valid()
if not valid:
raise Exception, "Invalid Treatment Item <hr />%s"% reason
record = InsertableRecord(database, "treatments")
record.setValue("patient_id", SETTINGS.current_patient.patient_id)
record.setValue("om_code", self.code.code)
record.setValue("completed", self.is_completed)
record.setValue("px_clinician", self.px_clinician)
record.setValue("tx_clinician", self.tx_clinician)
record.setValue("px_date", QtCore.QDate.currentDate())
record.setValue("tx_date", self.cmp_date)
record.setValue("added_by", SETTINGS.user)
record.setValue("comment", self.comment)
query, values = record.insert_query
q_query = QtSql.QSqlQuery(database)
q_query.prepare(query+ " returning ix")
for value in values:
q_query.addBindValue(value)
q_query.exec_()
if q_query.lastError().isValid():
logging.error(query)
error = q_query.lastError()
database.emit_caught_error(error)
return False
q_query.first()
ix = q_query.value(0).toInt()[0]
for data in self.metadata:
data.commit_db(database, ix)
return True
def __repr__(self):
item = '''TreatmentItem
code='%s'
completed='%s' date='%s' in_db='%s'
'''% (
self.code,
self.is_completed, self.cmp_date,
self.in_database,
)
return item
def __cmp__(self, other):
try:
return cmp(self.code, other.code)
except AttributeError as e:
return -1
if __name__ == "__main__":
logging.basicConfig(level = logging.DEBUG)