You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

18 KiB

V38:四维能力测评有限接入候选人评分设计

状态:已实现并通过真实 MySQL/Docker 关键黑盒验收,待建立 Git 检查点。 功能版本:V38。 基线:Flyway V1–V36、OpenAPI 148 paths / 182 operations、全量测试 1427/1427(mvn clean verify,0 Failures / 0 Errors / 0 Skipped)。 本设计只接入 V36 四维能力测评,不接入 V35 RIASEC 兴趣测评。

1. 目标与边界

1.1 目标

在不改变 V36 测评主链路的前提下,将学生最新、完整、仍有效的四维能力测评结果映射为候选人评分中的 ABILITY 维度,使候选评分卡可有限地反映能力测评结果。

1.2 必须满足的边界

  • 仅使用 V36 ability_results,不使用 V35 兴趣类型或职业方向结果。
  • 仅映射到 ABILITY;不写入或推断 GROWTH、ACTIVITY 等其他维度。
  • 不在测评提交时直接写 candidate_dimension_scores;V36 提交仍只写其 ability_* 表和既有审计。
  • 不新增企业查看测评详情的接口。企业仅通过既有候选评分卡看到 ABILITY 的聚合 score/status。
  • 不向任何评分接口返回题目、答案键、选项分值、题库标签、题目解析或测评答卷 ID。
  • 功能开关默认关闭;关闭时现有 ABILITY=PENDING 行为必须完全保持。
  • 不引入 AI 评分、人工覆写、最高分挑选、自动封禁或对既有学生数据的回填。

2. 已确认的产品与规则口径

项目 V38 锁定口径
可采纳来源 V36 最新 COMPLETED 且 valid_until > capturedNow 的结果
四维权重 LOGIC=35%、EXECUTION=30%、LEARNING=20%、COMMUNICATION=15%
结果选择 只取最新有效完成结果;按 completed_at DESC, attempt_id DESC 稳定排序;绝不取历史最高分
有效期 复用 V36 的 7 天;过期结果仍可由学生查看历史,但不得参与新评分计算
重测治理 复用 V36:有效结果期间禁止重测,结果到期后可重测,滚动 90 天至多完成 3 次
企业可见性 仅既有公司评分卡的 dimensions[]{dimension,score,status};无四维细分、短板、答卷或题目反馈
默认发布状态 OFF,不影响候选综合分、V32 排名或 V34 求职进度

3. 总体方案:评分时只读投影

采用评分时只读投影(read-through projection),而不是在 V36 提交后跨模块写入候选评分表。

V36 完成测评
  └─ 写 ability_attempts / ability_results(既有行为,不调用评分引擎)

学生或企业读取既有候选评分卡
  └─ CandidateScoringCalculator
       └─ AbilityAssessmentScoreSource(只读选择最新有效 ability_result)
            └─ AbilityScoreMapper(按 V38 固定权重计算 ABILITY)
                 └─ 既有批量 upsert candidate_dimension_scores(既有评分快照机制)

这样做的原因:

  1. V36 仍可独立工作,测评提交失败或评分开关关闭都不会影响其完成、结果和历史查询。
  2. 评分只在既有评分查询/重算时产生快照,沿用当前 candidate_dimension_scores 的写入模型。
  3. 不需要异步消息、定时回填或跨服务事务;结果到期后下一次评分自然变为 PENDING。
  4. 评分行的 sub_scores_json 保留最小化、可追溯的来源版本信息,不需新建投影表。

4. 数据选择与有效性判定

4.1 单一查询契约

新增一个只读仓库查询,语义为:

findLatestValidCompletedResult(studentUserId, typeCode, capturedNow)

查询必须同时满足:

  • ability_attempts.student_user_id = :studentUserId;
  • ability_attempts.status = 'COMPLETED';
  • ability_results.valid_until > :capturedNow;
  • 测评定义为 V36 能力测评类型且当前/答卷题集版本可识别;
  • 排序固定为 completed_at DESC, ability_attempts.id DESC;
  • 最多取一条。

capturedNow 必须在一次评分计算开始时由后端生成并全程复用,不能在同一计算中多次调用 now(),以避免临界过期时出现不一致。

4.2 结果结构防御

即使查询到结果,也仅在以下条件全部成立时计算:

  • dimension_scores_json 可被安全解析;
  • 恰有 LOGIC、COMMUNICATION、EXECUTION、LEARNING 四维;
  • 每维分数均在 [0,100],且均为有限数值;
  • 每维已答题数均为 6,答卷总已答题数为 24;
  • overall_score、题集版本和 V36 规则版本存在。

