实操考评数据库模型草案

日期:2026-07-06

本文档用于评审和对比实操考评模块的数据模型设计,不是 migration 文件,也不是最终 Django models.py。当前目标是帮助产品、后端和 AI agent 对表边界、字段责任、索引和状态语义达成一致。

本草案遵循以下前提:

1. 总体关系

[Diagram]

2. 状态字段

状态字段使用语义化字符串,外部 API 是否返回数字枚举由接口契约另定。

字段 可选值 说明
PracticalAssessment.publish_status draft, unpublished, published, closed 活动配置/发布/关闭状态。产品列表里的未开始、考评中、已过期建议由时间计算,不单独落库。
PracticalTask.status pending, active, ended, stopped 任务是否可执行。active 表示考评员当前可评分。
PracticalCandidate.score_status pending, assigned, scoring, scored 考生在活动内的评分推进状态。产品界面通常展示待评分、评分中、已评分。
PracticalAssignment.status active, released, submitted 考生是否在某个考评员当前评分范围内。
PracticalScoreRecord.status draft, submitted, cleared, replaced 单个考评员的一份评分记录状态。replaced 表示因重新评分被新记录替代,仅用于历史展示。
PracticalSignature.status pending, signed, voided 签字确认状态。
PracticalFaceRecord.status pending, passed, failed, passed_no_base_photo 人脸核验状态。无系统照片时仍记录拍照图像,按通过处理。
PracticalScoreResult.status pending, ready, published, invalid 最终得分计算状态。

3. 表设计

3.0 content_form 调整

这是对现有 content.Form 的必要补充,不是新增实操表。产品要求“考试试卷只能应用于考试场次,考评试卷只能应用于考评场次”,因此 Form 需要有稳定用途字段,不能只靠前端筛选。

字段 类型建议 说明
usage_type varchar(20) default exam exam/practical,考试试卷或考评试卷
usage_config JSON nullable 试卷用途扩展配置。考评试卷可记录仅允许一个单元、一个大题、实操考评题等约束版本

落地规则:

3.1 practical_assessment

实操考评活动主表。表示一次现场考评活动。

字段 类型建议 说明
id bigint PK 主键
tenant_id FK -> tenant.Tenant 租户隔离
public_id varchar(36) 对外稳定标识,避免暴露自增 ID
name varchar(200) 考评名称
description text nullable 活动说明
start_at datetime nullable 考评开始时间
end_at datetime nullable 考评结束时间
timezone varchar(50) 默认 Asia/Shanghai
publish_status varchar(30) draft/unpublished/published/closed
published_at datetime nullable 发布时间
closed_at datetime nullable 关闭时间
examiner_entry_code varchar(100) nullable 考评员端入口码或短链标识
score_rounds smallint default 1 考评规则,每道题或试卷需要的考评员人数,产品支持一评到十评
candidate_pick_limit int default 3 考评限制,考评员一次性可添加到评分界面的考生人数上限
max_candidates_per_examiner int nullable 单人考评上限,累计可提交评分的人数上限;为空表示不限制
review_mode varchar(20) paper/item,按卷评或按题评
blind_review bool default false 盲评。启用后考评员端只展示准考证号和默认头像
require_signature bool default false 是否要求签字确认
allow_modify_after_submit bool default false 提交评分后是否允许改分;启用签字确认时以签字前可改为准
enable_note bool default true 是否允许考评备注
calc_mode varchar(30) average/trim_average,平均分或去掉最高最低分求平均
require_face_verify bool default false 是否要求考评员进入任务前人脸核验
face_verify_mode varchar(30) photo_compare/public_security/detect_only,照片比对、公安验证或仅检测人脸
rule_config JSON 活动级评分规则完整配置快照,保存界面配置原始值和扩展项
personal_info_config JSON 产品“个人信息”配置,含可编辑、可见、必填、排序、自定义字段
total_candidates int default 0 考生数量冗余,便于列表展示
created_by_id FK -> tenant.Operator nullable 创建人
created_at datetime 创建时间
updated_at datetime 更新时间
deleted_at datetime nullable 软删除时间

建议索引:

索引 说明
unique(tenant_id, public_id) 对外标识唯一
index(tenant_id, publish_status) 活动列表筛选
index(tenant_id, start_at, end_at) 时间状态计算和列表排序

3.2 practical_subject

