finalize medical consultation agent backend

This commit is contained in:
刘金宝
2026-06-03 15:51:46 +08:00
parent 93d9e1c6a5
commit eb43573a44
33 changed files with 1063 additions and 281 deletions
+3 -3
View File
@@ -6,8 +6,8 @@ from app.models.knowledge import KnowledgeChunk, KnowledgeDocument, KnowledgeSou
from app.models.prompt import PromptTemplate
from app.models.source_case import CaseBase, CaseExamItem, ScoringRule, TeachingCase, TraditionalCase
from app.models.training import SessionOrder, SessionSubmission, TrainingSession
from app.models.training_record import TrainingRecord
from app.models.user import User, UserLearningProfile
from app.models.training_record import TrainingRecord, TrainingScoreDetail
from app.models.user import User
__all__ = [
"AuditLog",
@@ -25,6 +25,6 @@ __all__ = [
"SessionSubmission",
"TrainingSession",
"TrainingRecord",
"TrainingScoreDetail",
"User",
"UserLearningProfile",
]
+1 -1
View File
@@ -12,7 +12,7 @@ class AuditLog(Base):
__tablename__ = "audit_logs"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
user_id: Mapped[str | None] = mapped_column(String(128), index=True)
user_id: Mapped[str | None] = mapped_column(String(128), index=True, comment="Django用户中心ID")
tenant_id: Mapped[str | None] = mapped_column(String(128))
session_id: Mapped[int | None] = mapped_column(Integer, index=True)
action: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
+11 -11
View File
@@ -1,22 +1,22 @@
from __future__ import annotations
from sqlalchemy import Boolean, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy import BigInteger, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base
from app.models.mixins import TimestampMixin
BIGINT_PK = BigInteger().with_variant(Integer, "sqlite")
class Department(TimestampMixin, Base):
"""科室模型:维护病例、知识库和评分规则的科室分类"""
"""科室模型:使用用户端确定的 department 表字段"""
__tablename__ = "departments"
__tablename__ = "department"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(100), nullable=False)
code: Mapped[str] = mapped_column(String(50), nullable=False, unique=True, index=True)
parent_id: Mapped[int | None] = mapped_column(ForeignKey("departments.id"), nullable=True)
sort_order: Mapped[int] = mapped_column(Integer, default=0)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
id: Mapped[int] = mapped_column(BIGINT_PK, primary_key=True, autoincrement=True, comment="科室ID")
name: Mapped[str] = mapped_column(String(100), nullable=False, comment="科室名称")
category: Mapped[str] = mapped_column(String(50), nullable=False, comment="科室分类")
institution_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True, comment="所属机构ID")
parent: Mapped["Department | None"] = relationship(remote_side=[id])
__table_args__ = {"comment": "科室表"}
+2 -2
View File
@@ -29,7 +29,7 @@ class KnowledgeDocument(TimestampMixin, Base):
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
source_id: Mapped[int] = mapped_column(ForeignKey("knowledge_sources.id"), nullable=False, index=True)
department_id: Mapped[int | None] = mapped_column(ForeignKey("departments.id"), nullable=True, index=True)
department_id: Mapped[int | None] = mapped_column(ForeignKey("department.id"), nullable=True, index=True)
title: Mapped[str] = mapped_column(String(255), nullable=False)
task_type: Mapped[str | None] = mapped_column(String(64), index=True)
summary: Mapped[str | None] = mapped_column(Text)
@@ -46,7 +46,7 @@ class KnowledgeChunk(Base):
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
document_id: Mapped[int] = mapped_column(ForeignKey("knowledge_documents.id"), nullable=False, index=True)
department_id: Mapped[int | None] = mapped_column(ForeignKey("departments.id"), nullable=True, index=True)
department_id: Mapped[int | None] = mapped_column(ForeignKey("department.id"), nullable=True, index=True)
task_type: Mapped[str | None] = mapped_column(String(64), index=True)
chunk_text: Mapped[str] = mapped_column(Text, nullable=False)
keywords: Mapped[list | None] = mapped_column(JSON)
+4 -4
View File
@@ -10,13 +10,13 @@ BIGINT_PK = BigInteger().with_variant(Integer, "sqlite")
class TrainingSession(TimestampMixin, Base):
"""训练会话表:保存一次训练的运行状态、用户隔离信息和短期 memory key。"""
"""训练会话表:保存一次训练的运行状态、Django 用户隔离信息和短期 memory key。"""
__tablename__ = "training_session"
id: Mapped[int] = mapped_column(BIGINT_PK, primary_key=True, autoincrement=True, comment="训练会话ID")
session_code: Mapped[str] = mapped_column(String(64), nullable=False, unique=True, index=True, comment="会话编码")
user_id: Mapped[str] = mapped_column("external_user_id", String(128), nullable=False, index=True, comment="宿主系统用户ID")
user_id: Mapped[str] = mapped_column("external_user_id", String(128), nullable=False, index=True, comment="Django用户中心ID")
tenant_id: Mapped[str | None] = mapped_column(String(128), comment="租户或项目ID")
class_id: Mapped[str | None] = mapped_column(String(128), comment="班级或课程ID")
entry_scene: Mapped[str | None] = mapped_column(String(64), comment="入口场景")
@@ -45,7 +45,7 @@ class SessionOrder(Base):
id: Mapped[int] = mapped_column(BIGINT_PK, primary_key=True, autoincrement=True, comment="检查申请ID")
session_id: Mapped[int] = mapped_column(ForeignKey("training_session.id"), nullable=False, index=True, comment="训练会话ID")
user_id: Mapped[str] = mapped_column("external_user_id", String(128), nullable=False, index=True, comment="宿主系统用户ID")
user_id: Mapped[str] = mapped_column("external_user_id", String(128), nullable=False, index=True, comment="Django用户中心ID")
case_id: Mapped[int] = mapped_column(ForeignKey("case_base.id"), nullable=False, index=True, comment="病例ID")
case_exam_item_id: Mapped[int] = mapped_column("exam_item_id", ForeignKey("case_exam_item.id"), nullable=False, comment="检查项目ID")
item_code: Mapped[str] = mapped_column(String(64), nullable=False, comment="项目编码")
@@ -74,7 +74,7 @@ class SessionSubmission(TimestampMixin, Base):
id: Mapped[int] = mapped_column(BIGINT_PK, primary_key=True, autoincrement=True, comment="提交记录ID")
session_id: Mapped[int] = mapped_column(ForeignKey("training_session.id"), nullable=False, unique=True, comment="训练会话ID")
user_id: Mapped[str] = mapped_column("external_user_id", String(128), nullable=False, index=True, comment="宿主系统用户ID")
user_id: Mapped[str] = mapped_column("external_user_id", String(128), nullable=False, index=True, comment="Django用户中心ID")
primary_diagnosis: Mapped[str | None] = mapped_column(Text, comment="主要诊断")
differential_diagnoses: Mapped[list | None] = mapped_column(JSON, comment="鉴别诊断")
diagnosis_basis: Mapped[str | None] = mapped_column(Text, comment="诊断依据")
+26 -4
View File
@@ -3,8 +3,8 @@ from __future__ import annotations
from datetime import datetime
from decimal import Decimal
from sqlalchemy import BigInteger, DateTime, Integer, JSON, Numeric, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import BigInteger, DateTime, ForeignKey, Integer, JSON, Numeric, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base
from app.models.mixins import TimestampMixin
@@ -42,8 +42,8 @@ class TrainingRecord(TimestampMixin, Base):
rag_context_version: Mapped[str] = mapped_column(String(50), nullable=False, default="none", comment="RAG上下文版本")
case_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True, comment="病例ID")
teacher_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True, index=True, comment="教师ID")
user_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True, index=True, comment="数字用户ID")
external_user_id: Mapped[str] = mapped_column(String(128), nullable=False, index=True, comment="宿主系统用户ID")
user_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True, index=True, comment="Django用户中心数字ID")
external_user_id: Mapped[str] = mapped_column(String(128), nullable=False, index=True, comment="Django用户中心ID")
session_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True, index=True, comment="训练会话ID")
evaluation_record_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True, index=True, comment="兼容旧评价记录ID")
score_type: Mapped[str] = mapped_column(String(20), nullable=False, default="percentage", comment="分数类型")
@@ -53,3 +53,25 @@ class TrainingRecord(TimestampMixin, Base):
UniqueConstraint("session_id", name="uk_training_record_session"),
{"comment": "训练记录表"},
)
score_details = relationship("TrainingScoreDetail", back_populates="record", cascade="all, delete-orphan")
class TrainingScoreDetail(TimestampMixin, Base):
"""评分明细表:保存每条 scoring_rule 对应的 AI 评分、扣分原因、证据和置信度。"""
__tablename__ = "training_score_detail"
id: Mapped[int] = mapped_column(BIGINT_PK, primary_key=True, autoincrement=True, comment="评分明细ID")
record_id: Mapped[int] = mapped_column(ForeignKey("training_record.id"), nullable=False, index=True, comment="训练记录ID")
rule_id: Mapped[int | None] = mapped_column(ForeignKey("scoring_rule.id"), nullable=True, index=True, comment="评分规则ID")
dimension: Mapped[str] = mapped_column(String(50), nullable=False, index=True, comment="评分维度")
score: Mapped[Decimal] = mapped_column(Numeric(5, 2), nullable=False, default=0, comment="分数")
deducted_reason: Mapped[str | None] = mapped_column(Text, comment="扣分原因")
evidence_message_ids: Mapped[list] = mapped_column(JSON, nullable=False, default=list, comment="对应对话证据")
ai_confidence: Mapped[Decimal | None] = mapped_column(Numeric(5, 2), comment="AI评分置信度")
comment: Mapped[str | None] = mapped_column(Text, comment="评语")
record = relationship("TrainingRecord", back_populates="score_details")
__table_args__ = {"comment": "评分明细表"}
+34 -21
View File
@@ -1,33 +1,46 @@
from datetime import datetime
from sqlalchemy import DateTime, Integer, JSON, Numeric, String
from sqlalchemy import BigInteger, Boolean, DateTime, Integer, JSON, SmallInteger, String
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base
from app.models.mixins import TimestampMixin
BIGINT_PK = BigInteger().with_variant(Integer, "sqlite")
class User(TimestampMixin, Base):
"""宿主用户引用:保存外部 user_id,不承担登录注册职责。"""
"""用户端用户表:按 Django 用户中心确定字段建模,只读取不承担登录注册职责。"""
__tablename__ = "users"
__tablename__ = "user"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
external_user_id: Mapped[str] = mapped_column(String(128), nullable=False, unique=True, index=True)
display_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
id: Mapped[int] = mapped_column(BIGINT_PK, primary_key=True, autoincrement=True, comment="用户ID")
username: Mapped[str] = mapped_column(String(50), nullable=False, unique=True, index=True, comment="用户名")
password: Mapped[str] = mapped_column(String(255), nullable=False, comment="密码哈希")
real_name: Mapped[str] = mapped_column(String(50), nullable=False, comment="真实姓名")
phone: Mapped[str] = mapped_column(String(20), nullable=False, unique=True, index=True, comment="手机号")
avatar: Mapped[str] = mapped_column(String(255), nullable=False, default="", comment="头像")
gender: Mapped[int] = mapped_column(SmallInteger, nullable=False, default=0, comment="性别")
role_type: Mapped[str] = mapped_column(String(30), nullable=False, comment="角色类型")
title_name: Mapped[str] = mapped_column(String(50), nullable=False, default="", comment="职称")
major: Mapped[str] = mapped_column(String(100), nullable=False, default="", comment="专业")
training_stage: Mapped[str] = mapped_column(String(50), nullable=False, default="", comment="培训阶段")
learning_target: Mapped[str] = mapped_column(String(255), nullable=False, default="", comment="学习目标")
competency_profile: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict, comment="能力画像")
weak_dimensions: Mapped[list] = mapped_column(JSON, nullable=False, default=list, comment="薄弱维度")
strong_dimensions: Mapped[list] = mapped_column(JSON, nullable=False, default=list, comment="优势维度")
ai_preference: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict, comment="AI偏好")
total_training_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, comment="训练次数")
total_case_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, comment="病例数")
current_level: Mapped[str] = mapped_column(String(30), nullable=False, default="", comment="当前等级")
status: Mapped[int] = mapped_column(SmallInteger, nullable=False, default=1, comment="状态")
last_login: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, comment="最后登录")
last_login_time: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, comment="最后登录时间")
is_superuser: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, comment="是否超级用户")
is_staff: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, comment="是否员工")
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, comment="是否激活")
date_joined: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, comment="加入时间")
department_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True, index=True, comment="科室ID")
institution_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True, index=True, comment="机构ID")
class UserLearningProfile(TimestampMixin, Base):
"""学习档案模型:聚合完整评价记录形成用户能力画像。"""
__tablename__ = "user_learning_profiles"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
user_id: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
tenant_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
total_evaluations: Mapped[int] = mapped_column(Integer, default=0)
avg_score_percentage: Mapped[float | None] = mapped_column(Numeric(6, 2))
avg_score_five_point: Mapped[float | None] = mapped_column(Numeric(4, 2))
weak_dimensions: Mapped[list | None] = mapped_column(JSON)
last_evaluation_id: Mapped[int | None] = mapped_column(Integer)
last_trained_at: Mapped[datetime | None] = mapped_column(DateTime, index=True)
__table_args__ = {"comment": "用户表"}