实现细则(V38 发布前 P1):answeredCount 使用精确整数校验,仅接受数值意义上等于整数的表示 (如 JSON 整数 6 或 6.0、"6"、"6.0");6.9、6.5、"6.5"、溢出 int、空串、null 均视为不可用 (stripTrailingZeros + intValueExact,不使用可能截断小数的 intValue())。attemptAnsweredCount 必须非空且严格等于 24,不允许 null 放行。

任一条件不满足时,不抛给前端 500,也不使用部分数据;该学生本次 ABILITY 返回 PENDING,原因写为“无可用的完整能力测评结果”。内部日志仅记录结果 ID 与结构错误类型,禁止记录题目、答案或选项分值。

4.3 过期与重测

  • 无有效结果、仅有过期结果、或最新结果格式无效:ABILITY=PENDING,score=null。
  • 有效期内没有重测入口;到期后的新测评完成后,在下一次评分计算中自然替换为新结果。
  • 结果选择不比较分数,因此不能通过“多次测到高分”挑选最高成绩。
  • 已写入的旧月度快照保留为历史;当前月份按现有 upsert 语义在下一次评分查询/重算时刷新。

5. 分数映射

5.1 公式

设 V36 四维百分制结果为 L(逻辑)、C(沟通)、E(执行)、R(学习),则:

abilityScore = roundHalfUp(L × 0.35 + E × 0.30 + R × 0.20 + C × 0.15, 2)

分数范围为 0.00–100.00。所有中间计算使用 BigDecimal;最终 scale=2、HALF_UP。不得复用 V36 等权 overallScore 直接作为候选 ABILITY 分数,因为 V38 已确认使用不同的、显式的业务权重。

5.2 维度结果

条件 ABILITY.status ABILITY.score 对综合分的影响
开关关闭 PENDING null 与 V37 前完全一致,不参与平均
无有效完整结果 PENDING null 不参与平均
有效完整结果且开关开启 OK 映射后的 0–100 分 按既有候选评分规则参与可计算维度平均
来源读取异常/结构异常 PENDING null 不参与平均,记录脱敏内部告警

候选综合分继续遵循现有规则:只对 OK/ESTIMATED 且分数非空的维度重新归一化计算。故 V38 启用后会对拥有有效能力结果的学生产生真实影响;这是功能开关默认关闭的原因。

5.3 版本

  • 能力映射规则版本:ability-assessment-scoring-v0.1。
  • 开关开启后的候选评分规则版本:candidate-scoring-v0.2。
  • 开关关闭时保留现有 candidate-scoring-v0.1。
  • 每个写入的 ABILITY 快照必须记录两类版本,保证规则升级后可解释、可重算。

6. 快照、溯源与数据最小化

不新增表、不新增 Flyway。复用 candidate_dimension_scores 现有的唯一键 (student_user_id, dimension, period) 与 sub_scores_json。

6.1 持久化的最小来源字段

ABILITY 行的内部 sub_scores_json 仅允许下列字段:

{
  "source": "V36_ABILITY_ASSESSMENT",
  "sourceResultId": 123,
  "sourceCompletedAt": "2026-09-04T10:00:00Z",
  "validUntil": "2026-09-11T10:00:00Z",
  "questionSetVersion": "ability-v0.1",
  "assessmentRulesVersion": "ability-v0.1",
  "mappingRulesVersion": "ability-assessment-scoring-v0.1",
  "weights": {"LOGIC": 35, "EXECUTION": 30, "LEARNING": 20, "COMMUNICATION": 15},
  "dimensionScores": {"LOGIC": 80.00, "EXECUTION": 75.00, "LEARNING": 70.00, "COMMUNICATION": 65.00}
}

禁止写入:答卷 ID、题目 ID、题目编码、题库标签、题干、选项、用户选择、答案键、选项分值、题目解析、手机号、邮箱、令牌、请求体和完整审计详情。

sourceResultId 仅作内部溯源,任何 HTTP 响应均不得返回该字段。

6.2 响应投影

  • 学生综合卡:沿用既有 ABILITY 聚合分和状态。
  • 学生单维详情:可返回四维聚合分、权重、有效期、规则版本和说明,用于解释自己的分数;不得返回 sourceResultId 或题目级内容。
  • 企业候选评分卡:保持既有字段,只返回 ABILITY 的 dimension/score/status。
  • 趋势接口:只使用已写候选快照,不回读题目或 V36 原始答案。

7. 功能开关与上线策略

新增配置:

scoring:
  ability-assessment:
    enabled: false
模式 行为
false(默认) 不查询 V36 结果,ABILITY=PENDING;无评分行为变更
true 查询最新有效结果,按本设计映射;仅在既有评分计算/重算时写候选评分快照