活动下的科目/试卷分组。产品原型支持“添加科目”和在科目下添加试卷;没有明确科目时可创建默认分组“不限”。

字段 类型建议 说明
id bigint PK 主键
assessment_id FK -> PracticalAssessment 所属活动
public_id varchar(36) 对外稳定标识
name varchar(100) 科目名称,如“不限”“护理实操”
code varchar(50) 科目标识,默认分组可用 default
sort_order int default 0 排序
created_at datetime 创建时间
updated_at datetime 更新时间

建议索引:

索引 说明
unique(assessment_id, code) 同一活动内科目标识唯一
index(assessment_id, sort_order) 按活动读取科目

3.3 practical_rubric

活动内绑定的考评试卷和评分表快照。content.Form 仍是评分表编辑入口;发布/绑定后在这里保存快照,保证评分过程中结构稳定。

字段 类型建议 说明
id bigint PK 主键
assessment_id FK -> PracticalAssessment 所属活动
subject_id FK -> PracticalSubject nullable 所属科目
form_id FK -> content.Form 绑定的考评试卷
public_id varchar(36) 对外稳定标识
name varchar(200) 评分表名称,默认取 Form 名称
version int default 1 评分表快照版本
total_score decimal(8,2) 总分
rubric_snapshot JSON 评分表结构快照,含评分项、考核要求、评分标准、否定项
form_snapshot JSON nullable 原始 Form 关键字段快照
snapshot_hash varchar(64) nullable 快照摘要,用于变更检测
source_form_modified_at datetime nullable 生成快照时的 Form 修改时间
is_current bool default true 是否为当前评分使用版本
status varchar(30) active/disabled
created_at datetime 创建时间
updated_at datetime 更新时间

建议索引:

索引 说明
index(assessment_id, subject_id) 活动详情读取评分表
index(form_id) 从试卷反查使用情况
unique(assessment_id, form_id, version) 同活动同试卷版本唯一

版本规则:

3.4 practical_candidate

实操考评考生。独立于 exam.Entry

字段 类型建议 说明
id bigint PK 主键
assessment_id FK -> PracticalAssessment 所属活动
public_id varchar(36) 对外稳定标识
permit varchar(100) 准考证号/考生编号
name varchar(100) 姓名
phone varchar(30) nullable 手机号
identity_type varchar(50) nullable 证件类型
identity_number varchar(100) nullable 证件号,按租户加密策略处理
gender varchar(20) nullable 性别
photo_url varchar(500) nullable 照片地址
profile_snapshot JSON 按活动个人信息配置保存的考生资料
score_status varchar(30) pending/assigned/scoring/scored
final_score decimal(8,2) nullable 活动总分冗余,可由结果表汇总
created_at datetime 创建时间
updated_at datetime 更新时间
deleted_at datetime nullable 软删除时间

建议索引:

索引 说明
unique(assessment_id, permit) 同活动准考证号唯一
index(assessment_id, score_status) 考生状态筛选
index(assessment_id, name) 管理端搜索

3.5 practical_examiner

考评员账号。手机号是登录主体;活动关联通过任务绑定。

字段 类型建议 说明
id bigint PK 主键
tenant_id FK -> tenant.Tenant 租户隔离
public_id varchar(36) 对外稳定标识
name varchar(100) 姓名
phone varchar(30) 手机号
identity_type varchar(50) nullable 证件类型
identity_number varchar(100) nullable 证件号
gender varchar(20) nullable 性别
photo_url varchar(500) nullable 基准照片
fixed_code_hash varchar(128) nullable 固定验证码哈希,不建议明文保存
status varchar(30) active/disabled
last_login_at datetime nullable 最近登录时间
created_at datetime 创建时间
updated_at datetime 更新时间
deleted_at datetime nullable 软删除时间

建议索引:

索引 说明
unique(tenant_id, phone) 同租户手机号唯一
index(tenant_id, name) 管理端搜索

3.6 practical_task

评分任务。绑定活动、评分表、考评方式、时间、规则和评分范围。

