轻松聘AI开发
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.
 
 
 

17 KiB

R01-C 独立代码复验报告(评分与推荐规则结构收束)

日期:2026-09-18 分支:refactor/r01-structure-performance HEAD:9954600650c3e7cd40a5f4fa55bc38595973f214(复验全程未变) 复验对象:docs/r01-c-scoring-recommendation-rules-refactor-report.md 性质:仅复验 —— 未修改生产代码、未放宽既有断言、未暂存、未提交、未推送、未进入 R01-E


0. 结论

R01-C独立代码复验通过,可以进入精确暂存与独立提交检查点。

  • overviewStatus 确为唯一产出点;三处调用方全部委托;
  • 偏好公式实现点 2 → 1(HEAD 的 TalentDiscoveryService 确实内联了第二套等价公式);
  • 985/211 名单定义点 唯一;规则集不可变、无框架依赖、无 setter、集合只读;
  • 生产变更 新增 1 + 修改 2 = 3 个生产文件;
  • 三类变异均使对应测试失败,恢复后无残留;
  • Docker 已按给定路径启动,Testcontainers 26 项真实执行、Skipped=0;
  • 一处既有测试隔离脆弱性被独立发现并定性(见 §10.3),与 R01-C 无关。

1. 独立库存结果

不采信实施报告数字,独立搜索证据:

# 核查项 独立结论 证据
1 overviewStatus 是否只有 CandidateOverviewStatusPolicy 一个规则产出点 是 全 src 中 "PARTIAL" 仅在该类主代码出现一次(CandidateOverviewStatusPolicy.java:43);其余命中全部在测试的断言/测试数据中,非产出点
2 三个调用方是否全部委托该策略 是 CandidateScoringEngineService:123、:161(fromComputedResults);CompetitionRankingCalculator:103(fromSnapshots)—— 均为直接委托调用
3 JobRecommendationService 是否已经只是编排 是 preferenceScore 方法体 13 行:构造 Criteria/Target → JobPreferenceScoreCalculator.calculate(...),无任何内联权重
4 TalentDiscoveryService 修复前是否确实维护了第二套等价偏好公式 是(已由 HEAD 证实) git show HEAD:…TalentDiscoveryService.java 的 preferenceScore 为内联 total += 30/20/20/15/15 + BigDecimal.valueOf(got*100L).divide(...,2,HALF_UP) —— 与计算器逐字等价
5 本轮是否只新增一个规则集并修改两个文件 是 新增 CandidateScoringRuleSet.java;修改 CandidateScoringCalculator.java、TalentDiscoveryService.java
6 生产代码变更总数 新增 1 + 修改 2 = 3 个生产文件 与 git diff --name-status + 未跟踪文件清单一致

2. overviewStatus 唯一产出点证据

main/java/com/mydreamjob/candidatescore/CandidateOverviewStatusPolicy.java:43:  return "PARTIAL";

"PARTIAL" 在主代码中的唯一出现点即为该策略类(第 43 行)。 状态聚合器必须能够产出 PARTIAL,因此该字符串的出现位置即为产出点的充要证据。 调用方 3 处(CandidateScoringEngineService ×2、CompetitionRankingCalculator ×1)全部为委托。

结构门禁已加入独立复验测试:扫描 src/main/java/com/mydreamjob 全部 .java, 断言含 "PARTIAL" 的文件恰为 CandidateOverviewStatusPolicy.java。


3. 偏好公式修复前后实现点数量

时点 30/20/20/15/15 规则实现处 证据
修复前(HEAD) 2 —— recommendation/JobPreferenceScoreCalculator.java + talentdiscovery/TalentDiscoveryService.java(内联) git grep -l "total += 30|CITY_WEIGHT" HEAD → 2 个文件
修复后(工作区) 1 —— 仅 recommendation/JobPreferenceScoreCalculator.java 同 grep → 1 个文件

preferenceScore 方法定义仍为 2 处(两个 Service 各一),但两处都只是编排,均调用 JobPreferenceScoreCalculator.calculate(...);规则实现唯一。


4. 985/211 名单定义点数量

时点 定义点 证据
HEAD 2(C985、C211)均在 CandidateScoringCalculator.java git grep "Set<String> C985|Set<String> C211" HEAD
工作区 2(C985、C211)均在 candidatescore/rules/CandidateScoringRuleSet.java grep 主代码 → 唯一文件

判定为"唯一权威定义点":名单只在规范集内定义一次,计算器内已无名单定义 (独立测试断言 CandidateScoringCalculator.java 不含 Set<String> C985 / C211)。