发布顺序:

  1. 合入代码且以 false 发布;验证所有既有候选评分卡仍是 V37 行为。
  2. 在验收/小范围环境设为 true,核验映射、脱敏、排名影响与回滚。
  3. 经负责人确认后才在正式环境启用;启用后仍可立即设回 false。

关闭开关只停止后续映射,不删除已存在的快照或 V36 数据。若需消除当前月份的已映射 ABILITY,必须走单独、经批准的重算流程,不能在开关切换时隐式删除数据。

8. 模块职责与实现边界

建议新增以下内部组件:

  • AbilityAssessmentScoreSource:一次查询并解析最新有效 V36 结果;不暴露实体给 Controller。
  • AbilityScoreMapper:纯函数,校验四维、计算权重分、构造最小化来源描述。
  • CandidateScoringCalculator#computeAbility(...):替换现有硬编码 PENDING;开关关闭时保留旧分支。
  • CandidateScoringService:每次评分计算只调用一次 AbilityAssessmentScoreSource,不在八维循环内查询。

不得:

  • 改动 AbilityAttemptService.submit() 的事务边界、状态机、重测限制或审计。
  • 让评分模块更新 ability_attempts、ability_answers、ability_results。
  • 在 Controller 接受 studentUserId、能力分或结果 ID 来影响评分。
  • 新增企业端能力测评详情、管理员修改能力分或公开的“重算某学生能力分”接口。

9. 并发、一致性与性能

  • 评分计算使用一次 capturedNow 和最多一次 V36 来源查询;不得按四维各查一次,也不得循环读取题目/答案。
  • 已有候选评分批量 upsert 继续处理同月并发刷新;V38 不新增新的评分写表。
  • V36 提交与评分查询并发时:评分要么读取提交前无结果并返回 PENDING,要么读取完整、已提交的结果并返回 OK;不能读取半成品。
  • 结果刚好到期时以 capturedNow 判定;该次计算结束后不回滚同次快照,下一次计算按新时间重新判定。
  • 利用既有 ability_attempts(student_user_id, definition_id, completed_at) 索引进行学生级最新结果查询;实现前必须用 MySQL EXPLAIN 验证不扫全表。若实际查询计划无法命中索引,停止编码并单独评审最小索引迁移,不得临时修改既有 V35 迁移。

10. 权限、隐私与审计

10.1 权限矩阵

数据 STUDENT COMPANY ADMIN
本人完整 V36 结果与题目反馈 仅本人 禁止 禁止(V36 原有口径)
候选评分 ABILITY 聚合分 本人 仅有岗位归属的候选评分卡 禁止(沿用现有评分接口)
四维聚合分与有效期 本人单维评分详情 禁止 禁止
题目/答案键/评分规则/标签 禁止通过评分接口返回 禁止 禁止

10.2 审计

V38 不对普通评分查询新增逐次审计,避免读取评分卡时产生审计噪声。现有评分治理申诉/回滚审计保持原样。

如内部结构无效需记录诊断日志,日志白名单仅为 studentUserId(内部日志)、sourceResultId、failureKind、mappingRulesVersion 和 requestId;禁止记录测评内容。不得将该诊断写入对企业可见的响应或 audit_logs.details。

11. 评分治理与申诉兼容

  • 既有 ABILITY 申诉/评分回滚若可用,回滚仍然是按当前数据确定性重算,不恢复人工指定的旧能力分。
  • V38 不新增人工修改、豁免、补分或上传证明材料的入口。
  • 来源结果在回滚时已过期或无效,则重算为 PENDING;这是规则结果,不属于回滚失败。
  • V36 测评本身的题目、答案或判分争议不通过候选评分申诉接口处理,后续需单独定义测评申诉治理。

12. 接口与 OpenAPI 影响

本版不新增、删除或修改公网路径;既有候选评分 API 的顶层契约保持兼容:

GET /profile/me/candidate-scoring
GET /profile/me/candidate-scoring/dimensions/ABILITY
GET /profile/me/candidate-scoring/trend
GET /company/candidates/{applicationId}/scoring

因此静态和运行时 OpenAPI 均应继续为 148 paths / 182 operations。

学生的 ABILITY 单维详情可在既有通用 subScores/reasons/dataSources 容器中增加白名单字段;实现前须确认这些对象的现有 schema 允许兼容增加字段。若需要新增可见字段,必须同步 openapi-rc.json、verify_openapi.py 与联调文档,但不得改变公司端 schema。

13. 错误码

V38 不新增公网接口,正常的“无有效测评结果”属于 ABILITY=PENDING,不是 HTTP 错误。因此不新增 ErrorCode。