字段 类型建议 说明
id bigint PK 主键
assessment_id FK -> PracticalAssessment 所属活动
subject_id FK -> PracticalSubject nullable 所属科目
rubric_id FK -> PracticalRubric 使用的评分表快照
public_id varchar(36) 对外稳定标识
name varchar(200) 任务名称
status varchar(30) pending/active/ended/stopped
review_mode varchar(20) paper/item,按卷评或按题评
start_at datetime nullable 任务开始时间
end_at datetime nullable 任务结束时间
score_rounds smallint default 1 从活动复制的 N 评制快照
calc_mode varchar(30) 从活动复制的算分方式快照
blind_review bool default false 从活动复制的盲评快照
require_signature bool default false 从活动复制的签字确认快照
require_face_verify bool default false 从活动复制的人脸核验快照
face_verify_mode varchar(30) 从活动复制的人脸核验方式快照
enable_note bool default true 从活动复制的备注配置快照
allow_modify_after_submit bool default false 从活动复制的改分限制快照
max_candidates_per_examiner int nullable 从活动复制的单人考评上限快照
candidate_pick_limit int default 3 从活动复制的考评限制快照,一次性可添加到评分界面的考生数
rule_snapshot JSON 任务规则完整快照,手机端任务详情以此为准
created_at datetime 创建时间
updated_at datetime 更新时间
deleted_at datetime nullable 软删除时间

建议索引:

索引 说明
index(assessment_id, status) 活动任务列表
index(rubric_id) 按评分表查任务
index(start_at, end_at) 手机端任务状态计算

3.7 practical_task_examiner

任务与考评员绑定关系。

字段 类型建议 说明
id bigint PK 主键
task_id FK -> PracticalTask 任务
examiner_id FK -> PracticalExaminer 考评员
status varchar(30) active/disabled
assigned_count int default 0 已领取/分配数量冗余
submitted_count int default 0 已提交数量冗余
created_at datetime 创建时间

建议索引:

索引 说明
unique(task_id, examiner_id) 同任务同考评员唯一
index(examiner_id, status) 手机端任务列表

3.8 practical_task_item

按题评时的任务评分项范围。评分项来自 rubric_snapshot 中的稳定 item_key/path

字段 类型建议 说明
id bigint PK 主键
task_id FK -> PracticalTask 任务
rubric_id FK -> PracticalRubric 评分表快照
item_key varchar(100) 评分项稳定 key
item_path varchar(300) JSON path
item_title varchar(300) 评分项标题快照
full_score decimal(8,2) 满分
sort_order int default 0 排序

建议索引:

索引 说明
unique(task_id, item_key) 同任务评分项唯一
index(rubric_id, item_key) 评分项反查

3.9 practical_assignment

考生分配/领取记录。用于表达“考生已经进入某个考评员评分界面”。

字段 类型建议 说明
id bigint PK 主键
task_id FK -> PracticalTask 任务
candidate_id FK -> PracticalCandidate 考生
examiner_id FK -> PracticalExaminer 考评员
status varchar(30) active/released/submitted
source varchar(30) manual/claim/auto
assigned_at datetime 分配/领取时间
released_at datetime nullable 释放时间
submitted_at datetime nullable 评分提交时间

建议索引:

索引 说明
unique(task_id, candidate_id, examiner_id) 防重复领取
index(task_id, status) 任务下按状态查考生
index(examiner_id, status) 手机端当前评分列表
index(candidate_id) 考生详情查评分过程

限制规则:

3.10 practical_score_record

某考评员对某考生在某任务下的一份评分记录。

字段 类型建议 说明
id bigint PK 主键
assessment_id FK -> PracticalAssessment 冗余活动,便于查询
task_id FK -> PracticalTask 任务
assignment_id FK -> PracticalAssignment nullable 对应分配记录
candidate_id FK -> PracticalCandidate 考生
examiner_id FK -> PracticalExaminer 考评员
rubric_id FK -> PracticalRubric 评分表快照
rubric_version int 评分表版本
attempt_no int default 1 同一考评员对同一考生的第几次评分
previous_record_id FK -> self nullable 重新评分时指向上一份记录
replaced_by_id FK -> self nullable 被新评分替代时指向新记录
is_effective bool default true 是否参与最终成绩计算
public_id varchar(36) 对外稳定标识
status varchar(30) draft/submitted/cleared/replaced
total_score decimal(8,2) nullable 当前总得分
deducted_score decimal(8,2) nullable 当前总扣分
note text nullable 考评备注
signature_status varchar(30) not_required/pending/signed
score_snapshot JSON 提交时完整评分详情快照
started_at datetime nullable 首次进入评分时间
last_saved_at datetime nullable 最近保存草稿时间
submitted_at datetime nullable 提交时间
cleared_at datetime nullable 清分时间
created_at datetime 创建时间
updated_at datetime 更新时间

