# W1-T3 独立评审报告 ## 状态 完成。本次评审只读取实现、来源数据、契约和日志,并独立执行测试;未修改任何实现文件,仅写入本报告。 ## Verdict **FAIL** 存在 1 项 Critical 和 3 项 Important。当前 V36 虽然能在 H2 下通过既有测试,但没有落实已批准的“ESCO 负责职位—技能—分类主关系”方案,且部分金融职位使用与目标职位不符的 O*NET SOC 替代来源;同时 T1 清单中的多个技能分类没有导入。上述问题均阻断 T4。 ## 评审对象与依据 评审对象: - `D:\1\zhi\zhilai\mydreamjob-server\src\main\resources\db\migration\V36__skill_cloud_seed.sql` - `D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillCloudSeedDataIntegrationTest.java` 对照依据: - `changes/n03-skill-cloud/execution-contract.md` - `changes/n03-skill-cloud/design.md` - `changes/n03-skill-cloud/tasks.md` - `changes/n03-skill-cloud/data/source-manifest.md` - `docs/skill-taxonomy-sources.md` - `V35__skill_cloud_taxonomy.sql` - 本地 ESCO lookup、O*NET 31.0 CSV、实现者 RED/GREEN 日志 ## 独立检查结果 ### 1. 数量与基本约束 独立解析和 H2 测试结果一致: | 项目 | 结果 | | --- | ---: | | 行业 | 3 | | 职位 | 10 | | 技能分类 | 50 | | 技能 | 53 | | 技能别名 | 53 | | 职位技能关系 | 137 | | 非空 Importance | 133 | | 空 Importance | 4 | 每个职位均有 10 至 17 条职位技能关系,非空 Importance 数量均不少于 10。全部技能有中文标准名、非空 `source_code` 和至少一个别名;`normalized_alias` 无重复。当前 137 条关系的 `category_id` 均与同一 `occupation_id` 对应,未发现现有种子内的跨职位分类错配。 ### 2. ESCO 与 O*NET 来源核验 - V36 的 25 个 `source_type='ESCO'` URI 均可从 `changes/n03-skill-cloud/data/esco-skill-lookup.json` 找到,英文标题与 lookup 一致。 - V36 的 28 个 O*NET 技能来源三元组 `O*NET-SOC|Workplace Example|Element ID` 均能在 `software_skills.csv` 精确命中;未发现伪造组合。 - V36 注释声明的 43 组(SOC、Importance、语义元素)映射值均能在本地 O*NET CSV 中命中。其中 `Scikit-learn` 的当前工作区值已按 CSV 精确大小写修复。 - lookup 中本身存在明显误匹配,例如 Angular→computer programming、Spring Boot→install deicing boot、Node.js→apply electrolytes to cathodes and anodes、React→react to emergency situations in a live performance environment、Redis→redistribute wagered money、Microsoft Power BI→Microsoft Access。当前 V36 没有使用这些错误 URI,因此它们不是本批次已导入数据的直接错误,但该 lookup 不能作为未经复核的可信自动映射源。 ### 3. 独立测试 执行命令: ```powershell $env:JAVA_HOME='D:\java\jdk' & 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest' test ``` 结果: ```text SkillCloudSeedDataIntegrationTest: Tests run: 5, Failures: 0, Errors: 0, Skipped: 0 SkillTaxonomyMigrationTest: Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 Total: Tests run: 7, Failures: 0, Errors: 0, Skipped: 0 BUILD SUCCESS ``` 该结果与实现者 GREEN 日志一致。 ## Findings ### Critical #### C-1:V36 用 O*NET/项目映射替代了已批准的 ESCO 职位—技能—分类主关系 证据: - 执行契约第 55 行要求“使用 ESCO 作为职位—技能—分类主关系,O*NET Importance 作为 heat”。 - 设计 D1 第 32-42 行同样规定 ESCO 负责职位—技能—分类关系;来源清单第 34-39、51、60-63 行要求保留原上游来源 ID,并由项目维护跨源重要性映射。 - V35 第 34 行将 `skill_occupations.source_code` 定义为 `ESCO 职位来源 ID`。 - V36 第 30-39 行将 10 个职位的 `source_code` 全部写成 O*NET SOC,并将 `source_version_id` 指向 O*NET 来源版本。 - V36 第 203-340 行 137 条职位技能关系的 `source_version_id` 全部为 O*NET。 - 本地只有 ESCO 技能 lookup,没有 ESCO 职位—技能关系数据;V36 已导入的 25 个 ESCO 技能只提供了技能 URI,未提供任何 ESCO 职业关系。 影响:这不是字段命名的小问题,而是数据治理来源发生实质变化:职位节点被标成 O*NET SOC,职位技能关系改由 O*NET software_skills、项目分类和代理 Importance 人工组装。下游接口即使运行正确,也无法证明返回的是契约批准的 ESCO 主关系。该变化命中执行契约第 136 行的 Rewind 条件“数据来源发生实质变化”。 ### Important #### I-1:两个金融职位使用不能代表目标职位的替代 SOC,且替代映射未获批准或写入来源清单 证据: - V36 第 34 行将“金融数据分析师”写成 `13-2031.00`。本地 `occupation_data.csv` 中该 SOC 的标题是 **Budget Analysts**;原计划目标 `13-2051.00` 是 **Financial and Investment Analysts**。 - V36 第 35 行将“风控建模工程师”写成 `13-2053.00`。本地 CSV 中该 SOC 的标题是 **Insurance Underwriters**;原计划目标 `13-2054.00` 是 **Financial Risk Specialists**。 - V36 第 16-17 行仅以 SQL 注释说明替换;`source-manifest.md` 第 22-23 行没有批准代理 SOC,也没有记录替换后的目标语义。 - `13-2051.00` 和 `13-2054.00` 在本地四份 Importance CSV 中确实没有行,但“原目标无数据”不等于可以用语义不同的职业覆盖其来源 ID。 影响:Budget Analysts 与 Insurance Underwriters 的 O*NET 活动分布不能代表金融数据分析师和风控建模工程师。尤其是 Insurance Underwriters 作为风控建模工程师的热度来源,会导致技能热度与目标职位脱节。必须在来源清单和契约中明确代理规则与语义合理性,或重新选择可代表目标职位的来源;不能直接把替代 SOC 写入目标职位的 `source_code`。 #### I-2:T3 清单中的多个技能分类未落表,测试只要求至少一个分类因而漏检 证据: - `source-manifest.md` 第 18-27 行给出了每个职位的首批技能分类范围,第 30 行明确这些分类是“每个职位首批应覆盖的能力域”。 - V36 第 42-91 行为每个职位固定导入 5 个分类,共 50 个。 - 前端开发缺少清单中的“浏览器与调试”“测试”。 - DevOps 缺少“配置管理与 IaC”“可观测性”“安全”等独立能力域。 - 风控建模缺少“模型验证”等能力域。 - 教学设计师缺少“无障碍与规范”等能力域。 - 测试第 70-71 行只断言 `category_count >= 1`,因此即使只导入一个分类也会通过。 影响:已批准的 T3 分类覆盖范围没有完整交付,后续接口会缺少部分可筛选分类;当前测试无法阻止该类回归。 #### I-3:Importance 缺少逐技能来源映射和有效验证,现有数据大量依赖同值代理 证据: - V36 第 5-15 行说明所有技术技能使用“Working with Computers”等职业活动代理值,而不是 O*NET software_skills 自身的 Importance。 - V36 第 203-340 行的 133 条非空关系实际只形成 39 个不同的 `(occupation, importance)` 组合;多个技能共享同一代理值,例如同一职业内多数技术技能全部为同一个值。 - V35 第 93-95 行只保存 `importance` 和 `source_version_id`,没有保存用于推导热度的 O*NET Element ID;V36 的注释不是机器可追溯的数据。 - 测试第 106-127 行只精确校验前端 JavaScript 的 `4.88` 和 Figma 的 `null`,其余 132 条重要性记录或错误来源元素不会被发现。 影响:词云热度会因大量同值技能而主要退化为 `nameZh ASC` 排序,无法证明每个技能的热度确实来自对应 O*NET 来源。若继续该口径,应新增可追溯的 Importance 映射记录并扩大测试,或者重新定义可接受的代理规则和排序语义。 ### Minor 1. **“45 个 Importance 原值”的核验数量不实。** V36 第 5-15 行声明了 43 个职业/能力域映射,133 条非空关系只有 39 个不同 `(occupation, importance)` 组合;报告中“45 个 Importance 原值”没有可复现证据。这是核验报告计数问题,不代表已发现任意 CSV 伪造值。 2. **最终提交可能漏掉 Scikit-learn 大小写修复。** 当前工作区是 `Scikit-learn`,与 CSV 一致;但 `git status` 显示 V36 为 `AM`,即该修复尚未 staged。提交前必须确保修正包含在最终 T3 提交中。 3. **测试主要验证行数、非空值和两个单点值,不是完整来源契约测试。** 当前即使将除 JavaScript 外的 Importance 改错、将非空 ESCO/O*NET source code 改成任意非空字符串,或只保留每个职位一个分类,测试仍可能通过。分类跨职位错配也不会被测试发现,因为数据库没有覆盖 `(occupation_id, category_id)` 的复合外键。 ## 已确认没有问题的部分 - V36 是纯数据迁移:只有 7 个 `INSERT`,未包含 `UPDATE/DELETE/DROP/ALTER/CREATE`。 - `git diff HEAD` 在本任务实现范围内只显示 V36 和 T3 测试;V1-V35 未修改,`src/main/java/com/mydreamjob/skill` 不存在,未提前实现 T4 业务代码。 - 当前 137 条关系的技能分类均与所属 occupation 对应;所有技能、别名、来源版本和关系均满足 V35 现有外键及唯一约束。 - 28 个 O*NET 技能来源组合和 25 个 ESCO URI 均可从本地来源文件核实,没有发现伪造 O*NET software row 或伪造 ESCO URI。 ## 未决问题 - 必须决定是恢复 ESCO 职位—技能—分类主关系,还是正式修改 design、source manifest 和执行契约,承认以 O*NET SOC 与项目映射作为主关系。后者属于数据来源实质变化,应走 Rewind。 - 必须确认“代理 Importance”是否属于已批准方案;若属于,需记录每个技能或能力域的 O*NET Element ID、代理理由和质量规则,并重新定义同值排序的产品语义。 - 若继续使用 `13-2031.00` 和 `13-2053.00`,需要提供可审查的职位语义映射依据并更新来源清单;否则应更换代表 SOC。 - 本机未进行真实 MySQL 迁移验证,仅验证 H2 `MODE=MySQL`。该限制沿用 T2 的未决项。 ## 阻断项 T3 不通过。C-1、I-1、I-2、I-3 均必须在开始 T4 前处理;其中 C-1 和 I-1 优先触发规划/契约 Rewind 或获得明确的来源变更批准。既有 7 项测试通过不能覆盖这些来源和分类语义问题。