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 --checkexit 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>
定性证据链:
- 单独运行该类:4/4 全部通过(
mvn -Dtest="FairnessMonitoringHttpIntegrationTest" test→Tests run: 4, Failures: 0); - 失败仅在与其他类同批时出现,且两次都恰好多 15 行 → 典型的共享 H2 库数据泄漏;
- 泄漏源已定位:
candidatescore/dashboard/CompanyScoringDashboardPersistenceIntegrationTest以LocalDate.now()生成 当前期间(period = String.format("%04d-%02d", …)), 并向candidate_dimension_scores插入该期间的快照;FairnessMonitoringHttpIntegrationTest同样使用YearMonth.now(),且其populationSize未按自身前缀限定范围,因此把同批类的行一并计入; - 该泄漏源不在 R01-C 改动范围内(
git diff --name-only中无该文件); - R01-C 的生产改动仅为"教育规则常量改读规则集"与"偏好分改委托共用计算器", 不涉及任何持久化、清理或计数逻辑。
判定:属既有测试隔离脆弱性(与 A6/R01-B 定位的同类问题同源),非 R01-C 行为差异, 不构成 R01-C 阻断项。按任务要求如实记录、不写成通过,也不修改该类测试。
明确结论:
- R01-C 单独测试通过 ——
FairnessMonitoringHttpIntegrationTest单独运行 4/4 全绿; 全量mvn test/mvn clean verify(2743/2743)中该类亦通过; - 泄漏源(
CompanyScoringDashboardPersistenceIntegrationTest)与受害测试 (FairnessMonitoringHttpIntegrationTest)均不在 R01-C 生产改动内(3 个生产文件中无它们); - 该问题将由下一独立纯测试提交修复(修污染源:独占 period/studentId + 精确清理), 不并入 R01-C 提交;
- 该次定向组合命令(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独立代码复验通过,可以进入精确暂存与独立提交检查点。