建议索引:

索引 说明
unique(task_id, candidate_id, examiner_id, rubric_id, attempt_no) 同一评分版本下的评分次数唯一
index(assessment_id, status) 管理端进度统计
index(candidate_id, status, is_effective) 考生详情和最终成绩计算
index(examiner_id, status) 考评员已评次数

重新评分规则:

3.11 practical_score_item

评分记录下的单题/单评分项明细。用于管理端明细展示、按题查看、导出和统计。

字段 类型建议 说明
id bigint PK 主键
score_record_id FK -> PracticalScoreRecord 评分记录
item_key varchar(100) 评分项稳定 key
item_path varchar(300) JSON path
item_title varchar(300) 评分项标题快照
full_score decimal(8,2) 满分
score decimal(8,2) nullable 得分
deducted_score decimal(8,2) nullable 扣分
negative_selected bool default false 是否命中否定项
negative_detail JSON 命中的否定项
dimension_detail JSON 评分维度、扣分单位、标准、实际扣分
sort_order int default 0 排序
created_at datetime 创建时间
updated_at datetime 更新时间

建议索引:

索引 说明
unique(score_record_id, item_key) 一份评分记录同题唯一
index(item_key) 按题统计

3.12 practical_score_result

考生最终成绩。建议按“考生 + 科目/评分表”生成结果,再由业务层汇总活动总成绩。

字段 类型建议 说明
id bigint PK 主键
assessment_id FK -> PracticalAssessment 活动
subject_id FK -> PracticalSubject nullable 科目
rubric_id FK -> PracticalRubric nullable 评分表
candidate_id FK -> PracticalCandidate 考生
status varchar(30) pending/ready/published/invalid
final_score decimal(8,2) nullable 最终分
score_count int default 0 参与计算的评分记录数量
required_score_count int default 1 要求评分人次
signed_score_count int default 0 已完成签字且可计分的评分记录数量
pending_signature_count int default 0 已提交但待签字的评分记录数量
calc_mode varchar(30) 算分方式
score_record_ids JSON 参与计算的评分记录 ID
calc_detail JSON 计算过程明细
calculated_at datetime nullable 计算时间
published_at datetime nullable 成绩发布时间
updated_at datetime 更新时间

建议索引:

索引 说明
unique(assessment_id, candidate_id, subject_id, rubric_id) 同一结果维度唯一
index(assessment_id, status) 成绩列表
index(candidate_id) 考生详情

计分规则:

3.13 practical_signature

签字确认记录。既支持单个评分记录签字,也支持批量签字。

字段 类型建议 说明
id bigint PK 主键
assessment_id FK -> PracticalAssessment 活动
task_id FK -> PracticalTask 任务
examiner_id FK -> PracticalExaminer 签字考评员
candidate_id FK -> PracticalCandidate nullable 单考生签字时使用
score_record_id FK -> PracticalScoreRecord nullable 单记录签字时使用
scope_type varchar(30) record/batch
scope_data JSON 批量签字范围,如 record ids、candidate ids
signature_url varchar(500) 签名图片地址
status varchar(30) pending/signed/voided
signed_at datetime nullable 签字时间
created_at datetime 创建时间

建议索引:

索引 说明
index(task_id, examiner_id, status) 手机端待签字列表
index(score_record_id) 评分详情展示签名

3.14 practical_face_record

考评员人脸核验记录。

字段 类型建议 说明
id bigint PK 主键
assessment_id FK -> PracticalAssessment 活动
task_id FK -> PracticalTask nullable 任务
examiner_id FK -> PracticalExaminer 考评员
photo_url varchar(500) 本次拍照照片
base_photo_url varchar(500) nullable 系统基准照片
verify_mode varchar(30) photo_compare/public_security/detect_only
status varchar(30) pending/passed/failed/passed_no_base_photo
similarity decimal(8,4) nullable 比对分
provider varchar(50) nullable 供应商
failure_reason varchar(200) nullable 失败原因,如未检测到人脸、比对不通过、公安验证失败
provider_response JSON 供应商原始响应
verified_at datetime nullable 核验时间
created_at datetime 创建时间

建议索引:

索引 说明
index(task_id, examiner_id, status) 进入任务前核验
index(assessment_id, examiner_id) 管理端核验记录

核验规则:

3.15 practical_score_log