项 HEAD 工作区 一致
C985 唯一项 38 38 ✔
C211 字面量条数 62 62 ✔
C211 唯一项 61 61 ✔
北京科技大学 重复项 存在 原样保留 ✔

5. 规则集不可变性和依赖边界

逐项核验 CandidateScoringRuleSet.java:

要求 结果
final ✔ public final class CandidateScoringRuleSet(私有构造)
无 setter ✔ 主代码 grep "public void set" 命中 0
无可变集合泄漏 ✔ 集合经 Collections.unmodifiableSet(new HashSet<>(...)) 包装后返回
返回集合不可修改 ✔ 独立测试断言 add/remove/clear 均抛 UnsupportedOperationException
无 Spring 注解 ✔ @Component/@Service/@Autowired/@Value 命中 0
无数据库/Repository ✔ Repository/JdbcTemplate/EntityManager 命中 0
无配置中心/环境变量 ✔ System.getenv/Properties 命中 0
不接运行时可变配置 ✔ 全部规则为静态常量,只能随代码发布变更
BigDecimal 字符串构造 ✔ 17 个常量全部 new BigDecimal("…");new BigDecimal(<数字>) 命中 0
同值不同义不合并 ✔ EDUCATION_LEVEL_MASTER(85) 与 SCHOOL_TIER_985(85) 为不同常量;EDUCATION_LEVEL_UNKNOWN(50) 与 SCHOOL_TIER_MISSING(50) 同理
规则版本单一定义点 ✔ CandidateScoringRuleSet.VERSION 为权威;CandidateScoringCalculator.RULES_VERSION 现指向该常量(赋值而非字面量)

6. BigDecimal 精确结果与边界表(独立测试推导)

独立复验测试 R01CIndependentVerificationTest(18 个用例)全部通过, 期望值手工推算,断言 isEqualByComparingTo + scale + toPlainString()(不使用近似比较):

输入 期望 说明
五维全命中(可用权重 100) 100.00 上界
五维全缺失 / 零分母 null 非 0.00
城市(30) 命中、其余缺失 100.00 单维独立
用工类型(20) 命中、其余缺失 100.00
薪资(20) 命中(边界相等 20000 == 20000) 100.00 相等视为命中
行业(15) 命中、其余缺失 100.00
岗位族(15) 命中、其余缺失 100.00
城市命中(30) + 行业未命中(15) → 30/45 66.67 HALF_UP 舍入
城市未命中(30) + 行业命中(15) → 15/45 33.33
四方存在命中两项 → 15/65 23.08
薪资低一单位(19999 < 20000) 0.00 下界
薪资高一单位(20001) 100.00
岗位 salaryMax 为 null 0.00

跨服务逐值一致:以反射调用两个 Service 的私有 preferenceScore,同一组输入下 JobRecommendationService 与 TalentDiscoveryService 的返回值 isEqualByComparingTo 相等且 scale 相同(三组输入:全命中 / 部分命中 / 全不命中)。

overviewStatus 边界(独立推导):全可用→OK;部分可用→PARTIAL;全不可用(全 PENDING)→PENDING; 含 INSUFFICIENT_DATA→INSUFFICIENT_DATA;空列表(计算结果入口)→PENDING、(快照入口)→INSUFFICIENT_DATA; null(计算结果入口)→NPE;未知状态字符串→INSUFFICIENT_DATA;输入顺序反转结果不变。


7. 三类变异测试的失败证据

# 变异 文件 结果
1 if (computableCount > 0) → > 1(overviewStatus 边界) CandidateOverviewStatusPolicy 18 run / 1 failure ✔
2 INDUSTRY_WEIGHT = 15 → 10(偏好权重) JobPreferenceScoreCalculator 18 run / 1 failure ✔
3 985 名单删除 "北京大学", (被测试覆盖的学校) CandidateScoringRuleSet 18 run / 1 failure ✔

恢复核验:

  • 变异前的模式唯一性检查:三个替换串在目标文件中的出现次数均为 1;
  • 恢复后 git diff --quiet 对两个已跟踪目标文件均为逐字节一致; 其中 JobPreferenceScoreCalculator.java 的 SHA-256 恢复为 5771647b…, 与首次诊断时测得的哈希完全相同;
  • 未跟踪的 CandidateScoringRuleSet.java 经内容检查确认变异条目已还原 ("北京大学", "清华大学", "中国人民大学" 出现次数为 1);
  • git diff --name-status 仅显示 3 个预期文件,无变异残留;git diff --check exit 0;
  • 未使用 git restore / git checkout。

