from __future__ import annotations import json import sys from dataclasses import dataclass from pathlib import Path from typing import Any from sqlalchemy import inspect from sqlalchemy.exc import SQLAlchemyError sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from app.db.session import engine @dataclass(frozen=True) class TableSpec: """最终表结构规格:定义当前医疗问诊 Agent 完整功能所需的表、字段和关键索引。""" columns: tuple[str, ...] indexed_columns: tuple[str, ...] = () REQUIRED_SCHEMA: dict[str, TableSpec] = { "user": TableSpec( columns=( "id", "username", "real_name", "phone", "role_type", "department_id", "institution_id", "competency_profile", "weak_dimensions", "strong_dimensions", "ai_preference", "total_training_count", "total_case_count", "current_level", "status", ), indexed_columns=("id", "username", "phone", "department_id", "institution_id"), ), "department": TableSpec( columns=("id", "name", "category", "institution_id", "created_at", "updated_at"), indexed_columns=("id", "institution_id"), ), "case_base": TableSpec( columns=( "id", "title", "case_type", "difficulty", "chief_complaint", "description", "patient_age", "patient_gender", "publish_status", "status", "department_id", ), indexed_columns=("id", "case_type", "difficulty", "publish_status", "status", "department_id"), ), "traditional_case": TableSpec( columns=("id", "case_id", "standard_diagnosis", "standard_treatment", "guideline_reference"), indexed_columns=("id", "case_id"), ), "teaching_case": TableSpec( columns=("id", "case_id", "teaching_goal", "discussion_questions", "teacher_guide", "scoring_focus"), indexed_columns=("id", "case_id"), ), "scoring_rule": TableSpec( columns=("id", "case_id", "dimension", "competency_dimension", "score_weight", "scoring_standard", "rubric_json"), indexed_columns=("id", "case_id", "dimension", "competency_dimension"), ), "case_exam_item": TableSpec( columns=("id", "case_id", "item_code", "item_name", "item_type", "result_text", "is_key", "is_abnormal"), indexed_columns=("id", "case_id", "item_code", "item_type"), ), "training_session": TableSpec( columns=( "id", "session_code", "external_user_id", "case_id", "case_type", "training_mode", "score_type", "status", "memory_key", ), indexed_columns=("id", "session_code", "external_user_id", "case_id", "training_mode", "status"), ), "training_order": TableSpec( columns=("id", "session_id", "external_user_id", "case_id", "exam_item_id", "item_code", "result_text", "ordered_at"), indexed_columns=("id", "session_id", "external_user_id", "case_id"), ), "training_submission": TableSpec( columns=("id", "session_id", "external_user_id", "primary_diagnosis", "treatment_measures"), indexed_columns=("id", "external_user_id"), ), "training_record": TableSpec( columns=( "id", "user_id", "external_user_id", "session_id", "case_id", "total_score", "ai_feedback_structured", "pdf_file_path", ), indexed_columns=("id", "user_id", "external_user_id", "session_id", "case_id"), ), "training_score_detail": TableSpec( columns=( "id", "record_id", "rule_id", "dimension", "score", "deducted_reason", "evidence_message_ids", "ai_confidence", "comment", "created_at", "updated_at", ), indexed_columns=("id", "record_id", "rule_id", "dimension"), ), "prompt_templates": TableSpec( columns=("id", "template_code", "agent_type", "scene", "version_no", "model_type", "output_format", "file_path", "is_active"), indexed_columns=("id", "template_code", "agent_type", "scene"), ), "knowledge_sources": TableSpec( columns=("id", "source_code", "source_name", "source_type", "authority_level", "is_active"), indexed_columns=("id", "source_code", "source_type"), ), "knowledge_documents": TableSpec( columns=("id", "source_id", "department_id", "title", "task_type", "file_path", "is_active"), indexed_columns=("id", "source_id", "department_id", "task_type"), ), "knowledge_chunks": TableSpec( columns=("id", "document_id", "department_id", "task_type", "chunk_text", "keywords", "weight", "is_active"), indexed_columns=("id", "document_id", "department_id", "task_type"), ), "audit_logs": TableSpec( columns=("id", "user_id", "tenant_id", "session_id", "action", "resource_type", "request_id", "created_at"), indexed_columns=("id", "user_id", "session_id", "action", "created_at"), ), } def main() -> None: """最终结构检查:只读校验当前 Agent 完整功能所需数据库结构。""" try: report = build_schema_report() except SQLAlchemyError as exc: print( json.dumps( { "database_dialect": engine.dialect.name, "summary": { "can_run_demo": False, "database_available": False, "error": "database connection failed", "detail": str(exc).splitlines()[0], }, }, ensure_ascii=False, indent=2, ) ) raise SystemExit(2) from exc print(json.dumps(report, ensure_ascii=False, indent=2)) if not report["summary"]["can_run_demo"]: raise SystemExit(1) def build_schema_report() -> dict[str, Any]: """结构报告:检查必需表、字段和关键索引,不修改数据库。""" inspector = inspect(engine) existing_tables = set(inspector.get_table_names()) tables: dict[str, Any] = {} missing_tables: list[str] = [] missing_columns: list[str] = [] missing_indexes: list[str] = [] for table_name, spec in REQUIRED_SCHEMA.items(): if table_name not in existing_tables: missing_tables.append(table_name) tables[table_name] = {"exists": False} continue actual_columns = {column["name"] for column in inspector.get_columns(table_name)} actual_indexes = _indexed_columns(inspector, table_name) table_missing_columns = [name for name in spec.columns if name not in actual_columns] table_missing_indexes = [name for name in spec.indexed_columns if name not in actual_indexes] missing_columns.extend(f"{table_name}.{column_name}" for column_name in table_missing_columns) missing_indexes.extend(f"{table_name}.{column_name}" for column_name in table_missing_indexes) tables[table_name] = { "exists": True, "missing_columns": table_missing_columns, "missing_indexes": table_missing_indexes, } can_run_demo = not missing_tables and not missing_columns return { "database_dialect": engine.dialect.name, "identity_rule": "Authorization -> Django /api/user/users/me/ -> data.id -> user isolation fields", "tables": tables, "summary": { "checked_tables": len(REQUIRED_SCHEMA), "missing_tables": missing_tables, "missing_columns": missing_columns, "missing_indexes": missing_indexes, "index_warning": bool(missing_indexes), "can_run_demo": can_run_demo, }, } def _indexed_columns(inspector, table_name: str) -> set[str]: """索引读取:汇总普通索引、唯一索引和主键覆盖的字段。""" indexed: set[str] = set() primary_key = inspector.get_pk_constraint(table_name) or {} indexed.update(primary_key.get("constrained_columns") or []) for index in inspector.get_indexes(table_name): indexed.update(index.get("column_names") or []) for unique in inspector.get_unique_constraints(table_name): indexed.update(unique.get("column_names") or []) return indexed if __name__ == "__main__": main()