From 490445587768408180c02460d76998b4b551afe3 Mon Sep 17 00:00:00 2001 From: pwklppg <123456@.com> Date: Mon, 14 Sep 2026 17:38:46 +0800 Subject: [PATCH] docs: revise skill cloud sources and record W1 reviews --- changes/n03-skill-cloud/.spec-superflow.yaml | 35 +++ .../.superpowers/sdd/execution-plan.yaml | 24 ++ .../.superpowers/sdd/progress.md | 13 ++ .../.superpowers/sdd/reviews/W1-T1.md | 54 +++++ .../.superpowers/sdd/reviews/W1-T2.md | 98 +++++++++ .../sdd/reviews/W1-T3-final-review.md | 136 ++++++++++++ .../sdd/reviews/W1-T3-rereview-2.md | 207 ++++++++++++++++++ .../sdd/reviews/W1-T3-rereview.md | 158 +++++++++++++ .../.superpowers/sdd/reviews/W1-T3.md | 155 +++++++++++++ .../n03-skill-cloud/data/source-manifest.md | 12 +- changes/n03-skill-cloud/design.md | 180 +++++++++++++++ changes/n03-skill-cloud/execution-contract.md | 147 +++++++++++++ changes/n03-skill-cloud/tasks.md | 38 ++++ docs/skill-taxonomy-sources.md | 12 +- 14 files changed, 1257 insertions(+), 12 deletions(-) create mode 100644 changes/n03-skill-cloud/.spec-superflow.yaml create mode 100644 changes/n03-skill-cloud/.superpowers/sdd/execution-plan.yaml create mode 100644 changes/n03-skill-cloud/.superpowers/sdd/progress.md create mode 100644 changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T1.md create mode 100644 changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T2.md create mode 100644 changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3-final-review.md create mode 100644 changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3-rereview-2.md create mode 100644 changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3-rereview.md create mode 100644 changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3.md create mode 100644 changes/n03-skill-cloud/design.md create mode 100644 changes/n03-skill-cloud/execution-contract.md create mode 100644 changes/n03-skill-cloud/tasks.md diff --git a/changes/n03-skill-cloud/.spec-superflow.yaml b/changes/n03-skill-cloud/.spec-superflow.yaml new file mode 100644 index 0000000..ae03fe3 --- /dev/null +++ b/changes/n03-skill-cloud/.spec-superflow.yaml @@ -0,0 +1,35 @@ +version: 1 +state: executing +workflow: full +change_name: n03-skill-cloud +artifact_language: zh-CN +dp_0_confirmed: true +dp_0_result: confirmed +dp_0_timestamp: 2026-09-14T07:30:33Z +dp_0_decisions: | + 变更名称:n03-skill-cloud;一句话意图:基于 ESCO + O*NET 开源词库提供行业技能词云、个人 TOP10 技能匹配度和学习路径接口。 + 工作流:full;选择原因:新增模块、Flyway 数据结构、三个 API、权限与测试,属于跨模块行为变更。 + 约束:仅登录 STUDENT 可访问;行业和职位必填、技能分类默认全部;热度使用 O*NET Importance;无权重技能保留且排后;不调用 AI;不接课程平台;首版仅覆盖互联网、金融、教育及代表性职位;词库通过 Flyway 落库。 + 相关优化:保持聚焦,不纳入平台岗位热度聚合、后台词库维护、学习进度记录或前端开发。 + 沟通方式:按决策点审查,规划产物一次生成后统一确认。 + artifact_language=zh-CN +dp_1_result: confirmed +dp_1_timestamp: 2026-09-14T07:30:33Z +dp_2_result: approved: proposal, three delta specs, design, and tasks validated +dp_2_confirmed: true +dp_2_timestamp: 2026-09-14T07:51:20Z +contract_hash: 819762ae94f2cb3d11820904d013947c367e4bbb32fe8773a3510a253be9ca97 +dp_3_result: approved: execution contract B1-B3; revision 2 allows ESCO primary plus O*NET software_skills supplemental source with machine-traceable importance provenance +dp_3_confirmed: true +dp_3_timestamp: 2026-09-14T07:56:28Z +execution_mode: sdd +plan_revision: 1 +dp_4_result: approved: SDD selected; W1 serial T1-T3, W2 serial T4-T7 depends W1, W3 serial T8-T10 depends W2 +dp_4_confirmed: true +dp_4_timestamp: 2026-09-14T08:00:29Z +batches_completed: 0 +dp_5_result: adjudicated: approved one focused repair for exact per-skill source and full proxy assertions +dp_5_timestamp: 2026-09-14T09:21:01Z +artifacts: + order: [proposal, specs, design, tasks] + skip: [] \ No newline at end of file diff --git a/changes/n03-skill-cloud/.superpowers/sdd/execution-plan.yaml b/changes/n03-skill-cloud/.superpowers/sdd/execution-plan.yaml new file mode 100644 index 0000000..ac8e97c --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/execution-plan.yaml @@ -0,0 +1,24 @@ +plan_revision: 1 +selected_mode: sdd +contract_hash: 819762ae94f2cb3d11820904d013947c367e4bbb32fe8773a3510a253be9ca97 +created_at: 2026-09-14T08:00:29Z +current: W2 +waves: + - id: W1 + strategy: serial + tasks: [T1, T2, T3] + depends_on: [] + state: passed + receipt: pass (T1, T2, T3 final review) + - id: W2 + strategy: serial + tasks: [T4, T5, T6, T7] + depends_on: [W1] + state: eligible + receipt: null + - id: W3 + strategy: serial + tasks: [T8, T9, T10] + depends_on: [W2] + state: blocked + receipt: null \ No newline at end of file diff --git a/changes/n03-skill-cloud/.superpowers/sdd/progress.md b/changes/n03-skill-cloud/.superpowers/sdd/progress.md new file mode 100644 index 0000000..322ec64 --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/progress.md @@ -0,0 +1,13 @@ +# N03 SDD Progress Ledger + +- 2026-09-14T08:00:29Z Plan revision 1 created for mode SDD. +- W1 eligible: T1 → T2 → T3 (serial, no prerequisite). +- W2 blocked on W1: T4 → T5 → T6 → T7. +- W3 blocked on W2: T8 → T9 → T10.- 2026-09-14T08:05:32Z Task T1: complete (commit b45538d, review W1-T1 PASS, 10 representative positions fixed). +- 2026-09-14T08:19:38Z Task T2: complete (review W1-T2 PASS, RED->GREEN, 2 tests pass). +- 2026-09-14T08:28:32Z Task T2: complete (commit 93477bd, review W1-T2 PASS, migration tests 2/2). +- 2026-09-14T08:42:56Z Task T3: failed attempt 1/3 - critical source-model conflict; review W1-T3 FAIL. Hybrid O*NET software skills not authorized by current contract. +- 2026-09-14T09:07:23Z Task T3: failed attempt 2/3 - review W1-T3-rereview FAIL; data correct but tests only sampled provenance. +- 2026-09-14T09:17:50Z Task T3: failed attempt 3/3 - review W1-T3-rereview-2 FAIL; source data correct but test falsifiability still incomplete. Automatic dispatch stopped; human adjudication required. +- 2026-09-14T09:21:01Z DP-5 adjudication: continue with one focused test-falsifiability repair. +- 2026-09-14T09:38:16Z Task T3: complete after adjudicated repair (final review PASS; 10 tests green; 221 exact mappings; W1 complete). diff --git a/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T1.md b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T1.md new file mode 100644 index 0000000..f76e17e --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T1.md @@ -0,0 +1,54 @@ +# W1-T1 独立评审报告 + +## 结论 + +**PASS** + +T1 文档交付满足已批准执行契约中 B1 对来源、覆盖边界、映射责任、导入规则和迁移安全的要求,未发现会阻断 T2 开始的 Critical 或 Important finding。 + +## 评审范围与证据 + +- `D:\1\zhi\zhilai\changes\n03-skill-cloud\data\source-manifest.md` +- `D:\1\zhi\zhilai\docs\skill-taxonomy-sources.md` +- 对照依据:`execution-contract.md`、`tasks.md`、`design.md` 及三份 spec。 + +独立检查结果: + +1. 两份文件均存在且非空:manifest 为 8332 bytes/77 行,docs 为 8485 bytes/79 行。移除各自标题及 `docs` 的引言段后,事实正文逐行完全一致。 +2. 固定来源完整:两份文件均明确 ESCO dataset v1.2.1(Last update 10/12/2025,Release v5.7.0)、O*NET 31.0 Database、对应许可、官方归属 URL 和 ESCO 指定署名语句。证据:manifest 第 7-10、47-52 行;docs 第 9-12、49-54 行。 +3. 运行时离线约束明确:禁止运行时访问 ESCO、O*NET 或在线词库,首版数据随 Flyway 进入本地数据库。证据:manifest 第 10、43 行;docs 第 12、45 行。 +4. 项目映射责任明确:项目维护中文名称、别名、细粒度技能分类及行业/职位映射;上游来源 ID 原样保存、不得重编号或伪造。证据:manifest 第 36-40、58-63 行;docs 第 38-42、60-65 行。 +5. T2/T3 分工明确:T2 只建七张表、外键、唯一约束和索引;T3 才导入三类行业、代表性职位、分类、技能、别名及 O*NET Importance。证据:manifest 第 34 行;docs 第 36 行。 +6. 中文名与缺权规则明确:入选技能必须有非空 `name_zh`;无 O*NET Importance 的技能必须保留并写 `importance=null`,查询映射为 `heat=null` 且排在有权重技能之后。证据:manifest 第 37-38 行;docs 第 39-40 行。 +7. 迁移安全规则明确:计划使用 V35/V36;若编号被占用必须回退刷新;不得改写 V1-V34 或任何已执行迁移。当前迁移目录仍以 V34 为最新,V35/V36 均不存在,因此该规则与当前仓库状态一致。证据:manifest 第 42 行;docs 第 44 行。 +8. 覆盖矩阵覆盖互联网、金融、教育三类行业,共 10 个实际列出的代表性职位,计数为互联网 4、金融 3、教育 3,每个职位均给出首批技能分类范围。证据:manifest 第 14-30 行;docs 第 16-32 行。 +9. 未发现 `TBD`、`TODO`、“稍后确定”、“待定”或 placeholder;未发现与已批准 spec、design 或 execution contract 的行为冲突。 +10. 未发现 T1 越界实现:`com.mydreamjob.skill` 主代码和测试目录均不存在,V35/V36 迁移也不存在,符合 T1 只交付来源文档的范围。 + +## Findings + +### Critical + +无。 + +### Important + +无。 + +### Minor + +1. **派发文字中的“11 个职位”与实际枚举的 10 个职位不一致。** + 已批准 execution-contract、tasks.md、design.md 和 spec 均只规定“互联网、金融、教育及代表性职位”,没有规定硬性 11 个;本轮派发提示中的枚举实际为互联网 4、金融 3、教育 3,共 10 个。两份交付物覆盖全部实际枚举职位,并明确说明未自行新增未批准职位。该差异不影响 T1 验收,也不阻断 T2;建议后续派发统一按 10 个职位表述,除非人工补充第 11 个已批准职位名称。 + +2. **T1 固化的是技能分类范围和技能级来源规则,而不是逐个技能实例的 ESCO/O*NET 来源 ID 与中文标准名清单。** + 这与 T1 的“可审查数据边界”目标一致,不构成当前阻断;具体技能级来源 ID、中文标准名及 Importance 映射仍须在 T3 种子数据中逐项落实,并由迁移测试验证。 + +## 未决问题 + +- 无阻断 T2 的未决问题。 +- T3 必须履行文档已固定的技能级约束:稳定来源 ID 原样保存、`name_zh` 非空、缺失 Importance 写 `null`。 +- 如后续验收仍坚持“11 个职位”,必须由人工提供第 11 个已批准职位名称;当前无任何已批准依据可推导该职位。 + +## 阻断项 + +无。 \ No newline at end of file diff --git a/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T2.md b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T2.md new file mode 100644 index 0000000..50a6378 --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T2.md @@ -0,0 +1,98 @@ +# W1-T2 独立评审报告 + +## 状态 + +完成。本次评审未修改任何实现文件,仅写入本报告。 + +## Verdict + +**PASS** + +T2 已建立执行契约要求的七张词库表、关键字段、外键、唯一约束、CHECK 约束和两个查询索引;迁移可执行,且未顺带实现 T3 种子数据或 Java 业务代码。未发现阻断 T3 的 Critical 或 Important finding。 + +## 评审对象与依据 + +评审对象: + +- `D:\1\zhi\zhilai\mydreamjob-server\src\main\resources\db\migration\V35__skill_cloud_taxonomy.sql` +- `D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillTaxonomyMigrationTest.java` + +对照依据: + +- `D:\1\zhi\zhilai\changes\n03-skill-cloud\execution-contract.md` +- `D:\1\zhi\zhilai\changes\n03-skill-cloud\design.md` +- `D:\1\zhi\zhilai\changes\n03-skill-cloud\tasks.md` +- T2 RED/GREEN 日志 +- `git diff --cached`、`git diff HEAD` 和 `git diff --check` +- JDK 17 下的独立测试执行结果 + +## 独立检查结果 + +1. **七张表及关键列存在。** V35 第 5、17、28、44、60、77、88 行分别创建 `skill_source_versions`、`skill_industries`、`skill_occupations`、`skill_categories`、`skills`、`skill_aliases`、`occupation_skills`。测试通过真实 `DatabaseMetaData` 查询七个表的全部预期列,见测试第 55-74、223-240 行,不是 SQL 文本断言。 + +2. **外键真实存在。** V35 第 40-41、57、74、85、105-108 行建立行业、来源版本、职位、分类、技能和别名之间的 9 条外键;测试第 86-94 行通过 `getImportedKeys` 逐条验证。 + +3. **唯一约束真实存在。** V35 第 14、25、39、53、71-72、83-84、98 行覆盖来源版本、行业编码、行业内职位编码、职位内分类编码、技能编码、技能来源、别名归一化值、技能别名和职位技能关系;测试第 76-84 行逐项验证唯一索引。 + +4. **CHECK 约束符合契约。** 分类模板限定为 `PROGRAMMING_LANGUAGE`、`FRAMEWORK`、`TOOL`、`GENERAL`,见 V35 第 54-56 行;技能来源限定为 `ESCO`、`O*NET`、`PROJECT`,见第 73 行;关系类型限定为 `ESSENTIAL`、`OPTIONAL`、`PROJECT`,见第 102-104 行。非法模板、非法来源和非法关系类型均通过实际失败插入验证,见测试第 159-169、182-186 行。 + +5. **`importance` 可空且无默认权重。** V35 第 93 行定义 `DECIMAL(4,2)` 且没有 `NOT NULL` 或 `DEFAULT`;第 99-101 行只约束为空或位于 0.00-5.00。测试插入时省略该列并断言查询结果为 `null`,随后验证 `4.25` 可保存,见测试第 133-153 行。 + +6. **中文名称非空。** 行业、职位、分类和技能的中文名称均定义为 `NOT NULL`,见 V35 第 20、32、48、63 行;测试第 176-181 行通过插入 `name_zh=null` 并断言失败。 + +7. **两个查询索引真实存在。** V35 第 109-110 行建立 `(occupation_id, category_id)` 与 `(occupation_id, importance)` 索引;测试第 96-97 行通过数据库元数据验证。`occupation_id + importance` 可被 MySQL 正向或反向扫描用于热度降序查询。 + +8. **迁移范围正确。** `git diff --name-status HEAD` 在迁移和主代码范围内仅显示新增 `V35__skill_cloud_taxonomy.sql` 及新测试文件;V1-V34 未修改,V36 尚不存在,`src/main/java` 无变更,未顺带实现 T3 或 Java 业务代码。 + +9. **测试失败有效性、隔离和清理。** RED 日志第 60-125 行显示 V35 缺失时两项测试分别因缺表失败;测试使用独立的 `integrationdb_skill_taxonomy` H2 数据库,见第 32-33 行;`@AfterEach` 按外键逆序清理插入数据,见第 196-221 行。未发现仅源码文本断言、共享默认 H2 库污染或明显漏清理。 + +10. **MySQL/H2 兼容性。** `AUTO_INCREMENT`、InnoDB、`utf8mb4_unicode_ci`、`DATETIME DEFAULT/ON UPDATE CURRENT_TIMESTAMP`、表内 `KEY`、CHECK 和命名外键均属于 MySQL 8.4 支持的语法;H2 在 `MODE=MySQL` 下已成功执行整份迁移。未发现仅 H2 成立的 DDL 结构。由于本机没有 Docker,且本地 MySQL 服务无可用测试凭据,本报告未进行真实 MySQL 样库执行;该验证限制不阻断仅要求 H2 迁移测试的 T2。 + +## 独立测试结果 + +执行环境与命令: + +```powershell +$env:JAVA_HOME='D:\java\jdk' +& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' -Dtest=SkillTaxonomyMigrationTest test +``` + +结果: + +```text +Java version: 17.0.16 +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 +BUILD SUCCESS +Total time: 36.770 s +``` + +提供的 GREEN 日志与独立执行结果一致:JDK 17.0.16、2 项测试全部通过、`BUILD SUCCESS`。RED 日志也证明测试确实依赖真实 V35 迁移结果。 + +## Findings + +### Critical + +无。 + +### Important + +无。 + +### Minor + +1. **迁移测试只断言列存在,不直接断言全部列类型和可空性。** 测试第 223-240 行收集的是列名;当前 DDL 的类型和可空性经人工核对符合 design/契约,因此不阻断,但未来类型回归不会全部被测试发现。建议后续在数据库元数据断言中补充 `importance DECIMAL(4,2) nullable`、各 `name_zh NOT NULL` 及关键 ID 列类型检查。 + +2. **`importance` 的越界分支未行为测试。** 测试覆盖了 `null` 和 `4.25`,但未尝试 `-0.01` 或 `5.01`,因此当前 CHECK 上下界只经人工核对,见 V35 第 99-101 行。风险较低,可在 T3 或端到端回归中补充。 + +3. **约束失败断言捕获所有 `DataAccessException`,未锁定具体约束。** 测试第 330-332 行通过这一点;当前各失败用例的其余字段均合法,因此实际原因基本可确定,但测试信息量和诊断精度略低。建议后续断言约束名或 SQL 状态/错误码,进一步防止因无关错误而误通过。 + +4. **测试文件末尾存在多余空白行,`git diff --check` 报告 `new blank line at EOF`。** 证据为测试文件第 334 行。纯格式问题,不阻断 T3。 + +## 未决问题 + +- 本报告仅在 H2 `MODE=MySQL` 下实际执行了迁移;真实 MySQL 8.4 执行应在已有 MySQL 凭据或 Docker 环境中由后续发布级验证补充。静态检查未发现 MySQL 不兼容语法。 +- 分类必须属于职位属于服务层校验责任,T2 只提供基础外键;该跨列一致性须在 T4/T8 按 design 第 117、170 行落实并验证。 + +## 阻断项 + +无。 \ No newline at end of file diff --git a/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3-final-review.md b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3-final-review.md new file mode 100644 index 0000000..f1db1af --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3-final-review.md @@ -0,0 +1,136 @@ +# W1-T3 最终独立复审报告(DP-5 唯一复审) + +## 状态 + +完成。本轮只读取实现、V35/V36、期望映射、证据快照和前序评审报告,并独立执行指定测试、CSV 来源审计与文件哈希核验;未修改任何实现或测试文件,仅写入本报告。 + +## Verdict + +**PASS** + +第三轮 findings 已全部关闭,允许进入 T4。未发现 Critical 或 Important 阻断项。 + +## 独立测试输出 + +环境: + +- `JAVA_HOME=D:\java\jdk` +- Java:OpenJDK 17.0.16 +- Maven:`D:\maven\apache-maven-3.9.7\bin\mvn.cmd` +- 工作目录:`D:\1\zhi\zhilai\mydreamjob-server` + +命令: + +```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: 8, Failures: 0, Errors: 0, Skipped: 0 +SkillTaxonomyMigrationTest: Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 +Total: Tests run: 10, Failures: 0, Errors: 0, Skipped: 0 +BUILD SUCCESS +Total time: 39.479 s +``` + +## 第三轮 Finding 逐项关闭状态 + +### 1. 47/47 条 O*NET 技能来源逐技能精确锁定:关闭 + +- `skill-cloud-expected-mappings.csv` 含 47 条 `SKILL_SOURCE` + `O*NET` 记录,47 个技能编码和 47 个来源三元组均唯一。 +- `verifyExpectedMappings` 先通过 `assertSameMappingKeys` 精确比较期望文件与数据库的全部实体键;随后对每一条执行 `assertMappingMatches`,逐条比较 `source_type` 和 `source_code`。 +- `verifySkillEvidence` 逐条将 47 个 `skill_code -> SOC|Workplace Example|Element ID` 与证据快照核对,并用 `assertEvidenceKeySet` 要求期望软件来源集合与快照 47 条集合完全相等。 +- 交换任意两个 O*NET 技能来源时,至少一行会在逐字段断言处失败,不再存在仅验证“来源三元组集合合法”的缺口。 +- `assertMappingVerifierRejectsTampering` 的交换来源自检通过,且它调用的是全量循环实际使用的同一个比较函数。 + +### 2. 137/137 条非空 Importance 全字段精确断言:关闭 + +- 期望文件包含 139 条职位技能关系,其中 137 条非空 Importance、2 条空 Importance。 +- 137 条非空关系均包含非空 `expected_source_code`、`expected_element_id`、`expected_importance`,且 `expected_is_proxy=1`。 +- 全量循环对每条关系精确比较 `source_type`、`source_code`、`element_id`、`importance` 和 `importance_is_proxy`;数值使用 `0.001` 容差,其余字段使用严格相等。 +- 任意一条非空关系的 proxy 从 `1` 改为 `0`、来源 SOC/Element ID 被替换、或数值被修改,都会在对应关系上失败。 +- 2 条空 Importance 关系均断言来源 SOC、Element ID、Importance 为空且 `importance_is_proxy=0`。 + +### 3. expected-mappings 覆盖且仅覆盖 10/10 occupation、72/72 skill、139/139 relation:关闭 + +- 期望文件共 221 条数据行:`OCCUPATION_SOURCE=10`、`SKILL_SOURCE=72`、`RELATION=139`。 +- `record_type + entity_key` 重复数:0。 +- `assertExpectedMappingCounts` 锁定三类记录数量;`assertSameMappingKeys` 进一步比较期望与数据库完整键集合,缺项和多余项都会失败。 +- 独立测试通过证明 Flyway 实际生成的 10 个职位、72 个技能和 139 条关系与期望文件一一对应。 + +### 4. expected-mappings 与证据快照交叉一致,且 reference 匹配官方 O*NET 31.0 CSV:关闭 + +独立解析结果: + +| 检查项 | 结果 | +| --- | ---: | +| `onet-31-reference.csv` 数据行 | 92 | +| SOFTWARE 证据 | 47,键无重复 | +| IMPORTANCE 唯一证据 | 45,键无重复 | +| 47/47 SOFTWARE 在官方 `software_skills.csv` 精确命中 | 通过 | +| 45/45 IMPORTANCE 在声明官方 CSV 中精确命中 SOC、Element ID、Scale ID=IM、Data Value | 通过 | +| 137/137 加权关系的证据键和值可回溯到 reference | 通过 | +| 期望文件引用的 SOFTWARE 与 reference SOFTWARE 集合相等 | 通过 | +| 期望文件引用的唯一 IMPORTANCE 键与 reference IMPORTANCE 集合相等 | 通过 | + +`target/onet-31` 当前 CSV 哈希: + +```text +software_skills.csv 6AABB96B464288DB849580E6510530EFFF3F80E25DDE2BB23F1FC36BB526B016 +work_activities.csv 6C266205B2926C0C854A7A577AAE29FE492C71BB0DF7E197ADCA830B58FE4EA1 +knowledge.csv 88092E17D16096CDEBE292FEE4389DEB460EDDA24AE8B4637212C69C71562F55 +essential_skills.csv 68C4D31D8C55077238395DCA40CC4E6D32E6A608B5474BB8264B1F98AE415BD7 +transferable_skills.csv F17D0419E818A3BF2F55076510C76C4AEF01CC8F5AEE2670D2F44F6F44AC60F7 +occupation_data.csv A09EAE1D6609686E44E05B7290993A1C8B523D8CA224BC0EEDC194C855C3EE02 +``` + +这些 CSV 哈希与前次独立审计记录的 O*NET 31.0 官方发布包解压文件哈希一致。本轮再次请求官方 ZIP 时收到 HTTP 200,但连接在完成前中断;因此未重复完成 ZIP 级哈希计算。该限制不影响本轮映射验证,因为 reference 已与哈希不变的官方 CSV 逐行精确匹配。 + +### 5. 测试独立性、无 target 依赖、可证伪性:关闭 + +- `SkillCloudSeedDataIntegrationTest` 只使用 `ClassPathResource` 读取 `skill-cloud/onet-31-reference.csv` 和 `skill-cloud/skill-cloud-expected-mappings.csv`。 +- 测试源码未引用 `V36` 文件、`target/onet-31`、文件系统绝对路径或其他运行后才生成的期望数据。 +- 期望映射是独立静态资源;数据库实际值通过 Flyway 迁移和 JDBC 读取,不是从 V36 文本反推后生成期望文件。 +- 测试可在没有 `target/onet-31` 的前提下运行;`target/onet-31` 仅在本次人工来源审计中用于交叉核对。 +- 自检覆盖来源交换、proxy 翻转和 Importance 篡改,且生产断言路径已对全部 221 条期望实体执行。 + +### 6. 68/68 分类、8 个唯一 ESCO occupation URI、混合来源契约及 V35/V36 约束:关闭 + +- 测试 `seedDataCoversIndustriesOccupationsAndEveryDeclaredCategory` 通过:3 个行业、10 个职位、68 个分类,10 个职位的分类清单逐项一致,每个分类至少关联 1 条技能关系,每个职位至少 10 个加权技能。 +- 10 个职位均使用 ESCO 职位来源;8 个 URI 值唯一,均符合 `http://data.europa.eu/esco/occupation/...` 格式。DevOps 与量化开发按已批准契约复用软件开发职位 URI。 +- 混合来源契约保持明确:25 个技能为 ESCO,47 个具体技术为 O*NET `software_skills` 补充;`skills.source_type`、来源三元组唯一约束和来源版本外键一致。 +- V35 仍具备所需唯一约束、外键、查询索引、`importance` 0–5 CHECK、非空 Importance 来源完整性 CHECK 和 proxy `0/1` CHECK。 +- V36 的 137 条非空 Importance 均保存 O*NET SOC、Element ID 和 proxy=1;2 条无可靠映射的关系保留技能并写入 NULL,proxy=0。 +- H2 `MODE=MySQL` 集成迁移测试通过;本轮未新增真实 MySQL 8.4 执行要求。 + +## 文件哈希 + +```text +A9399B4C1D9525F3ED91694FE8F02433C1BF634B458C693D91A4014C1252183B V35__skill_cloud_taxonomy.sql +0D5D39A556B6D72CDB21422ED7020B1EAEC0BE8661635B497D12B40D9D104724 V36__skill_cloud_seed.sql +2A281B099AF4DB5E8117A99D8281191EBC60E68B4BC0E153551AD2013B0071A5 SkillCloudSeedDataIntegrationTest.java +4672287B3197784E4144EE36C322AD27AB3BD6955D6626D885E31349C9A19435 onet-31-reference.csv +F6D7545F01C18A6C13D17C00A051D757AED4A8BD2BEBA34757D5DEC1403A6C7B skill-cloud-expected-mappings.csv +``` + +## Findings + +### Critical + +无。 + +### Important + +无。 + +### Minor + +- 官方发布包 ZIP 本轮重下载未完成,reference 的官方身份依赖“官方 CSV 哈希不变 + 前次已建立的官方 ZIP 审计记录”。当前不影响 T3 正确性或 T4 放行。 +- 测试不会联网重新校验官方 CSV 哈希;这是有意的离线设计,但后续若更新 reference,仍需人工保留来源审计记录。 + +## 是否允许进入 T4 + +**允许。** 第三轮两项 Important finding 均已关闭,10/10 测试通过,未发现新的 Critical 或 Important 问题。 diff --git a/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3-rereview-2.md b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3-rereview-2.md new file mode 100644 index 0000000..d3b97a9 --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3-rereview-2.md @@ -0,0 +1,207 @@ +# W1-T3 最终独立复审报告(第二轮修复) + +## 状态 + +完成。本次复审只读取实现、迁移、来源清单、前两轮报告和本地/官方 CSV,并独立执行指定测试、来源审计及 ESCO URI 只读核验;未修改任何实现文件,仅写入本报告。 + +## Verdict + +**FAIL** + +第二轮唯一 Important finding 尚未完全关闭: + +- 47 条 O*NET `software_skills` 来源当前数据全部真实,但测试只验证“每条 DB 三元组存在于快照集合中”,没有验证“每个技能代码对应唯一正确的三元组”;交换两个未被单点断言的技能来源后,测试仍会通过。 +- 137 条非空 Importance 的值均已逐条核对,但测试对 `importance_is_proxy` 仍主要是 `0/1` 枚举检查,仅 10 条做 `proxy=1` 精确断言;将其余 127 条中的任一 `1` 改为 `0` 后,测试仍会通过。 + +以上属于回归保护缺口,不阻断当前种子数据的正确性,但按 TO-12 和本轮“篡改必须失败”的要求,仍阻断 T4。 + +## 独立测试结果 + +环境: + +- `JAVA_HOME=D:\java\jdk` +- Maven:`D:\maven\apache-maven-3.9.7\bin\mvn.cmd` +- 工作目录:`D:\1\zhi\zhilai\mydreamjob-server` + +执行: + +```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: 7, Failures: 0, Errors: 0, Skipped: 0 +SkillTaxonomyMigrationTest: Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 +Total: Tests run: 9, Failures: 0, Errors: 0, Skipped: 0 +BUILD SUCCESS +Total time: 39.800 s +``` + +## 来源审计方法与结果 + +### 1. O*NET 31.0 官方来源核验 + +独立下载官方发布包: + +- URL:`https://www.onetcenter.org/dl_files/database/db_31_0_csv.zip` +- HTTP:`200` +- 文件大小:`16,237,378` bytes +- HTTP `Last-Modified`:`Tue, 25 Aug 2026 03:46:00 GMT` +- 官方 ZIP SHA-256:`55033FC68B4C13EC23E7F74DC6378660F6E854E75D55D6E333AE0A761D3987CD` + +从官方 ZIP 解压后与 `mydreamjob-server/target/onet-31` 逐文件比较,SHA-256 完全一致: + +| 文件 | SHA-256 | +| --- | --- | +| `software_skills.csv` | `6AABB96B464288DB849580E6510530EFFF3F80E25DDE2BB23F1FC36BB526B016` | +| `work_activities.csv` | `6C266205B2926C0C854A7A577AAE29FE492C71BB0DF7E197ADCA830B58FE4EA1` | +| `knowledge.csv` | `88092E17D16096CDEBE292FEE4389DEB460EDDA24AE8B4637212C69C71562F55` | +| `essential_skills.csv` | `68C4D31D8C55077238395DCA40CC4E6D32E6A608B5474BB8264B1F98AE415BD7` | +| `transferable_skills.csv` | `F17D0419E818A3BF2F55076510C76C4AEF01CC8F5AEE2670D2F44F6F44AC60F7` | +| `occupation_data.csv` | `A09EAE1D6609686E44E05B7290993A1C8B523D8CA224BC0EEDC194C855C3EE02` | + +因此,`target/onet-31` 中的本轮 CSV 来源与官方 O*NET 31.0 发布包一致,不是本地改写版本。 + +### 2. 证据快照与官方 CSV 对照 + +`mydreamjob-server/src/test/resources/skill-cloud/onet-31-reference.csv`: + +- 总行数:92 条数据行。 +- `SOFTWARE`:47 条,47 个唯一键,无重复。 +- `IMPORTANCE`:45 条唯一 `SOC + Element ID` 组合,无重复。 +- 47/47 条 `SOFTWARE` 三元组均精确命中 `software_skills.csv`。 +- 45/45 条 `IMPORTANCE` 均命中声明的官方 CSV,`Data Value` 精确一致。 +- 未发现无依据行、伪造三元组或伪造数值。 +- `SOFTWARE` 的 `data_value` 均为空,符合 `software_skills` 没有 Importance 值的结构。 + +结论:当前证据快照确实来自 O*NET 31.0 官方 CSV,数据与官方 CSV 完全一致。 + +### 3. V36 数据全量审计 + +独立解析 V36 后确认: + +| 项目 | 结果 | +| --- | ---: | +| O*NET 技能 | 47 | +| DB 使用的 SOFTWARE 三元组集合与快照集合相等 | 是 | +| 职位技能关系 | 139 | +| 非空 Importance | 137 | +| 空 Importance | 2 | +| 非空关系正确命中快照键和值 | 137/137 | +| 非空关系当前 `importance_is_proxy=1` | 137/137 | +| 空关系的 SOC/Element ID 为 NULL、proxy=0 | 2/2 | +| 137 条关系使用的唯一 Importance 键 | 45 | +| 快照 IMPORTANCE 键集合与实际使用集合相等 | 是 | + +结论:当前数据层没有发现伪造来源、错误数值或错误代理标记。 + +### 4. Manifest 分类覆盖 + +从 `source-manifest.md` 独立解析 10 个职位及声明分类,并与 V36 对比: + +- 职位:10/10 一致。 +- 分类:68/68 存在。 +- 每个声明分类至少关联 1 条技能关系;本轮独立统计最小值为 2、最大值为 3。 +- 未发现分类遗漏或职位—分类清单不一致。 + +### 5. ESCO 职位 URI + +V36 与测试中的 10 个职位映射一致,其中 8 个唯一 ESCO occupation URI。对 8 个 URI 调用 ESCO 官方只读 API,全部返回 HTTP `200`: + +- 前端开发工程师 URI +- 后端开发工程师 URI +- 测试工程师 URI +- 金融数据分析师 URI +- 风控建模工程师 URI +- 教育产品经理 URI +- 教学设计师 URI +- 教育数据分析师 URI + +DevOps、量化开发复用软件开发者 URI 的项目代理关系已在 V36 注释中明确,和当前批准清单一致。 + +## 上一轮 Finding 关闭状态 + +| 第一轮/第二轮 Finding | 状态 | 证据 | +| --- | --- | --- | +| C-1:混合来源未经批准 | 关闭 | 契约、design、source manifest 均已明确 ESCO 主来源 + O*NET `software_skills` 补充;V35/V36 字段语义与修订契约一致。 | +| I-1:代理 SOC/ESCO URI 不可追溯 | 关闭 | 10 个职位均有 ESCO source version 和 URI;8 个唯一 URI 独立返回 200;代理 SOC、Element ID、proxy 均落表。 | +| I-2:manifest 分类遗漏 | 关闭 | 68/68 分类存在,每个分类至少 1 条关系,清单与 V36 一致。 | +| I-3a:Importance 逐条可追溯数据 | 关闭 | V35 增加来源字段;V36 的 137/137 非空关系均匹配官方快照,当前数据真实。 | +| I-3b:测试全量覆盖 47/47 软件来源 | **部分关闭** | 47 条都做集合成员验证,但只有 5 条做 `skillCode -> sourceCode` 精确映射断言;未断言的两个技能交换来源仍可通过。 | +| I-3c:测试全量覆盖 137/137 非空 Importance | **部分关闭** | 137 条都验证来源键和数值;但 `proxy` 只对 10 条精确断言,其余 127 条可在 1 和 0 之间翻转而不失败。 | + +## Findings + +### Important-1:47 条 O*NET 软件来源未做完整的逐技能精确映射断言 + +位置: + +- `mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudSeedDataIntegrationTest.java:160-183` +- 精确映射单点断言:`185-189` +- 辅助断言:`474-478` + +问题: + +当前循环对每条 DB `source_code` 只检查其三元组是否存在于快照中,再要求 DB 行数与快照行数相等。它验证了“集合完整”,但没有验证每个技能代码必须绑定哪个三元组。 + +可证伪反例: + +- 任意交换两个未被 `assertOnetSkillSource` 覆盖的 O*NET 技能来源,例如 `SPRING_BOOT` 与 `DJANGO`。 +- DB 仍然有 47 个唯一且合法的三元组,集合仍与快照相等。 +- 现有 5 条单点断言不涉及这两个技能,因此测试仍通过,但技能溯源已经被错误交换。 + +影响: + +`skills.source_code` 是来源追踪字段。来源交换会静默破坏“技能到上游来源 ID”的准确性,当前回归测试无法阻止。 + +修复要求: + +- 在快照中增加 `skill_code`(仅 SOFTWARE 行使用),或新增完整的 `skillCode -> SOC|Workplace Example|Element ID` 映射资源。 +- 对 47/47 条 O*NET 技能逐条断言精确来源,而不是仅做集合成员检查。 + +### Important-2:Importance Proxy 标记未做全量语义断言 + +位置: + +- 枚举检查:`SkillCloudSeedDataIntegrationTest.java:201-203` +- 循环内代理检查:`229-230` +- 仅 10 条精确断言:`271-294` +- 精确断言辅助方法:`481-494` + +问题: + +`assertTrue(proxy == 0 || proxy == 1)` 只证明值处于合法范围,不能证明它符合种子数据的来源语义。V36 当前明确要求 137 条非空 Importance 全部是跨来源代理,必须为 `1`;测试只对 10 条做 `proxy=1` 断言。 + +可证伪反例: + +- 将任意一个未被 `assertImportance` 覆盖的非空关系从 `importance_is_proxy=1` 改为 `0`。 +- 该行仍满足数据库 CHECK 和测试的 `0/1` 判断,SQL 计数也不检查非空代理必须为 1,因此测试仍通过。 +- `importance`、SOC、Element ID 和 Data Value 均未发生变化,但来源语义已经被篡改。 + +影响: + +代理标记是来源审计字段。当前测试不能保证 137 条全部保持已批准的 proxy 语义,无法满足“篡改 Proxy 标记必须失败”的要求。 + +修复要求: + +- 明确断言所有非空 Importance 的 `importance_is_proxy=1`,以及所有空 Importance 的 `importance_is_proxy=0`;或为 137 条关系提供逐关系期望映射。 +- 若业务允许直接 O*NET Importance 与代理 Importance 并存,需要按关系保存期望值并逐条断言。 + +### Minor-1:快照的官方来源属性依赖评审时外部核验,未被自动化锁定 + +`loadEvidence` 只验证快照头部、列数、允许的 `source_file` 名称和键唯一性,不会读取官方 CSV 或校验官方发布包哈希。 + +当前快照已由本次复审用官方 ZIP 哈希确认,因此不阻断;但未来若快照的 `source_file` 被改成同一允许集合内的另一个文件,只要测试资源本身同步修改,现有测试无法识别来源文件标签变化。建议在来源文档或测试资源旁记录官方 ZIP/CSV 哈希,作为后续审计基线。 + +## 未决问题 + +- T3 当前数据层已通过,不能因为数据正确就忽略 Important-1 和 Important-2 的回归缺口。 +- 真实 MySQL 8.4 尚未执行;本轮仅按要求执行 H2 `MODE=MySQL`。该限制沿用前轮,不构成本次新的阻断项。 +- V35/V36 当前满足修订后的混合来源契约;未发现需要再次 Rewind 的契约冲突。 + +## 阻断项 + +T3 仍为 **FAIL**。在补齐 47/47 逐技能精确来源映射和 137/137 Importance 代理语义断言前,不应进入 T4。当前测试 9/9 通过只能证明现有数据与当前快照一致,不能证明上述两类篡改必然失败。 diff --git a/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3-rereview.md b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3-rereview.md new file mode 100644 index 0000000..6dc2ae9 --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3-rereview.md @@ -0,0 +1,158 @@ +# W1-T3 独立复审报告(聚焦修复) + +## 状态 + +完成。仅读取实现、契约、来源数据与本地 O*NET CSV,并独立执行测试和来源审计;未修改任何实现文件,仅写入本报告。 + +## Verdict + +**FAIL** + +第一轮 Critical 已关闭,I-1、I-2 的当前数据问题已关闭;I-3 的“逐条可追溯数据”已关闭,但对应的回归测试仍未关闭:当前测试只精确断言 5/47 条 O*NET 技能来源和 10/137 条非空 Importance,且没有任何 CSV 驱动的来源完整性测试。多数不存在或伪造的 O*NET 行、错误 Importance 仍会通过测试。该项属于 Important,阻断 T4。 + +## 评审对象与依据 + +实现文件: + +- `D:\1\zhi\zhilai\mydreamjob-server\src\main\resources\db\migration\V35__skill_cloud_taxonomy.sql` +- `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\SkillTaxonomyMigrationTest.java` +- `D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillCloudSeedDataIntegrationTest.java` + +依据: + +- 修订后的 `execution-contract.md`、`design.md`、`tasks.md` +- `changes/n03-skill-cloud/data/source-manifest.md` +- `docs/skill-taxonomy-sources.md` +- 第一轮报告 `changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3.md` +- 修复报告日志 `mydreamjob-server/target/skill-taxonomy-fix-green.log` +- 本地 O*NET 31.0 CSV 与 `changes/n03-skill-cloud/data/esco-skill-lookup.json` +- ESCO 官方只读 API(用于职位 URI 真实性核验) + +## 独立测试结果 + +环境: + +- `JAVA_HOME=D:\java\jdk` +- Maven:`D:\maven\apache-maven-3.9.7\bin\mvn.cmd` +- 工作目录:`D:\1\zhi\zhilai\mydreamjob-server` + +执行: + +```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: 6, Failures: 0, Errors: 0, Skipped: 0 +SkillTaxonomyMigrationTest: Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 +Total: Tests run: 8, Failures: 0, Errors: 0, Skipped: 0 +BUILD SUCCESS +Total time: 40.195 s +``` + +## 第一轮 Finding 关闭状态 + +| 第一轮 Finding | 状态 | 独立证据 | +| --- | --- | --- | +| C-1:混合来源未授权,职位与关系来源被替换 | **关闭** | `execution-contract.md:7,20,56` 已明确授权“ESCO 主来源 + O*NET `software_skills` 补充”;`design.md:32-43` 与 `source-manifest.md:35-43` 同步;V36 中 10 个职位均使用 ESCO occupation URI,25 条 ESCO 技能和 47 条 O*NET 补充技能分源导入。 | +| I-1:金融/风控/教育产品经理代理映射不明确或 URI 不真实 | **关闭** | V36:6-28 明确代理 SOC;V35:93-96 将目标来源与 Importance 来源拆列;V36:274-413 的 137 条非空热度均设置 `importance_is_proxy=1`。独立调用 ESCO 官方 API,10 个职位行对应的 8 个唯一 URI 全部返回 200,标题分别为 web developer、software developer、software tester、financial analyst、risk manager、ICT product manager、instructional designer、data analyst。 | +| I-2:manifest 分类遗漏,测试只要求至少一个分类 | **当前数据关闭** | manifest 10 个职位的分类清单与 V36:59-127 的实际 68 个分类逐项一致,测试也已改为精确比较完整清单;独立解析确认 68/68 分类至少关联 1 条技能。剩余测试弱点见 Minor-1。 | +| I-3:Importance 缺少逐技能来源映射和有效验证 | **部分关闭,仍为 Importance 阻断** | V35:94-110 已增加 SOC、Element ID、代理标记和完整性约束;V36:274-413 的 137/137 条非空热度均带完整字段,并可精确核对 CSV。但测试只覆盖少量样本,不能阻止多数来源或热度被篡改;详见 Important-1。 | + +## 来源审计方法与数量 + +### 1. SQL 结构审计 + +独立解析 V36 的 `INSERT` 行,结果: + +| 对象 | 数量 | +| --- | ---: | +| 来源版本 | 2 | +| 行业 | 3 | +| 职位 | 10 | +| 技能分类 | 68 | +| 技能 | 72(ESCO 25,O*NET 47) | +| 技能别名 | 72 | +| 职位技能关系 | 139 | +| 非空 Importance | 137 | +| 空 Importance | 2 | +| 每个职位非空 Importance 最小值 | 12 | + +V36 仅有 7 条 `INSERT` 语句,没有 `CREATE`、`ALTER`、`DROP`、`UPDATE`、`DELETE`。`com.mydreamjob.skill` 主代码包不存在,未提前实现 T4;V1-V34 没有改动。 + +### 2. ESCO 来源审计 + +- 25/25 条 `source_type='ESCO'` 技能的 URI 都能在 `esco-skill-lookup.json` 中找到。 +- 10/10 个职位行的 `source_version_id=1`,职位来源均为 ESCO occupation URI。 +- 10 个职位使用 8 个唯一 ESCO occupation URI;`BACKEND_DEVELOPER`、`DEVOPS_ENGINEER`、`QUANTITATIVE_DEVELOPER` 复用 software developer URI,V36:47 已明确标注为项目代理。 +- 对 8 个唯一 URI 独立调用 `https://ec.europa.eu/esco/api/resource/occupation?uri=...&language=en`,全部 HTTP 200,返回的 URI 与请求一致。 + +### 3. O*NET `software_skills` 审计 + +- 使用本地 `mydreamjob-server/target/onet-31/software_skills.csv`(31,821 行)。 +- 47/47 条 O*NET 技能的 `(O*NET-SOC Code, Workplace Example, Element ID)` 三元组精确命中 CSV,无缺失或伪造组合。 +- 当前测试对这 47 条来源只有正则格式检查,另精确断言的来源样本为 5 条。 + +### 4. Importance 审计 + +- 使用本地 `work_activities.csv`、`knowledge.csv`、`essential_skills.csv`、`transferable_skills.csv`,共解析出 99,231 个 `(SOC, Element ID, Data Value)` 的 `IM/Importance` 键。 +- 137/137 条非空 Importance 均可按 `(importance_source_code, importance_element_id, importance)` 精确命中至少一个 CSV。 +- 137/137 条均满足 `importance_source_code` 和 `importance_element_id` 非空,且 `importance_is_proxy=1`。 +- 2/2 条空 Importance 的 SOC、Element ID 均为 NULL,`importance_is_proxy=0`。 +- 当前测试对 137 条非空热度中的 10 条做精确值断言,其余 127 条只检查字段存在,不检查数值是否真实。 + +## 阻断项 + +### Important-1:回归测试不能覆盖大多数 O*NET 来源与 Importance 篡改 + +**位置** + +- `SkillCloudSeedDataIntegrationTest.java:132-139`:47 条 O*NET 来源只验证正则格式。 +- `SkillCloudSeedDataIntegrationTest.java:141-145`:仅精确断言 5 条 O*NET `software_skills` 行。 +- `SkillCloudSeedDataIntegrationTest.java:152-163`:只验证非空字段是否存在及来源版本名称是否为 O*NET。 +- `SkillCloudSeedDataIntegrationTest.java:165-187`:仅精确断言 10 条 Importance 及 2 条 NULL。 + +**独立量化** + +- O*NET 技能总数为 47,精确来源断言为 5,剩余 42 条不存在“CSV 行必须存在”的回归保护。 +- 非空 Importance 总数为 137,精确值断言为 10,剩余 127 条即使被改成任意 0-5 的数值,现有测试仍会通过。 +- 测试文件未读取 `software_skills.csv`、`work_activities.csv`、`knowledge.csv`、`essential_skills.csv` 或 `transferable_skills.csv`,因此无法证明“当前值来自 O*NET 31.0 CSV”。 + +**影响** + +当前数据库中的 47/47 技能来源和 137/137 Importance 已由本次复审全量核对为真实;但数据正确性只存在于当前 SQL 快照,没有被自动化测试保护。未来修改若把未抽样的来源替换为格式合法但不存在的三件套,或把未抽样的 Importance 改成错误值,测试仍会通过,随后直接影响词云排序和匹配结果。 + +**修复方向** + +- 增加覆盖全部 47 条 O*NET 技能来源的精确来源清单或 CSV 驱动断言。 +- 增加覆盖全部 137 条非空 Importance 的精确映射断言;若 CSV 不进入正常测试运行环境,至少提交一份受版本控制的完整映射基线,并校验行数、键集合和值。 +- 对 137/137 条映射逐项验证 `SOC + Element ID + Data Value`,而不是只验证非空和应用层来源版本。 + +## 未决问题 + +### Minor-1:每个分类至少关联一个技能未进入断言 + +`SkillCloudSeedDataIntegrationTest.java:89-113` 验证完整分类清单,但只对职位的技能总数和非空热度总数要求 `>= 10`,没有验证每个分类的关联技能数 `>= 1`。当前 68/68 分类均通过独立数据审计,因此不阻断;后续若新增或删改分类,建议补一条分组断言。 + +### Minor-2:Importance 越界值测试仍缺失 + +V35:102-104 定义了 0-5 的 CHECK,但 T3 测试覆盖的是 provenance 缺失、代理标记非法和成功带来源更新,没有直接断言 `importance < 0` 或 `> 5` 在 H2 `MODE=MySQL` 下失败。MySQL 8.4 会执行 CHECK,当前没有发现生产不可兼容语法,但真实 MySQL 尚未在本次环境中执行。 + +### Minor-3:执行计划中的契约哈希未同步修订版 + +当前 `execution-contract.md` 的 SHA-256 为 `98d869bc3b7e29b6fefffd973e561be50ba21ef4f59f4146ccd47cf8164957da`,与 `.spec-superflow.yaml` 一致;但 `.superpowers/sdd/execution-plan.yaml` 仍记录旧哈希 `9ae4be4a8561d7f303804c84d75061bbf2c8b3be2e7c79495689c19ba09d028e`。不影响 T3 数据正确性,建议在 W1 复评通过后同步执行计划。 + +## 未阻断的确认项 + +- V35 的新增列、provenance CHECK、代理标记 CHECK 在 H2 `MODE=MySQL` 下通过测试。 +- `importance_source_code`、`importance_element_id` 的字段长度足以容纳当前 O*NET SOC 和 Element ID。 +- V36 保持纯数据迁移,未提前引入 T4 Java 业务代码。 +- 未修改 V1-V34。 + +## 阻断项汇总 + +T3 当前不通过。第一轮 C-1、I-1、I-2 的阻断已关闭,I-3 的当前数据已清除,但对应的自动化回归保护仍不足,必须补齐 Important-1 后才能交给 T4。 \ No newline at end of file diff --git a/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3.md b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3.md new file mode 100644 index 0000000..6567421 --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3.md @@ -0,0 +1,155 @@ +# 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 项测试通过不能覆盖这些来源和分类语义问题。 \ No newline at end of file diff --git a/changes/n03-skill-cloud/data/source-manifest.md b/changes/n03-skill-cloud/data/source-manifest.md index 04abb24..12b68d9 100644 --- a/changes/n03-skill-cloud/data/source-manifest.md +++ b/changes/n03-skill-cloud/data/source-manifest.md @@ -5,7 +5,7 @@ | 来源 | 固定版本 | 用途 | 许可/复用条件 | 官方地址与归属 | | --- | --- | --- | --- | --- | | ESCO | ESCO dataset v1.2.1(Last update 10/12/2025,发布页记录 Release v5.7.0) | 提供行业/职位映射所需的职业—技能—技能分类主关系、稳定来源 ID 和层级结构 | 依据 Commission Decision 2011/833/EU,可免费下载、使用、复制和复用。文档和产品界面须保留官方署名语句:`This service uses the ESCO classification of the European Commission.` | 数据集下载页:https://esco.ec.europa.eu/en/use-esco/download
复用条件说明:https://esco.ec.europa.eu/en/about-esco/faq | -| O*NET | O*NET 31.0 Database | 提供职业—技能关联的 Importance 数值,作为首版词云热度 `heat` 的唯一来源 | License 为 CC BY 4.0,归属与许可说明见 https://creativecommons.org/licenses/by/4.0/ | 官方数据库页:https://www.onetcenter.org/database.html | +| O*NET | O*NET 31.0 Database | 为 ESCO 未收录的具体技术提供 `software_skills` 补充来源,并通过职业/工作活动 Importance 提供首版词云热度 `heat` | License 为 CC BY 4.0,归属与许可说明见 https://creativecommons.org/licenses/by/4.0/ | 官方数据库页:https://www.onetcenter.org/database.html | 运行时不得访问 ESCO、O*NET 或任何在线词库服务。所有首版数据必须随 Flyway 迁移进入本地数据库,并保留来源版本与归属信息。 @@ -32,13 +32,13 @@ ## 3. 导入规则 1. **职责分工**:T2 只建立七张词库表、外键、唯一约束和查询索引;T3 只导入上述三类行业、10 个已列代表性职位及其关联技能分类、技能、别名和 O*NET Importance。 -2. **代表性子集**:不导入 ESCO/O*NET 全量数据;不把平台自由文本岗位或行业直接当作标准词库节点。 +2. **代表性子集与混合来源**:不导入 ESCO/O*NET 全量数据;ESCO 是职位、通用技能和分类主表;ESCO 无可靠节点的具体技术使用 O*NET `software_skills` 补充;不把平台自由文本岗位或行业直接当作标准词库节点。 3. **来源 ID 不变**:ESCO/O*NET 来源 ID 原样保存在稳定来源字段中;项目不得重编号、覆盖或伪造上游 ID。 4. **中文标准名必填**:每个入选技能必须有非空 `name_zh` 中文标准名;中文名称由项目维护,但不替代或修改上游来源 ID。 -5. **热度缺失**:技能无法映射 O*NET Importance 时必须保留,写入 `importance=null`;查询时映射为 `heat=null`,并排在有热度技能之后。 +5. **热度来源**:每条非空 Importance 必须保存 O*NET SOC、Element ID 和代理标记;技能无法映射 O*NET Importance 时必须保留,写入 `importance=null`;查询时映射为 `heat=null`,并排在有热度技能之后。 6. **项目映射责任**:项目维护中文名称、别名、技能分类和行业/职位映射,不修改开源来源 ID。行业/职位映射、细粒度分类和跨源技能对应关系均作为项目适配层记录。 7. **别名归一化**:生成 `normalized_alias` 时依次执行 Unicode NFKC、首尾空白移除、大小写折叠、连字符转空格、连续空白折叠为单空格、句点移除;`+` 和 `#` 必须保留,不能删除或替换。 -8. **来源可追溯**:来源清单写入 `skill_source_versions`,至少记录来源名称、固定版本、许可、归属 URL 和导入时间;职位技能关系记录关联的来源版本。 +8. **来源可追溯**:来源清单写入 `skill_source_versions`,至少记录来源名称、固定版本、许可、归属 URL 和导入时间;O*NET 补充技术的 `source_code` 使用真实 `SOC|Workplace Example|Element ID` 组合,职位技能关系记录 O*NET SOC、Element ID、代理标记及关联的来源版本。 9. **迁移不可改写**:T2/T3 计划使用 `V35__skill_cloud_taxonomy.sql` 和 `V36__skill_cloud_seed.sql`。执行前若编号已被占用,必须回退规划并刷新编号;后续词库更新只能新增 Flyway 迁移,不得改写 V1–V34 或任何已执行迁移。 10. **运行时离线**:接口运行时不得调用外部词库、AI、课程平台或同步服务;排名只读取本地 Flyway 数据。 @@ -60,7 +60,7 @@ ESCO 产品署名必须使用:`This service uses the ESCO classification of th | 中文名称和别名 | 不保证完整中文覆盖 | 补齐中文标准名、别名及归一化值 | | 细粒度技能分类 | 不保证符合本项目展示粒度 | 在职位下维护分类和展示名称 | | 行业/职位映射 | 不保证覆盖三类项目行业和中文职位名 | 维护行业—职位—分类—技能映射 | -| 跨源技能重要性关联 | 不提供项目所需的稳定本地关系 | 维护 ESCO 技能与 O*NET Importance 的映射 | +| 跨源技能与重要性关联 | 不提供项目所需的稳定本地关系 | 维护 ESCO 技能到 O*NET `software_skills` 的补充映射,以及每条 Importance 的 SOC/Element ID/代理标记 | ## 6. 验收检查项 @@ -71,7 +71,7 @@ ESCO 产品署名必须使用:`This service uses the ESCO classification of th - AC-5:文档明确 O*NET License 为 CC BY 4.0,并记录官方数据库页与许可 URL。 - AC-6:文档明确项目维护中文名称、别名、技能分类和行业/职位映射,不修改开源来源 ID。 - AC-7:文档明确 T2 建结构、T3 导数据,只导入代表性职位和关联技能;技能中文标准名必填。 -- AC-8:文档明确 O*NET Importance 缺失时保留技能并写入 `importance=null`,查询侧映射为 `heat=null`。 +- AC-8:文档明确 O*NET 补充技术、每条 Importance 的 SOC/Element ID/代理标记,以及无映射时保留技能并写入 `importance=null`、查询侧映射为 `heat=null`。 - AC-9:文档明确别名归一化遵循 NFKC、首尾空白、大小写、连字符、连续空白、句点规则,且保留 `+`、`#`。 - AC-10:文档明确后续词库更新只能新增 Flyway 迁移,不得改写 V1–V34 或已执行迁移。 - AC-11:来源、覆盖、导入与责任边界中不得出现未决占位或延期确认语句。 diff --git a/changes/n03-skill-cloud/design.md b/changes/n03-skill-cloud/design.md new file mode 100644 index 0000000..cff25a3 --- /dev/null +++ b/changes/n03-skill-cloud/design.md @@ -0,0 +1,180 @@ +# 行业技能词云与学习路径设计 + +## 事实与约束 + +- 仓库当前为 Spring Boot 3.5.16、Java 17、MySQL/Flyway/JPA,最新已存在迁移为 `V34`。 +- 学生技能存储在 `student_profiles.skills_json`,历史数据可能为空、重复或无法解析。 +- 企业行业与岗位业务数据是自由文本,不能作为稳定的“行业 → 职位 → 技能”分类源。 +- ESCO 提供职位、技能和技能分类关系;O*NET 提供 Importance 数值。两者均需保留来源版本与许可归属。 +- 首版只导入互联网、金融、教育及代表性职位,避免全量翻译和超大迁移。 +- 三个接口均为只读、仅 STUDENT,身份只取 JWT,响应统一使用 `ApiResponse`。 +- 词云热度是静态 O*NET Importance,不随平台岗位发布和投递行为变化。 +- 计算完全确定,不调用 AI、网络课程服务或第三方运行时接口。 + +## Goals + +- 建立可持续扩展的开源技能知识库,并允许项目维护中文名称、别名和细粒度分类。 +- 提供完整、稳定、可筛选的热度排序技能词云。 +- 以同一排序结果计算个人 TOP10 匹配度,避免不同接口结果不一致。 +- 基于未命中技能生成最小可用的学习优先级和建议。 +- 保持现有模块接口语义不变,并满足项目测试、OpenAPI 和黑盒验收约定。 + +## Non-Goals + +- 不做岗位实时文本挖掘、平台岗位热度聚合或用户行为周榜。 +- 不导入 ESCO/O*NET 全量数据,不建设后台维护界面。 +- 不保存匹配快照、学习进度、完成状态或提醒。 +- 不提供课程、讲师、价格、时长、链接和 AI 对话。 +- 不向 COMPANY/ADMIN 暴露个人匹配和学习路径。 + +## Decisions + +### D1: 以 ESCO + O*NET 作为静态知识底座 + +**Choice**:ESCO 作为职位、通用技能和分类主表;当具体技术(如 React、Docker、Spring Boot)没有可靠 ESCO 节点时,允许使用 O*NET `software_skills` 作为补充技能来源。O*NET 继续提供可追溯 Importance;项目只维护中文标签、别名和映射。 + +**Rationale**:能用开源数据覆盖技能层级,同时保留 O*NET 明确的重要度字段,不需要自行创造热度权重。 + +**Alternatives**: +- 仅 O*NET:重要度完整,但英文和美国职业体系适配成本高。 +- 严格仅 ESCO:来源单一,但会丢失 React、Docker 等 ESCO 未收录的具体技能。 +- 自建词典:可控性高,但需要长期运营且缺少来源可信度。 + +**Consequences**:需要明确区分 ESCO 主来源技能与 O*NET 补充技术;每条热度必须记录 O*NET SOC 和 Element ID,无法映射时保留技能并写空热度。 + +### D2: 词库使用关系表落 Flyway + +**Choice**:新增行业、职位、技能分类、技能、别名、职位技能关系和来源版本表,通过 Flyway 迁移导入代表性数据。 + +**Rationale**:接口需要多条件过滤、稳定排序和可追溯来源;关系表可利用外键、唯一约束和索引保证数据质量。 + +**Alternatives**: +- 静态 JSON:实现快,但无法方便地按节点过滤、校验引用和演进。 +- 图数据库:适合复杂关系,但超出当前技术栈和首版范围。 + +**Consequences**:迁移文件需要严格审查,后续词库更新必须创建新迁移,不能修改已执行迁移。 + +### D3: 三个接口共享同一条排名管道 + +**Choice**:先查询筛选范围内的技能列表并按热度排序,再从同一列表截取 TOP10;匹配和学习路径均调用同一领域服务,不重新排序。 + +**Rationale**:词云、匹配度和学习路径必须对“热门技能集合”保持一致,减少重复逻辑和边界分歧。 + +**Alternatives**: +- 每个接口独立查询和排序:实现分散,容易出现排序不一致。 +- 持久化统一排名快照:可缓存,但当前数据规模不需要额外调度和一致性成本。 + +**Consequences**:排序规则、空热度规则和别名规则只有一个实现位置;单元测试必须聚焦该共享管道。 + +### D4: TOP10 不单独存储或返回 + +**Choice**:词云接口返回完整排序列表,前端直接取前十条;匹配服务在内部最多取前十条。 + +**Rationale**:用户已确认不需要独立 TOP10 数据源,列表本身是唯一事实来源。 + +**Alternatives**:响应同时返回 `top10` 字段或提供独立接口;会重复数据并增加一致性风险。 + +**Consequences**:前端必须按约定截取前十条;接口文档需明确列表已按排名排序。 + +### D5: 匹配和建议按请求实时计算且不持久化 + +**Choice**:每次读取当前学生技能、词库和筛选条件后计算匹配度和学习路径,不保存计算结果。 + +**Rationale**:数据量小、算法确定,且用户未要求历史记录;避免引入无用状态和隐私数据留存。 + +**Alternatives**:保存每日匹配快照或学习计划;会增加表、清理与一致性复杂度,首版无收益。 + +**Consequences**:学生技能或词库更新后,结果立即变化;响应必须包含足够字段让前端独立展示。 + +### D6: 学习建议使用分类模板 + +**Choice**:技能分类映射到编程语言、框架、工具和通用四类建议模板。 + +**Rationale**:满足“技能 + 优先级 + 简化建议”,无需依赖外部课程数据。 + +**Alternatives**:调用 AI 或维护课程表;前者不确定且超出约束,后者缺少可用授权数据。 + +**Consequences**:建议保持简短、行业无关和可重复;后续可在不改变接口结构的前提下细化模板。 + +## Data Model + +| 表 | 关键字段 | 作用 | +| --- | --- | --- | +| `skill_industries` | `id`, `code`, `name_zh`, `source_code`, `sort_order` | 行业节点 | +| `skill_occupations` | `id`, `industry_id`, `code`, `name_zh`, `source_code`, `sort_order` | 行业下职位 | +| `skill_categories` | `id`, `occupation_id`, `code`, `name_zh`, `suggestion_template_code`, `sort_order` | 职位下技能分类 | +| `skills` | `id`, `code`, `name_zh`, `name_en`, `source_type`, `source_code` | 标准技能 | +| `skill_aliases` | `id`, `skill_id`, `alias`, `normalized_alias` | 中英文、大小写和写法别名 | +| `occupation_skills` | `occupation_id`, `skill_id`, `category_id`, `importance`, `importance_source_code`, `importance_element_id`, `importance_is_proxy`, `relationship_type`, `source_version` | 职位技能关系、热度及其机器可追溯来源 | +| `skill_source_versions` | `source_name`, `source_version`, `license`, `attribution_url`, `imported_at` | 来源与许可追溯 | + +约束与索引: + +- `skill_occupations.industry_id + code` 唯一,防止同一行业内职位重复。 +- `skill_categories.occupation_id + code` 唯一。 +- `skill_aliases.normalized_alias` 全局唯一,避免同一写法映射到多个技能。 +- `occupation_skills.occupation_id + skill_id` 唯一。 +- 每条非空 `importance` 必须有 O*NET `importance_source_code` 和 `importance_element_id`;代理映射必须设置 `importance_is_proxy=1`。 +- 查询索引覆盖 `occupation_id + category_id` 和 `occupation_id + importance`。 +- `category_id` 必须属于 `occupation_id`,通过服务校验并在必要测试中验证。 + +## API Contract + +### `GET /api/v1/skills/word-cloud` + +查询参数:`industryId` 必填,`occupationId` 必填,`categoryId` 可选。 + +返回 `data`: + +- `industry`、`occupation`、`category`:实际筛选节点。 +- `total`:技能总数。 +- `items`:按 `heat DESC, nameZh ASC, skillId ASC` 排序,包含 `rank`、`skillId`、`code`、`nameZh`、`nameEn`、`categoryId`、`categoryName`、`heat`、`heatSource`。 + +### `GET /api/v1/skills/match` + +使用与词云相同的三个查询参数。返回 `data`: + +- `matchScore`:0–100 整数。 +- `matchedCount` 与 `totalCount`。 +- `matchedSkills` 与 `missingSkills`:使用精简技能项,缺失技能保持热度顺序。 + +### `GET /api/v1/skills/learning-path` + +使用与词云相同的三个查询参数。返回 `data`: + +- `total`。 +- `items`:每项包含 `priority`、`skillId`、`skillName`、`categoryId`、`categoryName`、`suggestion`。 + +## Processing Rules + +1. Flyway 先建立表和约束,再导入代表性词库数据。 +2. 查询服务校验行业、职位、分类关系;ESCO 作为职位与通用技能主表,O*NET `software_skills` 仅补充 ESCO 缺失的具体技术,构建统一的排序技能列表。 +3. 匹配服务解析当前学生 `skills_json`;解析失败按空技能处理并记录告警,不输出原始 JSON。 +4. 学生技能和技能别名统一执行 Unicode 规范化、去首尾空白、大小写折叠、连续空白折叠及常见标点归一化。 +5. TOP10 大小取 `min(10, 列表大小)`;匹配度按 `round(matched * 100.0 / size)` 计算,`size=0` 时返回 0。 +6. 学习路径遍历缺失技能,按列表顺序生成连续优先级,并依据 `suggestion_template_code` 产生建议。 +7. 所有计算在只读事务中完成,不写入匹配或学习路径记录。 + +## Security and Privacy + +- 在 SecurityFilterChain 中将 `/skills/**` 限制为已认证请求,Controller/Service 再断言 STUDENT 角色。 +- 身份只取 SecurityContext/JWT,不接受 `studentUserId`、`userId` 或 `skills` 请求参数。 +- 响应不包含原始技能 JSON、手机号、邮箱、简历内容或词库内部敏感字段。 +- 日志只记录异常类型与请求上下文,不记录完整学生技能数组。 + +## Risks and Verification + +| 风险 | 处理 | 验证证据 | +| --- | --- | --- | +| ESCO/O*NET 中文覆盖不足 | 首版只导入代表性职位,所有入选技能必须有中文标准名 | 种子数据检查:中文名非空率 100% | +| 别名冲突导致匹配不确定 | `normalized_alias` 全局唯一,迁移失败即阻断 | Flyway 唯一约束测试和别名单测 | +| Importance 缺失 | `heat=null`,排序末位,保留技能 | 排序单元测试和 H2 Repository 测试 | +| 分类关系错配 | 校验分类属于职位、职位属于行业 | 非法组合 MockMvc 400 场景 | +| 迁移过大影响启动 | 首版限制三类行业和代表性职位,种子数据可控 | 迁移在 H2/MySQL 测试库执行耗时记录 | +| 学生技能 JSON 损坏 | 捕获解析异常并按空技能处理 | 异常 JSON 服务测试,断言 200 和匹配度 0 | +| 三个接口结果漂移 | 使用共享排名服务与同一 DTO 排序 | 集成测试断言 TOP10、匹配和学习路径顺序一致 | +| 许可或版本遗漏 | 保存来源版本、许可和归属 URL | 来源说明文档审查与迁移数据断言 | + +## Completion Boundary + +设计完成的标准是:四个规划产物一致、所有需求有可测试场景、任务覆盖三个接口和 Flyway 数据变更、没有未确认的兼容性或权限决策。实现和验收不在本设计阶段启动。 \ No newline at end of file diff --git a/changes/n03-skill-cloud/execution-contract.md b/changes/n03-skill-cloud/execution-contract.md new file mode 100644 index 0000000..20b415d --- /dev/null +++ b/changes/n03-skill-cloud/execution-contract.md @@ -0,0 +1,147 @@ +# N03 行业技能词云与学习路径执行契约 + +## Intent Lock + +**问题**:学生无法从行业和职位维度查看招聘市场热门技能,无法量化自己相对 TOP10 的掌握程度,也没有针对缺失技能的优先级建议。 + +**意图**:基于 ESCO + O*NET 开源知识底座,通过 Flyway 建立可追溯的行业—职位—技能分类—技能数据。ESCO 作为职位、通用技能和分类主表,O*NET `software_skills` 仅补充 ESCO 缺失的具体技术;O*NET Importance 提供热度及机器可追溯来源。系统提供仅 STUDENT 可访问的词云、个人匹配度和学习路径三个确定性接口。 + +**完成证明**:V35/V36 迁移成功执行;三个接口按同一热度列表工作;权限、排序、别名、匹配分母、空数据和通用学习建议均通过自动化测试;OpenAPI 与文档一致。 + +## Scope Fence + +### 允许范围 + +- 新增 `mydreamjob-server/src/main/resources/db/migration/V35__skill_cloud_taxonomy.sql` 和 `V36__skill_cloud_seed.sql`。 +- 新增 `com.mydreamjob.skill` 领域包、Repository、Service、DTO 和三个 Controller。 +- 修改安全配置以保护 `/skills/**`,仅允许已登录 STUDENT。 +- 新增 ESCO/O*NET 来源说明、接口文档、OpenAPI 契约和测试。\n- 新增从 O*NET 31.0 官方 CSV 提取的测试证据快照,仅用于全量核对种子来源和 Importance,不参与运行时。 +- 首版数据只覆盖互联网、金融、教育及代表性职位。 +- 具体技术无可靠 ESCO 节点时,允许使用 O*NET `software_skills` 作为补充来源。 +- 词云热度只取 O*NET Importance;无映射时 `heat=null`。 +- 匹配度使用当前 TOP10 和实际技能数;学习路径只返回缺失技能、优先级和通用建议。 + +### 禁止范围 + +- 不修改 V1–V34 历史迁移,不改变已有接口语义。 +- 不接 AI、课程平台、第三方运行时接口或在线词库同步。 +- 不按平台岗位 `jobId`、发布量或用户行为计算热度。 +- 不实现后台词库维护、学习进度、提醒、历史快照或前端页面。 +- 不向 COMPANY/ADMIN 返回个人匹配或学习路径。 +- 不在响应或日志中输出原始 `skills_json`、手机号、邮箱、简历内容。 + +## Approved Behavior + +| ID | 已批准行为 | 批次 | +| --- | --- | --- | +| REQ-SC-1 | `/skills/word-cloud`、`/skills/match`、`/skills/learning-path` 只允许已登录 STUDENT;匿名返回 401,COMPANY/ADMIN 返回 403。 | B3 | +| REQ-SC-2 | `industryId`、`occupationId` 必填且必须互为关联;`categoryId` 可选,存在时必须属于目标职位;非法组合返回 400。 | B2、B3 | +| REQ-SC-3 | 词云返回筛选范围内全部技能,按 `heat DESC, nameZh ASC, skillId ASC` 排序;`heat=null` 排在有值技能之后。 | B2 | +| REQ-SC-4 | TOP10 不单独存储或返回,前端取词云前十条;少于十条时全部结果构成实际 TOP10。 | B2、B3 | +| REQ-SC-5 | 每个技能项包含技能 ID、名称、分类、`heat`、稳定排名和可追溯来源标识;响应使用 `ApiResponse`。 | B2、B3 | +| REQ-SM-1 | 个人匹配只使用当前筛选结果的 `min(10, actualSize)` 个技能,不另建排序。 | B2 | +| REQ-SM-2 | 学生技能只从 JWT 对应学生的 `student_profiles.skills_json` 读取;缺失、空数组或损坏 JSON 按无技能处理。 | B2 | +| REQ-SM-3 | 匹配使用标准技能和别名,执行 NFKC、首尾空白、大小写、连字符转空格、连续空白折叠和句点移除归一化;`+`、`#` 不得删除。 | B2 | +| REQ-SM-4 | 匹配度为 `round(matched * 100 / min(10, actualSize))`;`actualSize=0` 时返回 0;重复命中同一技能只计一次。 | B2 | +| REQ-SM-5 | 响应分别返回命中与缺失技能,缺失列表保持 TOP10 顺序,两者并集等于实际 TOP10。 | B2、B3 | +| REQ-LP-1 | 学习路径只包含当前 TOP10 的未命中技能,不包含已命中或 TOP10 之外的技能。 | B2 | +| REQ-LP-2 | 学习路径沿用词云顺序,优先级从 1 连续编号;空热度技能仍在有权重技能之后。 | B2 | +| REQ-LP-3 | 学习建议由编程语言、框架、工具、通用四类确定性模板生成;不调用 AI 或外部服务,不含课程链接、时长或价格。 | B2 | +| REQ-LP-4 | 每项只返回技能 ID、名称、分类、优先级和简化建议;无缺口时返回空列表和总数 0。 | B2、B3 | + +需求覆盖结果:三份 spec 中全部 14 条 `MUST`/`SHALL` 行为均已映射,无未映射需求。 + +## Architecture and Interface Constraints + +- 使用 ESCO 作为职位、通用技能和分类主关系;具体技术无可靠 ESCO 节点时,允许使用 O*NET `software_skills` 作为补充技能来源。 +- O*NET Importance 作为 `heat`;项目维护中文名称、别名和细粒度分类。 +- 数据模型固定为 `skill_industries`、`skill_occupations`、`skill_categories`、`skills`、`skill_aliases`、`occupation_skills`、`skill_source_versions` 七张表。 +- `skill_aliases.normalized_alias` 全局唯一;`occupation_skills(occupation_id, skill_id)` 唯一;分类必须属于职位,职位必须属于行业。 +- `occupation_skills` 必须保存 `importance_source_code`、`importance_element_id` 和 `importance_is_proxy`;非空 `importance` 必须可追溯到 O*NET 31.0 CSV。 +- 三个接口共享同一个只读排名管道和技能投影;不得复制三套排序规则。 +- 接口路径固定为: + - `GET /api/v1/skills/word-cloud` + - `GET /api/v1/skills/match` + - `GET /api/v1/skills/learning-path` +- 三个接口查询参数均为 `industryId`、`occupationId` 必填,`categoryId` 可选。 +- 计算按请求实时完成,不持久化匹配或学习路径。 +- 学生技能解析异常记录不含原始内容的告警,接口返回 200 和空技能匹配结果。 +- 首版只使用代表性数据;迁移编号以当前仓库下一个可用版本为准。若合并前 V35/V36 已被其他变更占用,必须回退规划阶段刷新编号和契约。 + +## Execution Batches + +### Batch B1: 数据与来源基础 + +- 覆盖任务:T1、T2、T3,T3 在第 1 次评审后执行来源与分类修复。 +- 交付物:来源清单、`V35__skill_cloud_taxonomy.sql`、`V36__skill_cloud_seed.sql`、迁移测试。 +- 完成定义:代表性数据包含三类行业和约定职位;所有入选技能有中文标准名;ESCO 职位来源、O*NET 补充技术、每个 Importance 的 SOC/Element ID 均可机器追溯;自动化测试通过版本化证据快照全量核对 O*NET 来源和数值。 +- 依赖:无。 +- 评审门:B1 完成并由 `code-reviewer` 记录 `pass` 后才能开始 B2。 + +### Batch B2: 排名、匹配与学习路径 + +- 覆盖任务:T4、T5、T6、T7。 +- 交付物:实体/Repository、共享词云查询排名服务、别名归一化、个人匹配服务、学习路径模板服务及单元测试。 +- 完成定义:REQ-SC-2 至 REQ-SC-5、REQ-SM-1 至 REQ-SM-5、REQ-LP-1 至 REQ-LP-4 的领域行为可重复且确定。 +- 依赖:B1 通过评审。 +- 评审门:B2 完成并由 `code-reviewer` 记录 `pass` 后才能开始 B3。 + +### Batch B3: 接口、权限、契约与回归 + +- 覆盖任务:T8、T9、T10。 +- 交付物:三个 Controller、权限配置、MockMvc/H2 集成测试、`docs/skill-cloud-api.md`、OpenAPI 和前端联调文档更新。 +- 完成定义:REQ-SC-1 至 REQ-SC-5、REQ-SM-5、REQ-LP-4 的接口行为通过;匿名 401、非 STUDENT 403、非法参数 400;文档与运行时一致。 +- 依赖:B2 通过评审。 +- 评审门:B3 完成并由 `code-reviewer` 记录 `pass` 后才能进入验证和收尾。 + +## Test Obligations + +| ID | 测试义务 | 批次 | 证据 | +| --- | --- | --- | --- | +| TO-1 | Flyway 创建七张表、外键、唯一约束和索引,中文名与来源 ID 完整。 | B1 | `mvn -Dtest=SkillTaxonomyMigrationTest test` | +| TO-2 | 行业/职位/分类关系校验,非法组合返回 400。 | B2、B3 | Repository/Service 单测与 MockMvc | +| TO-3 | 热度降序、空热度末位、同热度稳定排序、空范围列表。 | B2 | `mvn -Dtest=SkillCloudQueryServiceTest test` | +| TO-4 | TOP10 少于十条时使用实际数量,匹配分母为实际数量,`1/3` 四舍五入为 33。 | B2 | `mvn -Dtest=SkillMatchServiceTest test` | +| TO-5 | 大小写、空格、连字符、句点、`C++`、`C#` 和重复别名匹配。 | B2 | 归一化与匹配单测 | +| TO-6 | 无技能、无资料、损坏 JSON、全部命中和全部未命中场景。 | B2、B3 | Service 与 MockMvc 测试 | +| TO-7 | 学习路径只含未命中技能,优先级连续,无缺口返回空列表。 | B2 | `mvn -Dtest=SkillLearningPathServiceTest test` | +| TO-8 | 四类建议模板稳定、行业无关,不含链接、时长、价格或 AI 内容。 | B2 | 模板单测 | +| TO-9 | 三个接口权限、响应结构和参数错误符合契约。 | B3 | `mvn -Dtest=SkillCloudControllerTest test` | +| TO-10 | 三个接口复用同一 TOP10,响应不含原始 `skills_json`。 | B3 | H2 集成测试 | +| TO-11 | 全量回归、打包和静态 OpenAPI 契约通过。 | B3 | `mvn test`、`mvn clean verify`、`python docs\verify_openapi.py` |\n| TO-12 | 测试读取 O*NET 31.0 证据快照,全量核对 47 条 O*NET 技术来源及全部非空 Importance;任一来源不存在或数值被篡改都必须失败。 | B1 | `mvn -Dtest=SkillCloudSeedDataIntegrationTest test` | + +## Build Rules + +- 每个 Batch 按 TDD 先写失败测试,再实现最小行为,不得跳过测试。 +- 只修改范围内文件;发现需要修改已验收接口或历史迁移时立即停止并触发 Rewind。 +- 迁移和种子数据必须可在 H2(`MODE=MySQL`)与真实 MySQL 执行,不依赖外部文件路径。 +- 所有排序、匹配和建议必须确定性;禁止随机、当前时间或请求次数影响结果。 +- 任何新增日志不得包含个人技能 JSON、Token、手机号、邮箱或简历文本。 +- OpenAPI 注解、示例和实际响应必须一致,不能只改文档不改运行时行为。 + +## Review Gates + +- **DP-3**:本契约必须由用户明确批准并记录后,才能开始任何实现。 +- **Batch Review**:B1、B2、B3 每个 Batch 完成后由 `code-reviewer` 按规范符合性和代码质量评审,记录 `pass` 或 `fail`。 +- **DP-4**:批准契约后由 `build-executor` 给出执行模式与 wave 计划,用户确认后开始执行。 +- **Verification Gate**:实现完成后执行全量测试、OpenAPI 校验和必要的 MySQL/Docker 黑盒验证。 +- **DP-7**:由 `release-archivist` 完成最终验证与关闭,不在本契约内自动完成。 + +## Rewind Conditions + +出现以下任一情况必须停止执行并回到规划阶段刷新工件和本契约: + +- 需要新增、删除或改变用户可见接口、筛选参数、权限或响应语义。 +- TOP10、匹配度分母、排序规则或空数据处理发生改变。 +- 需要引入 AI、课程平台、实时岗位热度或后台维护接口。 +- 需要导入全量词库或扩大首版行业/职位范围。 +- 需要新增除 ESCO、O*NET 之外的其他技能来源。 +- V35/V36 被其他变更占用,或历史迁移需要修改。 +- ESCO/O*NET 许可、中文映射策略或数据来源发生实质变化。 +- 发现某个已批准 `MUST`/`SHALL` 无法映射到测试或执行批次。 + +## DP-3 Approval Gate + +执行者确认:本契约完整覆盖已批准的 14 条必需行为,无未映射需求,B1→B2→B3 依赖清晰,所有测试义务均有证据命令。 + +**批准方式**:用户明确回复批准本执行契约后,记录 `dp_3_result`、`dp_3_timestamp`,再执行 `specifying → bridging → approved-for-build` 状态转换。DP-3 记录前禁止进入 `build-executor`。 \ No newline at end of file diff --git a/changes/n03-skill-cloud/tasks.md b/changes/n03-skill-cloud/tasks.md new file mode 100644 index 0000000..67ff6cb --- /dev/null +++ b/changes/n03-skill-cloud/tasks.md @@ -0,0 +1,38 @@ +# 实施任务 + +## 交付与证明映射 + +| 需求域 | 规划来源 | 实现任务 | 主要证明 | +| --- | --- | --- | --- | +| 词库来源与覆盖 | proposal、design | T1、T2、T3 | 来源清单、中文名检查、Flyway 执行成功 | +| 词云筛选与排序 | skill-cloud spec | T4、T5 | Repository/Service 单测、MockMvc 场景 | +| TOP10 派生与一致性 | skill-cloud、skill-match spec | T5、T7、T8 | 集成测试断言同一排序结果 | +| 个人技能匹配 | skill-match spec | T6、T8 | 别名、分母、空数据和部分命中单测 | +| 学习路径与建议 | learning-path spec | T7、T8 | 优先级连续、模板建议和空列表测试 | +| 权限与隐私 | 三份 spec、design | T8、T10 | 401/403、响应脱敏和日志检查 | +| 文档与契约 | proposal、design | T1、T10 | `docs/verify_openapi.py`、接口文档审查 | + +## 任务 + +- [ ] 1. 固化 ESCO + O*NET 代表性数据清单:在 `changes/n03-skill-cloud/data/source-manifest.md` 和 `docs/skill-taxonomy-sources.md` 记录行业、职位、技能范围、源版本、许可、归属 URL、中文映射负责人和导入规则。产出:互联网、金融、教育及代表性职位的可审查数据边界。依赖:无。证据:人工审查清单,所有入选技能均有稳定来源 ID 和中文标准名。 +- [ ] 2. 新增 Flyway 词库结构迁移:创建 `mydreamjob-server/src/main/resources/db/migration/V35__skill_cloud_taxonomy.sql`,实现七张词库表、外键、唯一约束、查询索引,以及 `occupation_skills.importance_source_code`、`importance_element_id`、`importance_is_proxy` 来源追溯字段,不修改 V1–V34。产出:H2/MySQL 均可创建词库结构。依赖:T1。证据:`mvn -Dtest=SkillTaxonomyMigrationTest test`。 +- [ ] 3. 导入代表性种子数据:创建 `mydreamjob-server/src/main/resources/db/migration/V36__skill_cloud_seed.sql`,导入三类行业、代表性职位、全部已声明技能分类、ESCO 主来源技能、O*NET `software_skills` 补充技术、别名和 O*NET Importance;每条非空热度必须保存真实 SOC、Element ID 和代理标记,无映射时 `importance` 留空。产出:种子数据可重复执行迁移并满足中文名约束。依赖:T2。证据:迁移测试断言数量、中文名、分类覆盖、ESCO 职位来源、47 条 O*NET 技术来源和全部非空 Importance 与版本化证据快照一致。 +- [ ] 4. 建立词库领域与查询仓储:在 `mydreamjob-server/src/main/java/com/mydreamjob/skill/` 新增实体、Repository 和查询投影,覆盖行业—职位—分类校验及技能关系查询。产出:能按 `industryId`、`occupationId`、可选 `categoryId` 读取技能。依赖:T2、T3。证据:`mvn -Dtest=SkillTaxonomyRepositoryTest test`。 +- [ ] 5. 实现共享词云排名服务与 DTO:在 `mydreamjob-server/src/main/java/com/mydreamjob/skill/` 实现统一筛选、`heat DESC, nameZh ASC, skillId ASC` 排序和完整列表输出;返回 `rank`、技能身份、分类、`heat`、来源字段。产出:词云和 TOP10 使用同一个事实来源。依赖:T4。证据:`mvn -Dtest=SkillCloudQueryServiceTest test`,覆盖空热度、同热度、空范围和分类缺省。 +- [ ] 6. 实现个人技能匹配服务:新增 `SkillMatchService`、别名归一化器和匹配 DTO,只从 JWT 对应学生的 `skills_json` 读取技能;按 `min(10, actualSize)` 截取并计算整数匹配度。产出:命中、缺失、分母和异常 JSON 行为符合规范。依赖:T5。证据:`mvn -Dtest=SkillMatchServiceTest test`,覆盖 0、1/3=33、全命中、少技能和重复别名。 +- [ ] 7. 实现学习路径生成服务:新增 `SkillLearningPathService` 和分类模板,按缺失技能顺序生成连续优先级与通用建议;不接 AI 或外部资源。产出:无缺口返回空列表,有缺口返回最小学路径。依赖:T6。证据:`mvn -Dtest=SkillLearningPathServiceTest test`。 +- [ ] 8. 暴露三个 STUDENT 接口并登记权限:新增 `SkillCloudController`,实现 `GET /skills/word-cloud`、`GET /skills/match`、`GET /skills/learning-path`,在 `SecurityConfig` 中加入 `/skills/**` 的登录与 STUDENT 校验,并补充 OpenAPI 注解。产出:三个 API 只接受 STUDENT,响应统一为 `ApiResponse`。依赖:T5、T6、T7。证据:`mvn -Dtest=SkillCloudControllerTest test`,覆盖匿名 401、COMPANY/ADMIN 403、参数非法 400 和学生成功 200。 +- [ ] 9. 补齐持久化与端到端回归测试:在 `mydreamjob-server/src/test/java/com/mydreamjob/skill/` 增加 H2 持久化、排序一致性、匹配与学习路径复用同一 TOP10 的集成测试,并验证响应不含原始 `skills_json`。产出:三个接口的边界与一致性有自动化证明。依赖:T3、T8。证据:`mvn test`。 +- [ ] 10. 更新接口与来源文档并执行发布级验证:新增 `docs/skill-cloud-api.md`,更新 `docs/frontend-integration.md`、`docs/openapi-rc.json` 和词库来源说明,明确 TOP10 由前端截取、参数规则、响应字段、无 AI 和许可归属。依赖:T8、T9。产出:静态契约、运行时行为和文档一致。证据:`python docs\verify_openapi.py`、`mvn clean verify`。 + +## 实施顺序 + +T1 → T2 → T3 → T4 → T5 → T6 → T7 → T8 → T9 → T10。 + +## 退出条件 + +- 所有任务完成且证据命令通过。 +- 三份 spec 的场景全部有对应自动化或明确人工契约验证。 +- 词库来源版本和许可可追溯,未修改历史迁移。 +- 未实现非目标范围;没有课程、AI、后台维护或个人进度持久化。 +- OpenAPI 静态契约与运行时接口一致,且 STUDENT 权限边界可验证。 \ No newline at end of file diff --git a/docs/skill-taxonomy-sources.md b/docs/skill-taxonomy-sources.md index 0b2256e..049e69a 100644 --- a/docs/skill-taxonomy-sources.md +++ b/docs/skill-taxonomy-sources.md @@ -7,7 +7,7 @@ | 来源 | 固定版本 | 用途 | 许可/复用条件 | 官方地址与归属 | | --- | --- | --- | --- | --- | | ESCO | ESCO dataset v1.2.1(Last update 10/12/2025,发布页记录 Release v5.7.0) | 提供行业/职位映射所需的职业—技能—技能分类主关系、稳定来源 ID 和层级结构 | 依据 Commission Decision 2011/833/EU,可免费下载、使用、复制和复用。文档和产品界面须保留官方署名语句:`This service uses the ESCO classification of the European Commission.` | 数据集下载页:https://esco.ec.europa.eu/en/use-esco/download
复用条件说明:https://esco.ec.europa.eu/en/about-esco/faq | -| O*NET | O*NET 31.0 Database | 提供职业—技能关联的 Importance 数值,作为首版词云热度 `heat` 的唯一来源 | License 为 CC BY 4.0,归属与许可说明见 https://creativecommons.org/licenses/by/4.0/ | 官方数据库页:https://www.onetcenter.org/database.html | +| O*NET | O*NET 31.0 Database | 为 ESCO 未收录的具体技术提供 `software_skills` 补充来源,并通过职业/工作活动 Importance 提供首版词云热度 `heat` | License 为 CC BY 4.0,归属与许可说明见 https://creativecommons.org/licenses/by/4.0/ | 官方数据库页:https://www.onetcenter.org/database.html | 运行时不得访问 ESCO、O*NET 或任何在线词库服务。所有首版数据必须随 Flyway 迁移进入本地数据库,并保留来源版本与归属信息。 @@ -34,13 +34,13 @@ ## 3. 导入规则 1. **职责分工**:T2 只建立七张词库表、外键、唯一约束和查询索引;T3 只导入上述三类行业、10 个已列代表性职位及其关联技能分类、技能、别名和 O*NET Importance。 -2. **代表性子集**:不导入 ESCO/O*NET 全量数据;不把平台自由文本岗位或行业直接当作标准词库节点。 +2. **代表性子集与混合来源**:不导入 ESCO/O*NET 全量数据;ESCO 是职位、通用技能和分类主表;ESCO 无可靠节点的具体技术使用 O*NET `software_skills` 补充;不把平台自由文本岗位或行业直接当作标准词库节点。 3. **来源 ID 不变**:ESCO/O*NET 来源 ID 原样保存在稳定来源字段中;项目不得重编号、覆盖或伪造上游 ID。 4. **中文标准名必填**:每个入选技能必须有非空 `name_zh` 中文标准名;中文名称由项目维护,但不替代或修改上游来源 ID。 -5. **热度缺失**:技能无法映射 O*NET Importance 时必须保留,写入 `importance=null`;查询时映射为 `heat=null`,并排在有热度技能之后。 +5. **热度来源**:每条非空 Importance 必须保存 O*NET SOC、Element ID 和代理标记;技能无法映射 O*NET Importance 时必须保留,写入 `importance=null`;查询时映射为 `heat=null`,并排在有热度技能之后。 6. **项目映射责任**:项目维护中文名称、别名、技能分类和行业/职位映射,不修改开源来源 ID。行业/职位映射、细粒度分类和跨源技能对应关系均作为项目适配层记录。 7. **别名归一化**:生成 `normalized_alias` 时依次执行 Unicode NFKC、首尾空白移除、大小写折叠、连字符转空格、连续空白折叠为单空格、句点移除;`+` 和 `#` 必须保留,不能删除或替换。 -8. **来源可追溯**:来源清单写入 `skill_source_versions`,至少记录来源名称、固定版本、许可、归属 URL 和导入时间;职位技能关系记录关联的来源版本。 +8. **来源可追溯**:来源清单写入 `skill_source_versions`,至少记录来源名称、固定版本、许可、归属 URL 和导入时间;O*NET 补充技术的 `source_code` 使用真实 `SOC|Workplace Example|Element ID` 组合,职位技能关系记录 O*NET SOC、Element ID、代理标记及关联的来源版本。 9. **迁移不可改写**:T2/T3 计划使用 `V35__skill_cloud_taxonomy.sql` 和 `V36__skill_cloud_seed.sql`。执行前若编号已被占用,必须回退规划并刷新编号;后续词库更新只能新增 Flyway 迁移,不得改写 V1–V34 或任何已执行迁移。 10. **运行时离线**:接口运行时不得调用外部词库、AI、课程平台或同步服务;排名只读取本地 Flyway 数据。 @@ -62,7 +62,7 @@ ESCO 产品署名必须使用:`This service uses the ESCO classification of th | 中文名称和别名 | 不保证完整中文覆盖 | 补齐中文标准名、别名及归一化值 | | 细粒度技能分类 | 不保证符合本项目展示粒度 | 在职位下维护分类和展示名称 | | 行业/职位映射 | 不保证覆盖三类项目行业和中文职位名 | 维护行业—职位—分类—技能映射 | -| 跨源技能重要性关联 | 不提供项目所需的稳定本地关系 | 维护 ESCO 技能与 O*NET Importance 的映射 | +| 跨源技能与重要性关联 | 不提供项目所需的稳定本地关系 | 维护 ESCO 技能到 O*NET `software_skills` 的补充映射,以及每条 Importance 的 SOC/Element ID/代理标记 | ## 6. 验收检查项 @@ -73,7 +73,7 @@ ESCO 产品署名必须使用:`This service uses the ESCO classification of th - AC-5:文档明确 O*NET License 为 CC BY 4.0,并记录官方数据库页与许可 URL。 - AC-6:文档明确项目维护中文名称、别名、技能分类和行业/职位映射,不修改开源来源 ID。 - AC-7:文档明确 T2 建结构、T3 导数据,只导入代表性职位和关联技能;技能中文标准名必填。 -- AC-8:文档明确 O*NET Importance 缺失时保留技能并写入 `importance=null`,查询侧映射为 `heat=null`。 +- AC-8:文档明确 O*NET 补充技术、每条 Importance 的 SOC/Element ID/代理标记,以及无映射时保留技能并写入 `importance=null`、查询侧映射为 `heat=null`。 - AC-9:文档明确别名归一化遵循 NFKC、首尾空白、大小写、连字符、连续空白、句点规则,且保留 `+`、`#`。 - AC-10:文档明确后续词库更新只能新增 Flyway 迁移,不得改写 V1–V34 或已执行迁移。 - AC-11:来源、覆盖、导入与责任边界中不得出现未决占位或延期确认语句。 \ No newline at end of file