透明说明:脚本首次报告的三次 restored_ok=NO 经逐层排查确认是我方脚本的变量取值缺陷, 而非文件损坏 —— 用同一 Python 读写惯用法对未变异文件做往返实验,SHA-256 保持不变; 再对目标文件执行"变异→还原"全程,A/B 哈希一致(5771647b…)。 上述三项内容级核验(git 逐字节一致 + 哈希比对 + 变异条目还原)均已独立完成。


8. Docker 启动路径与 docker info 结果

项 结果
复验开始时 docker info 失败(failed to connect … npipe:////./pipe/dockerDesktopLinuxEngine)
指定的已知路径 D:\DevApps\DockerDesktop\Docker Desktop.exe —— 存在(15,142,832 字节,2025-07-23)
启动命令 Start-Process -FilePath 'D:\DevApps\DockerDesktop\Docker Desktop.exe' -WindowStyle Hidden
轮询 每 15 秒一次,上限 180 秒
结果 约 15 秒后成功:docker info → ServerVersion=29.6.2 ✔

未重建、删除或清理任何容器、volume 或 image。


9. Testcontainers 实际执行数

mvn -Dtest="JobSearchSpecificationMySqlIntegrationTest,CandidateScoringRefreshMySqlIntegrationTest,JobSearchQuerySideEffectMySqlIntegrationTest,JobSearchQueryExplainMySqlIntegrationTest" test
→ Tests run: 26, Failures: 0, Errors: 0, Skipped: 0   BUILD SUCCESS
测试类 Tests Skipped
JobSearchSpecificationMySqlIntegrationTest 15 0
CandidateScoringRefreshMySqlIntegrationTest 6 0
JobSearchQuerySideEffectMySqlIntegrationTest 4 0
JobSearchQueryExplainMySqlIntegrationTest 1 0
合计 26 0 ✔

这 26 项正是此前因 Docker 不可用而跳过的全部用例,现已真实执行并通过。


10. 定向、全量、clean verify 的 Tests/Failures/Errors/Skipped

命令 Tests Failures Errors Skipped 结论
独立复验测试 R01CIndependentVerificationTest 18 0 0 0 ✔
定向 *CandidateScoring*Test,*OverviewStatus*Test,*JobPreference*Test,*JobRecommendation*Test,*TalentDiscovery*Test,*Fairness*Test 296 2 0 0 见 §10.3(既有隔离脆弱性)
mvn test 见 §10.1
mvn clean verify 见 §10.1

10.1 全量与 clean verify

mvn test          → Tests run: 2743, Failures: 0, Errors: 0, Skipped: 0   BUILD SUCCESS
mvn clean verify  → Tests run: 2743, Failures: 0, Errors: 0, Skipped: 0   BUILD SUCCESS
项 数值
测试总数 2743
下限校验 实施基线 2725 + 本轮独立新增 18 = 2743 ✔ 恰好等于下限
Failures / Errors / Skipped 0 / 0 / 0 ✔
BUILD SUCCESS ✔

Skipped=0 的达成路径:实施阶段因 Docker 不可用而跳过的 26 个 Testcontainers 用例, 在本轮 Docker 启动后全部真实执行(见 §9),因此不再计入 Skipped; 2743 = 2725(实施阶段总数,含 26 项跳过)+ 18(本轮独立新增)= 2699 执行 + 26 执行 + 18。

该结果同时反证 §10.3 的定性:FairnessMonitoringHttpIntegrationTest 在全量顺序下通过 (mvn test 与 mvn clean verify 均为 0 失败),说明其失败只在我方自选的定向类组合下触发, 是组合相关的既有隔离脆弱性,而非 R01-C 的行为差异。

10.2 Testcontainers 已执行

见 §9(26 项,Skipped=0)。

10.3 定向命令中的 2 个失败:既有测试隔离脆弱性(与 R01-C 无关)

现象:定向命令中 FairnessMonitoringHttpIntegrationTest 有 2 项失败:

$.data.populationSize expected:<100> but was:<115>
$.data.populationSize expected:<200> but was:<215>

定性证据链:

  1. 单独运行该类:4/4 全部通过(mvn -Dtest="FairnessMonitoringHttpIntegrationTest" test → Tests run: 4, Failures: 0);
  2. 失败仅在与其他类同批时出现,且两次都恰好多 15 行 → 典型的共享 H2 库数据泄漏;
  3. 泄漏源已定位:candidatescore/dashboard/CompanyScoringDashboardPersistenceIntegrationTest 以 LocalDate.now() 生成 当前期间(period = String.format("%04d-%02d", …)), 并向 candidate_dimension_scores 插入该期间的快照; FairnessMonitoringHttpIntegrationTest 同样使用 YearMonth.now(),且其 populationSize 未按自身前缀限定范围,因此把同批类的行一并计入;
  4. 该泄漏源不在 R01-C 改动范围内(git diff --name-only 中无该文件);
  5. R01-C 的生产改动仅为"教育规则常量改读规则集"与"偏好分改委托共用计算器", 不涉及任何持久化、清理或计数逻辑。

判定:属既有测试隔离脆弱性(与 A6/R01-B 定位的同类问题同源),非 R01-C 行为差异, 不构成 R01-C 阻断项。按任务要求如实记录、不写成通过,也不修改该类测试。

明确结论:

  1. R01-C 单独测试通过 —— FairnessMonitoringHttpIntegrationTest 单独运行 4/4 全绿; 全量 mvn test / mvn clean verify(2743/2743)中该类亦通过;
  2. 泄漏源(CompanyScoringDashboardPersistenceIntegrationTest)与受害测试 (FairnessMonitoringHttpIntegrationTest)均不在 R01-C 生产改动内(3 个生产文件中无它们);
  3. 该问题将由下一独立纯测试提交修复(修污染源:独占 period/studentId + 精确清理), 不并入 R01-C 提交;
  4. 该次定向组合命令(296/2 失败)不得写成全绿 —— 上表如实保留 Failures=2。

11. OpenAPI 数量

python ..\docs\verify_openapi.py
→ paths: 176 / operations: 214
→ All checks passed!

git diff -- docs/openapi-rc.json → 空,openapi-rc.json 未被修改 ✔


12. 精确文件清单

本轮 R01-C 生产改动(新增 1 + 修改 2 = 3 个生产文件)

新增: mydreamjob-server/src/main/java/com/mydreamjob/candidatescore/rules/CandidateScoringRuleSet.java
修改: mydreamjob-server/src/main/java/com/mydreamjob/candidatescore/CandidateScoringCalculator.java
修改: mydreamjob-server/src/main/java/com/mydreamjob/talentdiscovery/TalentDiscoveryService.java

实施者新增测试(4):mydreamjob-server/src/test/java/com/mydreamjob/common/r01c/ 下 OverviewStatusPolicyCharacterizationTest、PreferenceScoreCharacterizationTest、 CandidateScoringRuleSetCharacterizationTest、RecommendationOrderingCharacterizationTest

本轮独立复验新增(2)

mydreamjob-server/src/test/java/com/mydreamjob/common/r01c/R01CIndependentVerificationTest.java
docs/r01-c-independent-verification-20260918.md

文档(1 修改):docs/backend-development-roadmap.md

本轮未修改任何生产代码 —— 复验前后 git diff --name-only -- …/src/main 恒为上述 2 个文件。


13. 是否修改迁移、ErrorCode、接口、依赖

项 结果
V1–V45 / Flyway 未修改(git diff --name-status -- …/db/migration 为空)
ErrorCode 未修改
DTO 未修改
公网接口 / OpenAPI 契约 未修改(176/214,openapi-rc.json 未变)
pom.xml / 依赖 未修改
Security / Token / 角色 / requestId 未修改
D4 快照物化语义 未修改
排名、申诉、公平性抑制规则 未修改

未为了测试通过修改生产公式或放宽既有断言(独立测试的期望值均独立推导; 实施者的 40 个特征测试断言未被改动)。


14. Git 状态

项 结果
分支 refactor/r01-structure-performance ✔
HEAD 9954600650c3e7cd40a5f4fa55bc38595973f214(未变)✔
git diff --cached --name-only 空 ✔
git diff --check exit 0 ✔
git diff --name-status 仅 docs/backend-development-roadmap.md、CandidateScoringCalculator.java、TalentDiscoveryService.java(无变异残留)✔
HEAD 与 upstream 0 0 ✔
历史未跟踪文件 完整保留 ✔
残留 Java/Maven 进程 无 ✔

15. 未暂存、未提交、未推送、未进入 R01-E

确认: 未执行 git add;未创建提交;未推送;未执行 reset/restore/checkout/clean; 未进入 R01-E;未继续开发。


R01-C独立代码复验通过,可以进入精确暂存与独立提交检查点。