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}"