from __future__ import annotations import sys from pathlib import Path from sqlalchemy import select sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from app.db.base import Base from app.db.session import SessionLocal, engine from app.models import ( CaseBase, CaseExamItem, Department, KnowledgeChunk, KnowledgeDocument, KnowledgeSource, PromptTemplate, ScoringRule, TeachingCase, TraditionalCase, ) def init_database() -> None: """数据库初始化:创建当前新表体系并写入第一版 Demo 种子数据。""" Base.metadata.create_all(bind=engine) with SessionLocal() as db: seed_demo_data(db) db.commit() def seed_demo_data(db) -> None: """Demo 数据初始化:仅为本地/测试库写入儿科病例和基础训练数据。""" department = _get_or_create_department(db) case = _get_or_create_case_base(db, department.id) _seed_traditional_case(db, case.id) _seed_teaching_case(db, case.id) _seed_exam_items(db, case.id) _seed_scoring_rules(db, case.id) _seed_knowledge(db, department.id) _seed_prompts(db) def _get_or_create_department(db) -> Department: """科室种子:写入儿科科室。""" department = db.scalar(select(Department).where(Department.name == "儿科")) if department: return department department = Department(name="儿科", category="clinical", institution_id=1) db.add(department) db.flush() return department def _get_or_create_case_base(db, department_id: int) -> CaseBase: """病例主表种子:以 case_base 作为病例唯一主表。""" case = db.scalar(select(CaseBase).where(CaseBase.title == "支气管肺炎 - 6岁男性患儿")) if case: return case case = CaseBase( title="支气管肺炎 - 6岁男性患儿", case_type="diagnosis_treatment", difficulty="medium", difficulty_score=2, chief_complaint="发热、咳嗽4天,喘息1天", description=( "患儿4天前无明显诱因出现发热,最高体温39.2℃,伴阵发性咳嗽,后有少量白色黏痰。" "1天前出现喘息,夜间明显,活动后加重。精神较差,食欲下降,小便略少。" ), patient_age=6, patient_gender="male", tags="pediatrics,pneumonia,demo", symptom_tags=["发热", "咳嗽", "喘息", "精神食纳差"], disease_tags=["支气管肺炎"], competency_tags=["问诊完整性", "儿科查体规范", "关键症状识别", "诊断准确性", "治疗计划合理性"], guideline_tags=["CAP_2019", "HUMANISTIC_CARE"], knowledge_points=["血常规", "CRP", "胸片", "血氧饱和度", "肺部湿啰音"], icd_codes="", estimated_minutes=20, osce_enabled=False, rag_enabled=True, ai_prompt_template="app/prompts/patient/practice.md", multimodal_assets=[], vector_status=0, publish_status=1, status=1, created_by_id=None, department_id=department_id, ) db.add(case) db.flush() return case def _seed_traditional_case(db, case_id: int) -> None: """传统病例种子:练习模式读取 case_base + traditional_case。""" if db.scalar(select(TraditionalCase).where(TraditionalCase.case_id == case_id)): return db.add( TraditionalCase( case_id=case_id, standard_diagnosis="支气管肺炎", standard_treatment=( "抗感染、止咳平喘、改善氧合、严密观察病情变化;必要时雾化吸入缓解喘息," "监测体温、呼吸、血氧、精神反应和饮水尿量,出现低氧或呼吸困难加重时及时升级处理。" ), guideline_reference=( "诊断依据:发热、咳嗽、喘息,肺部湿啰音,炎症指标升高,胸片提示右下肺片状模糊影," "符合儿童社区获得性肺炎/支气管肺炎诊断思路。严重程度需结合呼吸频率、SpO2、意识、循环和进食饮水情况。" ), ) ) def _seed_teaching_case(db, case_id: int) -> None: """教学互动病例种子:教学互动模式读取 case_base + teaching_case。""" if db.scalar(select(TeachingCase).where(TeachingCase.case_id == case_id)): return db.add( TeachingCase( case_id=case_id, teaching_goal="围绕儿科肺炎问诊、检查选择、诊断依据、治疗决策和医患沟通完成互动训练。", discussion_questions="如何判断病情严重程度?哪些检查是关键检查?治疗方案如何兼顾抗感染、平喘和氧合监测?", teacher_guide="观察学生是否完整追问发热、咳嗽、喘息、既往史、接触史,并能解释胸片、炎症指标和血氧。", scoring_focus="问诊完整性、检查合理性、诊断准确性、治疗计划、风险预案、人文沟通。", ) ) def _seed_exam_items(db, case_id: int) -> None: """检查项目种子:写入病例可申请检查和固定返回结果。""" if db.scalar(select(CaseExamItem).where(CaseExamItem.case_id == case_id)): return items = [ ("blood_routine", "血常规", "lab", "WBC 12.5×10^9/L,中性粒细胞比例72%,提示感染及炎症反应。", {"wbc": "12.5×10^9/L", "neutrophil": "72%"}, True, True, 1), ("crp", "CRP", "lab", "CRP 28 mg/L,提示炎症反应升高。", {"crp": "28 mg/L"}, True, True, 2), ("chest_xray", "胸片", "imaging", "双下肺纹理增多,右下肺片状模糊影,支持肺部感染。", {"finding": "右下肺片状模糊影"}, True, True, 3), ("spo2", "血氧饱和度", "vital_sign", "室内空气 SpO2 94%,处于临界偏低范围。", {"spo2": "94%"}, True, True, 4), ("mp_igm", "肺炎支原体IgM", "lab", "肺炎支原体IgM阴性。", {"mp_igm": "negative"}, False, False, 5), ] for code, name, item_type, result_text, structured, is_key, abnormal, order in items: db.add( CaseExamItem( case_id=case_id, item_code=code, item_name=name, item_type=item_type, category=item_type, result_text=result_text, result_structured=structured, is_key=is_key, is_abnormal=abnormal, score_weight=5.0 if is_key else 1.0, display_order=order, ) ) def _seed_scoring_rules(db, case_id: int) -> None: """评分规则种子:写入 scoring_rule,评价时作为基础评分细则。""" if db.scalar(select(ScoringRule).where(ScoringRule.case_id == case_id)): return rules = [ ("信息获取", "问诊完整性", 25, "覆盖现病史、既往史、个人史、家族史、儿科特异性症状与家属担忧。"), ("分析推理", "诊断与鉴别诊断", 25, "结合症状、体征、胸片、炎症指标和血氧支持支气管肺炎诊断,并列出合理鉴别诊断。"), ("处置决策", "检查与治疗方案", 20, "检查申请合理,治疗原则覆盖抗感染、止咳平喘、改善氧合、风险预案和随访。"), ("沟通人文", "家属沟通", 15, "向家属说明病情、用药注意事项、危险信号、复诊或住院指征,并回应焦虑。"), ("临床整合", "流程与整体思维", 15, "流程连贯,把问诊、检查、诊断、治疗和沟通整合成完整临床决策。"), ] for dimension, competency, weight, standard in rules: db.add( ScoringRule( case_id=case_id, dimension=dimension, competency_dimension=competency, score_weight=weight, ai_auto_score=True, osce_dimension=False, scoring_standard=standard, rubric_json={"max_score": weight, "criteria": standard}, ) ) def _seed_knowledge(db, department_id: int) -> None: """知识库种子:写入评分参考指南和人文沟通片段。""" if db.scalar(select(KnowledgeSource).where(KnowledgeSource.source_code == "CAP_2019")): return source = KnowledgeSource( source_code="CAP_2019", source_name="儿童社区获得性肺炎诊疗规范(2019年版)", source_type="clinical_guideline", authority_level=5, is_active=True, ) human = KnowledgeSource( source_code="HUMANISTIC_CARE", source_name="问诊沟通与人文关怀要求", source_type="humanistic_care", authority_level=4, is_active=True, ) db.add_all([source, human]) db.flush() doc = KnowledgeDocument( source_id=source.id, department_id=department_id, title="儿童社区获得性肺炎诊疗规范摘要", task_type="diagnosis_treatment", summary="用于肺炎病例诊断、严重程度评估和治疗评分参考。", file_path="docs/knowledge/cap_2019.md", is_active=True, ) human_doc = KnowledgeDocument( source_id=human.id, department_id=department_id, title="儿科问诊人文关怀要点", task_type="diagnosis_treatment", summary="用于评价家属沟通、知情告知和健康教育。", file_path="docs/knowledge/humanistic_care.md", is_active=True, ) db.add_all([doc, human_doc]) db.flush() db.add_all( [ KnowledgeChunk( document_id=doc.id, department_id=department_id, task_type="diagnosis_treatment", chunk_text="儿童肺炎诊断需综合发热、咳嗽、喘息、肺部湿啰音、炎症指标和胸部影像学新发浸润影。", keywords=["发热", "咳嗽", "喘息", "胸片异常"], weight=5.0, is_active=True, ), KnowledgeChunk( document_id=doc.id, department_id=department_id, task_type="diagnosis_treatment", chunk_text="严重程度评估应关注呼吸频率、血氧饱和度、意识状态、循环状态和进食饮水情况。", keywords=["血氧饱和度下降", "呼吸", "严重程度"], weight=4.5, is_active=True, ), KnowledgeChunk( document_id=human_doc.id, department_id=department_id, task_type="diagnosis_treatment", chunk_text="儿科问诊需要向家属说明病情观察指标、用药注意事项、复诊指征,并给予情绪安抚。", keywords=["沟通", "健康教育", "家属"], weight=4.0, is_active=True, ), ] ) def _seed_prompts(db) -> None: """提示词种子:写入 Markdown 模板元数据,正文保存在 prompts 目录。""" templates = [ ("patient_practice", "patient", "practice", "v1", "fast", "text", "app/prompts/patient/practice.md"), ("patient_teaching", "patient", "teaching", "v1", "fast", "text", "app/prompts/patient/teaching.md"), ("novice_case_hint", "hint", "novice", "v1", "fast", "json", "app/prompts/hint/novice_case_hint.md"), ("scoring_pediatrics_pneumonia", "scoring", "pediatrics_pneumonia", "v1", "fast", "json", "app/prompts/scoring/pediatrics_pneumonia.md"), ("report_evaluation", "report", "evaluation", "v1", "fast", "json", "app/prompts/report/evaluation_report.md"), ] for code, agent_type, scene, version, model_type, output_format, file_path in templates: template = db.scalar(select(PromptTemplate).where(PromptTemplate.template_code == code, PromptTemplate.version_no == version)) if not template: template = PromptTemplate(template_code=code, version_no=version) template.agent_type = agent_type template.scene = scene template.model_type = model_type template.output_format = output_format template.file_path = file_path template.is_active = True db.add(template) def ensure_storage_dirs() -> None: """目录初始化:创建报告导出目录。""" Path("storage/reports").mkdir(parents=True, exist_ok=True) if __name__ == "__main__": ensure_storage_dirs() init_database() print("Demo database initialized.")