aboutsummaryrefslogtreecommitdiffstats
path: root/97suifangqa/apps/indicator/models.py
diff options
context:
space:
mode:
Diffstat (limited to '97suifangqa/apps/indicator/models.py')
-rw-r--r--97suifangqa/apps/indicator/models.py1127
1 files changed, 1127 insertions, 0 deletions
diff --git a/97suifangqa/apps/indicator/models.py b/97suifangqa/apps/indicator/models.py
new file mode 100644
index 0000000..bd57d87
--- /dev/null
+++ b/97suifangqa/apps/indicator/models.py
@@ -0,0 +1,1127 @@
+# -*- coding: utf-8 -*-
+#
+# Weitian Li <liweitianux@foxmail.com>
+# updated: 2013/08/12
+#
+
+"""
+apps/indicator models
+"""
+
+from django.db import models
+from django.contrib import admin
+from django.contrib.auth.models import User
+# '@permalink' is no longer recommended
+from django.core.urlresolvers import reverse
+
+import re
+import datetime
+
+import sympy
+from sympy.core.sympify import SympifyError
+
+from utils.xpinyin import Pinyin
+
+
+class IndicatorCategory(models.Model): # {{{
+ """
+ 对 Indicator 进行分类,用于前端按分类显示和选择指标。
+ """
+ name = models.CharField(u"指标类别名称", max_length=100)
+ pinyin = models.CharField(u"拼音", max_length=200,
+ editable=False, blank=True)
+ englishName = models.CharField(u"Indicator Category Name",
+ max_length=200, blank=True)
+ description = models.TextField(u"指标类别描述", blank=True)
+ # 记录添加的用户,用户只能修改自己添加的对象
+ addByUser = models.ForeignKey(User, verbose_name=u"添加的用户",
+ related_name="indicator_categories")
+
+ class Meta:
+ verbose_name_plural = u"指标类别"
+ ordering = ['pinyin', 'id']
+
+ def __unicode__(self):
+ return u"< IndicatorCategory: #%s, %s addBy %s >"\
+ % (self.id, self.name, self.addByUser.username)
+
+ def show(self):
+ """
+ used in 'search/search.html'
+ to show search result
+ """
+ return self.__unicode__()
+
+ def get_absolute_url(self):
+ # need define url with name='show-category', 'pk' as parameter
+ return reverse('show-category',
+ kwargs={'pk': self.id})
+
+ # auto generate `pinyin'
+ def save(self, **kwargs):
+ p = Pinyin()
+ self.pinyin = p.get_pinyin(self.name)
+ super(IndicatorCategory, self).save(**kwargs)
+
+ def dump(self, **kwargs):
+ dump_data = {
+ 'id': self.id,
+ 'name': self.name,
+ 'pinyin': self.pinyin,
+ 'englishName': self.englishName,
+ 'description': self.description,
+ 'addByUser_id': self.addByUser.id,
+ }
+ return dump_data
+# }}}
+
+
+class Indicator(models.Model): # {{{
+ """
+ 指标模型
+ """
+ name = models.CharField(u"指标名称", max_length=100)
+ pinyin = models.CharField(u"拼音", max_length=200,
+ editable=False, blank=True)
+ englishName = models.CharField(u"Indicator Name",
+ max_length=200, blank=True)
+ description = models.TextField(u"指标描述", blank=True)
+ # Indicator 接受数据类型/格式等说明/示例
+ helpText = models.CharField(u"帮助", max_length=300, blank=True)
+ # 记录添加指标的用户,用户只能修改自己添加的指标
+ addByUser = models.ForeignKey(User, verbose_name=u"添加的用户",
+ related_name="indicators")
+ # Category
+ categories = models.ManyToManyField(IndicatorCategory,
+ verbose_name=u"所属类别", related_name="indicators")
+ # DATA_TYPES for indicator
+ INTEGER_TYPE = u'IN' # 整数型
+ FLOAT_TYPE = u'FL' # 浮点型
+ RANGE_TYPE = u'RG' # 范围型(eg. 250-500)
+ FLOAT_RANGE_TYPE = u'FR' # 浮点型/范围型,接受定值或范围
+ PM_TYPE = u'PM' # +/- 型
+ RADIO_TYPE = u'RD' # 单选型
+ CHECKBOX_TYPE = u'CB' # 多选多
+ DATA_TYPES = (
+ (INTEGER_TYPE, u"整数型"),
+ (FLOAT_TYPE, u"浮点定值型"),
+ (RANGE_TYPE, u"浮点范围型"),
+ (FLOAT_RANGE_TYPE, u"定值或范围型"),
+ (PM_TYPE, u"阴阳型(+/-)"),
+ #(RADIO_TYPE, u"单选型"),
+ #(CHECKBOX_TYPE, u"多选型"),
+ )
+ dataType = models.CharField(u"数据类型", max_length=2,
+ choices=DATA_TYPES)
+
+ class Meta:
+ verbose_name_plural = u"医学指标"
+ ordering = ['pinyin', 'id']
+
+ def __unicode__(self):
+ return u"< Indicator: #%s, %s, dataType %s addBy %s >"\
+ % (self.id, self.name, self.dataType,
+ self.addByUser.username)
+
+ def show(self):
+ """
+ used in 'search/search.html'
+ to show search result
+ """
+ return self.__unicode__()
+
+ def get_absolute_url(self):
+ return reverse('show-indicator',
+ kwargs={'pk': self.id})
+
+ # auto generate `pinyin'
+ def save(self, **kwargs):
+ p = Pinyin()
+ self.pinyin = p.get_pinyin(self.name)
+ super(Indicator, self).save(**kwargs)
+
+ def check_unit(self, **kwargs):
+ """
+ Check if the validity of the units specified for the indicator.
+ A indicator must have one 'standard unit'.
+ if indicator.dataType in [INTEGER_TYPE, PM_TYPE],
+ then units are not needed.
+ """
+ if self.dataType in [self.FLOAT_TYPE, self.RANGE_TYPE,
+ self.FLOAT_RANGE_TYPE]:
+ std_unit = self.units.filter(standard=True)
+ if std_unit:
+ return True
+ else:
+ print u"Indicator id=%s 未指定标准单位" % self.id
+ return False
+ else:
+ print u"dataType=%s 不需要单位" % self.dataType
+ return True
+
+ def _get_unit(self, type="standard"):
+ if type == "standard":
+ _units = self.units.filter(standard=True)
+ elif type == "other":
+ _units = self.units.filter(standard=False)
+ else:
+ _units = []
+ return list(_units)
+
+ def get_unit(self, type="standard"):
+ """
+ return a 'list' which contains the 'Unit's
+ related to the indicator
+ get_unit(type):
+ type = standard(default), other, all
+ this return the 'standard unit' by default
+ if 'type="all"', the 'standard unit' comes first
+ """
+ if type == "all":
+ # get all units
+ # standard unit first
+ _units = self._get_unit(type="standard")\
+ + self._get_unit(type="other")
+ return _units
+ else:
+ return self._get_unit(type)
+
+ def check_confine(self):
+ """
+ check the existence of the related InnateConfine
+ """
+ try:
+ c = self.innate_confine
+ return True
+ except InnateConfine.DoesNotExist:
+ print u'Indicator id=%s 未指定 InnateConfine' % self.id
+ raise ValueError(u'Indicator id=%s 未指定 InnateConfine'
+ % self.id)
+ return False
+
+ def get_confine(self):
+ """
+ dump the confine data from the related InnateConfine
+ """
+ try:
+ c = self.innate_confine
+ return c.dump()
+ except InnateConfine.DoesNotExist:
+ print u'Indicator id=%s 未指定 InnateConfine' % self.id
+ return {}
+
+ def is_ready(self):
+ """
+ check the status of this indicator,
+ if 'Unit's and 'InnateConfine' are correctly specified,
+ then the Indicator is ready to use. returned 'True'
+ """
+ return (self.check_unit() and self.check_confine())
+
+ def dump(self, **kwargs):
+ dump_data = {
+ 'id': self.id,
+ 'name': self.name,
+ 'pinyin': self.pinyin,
+ 'englishName': self.englishName,
+ 'description': self.description,
+ 'helpText': self.helpText,
+ 'addByUser_id': self.addByUser.id,
+ 'dataType': self.dataType,
+ 'categories_id': [c.id
+ for c in self.categories.all()],
+ 'units_id': [u.id
+ for u in self.get_unit(type="all")]
+ }
+ return dump_data
+# }}}
+
+
+class UserIndicator(models.Model): # {{{
+ """
+ 记录某用户关注了哪些指标
+ """
+ user = models.OneToOneField(User, verbose_name=u"用户",
+ related_name="user_indicator")
+ followedIndicators = models.ManyToManyField(Indicator,
+ verbose_name=u"关注的指标",
+ related_name="user_indicators",
+ null=True, blank=True)
+
+ class Meta:
+ verbose_name_plural = u"用户指标信息"
+
+ def __unicode__(self):
+ return u"< UserIndicator: for %s >" % self.user.username
+# }}}
+
+
+class IndicatorRecord(models.Model): # {{{
+ """
+ 指标记录
+ 对应某指标某一次的数据记录
+ """
+ indicator = models.ForeignKey(Indicator, verbose_name=u"化验指标",
+ related_name="indicator_records")
+ user = models.ForeignKey(User, verbose_name=u"用户",
+ related_name="indicator_records")
+ # date
+ created_at = models.DateTimeField(u"创建时间", auto_now_add=True)
+ updated_at = models.DateTimeField(u"更新时间",
+ auto_now_add=True, auto_now=True)
+ # data
+ date = models.DateField(u"化验日期")
+ # TODO: limit_choices_to
+ unit = models.ForeignKey("Unit", verbose_name=u"数据单位",
+ related_name="indicator_records", null=True, blank=True)
+ value = models.CharField(u"指标数据值", max_length=30,
+ blank=True)
+ val_max = models.FloatField(u"数据范围上限",
+ null=True, blank=True)
+ val_min = models.FloatField(u"数据范围下限",
+ null=True, blank=True)
+ notes = models.TextField(u"记录说明", blank=True)
+
+ class Meta:
+ verbose_name_plural = u"指标数据记录"
+ ordering = ['id', 'date', 'created_at']
+
+ def __unicode__(self):
+ return u"< IndicatorRecord: #%s; %s, %s, %s >" % (self.id,
+ self.user.username, self.indicator.name, self.date)
+
+ def get_absolute_url(self):
+ return reverse('show-record',
+ kwargs={'pk': self.id})
+
+ def save(self, **kwargs):
+ if self.is_valid() and self.check_confine:
+ super(IndicatorRecord, self).save(**kwargs)
+ else:
+ raise ValueError(u'您输入的数据不符合要求')
+
+ def is_valid(self, **kwargs): # {{{
+ """验证输入数据是否合法"""
+ if self.indicator.dataType == self.indicator.INTEGER_TYPE:
+ # 整数型
+ try:
+ value = int(self.value)
+ return True
+ except ValueError:
+ raise ValueError(u'您提交的指标数据类型不正确')
+ return False
+ elif self.indicator.dataType == self.indicator.FLOAT_TYPE:
+ # 浮点型
+ if not self.unit:
+ raise ValueError(u'未填写单位')
+ return False
+ try:
+ value = float(self.value)
+ return True
+ except ValueError:
+ raise ValueError(u'value 数据类型不正确')
+ return False
+ elif self.indicator.dataType == self.indicator.RANGE_TYPE:
+ # 范围型
+ if not self.unit:
+ raise ValueError(u'未填写单位')
+ return False
+ if (self.val_max is None) or (self.val_min is None):
+ raise ValueError(u'val_max 或 val_min 未填写')
+ return False
+ if (self.val_max <= self.val_min):
+ raise ValueError(u'val_max <= val_min')
+ return False
+ return True
+ elif self.indicator.dataType == self.indicator.FLOAT_RANGE_TYPE:
+ # 定值/范围型 (浮点定值优先)
+ if not self.unit:
+ raise ValueError(u'未填写单位')
+ return False
+ if self.value:
+ # 定值
+ try:
+ value = float(self.value)
+ return True
+ except ValueError:
+ raise ValueError(u'value 数据类型不正确')
+ return False
+ elif (self.val_max is not None) and (self.val_min is not None):
+ # 范围值
+ if (self.val_max <= self.val_min):
+ raise ValueError(u'val_max <= val_min')
+ return False
+ else:
+ return True
+ else:
+ raise ValueError(u'您提交的指标数据不符合要求')
+ return False
+ elif self.indicator.dataType == self.indicator.PM_TYPE:
+ # +/- 型,无单位要求
+ if (len(self.value) == 1) and (self.value in [u'+', u'-']):
+ return True
+ else:
+ raise ValueError(u'value 只接受 "+" 或 "-"')
+ return False
+ ## TODO: RADIO_TYPE, CHECKBOX_TYPE
+ elif self.indicator.dataType in [self.indicator.RADIO_TYPE,
+ self.indicator.CHECKBOX_TYPE]:
+ raise ValueError(u'RADIO_TYPE, CHECKBOX_TYPE 验证未实现')
+ return False
+ else:
+ raise ValueError(u'指标数据类型不合法')
+ return False
+ # }}}
+
+ def check_confine(self, **kwargs): # {{{
+ """
+ check if the record data within the related confine:
+ math_min <= value <= math_max
+
+ NOTE: convert record data to 'standard unit' before comparison
+ """
+ sind = self.indicator
+ # unit relation
+ unit_rel = self.unit.relation
+ v = sympy.symbols('v')
+ rel_sym = sympy.sympify(unit_rel)
+ # error message
+ errmsg = u"'%s' 求值错误,请检查只含有变量 'v'" % unit_rel
+ # check
+ if sind.dataType in [sind.FLOAT_TYPE, sind.RANGE_TYPE,
+ sind.FLOAT_RANGE_TYPE]:
+ if not sind.check_confine():
+ return False
+ # InnateConfine is ok
+ sic = sind.innate_confine
+ # value
+ if self.value:
+ try:
+ value = float(self.value)
+ except ValueError:
+ print u'ERROR: value="%s" cannot convert to float'\
+ % self.value
+ return False
+ # 'value' unit conversion
+ try:
+ value_std = float(rel_sym.evalf(subs={v: value}))
+ except ValueError:
+ print errmsg
+ raise ValueError(errmsg)
+ if (value_std < sic.math_min) or (
+ value_std > sic.math_max):
+ print u'ERROR: value(std) < math_min or value(std) > math_max'
+ return False
+ # val_max
+ if self.val_max is not None:
+ # unit conversion
+ try:
+ val_max_std = float(rel_sym.evalf(
+ subs={v: self.val_max}))
+ except ValueError:
+ print errmsg
+ raise ValueError(errmsg)
+ if (val_max_std <= sic.math_min) or (
+ val_max_std > sic.math_max):
+ print u'ERROR: val_max(std) <= math_min or val_max(std) > math_max'
+ return False
+ # val_min
+ if self.val_min is not None:
+ try:
+ val_min_std = float(rel_sym.evalf(
+ subs={v: self.val_min}))
+ except ValueError:
+ print errmsg
+ raise ValueError(errmsg)
+ if (val_min_std < sic.math_min) or (
+ val_min_std >= sic.math_max):
+ print u'ERROR: val_min(std) < math_min or val_min(std) >= math_max'
+ return False
+ # check finished
+ return True
+ else:
+ # INTEGER_TYPE or PM_TYPE
+ return True
+
+ # }}}
+
+ def get_data(self, **kwargs): # {{{
+ """
+ get the record data
+ in unit originally filled by the user
+ """
+ # check the indicator.dataType
+ sind = self.indicator
+ if sind.dataType in [sind.FLOAT_TYPE, sind.RANGE_TYPE,
+ sind.FLOAT_RANGE_TYPE]:
+ # self.value
+ if self.value:
+ value = float(self.value)
+ else:
+ value = None
+ # self.val_max
+ if self.val_max:
+ val_max = self.val_max
+ else:
+ val_max = None
+ # self.val_min
+ if self.val_min:
+ val_min = self.val_min
+ else:
+ val_min = None
+ # output data
+ data = {
+ 'date': self.date.isoformat(),
+ 'value': value,
+ 'val_max': val_max,
+ 'val_min': val_min,
+ 'unit': self.unit.dump(),
+ 'notes': self.notes,
+ 'record_histories_id': [rh.id
+ for rh in self.record_histories.all()],
+ }
+ else:
+ data = {
+ 'date': self.date.isoformat(),
+ 'value': self.value,
+ 'val_max': self.val_max,
+ 'val_min': self.val_min,
+ 'unit': {},
+ 'notes': self.notes,
+ 'record_histories_id': [rh.id
+ for rh in self.record_histories.all()],
+ }
+ return data
+ # }}}
+
+ def get_data_std(self, **kwargs): # {{{
+ """
+ get the record data in 'standard unit'
+ """
+ # check the indicator.dataType
+ sind = self.indicator
+ if sind.dataType in [sind.FLOAT_TYPE, sind.RANGE_TYPE,
+ sind.FLOAT_RANGE_TYPE]:
+ # check if self.unit standard
+ if self.unit.standard:
+ return self.get_data(**kwargs)
+ # check if specified 'standard unit' for this indicator
+ elif sind.check_unit():
+ # unit relation
+ std_unit = sind.get_unit(type="standard")[0]
+ unit_rel = self.unit.relation
+ v = sympy.symbols('v')
+ rel_sym = sympy.sympify(unit_rel)
+ # error message
+ errmsg = u"'%s' 求值错误,请检查只含有变量 'v'" % unit_rel
+ # self.value
+ if self.value:
+ value = float(self.value)
+ try:
+ value_std = float(rel_sym.evalf(
+ subs={v: value}))
+ except ValueError:
+ print errmsg
+ raise ValueError(errmsg)
+ else:
+ value_std = None
+ # self.val_max
+ if self.val_max:
+ val_max = self.val_max
+ try:
+ val_max_std = float(rel_sym.evalf(
+ subs={v: val_max}))
+ except ValueError:
+ print errmsg
+ raise ValueError(errmsg)
+ else:
+ val_max_std = None
+ # self.val_min
+ if self.val_min:
+ val_min = self.val_min
+ try:
+ val_min_std = float(rel_sym.evalf(
+ subs={v: val_min}))
+ except ValueError:
+ print errmsg
+ raise ValueError(errmsg)
+ else:
+ val_min_std = None
+ # output data
+ data_std = {
+ 'date': self.date.isoformat(),
+ 'value': value_std,
+ 'val_max': val_max_std,
+ 'val_min': val_min_std,
+ 'unit': std_unit.dump(),
+ 'notes': self.notes,
+ 'record_histories_id': [rh.id
+ for rh in self.record_histories.all()],
+ }
+ return data_std
+ else:
+ print u"id=%s Indicator 尚未指定标准单位" % sind.id
+ return {}
+ else:
+ return self.get_data(**kwargs)
+ # }}}
+
+ def is_normal(self, **kwargs): # {{{
+ """
+ compare the given data with the indicator confines.
+
+ if the data within the confines, then return 'True',
+ which suggests the indicator is normal.
+ if the data out of the confines, then return 'False'.
+
+ * return 'None' if there are other problems.
+ """
+ sind = self.indicator
+ # 先检查 Unit 和 InnateConfine 是否已经正确指定
+ if not sind.is_ready():
+ print u"ERROR: Indicator id=%s NOT ready yet" % sind.id
+ return None
+ sic = sind.innate_confine
+ # 获取以标准单位为单位的数据
+ data_std = self.get_data_std()
+ # 根据数据类型判断是否处于正常情况
+ if sind.dataType == sind.INTEGER_TYPE:
+ # 整数型
+ value = int(data_std['value'])
+ val_norm = int(sic.val_norm)
+ # XXX: modify accordingly
+ if value == val_norm:
+ return True
+ else:
+ return False
+ elif sind.dataType == sind.FLOAT_TYPE:
+ # 浮点型
+ value = data_std['value']
+ human_max = sic.human_max
+ human_min = sic.human_min
+ if (value <= human_max) and (value >= human_min):
+ return True
+ else:
+ return False
+ elif sind.dataType == sind.RANGE_TYPE:
+ # 范围型
+ val_max = data_std['val_max']
+ val_min = data_std['val_min']
+ human_max = sic.human_max
+ human_min = sic.human_min
+ if (val_max <= human_max) and (val_min >= human_min):
+ return True
+ else:
+ return False
+ elif sind.dataType == sind.FLOAT_RANGE_TYPE:
+ # 浮点型/范围型
+ if self.value:
+ value = float(data_std['value'])
+ human_max = sic.human_max
+ human_min = sic.human_min
+ if (value <= human_max) and (value >= human_min):
+ return True
+ else:
+ return False
+ elif self.val_max and self.val_min:
+ # 范围值
+ val_max = data_std['val_max']
+ val_min = data_std['val_min']
+ human_max = sic.human_max
+ human_min = sic.human_min
+ if (val_max <= human_max) and (val_min >= human_min):
+ return True
+ else:
+ return False
+ else:
+ print u'数据类型错误'
+ raise ValueError(u'数据类型错误')
+ return None
+ elif sind.dataType == sind.PM_TYPE:
+ # 阴阳(+/-)型
+ value = data_std['value']
+ val_norm = sic.val_norm
+ if value == val_norm:
+ return True
+ else:
+ return False
+ elif sind.dataType in [sind.RADIO_TYPE, sind.CHECKBOX_TYPE]:
+ print u'RADIO_TYPE, CHECKBOX_TYPE 验证未实现'
+ raise ValueError(u'RADIO_TYPE, CHECKBOX_TYPE 验证未实现')
+ return None
+ else:
+ print u'数据类型不合法'
+ raise ValueError(u'数据类型不合法')
+ return None
+ # }}}
+
+ def dump(self, **kwargs):
+ dump_data = {
+ 'id': self.id,
+ 'indicator_id': self.indicator.id,
+ 'user_id': self.user.id,
+ 'created_at': self.created_at.isoformat(),
+ 'updated_at': self.updated_at.isoformat(),
+ 'date': self.date.isoformat(),
+ 'unit_id': self.unit.id,
+ 'value': self.value,
+ 'val_max': self.val_max,
+ 'val_min': self.val_min,
+ 'notes': self.notes,
+ 'record_histories_id': [rh.id
+ for rh in self.record_histories.all()],
+ }
+ return dump_data
+# }}}
+
+
+class RecordHistory(models.Model): # {{{
+ """
+ 指标记录 IndicatorRecord 的历史数据和对应的修改原因
+ """
+ indicatorRecord = models.ForeignKey("IndicatorRecord",
+ verbose_name=u"指标数据记录",
+ related_name="record_histories")
+ # modification datetime
+ created_at = models.DateTimeField(u"创建时间", auto_now_add=True)
+ # modification reason
+ reason = models.TextField(u"修改原因")
+ # original data before modification
+ date_bak = models.DateField(u"原化验日期", blank=True,
+ editable=False)
+ unit_bak = models.ForeignKey("Unit", verbose_name=u"原数据单位",
+ related_name="record_histories",
+ null=True, blank=True, editable=False)
+ value_bak = models.CharField(u"原指标数据值", max_length=30,
+ blank=True, editable=False)
+ val_max_bak = models.FloatField(u"原数据范围上限",
+ null=True, blank=True, editable=False)
+ val_min_bak = models.FloatField(u"原数据范围下限",
+ null=True, blank=True, editable=False)
+ notes_bak = models.TextField(u"原记录说明", blank=True,
+ editable=False)
+
+ class Meta:
+ verbose_name_plural = u"记录修改历史"
+ ordering = ['indicatorRecord__id', 'created_at']
+
+ def __unicode__(self):
+ return u"< RecordHistory: for Record #%s, %s >"\
+ % (self.indicatorRecord.id, self.created_at)
+
+ def save(self, **kwargs):
+ sr = self.indicatorRecord
+ # get history data from *not-saved* IndicatorRecord
+ self.date_bak = sr.date
+ self.unit_bak = sr.unit
+ self.value_bak = sr.value
+ self.val_max_bak = sr.val_max
+ self.val_min_bak = sr.val_min
+ self.notes_bak = sr.notes
+ # save
+ super(RecordHistory, self).save(**kwargs)
+
+ def dump(self, **kwargs):
+ dump_data = {
+ 'id': self.id,
+ 'indicatorRecord_id': self.indicatorRecord.id,
+ 'created_at': self.created_at.isoformat(),
+ 'reason': self.reason,
+ 'date_bak': self.date_bak.isoformat(),
+ 'unit_bak_id': self.unit_bak.id,
+ 'value_bak': self.value_bak,
+ 'val_max_bak': self.val_max_bak,
+ 'val_min_bak': self.val_min_bak,
+ 'notes_bak': self.notes_bak,
+ }
+ return dump_data
+# }}}
+
+
+class Unit(models.Model): # {{{
+ """
+ 指标单位
+ 是否为标准单位,其他单位与标准单位的换算关系
+ """
+ # related to the `indicator'
+ indicator = models.ForeignKey(Indicator, verbose_name=u"指标",
+ related_name="units")
+ name = models.CharField(u"单位名称", max_length=50)
+ symbol = models.CharField(u"单位符号", max_length=50)
+ standard = models.BooleanField(u"是否标准单位", default=False)
+ # conversion relation
+ relation = models.CharField(u"与标准单位的映射",
+ help_text=u"value (std_unit) = f(v)",
+ max_length=100, blank=True)
+ description = models.TextField(u"单位描述", blank=True)
+ # 记录添加的用户,用户只能修改自己添加的对象
+ addByUser = models.ForeignKey(User, verbose_name=u"添加的用户",
+ related_name="units")
+
+ class Meta:
+ verbose_name_plural = u"单位"
+
+ def __unicode__(self):
+ if self.standard:
+ _std = ' (*)'
+ else:
+ _std = ''
+ return u"< Unit: %s%s for %s, addBy %s >" % (self.name,
+ _std, self.indicator.name, self.addByUser.username)
+
+ def is_valid(self):
+ if self.standard:
+ std_unit_list = self.indicator.get_unit(type="standard")
+ if std_unit_list:
+ std_unit = std_unit_list[0]
+ if self.id == std_unit.id:
+ return True
+ else:
+ print u"该指标已经指定了标准单位"
+ raise ValueError(u"该指标已经指定了标准单位")
+ return False
+ else:
+ return True
+ else:
+ if (not self.relation):
+ print u"单位映射关系未填写"
+ raise ValueError(u"单位映射关系未填写")
+ return False
+ else:
+ try:
+ fsym = sympy.sympify(self.relation)
+ return True
+ except SympifyError:
+ print u"'%s' 不是合法的算术表达式" % self.relation
+ raise ValueError(u"'%s' 不是合法的算术表达式"\
+ % self.relation)
+ return False
+
+ def save(self, **kwargs):
+ if self.standard:
+ self.relation = "v"
+ if self.is_valid():
+ super(Unit, self).save(**kwargs)
+
+ def dump(self, **kwargs):
+ dump_data = {
+ 'id': self.id,
+ 'indicator_id': self.indicator.id,
+ 'name': self.name,
+ 'symbol': self.symbol,
+ 'standard': self.standard,
+ 'relation': self.relation,
+ 'addByUser_id': self.addByUser.id,
+ }
+ return dump_data
+# }}}
+
+
+class InnateConfine(models.Model): # {{{
+ """
+ 指标数据范围
+ 数学可能值范围,人体正常值范围
+
+ 注意:
+ 如果数据类型需要单位,则必须使用"标准单位";
+ IndicatorRecord.is_normal() 方法需要如此;
+ 因为 标准单位 到 其他单位 的换算没有实现。
+ """
+ # indicator
+ indicator = models.OneToOneField("Indicator",
+ verbose_name=u"指标", related_name="innate_confine")
+ # unit
+ # TODO: limit_choices_to
+ unit = models.ForeignKey("Unit", related_name="innate_confines",
+ verbose_name=u"单位", null=True, blank=True)
+ # normal value (for INTEGER_TYPE, PM_TYPE)
+ val_norm = models.CharField(u"正常值", max_length=30, blank=True,
+ help_text=u'填写"整数型","阴阳(+/-)型数据"')
+ # normal range
+ human_max = models.FloatField(u"人体正常值上限",
+ null=True, blank=True)
+ human_min = models.FloatField(u"人体正常值下限",
+ null=True, blank=True)
+ # possbile range
+ math_max = models.FloatField(u"数学可能值上限",
+ null=True, blank=True)
+ math_min = models.FloatField(u"数学可能值下限",
+ null=True, blank=True)
+ # description or notes
+ description = models.TextField(u"描述", blank=True)
+ # 记录添加的用户,用户只能修改自己添加的对象
+ addByUser = models.ForeignKey(User, verbose_name=u"添加的用户",
+ related_name="innate_confines")
+
+ class Meta:
+ verbose_name_plural = u"固有数值范围"
+
+ def __unicode__(self):
+ return u"< InnateConfine: for %s, addBy %s >"\
+ % (self.indicator.name, self.addByUser.username)
+
+ def save(self, **kwargs):
+ """
+ check the data before save
+ """
+ if self.is_valid():
+ super(InnateConfine, self).save(**kwargs)
+ else:
+ print u"您填写的数据不符合要求,请检查"
+ return self
+
+ def is_valid(self): # {{{
+ """
+ check the validity of data
+ """
+ sind = self.indicator
+ if sind.dataType in [sind.FLOAT_TYPE, sind.RANGE_TYPE,
+ sind.FLOAT_RANGE_TYPE]:
+ # check unit
+ if not (self.unit and self.unit.standard):
+ raise ValueError(u'单位未填写/不是标准单位')
+ return False
+ if (self.human_max is None) or (self.human_min is None):
+ raise ValueError(u'Error: human_max 或 human_min 未填写')
+ return False
+ if not (self.human_max > self.human_min):
+ raise ValueError(u'Error: human_max <= human_min')
+ return False
+ # check 'math_max' and 'math_min'
+ if (self.math_max is None) or (self.math_min is None):
+ raise ValueError(u'Error: math_max 或 math_min 未填写')
+ return False
+ if not (self.math_max > self.math_min):
+ raise ValueError(u'Error: math_max <= math_min')
+ return False
+ # compare 'human*' and 'math*'
+ if (self.human_max > self.math_max) or (
+ self.human_min < self.math_min):
+ raise ValueError(u'Error: human_max>math_max / human_min<math_min')
+ return False
+ # check finished
+ return True
+ elif sind.dataType == sind.INTEGER_TYPE:
+ # 整数型
+ try:
+ val_norm = int(self.val_norm)
+ return True
+ except ValueError:
+ raise ValueError(u'val_norm="%s" 不是整数型值'
+ % self.val_norm)
+ return False
+ elif sind.dataType == sind.PM_TYPE:
+ # 阴阳(+/-)型
+ if (len(self.val_norm) == 1) and (
+ self.val_norm in [u'+', u'-']):
+ return True
+ else:
+ raise ValueError(u'value 只接受 "+" 或 "-"')
+ return False
+ ## TODO: RADIO_TYPE, CHECKBOX_TYPE
+ elif sind.dataType in [sind.RADIO_TYPE, sind.CHECKBOX_TYPE]:
+ raise ValueError(u'RADIO_TYPE, CHECKBOX_TYPE 验证未实现')
+ return False
+ else:
+ raise ValueError(u'数据不符合要求')
+ return False
+ # }}}
+
+ def dump(self, **kwargs):
+ dump_data = {
+ 'id': self.id,
+ 'indicator_id': self.indicator.id,
+ 'unit': self.unit.dump(),
+ 'val_norm': self.val_norm,
+ 'human_max': self.human_max,
+ 'human_min': self.human_min,
+ 'math_max': self.math_max,
+ 'math_min': self.math_min,
+ 'addByUser_id': self.addByUser.id,
+ }
+ return dump_data
+# }}}
+
+
+class StatisticalConfine(models.Model): # {{{
+
+ deviation_ceiling = models.FloatField(u"统计偏差范围上限", null=True, blank=True)
+ deviation_floor = models.FloatField(u"统计偏差范围限", null=True, blank=True)
+
+ class Meta:
+ verbose_name_plural = u"统计数值范围"
+
+ def __unicode__(self):
+ return "< StatisticalConfine: %s >" % self.id
+# }}}
+
+
+#class UnitLabSheet(models.Model): # {{{
+#
+# equipment = models.CharField(u"化验设备", max_length=100, null=True, blank= True)
+# #figure = models.OneToOneField("figure.Figure", verbose_name=u"图片", related_name="unitlabsheet")
+# unit_standard = models.ForeignKey("UnitStandard", verbose_name = u"单位标准", related_name="unit_lab_sheets", null=True, blank=True)
+#
+# class Meta:
+# verbose_name_plural = u"标准化验单"
+#
+# def __unicode__(self):
+# return "< UnitLabSheet: %s >" % self.id
+## }}}
+
+
+#class ReviseReason(models.Model): # {{{
+# """
+# 记录 IndicatorRecord 数据修改原因
+# 医学数据重要且要求准确,不可随意修改
+# ReviseReason 添加后不允许再修改?
+# """
+# # TODO: 中文支持
+# content = models.TextField(u"内容")
+# created_at = models.DateTimeField(u"创建时间",
+# editable=False, auto_now_add=True)
+# user = models.ForeignKey(User, verbose_name=u"用户")
+#
+# class Meta:
+# verbose_name_plural=u"指标记录修改原因"
+#
+# def __unicode__(self):
+# return u"< ReviseReason: %s, %s >" % (self.id,
+# self.user.username)
+#
+# def dump(self, **kwargs):
+# dump_data = {
+# 'id': self.id,
+# 'content': self.content,
+# 'created_at': self.created_at.isoformat(),
+# 'user_id': self.user.id,
+# }
+# return dump_data
+## }}}
+
+
+class RelatedIndicator(models.Model): # {{{
+ """
+ 记录 blog/annotation 与哪些 indicator 关联,
+ 以及关联的权重。
+
+ 用于为用户推荐可以关注的指标。
+ """
+ # indicator
+ indicator = models.ForeignKey("Indicator",
+ related_name="related_indicators",
+ verbose_name=u"待关联指标")
+ # type of related object
+ ANNOTATION_TYPE = 'AN'
+ BLOG_TYPE = 'BL'
+ OBJECT_TYPES = (
+ (ANNOTATION_TYPE, '文章注释'),
+ (BLOG_TYPE, '文章'),
+ )
+ objectType = models.CharField(u"待关联目标类型", max_length=2,
+ choices=OBJECT_TYPES)
+ # objects
+ annotation = models.ForeignKey("sciblog.BlogAnnotation",
+ related_name="related_indicators",
+ verbose_name=u"待关联文章注释", null=True, blank=True)
+ blog = models.ForeignKey("sciblog.SciBlog",
+ related_name="related_indicators",
+ verbose_name=u"待关联文章", null=True, blank=True)
+ # weight
+ weight = models.FloatField(u"权重", help_text=u"范围: 0-10")
+ # datetime
+ created_at = models.DateTimeField(u"创建时间", auto_now_add=True)
+ updated_at = models.DateTimeField(u"更新时间",
+ auto_now_add=True, auto_now=True)
+
+ class Meta:
+ verbose_name_plural = u"指标关联信息"
+ ordering = ['objectType']
+
+ def __unicode__(self):
+ if self.objectType == self.ANNOTATION_TYPE:
+ info = 'Annotation #%s' % self.annotation.id
+ elif self.objectType == self.BLOG_TYPE:
+ info = 'Blog #%s' % self.blog.id
+ else:
+ info = '%s' % self.objectType
+ return u"< RelatedIndicator: %s -> %s >"\
+ % (info, self.indicator.name)
+
+ def save(self, **kwargs):
+ if self.is_valid():
+ if self.objectType == self.ANNOTATION_TYPE:
+ self.blog = None
+ if self.objectType == self.BLOG_TYPE:
+ self.annotation = None
+ # save
+ super(RelatedIndicator, self).save(**kwargs)
+ else:
+ return self
+
+ def is_valid(self, **kwargs): # {{{
+ """
+ annotation/blog must be consistent with objectType
+ """
+ # check objectType
+ if self.objectType == self.ANNOTATION_TYPE:
+ if not self.annotation:
+ raise ValueError(u"Error: annotation 未填写")
+ return False
+ elif self.objectType == self.BLOG_TYPE:
+ if not self.blog:
+ raise ValueError(u"Error: blog 未填写")
+ return False
+ else:
+ raise ValueError(u"Error: objectType 不合法")
+ return False
+ # check weight range
+ if (self.weight < 0.0) or (self.weight > 10.0):
+ raise ValueError(u"Error: weight < 0.0 / weight > 10.0")
+ return False
+ # finished
+ return True
+ # }}}
+
+ def dump(self, **kwargs):
+ # annotation_id
+ if self.annotation:
+ annotation_id = self.annotation.id
+ else:
+ annotation_id = None
+ # blog_id
+ if self.blog:
+ blog_id = self.blog.id
+ else:
+ blog_id = None
+ # dump data
+ dump_data = {
+ 'id': self.id,
+ 'indicator_id': self.indicator.id,
+ 'objectType': self.objectType,
+ 'annotation_id': annotation_id,
+ 'blog_id': blog_id,
+ 'weight': self.weight,
+ 'created_at': self.created_at.isoformat(),
+ 'updated_at': self.updated_at.isoformat(),
+ }
+ return dump_data
+# }}}
+
+
+
+admin.site.register([
+ IndicatorCategory,
+ Indicator,
+ UserIndicator,
+ IndicatorRecord,
+ RecordHistory,
+ Unit,
+ InnateConfine,
+ StatisticalConfine,
+ #UnitLabSheet,
+ #ReviseReason,
+ RelatedIndicator,
+ ])
+
+# vim: set ts=4 sw=4 tw=0 fenc=utf-8 ft=python: #