任何来源 JSON 异常、查询异常或规则版本无法识别都必须被隔离为 PENDING,并保留 200 的既有评分响应。数据库不可用等系统级故障仍按全局异常处理器既有语义处理,不伪造评分结果。

14. 测试计划

14.1 单元测试

  • 开关关闭:不查询 V36 来源,ABILITY=PENDING,候选评分规则版本仍为 v0.1。
  • 无结果、仅过期结果、结构不完整结果、分数越界结果:均为 PENDING,无 500。
  • 有效结果:严格验证权重公式、两位小数 HALF_UP、OK 状态、来源版本。
  • 多份有效历史:选 completed_at 最新者,不选总分最高者;同时间用 attempt ID 稳定决胜。
  • 来源字段白名单:持久化 JSON 和 HTTP 响应均不含答卷 ID、题目、答案、答案键、分值、标签。
  • computeAbility 只触发一次来源查询;不得产生 N+1。

14.2 集成与 MockMvc 测试

  • H2 MySQL 模式下完整 V36 有效结果 → 候选评分写入 ABILITY 快照;过期后重算同周期 ABILITY=PENDING。
  • V36 提交本身不写 candidate_dimension_scores;只有既有候选评分查询/重算写快照。
  • 学生单维详情可见聚合四维和有效期;公司端只见 ABILITY 总分/状态;COMPANY/ADMIN/匿名权限回归。
  • 并发 V36 提交与候选评分读取不出现半结果、500 或重复快照异常。
  • V32 排名和 V34 职业进度在开关关闭时字节级/字段级保持现有结果;开关开启时仅允许因 ABILITY 可计算带来的预期分数变化。

14.3 MySQL/Docker 黑盒

  1. 启动真实 MySQL/Redis/RabbitMQ 和最新 JAR;确认 health 200、Flyway 仍止于 V36、OpenAPI 148/182。
  2. 使用独立学生账号完成一份 V36 测评,先验证提交后 candidate_dimension_scores 行数不变。
  3. 开关 false 调用候选评分:ABILITY=PENDING;再在隔离环境将开关设为 true,调用评分:ABILITY=OK 且总分符合公式。
  4. 核对 sub_scores_json 白名单;企业 API 不含四维、结果 ID、题目、答案、标签或评分规则。
  5. 将结果有效期置为过去时间(只在独立验收数据上操作),重算后验证 ABILITY=PENDING;历史 V36 结果仍可由本人读取。
  6. 创建两份符合 V36 重测规则的历史结果,验证取最新而不是最高;验证并发评分刷新无 500。
  7. 队列最终 ready=0/unacked=0;回归 V32 排名、V34 职业进度、V35 RIASEC、V36 能力结果、V37 风险概览。

15. 回滚与冻结门槛

15.1 回滚

情况 操作
映射分数不符合预期 设 scoring.ability-assessment.enabled=false,停止后续接入
需要恢复当前月评分 经批准后调用既有评分重算流程;不直接删表或手改快照
发现题库/测评结果质量问题 保持 V36 历史,关闭 V38 开关;不篡改历史题目或答案

V38 不新增迁移,故不存在本模块 DDL 回滚。不得修改已执行的 V35/V36 Flyway 文件。

15.2 冻结条件

  • 所有 V38 单元、集成、MockMvc 测试通过;全量 mvn clean verify 通过。
  • python docs/verify_openapi.py 通过且 OpenAPI 仍为 148/182。
  • 真实 MySQL/Docker 黑盒为 FAIL=0,关键项不得 SKIP。
  • 开关关闭和开启两条路径均通过;V36 主链路、V32/V34/V35/V37 回归通过。
  • 不泄露题目、答案、答案键、评分规则、标签、来源结果/答卷 ID;企业端仅有聚合能力分。
  • 未对 V35/V36 题库或既有 Flyway 做回写;没有未授权的业务数据删除或全量回填。

16. 已确认的编码授权边界

负责人已于 2026-09-04 确认以下边界,编码必须严格遵守:

  1. 开关开启时,允许 ABILITY 参与候选综合分,并允许其按既有评分口径影响 V32 同岗位排名。
  2. 仅在开关开启时,候选评分规则版本允许由 candidate-scoring-v0.1 升级为 candidate-scoring-v0.2。
  3. 学生单维评分详情可展示四维聚合分、权重和有效期;企业端始终只见 ABILITY 聚合分与状态。
  4. 本版采用无新 Flyway、无新公网接口的只读投影方案。

后续若要新增人工改分、企业端明细、V35/RIASEC 接入、GROWTH 映射、历史回填或任何公开接口,必须另行设计和授权,不得作为 V38 编码的附带改动。