评分审计日志。用于提交、清分、改分、重算、签字等关键行为追踪。

字段 类型建议 说明
id bigint PK 主键
assessment_id FK -> PracticalAssessment 活动
task_id FK -> PracticalTask nullable 任务
candidate_id FK -> PracticalCandidate nullable 考生
examiner_id FK -> PracticalExaminer nullable 考评员
score_record_id FK -> PracticalScoreRecord nullable 评分记录
actor_type varchar(30) operator/examiner/system/api
actor_id varchar(100) nullable 操作人标识
action varchar(50) save/submit/clear/modify/recalculate/sign
before_data JSON 操作前关键数据
after_data JSON 操作后关键数据
ip varchar(64) nullable IP
user_agent varchar(500) nullable UA
created_at datetime 创建时间

建议索引:

索引 说明
index(assessment_id, created_at) 活动审计
index(score_record_id, created_at) 评分记录审计
index(candidate_id, created_at) 考生详情审计

3.16 practical_api_log

手机端和外部接口调用日志。MVP 可先只记录异常和关键接口。

字段 类型建议 说明
id bigint PK 主键
tenant_id FK -> tenant.Tenant 租户
assessment_id FK -> PracticalAssessment nullable 活动
actor_type varchar(30) examiner/api/operator
actor_id varchar(100) nullable 调用方标识
endpoint varchar(200) 接口路径
method varchar(10) HTTP method
request_id varchar(100) nullable 请求 ID
status_code int HTTP 状态
error_code varchar(50) nullable 业务错误码
request_data JSON nullable 请求摘要,敏感字段脱敏
response_data JSON nullable 响应摘要,必要时只记录错误
ip varchar(64) nullable IP
created_at datetime 创建时间

建议索引:

索引 说明
index(tenant_id, created_at) 租户接口日志
index(assessment_id, created_at) 活动接口日志
index(request_id) 排查单次请求

4. 关键设计取舍

4.1 为什么保留 content.Form

评分表编辑、预览、复制等能力应继续使用 Kaola 既有试卷体系。实操模块不复制一套评分表主数据,而是在 PracticalRubric 保存绑定关系和发布快照。

这样可以同时满足:

4.2 为什么需要 PracticalSubject

产品原型支持一个考评活动内添加科目,并在科目下添加试卷。若只在活动上直接挂一个评分表,会限制多科目、多试卷场景。

PracticalSubject 作为活动下的分组层:

4.3 为什么 assignedscoring 要分开

产品里的“评分中”是考生已经加入考评员评分界面,但还没有完成全部评分。仅预分配到任务范围内,不代表已经进入评分界面。

因此建议:

4.4 为什么签字单独成表

签字不是评分项本身,而是提交后的确认行为。它可能是单条评分记录签字,也可能是对一批已评分考生统一签字。单独成表能保留签名图片、签字范围和审计记录。

4.5 为什么结果表独立于 outcome.Result

实操考评不是在线考试 Entry 的成绩。最终得分来自现场评分记录、多评规则、签字确认和任务规则,不应写入 outcome.Result 主链路。若未来要统一报表,可在服务层做汇总视图或导出适配。

5. MVP 建表建议

一期除新增实操表外,还必须给 content.Form 增加 usage_type,否则无法可靠限制“考试试卷”和“考评试卷”的使用边界。

一期必须建:

可延后或只记录关键接口:

6. 后续落 Django Model 时的注意点

  1. 先给 content.Form 增加 usage_type 并回填历史试卷为 exam,再开发实操试卷编辑约束。
  2. 新增 app/practical/models/ 后再补 apps.pyadmin.pymigrations/
  3. 评审前不应执行 makemigrationsmigrate
  4. 所有核心表必须带租户隔离,直接或通过活动间接限制到租户。
  5. 活动配置字段是主数据,任务字段是发布/创建任务时的快照,手机端接口优先读任务快照。
  6. JSON 字段建议使用项目现有 jsonfield.JSONFieldJSONFIELD_DUMP_KWARGS
  7. 证件号、手机号、照片地址等字段最终要按 Kaola 现有隐私和加密策略处理。
  8. time_status 不建议落库,应由 start_at/end_at/publish_status 计算。
  9. 签字、人脸和评分提交都应写 PracticalScoreLog
  10. 成绩计算必须过滤 is_effective=false 的旧评分记录;开启签字确认时还必须过滤未签字记录。