Files
medical_training/apps/case/models.py
T
2026-06-12 17:19:23 +08:00

225 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.db import models
from apps.common.models import BaseModel, SoftDeleteModel
from apps.user.models import User
class CaseBase(SoftDeleteModel):
"""病例主表(软删除:`is_deleted=1` 即「已下架」;`objects` 默认过滤已删)。"""
CASE_TYPE_CHOICES = [
('traditional', '传统病例'),
('script', '剧本病例'),
('teaching', '教学互动病例'),
('osce', 'OSCE'),
]
STATUS_CHOICES = [
(0, '禁用'),
(1, '正常'),
]
# 病例状态机:草稿 →[提交]→ 正常 →[医院管理员发布]→ 已发布;下架 = 软删除(is_deleted=1
PUBLISH_STATUS_CHOICES = [
(0, '草稿'),
(1, '正常'),
(2, '已发布'),
]
id = models.BigAutoField(primary_key=True)
title = models.CharField('病例标题', max_length=255)
case_type = models.CharField('病例类型', max_length=30, choices=CASE_TYPE_CHOICES)
difficulty = models.CharField('难度', max_length=20, blank=True)
difficulty_score = models.IntegerField('AI难度评分', null=True, blank=True)
institution = models.ForeignKey(
'user.Institution', on_delete=models.SET_NULL,
null=True, blank=True, related_name='cases', verbose_name='所属机构'
)
department = models.ForeignKey(
'user.Department', on_delete=models.SET_NULL,
null=True, blank=True, verbose_name='所属科室'
)
chief_complaint = models.TextField('主诉', blank=True)
description = models.TextField('病例简介', blank=True)
patient_age = models.IntegerField('患者年龄', null=True, blank=True)
patient_gender = models.CharField('患者性别', max_length=10, blank=True)
tags = models.CharField('标签', max_length=500, blank=True)
symptom_tags = models.JSONField('症状标签', default=list, blank=True)
disease_tags = models.JSONField('疾病标签', default=list, blank=True)
competency_tags = models.JSONField('能力标签', default=list, blank=True)
guideline_tags = models.JSONField('指南标签', default=list, blank=True)
knowledge_points = models.JSONField('知识点', default=list, blank=True)
icd_codes = models.CharField('ICD编码', max_length=500, blank=True)
estimated_minutes = models.IntegerField('预计训练时长', null=True, blank=True)
osce_enabled = models.BooleanField('是否OSCE', default=False)
rag_enabled = models.BooleanField('是否启用知识增强', default=False)
ai_prompt_template = models.TextField('AI角色Prompt', blank=True)
multimodal_assets = models.JSONField('图片/影像/附件', default=dict, blank=True)
vector_status = models.SmallIntegerField('是否向量化', default=0)
created_by = models.ForeignKey(
User, on_delete=models.SET_NULL,
null=True, blank=True, verbose_name='创建人'
)
publish_status = models.SmallIntegerField('发布状态', choices=PUBLISH_STATUS_CHOICES, default=0)
status = models.SmallIntegerField('状态', choices=STATUS_CHOICES, default=1)
class Meta:
db_table = 'case_base'
verbose_name = '病例'
verbose_name_plural = '病例'
def __str__(self):
return self.title
class TraditionalCase(BaseModel):
"""传统病例表"""
id = models.BigAutoField(primary_key=True)
case = models.OneToOneField(
CaseBase, on_delete=models.CASCADE,
verbose_name='病例'
)
standard_diagnosis = models.TextField('标准诊断', blank=True)
standard_treatment = models.TextField('标准治疗', blank=True)
guideline_reference = models.TextField('指南参考', blank=True)
class Meta:
db_table = 'traditional_case'
verbose_name = '传统病例'
verbose_name_plural = '传统病例'
def __str__(self):
return f"传统病例: {self.case.title}"
class ScriptCase(BaseModel):
"""剧本病例表"""
id = models.BigAutoField(primary_key=True)
case = models.OneToOneField(
CaseBase, on_delete=models.CASCADE,
verbose_name='病例'
)
scenario_setting = models.TextField('场景设定', blank=True)
emotional_state = models.CharField('情绪状态', max_length=50, blank=True)
cultural_level = models.CharField('文化水平', max_length=50, blank=True)
branch_logic = models.TextField('分支逻辑', blank=True)
hidden_clues = models.TextField('隐藏线索', blank=True)
class Meta:
db_table = 'script_case'
verbose_name = '剧本病例'
verbose_name_plural = '剧本病例'
def __str__(self):
return f"剧本病例: {self.case.title}"
class TeachingCase(BaseModel):
"""教学互动病例表"""
id = models.BigAutoField(primary_key=True)
case = models.OneToOneField(
CaseBase, on_delete=models.CASCADE,
verbose_name='病例'
)
teaching_goal = models.TextField('教学目标', blank=True)
discussion_questions = models.TextField('讨论问题', blank=True)
teacher_guide = models.TextField('教师指南', blank=True)
scoring_focus = models.TextField('评分重点', blank=True)
class Meta:
db_table = 'teaching_case'
verbose_name = '教学互动病例'
verbose_name_plural = '教学互动病例'
def __str__(self):
return f"教学病例: {self.case.title}"
class CaseStage(BaseModel):
"""病例阶段表"""
STAGE_MODE_CHOICES = [
('dialogue', '对话'),
('osce', 'OSCE'),
('choice', '选择'),
]
id = models.BigAutoField(primary_key=True)
case = models.ForeignKey(
CaseBase, on_delete=models.CASCADE,
related_name='stages', verbose_name='病例'
)
stage_type = models.CharField('阶段类型', max_length=50, blank=True)
stage_name = models.CharField('阶段名称', max_length=100)
stage_mode = models.CharField('阶段模式', max_length=30, choices=STAGE_MODE_CHOICES, default='dialogue')
stage_goal = models.TextField('阶段目标', blank=True)
ai_role_prompt = models.TextField('AI阶段Prompt', blank=True)
standard_action = models.TextField('标准动作', blank=True)
expected_questions = models.TextField('期望问题', blank=True)
scoring_points = models.TextField('评分点', blank=True)
timeout_seconds = models.IntegerField('超时时间', null=True, blank=True)
unlock_condition = models.CharField('解锁条件', max_length=255, blank=True)
sort_order = models.IntegerField('排序', default=0)
class Meta:
db_table = 'case_stage'
verbose_name = '病例阶段'
verbose_name_plural = '病例阶段'
ordering = ['sort_order', 'id']
def __str__(self):
return f"{self.case.title} - {self.stage_name}"
class ScoringRule(BaseModel):
"""评分规则表"""
id = models.BigAutoField(primary_key=True)
case = models.ForeignKey(
CaseBase, on_delete=models.CASCADE,
related_name='scoring_rules', verbose_name='病例'
)
dimension = models.CharField('评分维度', max_length=50)
competency_dimension = models.CharField('能力维度', max_length=50, blank=True)
score_weight = models.DecimalField('权重', max_digits=5, decimal_places=2, default=1.00)
ai_auto_score = models.BooleanField('AI自动评分', default=False)
osce_dimension = models.BooleanField('是否OSCE', default=False)
scoring_standard = models.TextField('评分标准', blank=True)
rubric_json = models.JSONField('评分Rubric', default=dict, blank=True)
class Meta:
db_table = 'scoring_rule'
verbose_name = '评分规则'
verbose_name_plural = '评分规则'
def __str__(self):
return f"{self.case.title} - {self.dimension}"
class CaseExamItem(BaseModel):
"""病例检查/检验项目表(与 consultation 库 case_exam_item 同构)"""
case = models.ForeignKey(
CaseBase, on_delete=models.CASCADE,
related_name='exam_items', db_column='case_id',
verbose_name='病例',
)
item_code = models.CharField('检查项目编码', max_length=64)
item_name = models.CharField('检查项目名称', max_length=128)
item_type = models.CharField('项目类型', max_length=32)
category = models.CharField('项目分类', max_length=64, blank=True, default='')
result_text = models.TextField('结果文本')
result_structured = models.JSONField('结构化结果', null=True, blank=True, default=None)
is_key = models.BooleanField('是否关键检查', default=False)
is_abnormal = models.BooleanField('是否异常', default=False)
score_weight = models.DecimalField('评分权重', max_digits=5, decimal_places=2, default=1.00)
display_order = models.IntegerField('展示顺序', default=0)
class Meta:
db_table = 'case_exam_item'
verbose_name = '病例检查项'
verbose_name_plural = '病例检查项'
ordering = ['display_order', 'id']
constraints = [
models.UniqueConstraint(
fields=['case', 'item_code'],
name='uk_case_exam_item_code',
),
]
def __str__(self):
return f"{self.case_id}:{self.item_code}"