Browse Source

docs: add skill cloud planning specifications

backup/master-local-20261012
pwklppg 1 month ago
parent
commit
b960036aa3
  1. 59
      changes/n03-skill-cloud/proposal.md
  2. 84
      changes/n03-skill-cloud/specs/learning-path/spec.md
  3. 98
      changes/n03-skill-cloud/specs/skill-cloud/spec.md
  4. 98
      changes/n03-skill-cloud/specs/skill-match/spec.md

59
changes/n03-skill-cloud/proposal.md

@ -0,0 +1,59 @@
# N03 行业技能词云与学习路径接口
## Why
当前系统已经具备学生资料、技能标签、岗位匹配和求职进度能力,但学生无法从行业与职位的视角看到“市场在要求哪些技能”,也无法知道自己相对热门技能 TOP10 的掌握程度,更无法直接得到按优先级排列的学习补缺清单。仓库截至 V34 没有技能词云、行业技能热度或个人学习路径实现,现有 `student_profiles.skills_json` 与 `jobs.requirements` 也尚未被统一为可查询的技能分类体系。
本变更通过引入 ESCO + O*NET 的开源知识底座,为“行业 → 职位 → 技能分类 → 技能”建立可版本化数据,并为已登录学生提供三个确定性接口:查看词云与 TOP10、计算个人技能匹配度、生成缺失技能学习路径。
## What Changes
- 新增 Flyway 词库表,保存行业、职位、技能分类、技能、别名、职位技能关系、O*NET Importance 热度及数据来源版本。
- 首版导入互联网、金融、教育行业及代表性职位;中文名称、别名和细粒度技能分类由项目维护映射。
- 新增 `GET /api/v1/skills/word-cloud`:按行业、职位、可选技能分类返回全部技能,按热度降序排列。
- 新增 `GET /api/v1/skills/match`:用当前学生技能命中当前筛选结果 TOP10,返回匹配度、命中技能和缺失技能。
- 新增 `GET /api/v1/skills/learning-path`:对缺失 TOP10 技能按热度生成优先级和通用学习建议。
- 三个接口仅允许已登录 STUDENT 访问,统一使用现有 JWT、角色校验和 `ApiResponse` 规范。
- 更新 OpenAPI 契约、前端联调文档和词库来源说明。
## Scope
### In Scope
- ESCO 与 O*NET 的代表性数据导入、中文映射、来源版本和许可归属。
- 行业、职位、技能分类、技能、别名、热度及职位技能关系查询。
- 当前筛选范围内的完整技能词云,以及可由前端直接截取的 TOP10。
- 个人 `skills_json` 与 TOP10 的别名归一化匹配。
- 匹配度按 `命中数 / min(10, 实际技能数)` 计算,实际技能数为 0 时返回 0。
- 缺失技能按同一热度排序生成优先级 1、2、3…。
- 按技能分类生成通用学习建议,不依赖 AI 或外部服务。
- 单元测试、MockMvc 测试、H2 持久化测试和 OpenAPI 静态校验。
### Out of Scope
- 平台岗位 `jobId`、岗位发布频率或实时文本抽取参与热度计算。
- ESCO/O*NET 全量导入、自动翻译全量技能名称、在线同步开源词库。
- 后台词库维护页面、运营导入工具或运行时更新接口。
- AI 生成课程大纲、课程链接、讲师、学习时长或培训平台对接。
- 保存个人匹配历史、学习进度、完成状态或提醒任务。
- 企业和管理员查看学生个人匹配结果。
- 前端页面、交互设计和可视化实现。
## Impact
- `mydreamjob-server/src/main/resources/db/migration`:新增词库结构迁移和代表性种子数据。
- `mydreamjob-server/src/main/java/com/mydreamjob`:新增技能词云领域包、实体、Repository、Service、DTO 和 Controller,并调整安全配置。
- `mydreamjob-server/src/test/java/com/mydreamjob`:新增查询排序、匹配、学习路径和权限场景测试。
- `docs/openapi-rc.json`、`docs/frontend-integration.md`:登记三个接口和响应示例。
- 新增词库来源与版本说明文档,保留 ESCO/O*NET 的许可和数据版本。
- 不修改已有岗位匹配、候选人评分、求职进度等已验收接口语义。
## Proof of Completion
- Flyway 在 MySQL 模式测试库成功创建词库表并导入代表性数据。
- 三个接口返回统一 `ApiResponse`,匿名访问为 401,非 STUDENT 为 403。
- 词云结果按 O*NET Importance 降序;无权重技能保留且排在有权重技能之后;同权重排序稳定。
- TOP10 与词云使用同一排序结果,匹配度和学习路径复用该结果。
- 匹配度覆盖 0、少于 10 项、恰好 10 项、全部命中和部分命中场景。
- 学习路径优先级连续,且只包含未命中的 TOP10 技能;无缺口时返回空列表。
- `mvn test`、`mvn clean verify` 和 `python docs/verify_openapi.py` 全部通过。

84
changes/n03-skill-cloud/specs/learning-path/spec.md

@ -0,0 +1,84 @@
# Learning Path Specification
## ADDED Requirements
### Requirement: Learning path consumes unmatched TOP10 skills
学习路径接口 MUST 复用个人技能匹配结果,并只包含当前 TOP10 中未命中的技能;不得包含已命中技能或 TOP10 之外的技能。
#### Scenario: Student has skill gaps
- **WHEN** 当前 TOP10 中部分技能未命中
- **THEN** 学习路径 MUST 包含全部未命中技能且不含已命中技能
#### Scenario: Student has no skill gaps
- **WHEN** 当前 TOP10 全部命中
- **THEN** 学习路径 MUST 返回空列表和总数 0
#### Scenario: Student has no matching skill evidence
- **WHEN** 学生没有技能数据
- **THEN** 学习路径 MUST 包含当前 TOP10 的全部技能
### Requirement: Learning priority follows heat order
学习路径项 MUST 按与 TOP10 相同的热度顺序排列,并从 1 开始连续编号;空热度技能继续遵守词云的末位排序规则。
#### Scenario: Multiple missing skills are ordered
- **WHEN** 缺失技能包含多个热度不同的技能
- **THEN** 热度最高的技能 MUST 获得优先级 1,后续技能依次编号
#### Scenario: Missing skills have equal heat
- **WHEN** 缺失技能热度相同
- **THEN** 优先级 MUST 沿用词云的稳定次级排序,重复请求结果一致
#### Scenario: Only one skill is missing
- **WHEN** 当前 TOP10 只有一项未命中
- **THEN** 该技能 MUST 返回优先级 1
### Requirement: Learning suggestions are deterministic and industry-neutral
每项学习建议 MUST 由后端按技能分类的通用模板生成,不调用 AI、知识库问答或外部课程服务;建议不得包含课程链接、讲师、价格、学习时长或完成度。
#### Scenario: Programming language gap is returned
- **WHEN** 缺失技能属于编程语言分类
- **THEN** 建议 MUST 描述基础语法学习和编码练习方向
#### Scenario: Framework gap is returned
- **WHEN** 缺失技能属于框架分类
- **THEN** 建议 MUST 描述核心概念学习和小型项目实践方向
#### Scenario: Tool gap is returned
- **WHEN** 缺失技能属于工具分类
- **THEN** 建议 MUST 描述安装配置和工程实践方向
#### Scenario: Unknown category is returned
- **WHEN** 缺失技能无法匹配特定模板分类
- **THEN** 系统 MUST 返回一条行业无关的通用学习建议
### Requirement: Learning path response is actionable and minimal
每个学习路径项 MUST 返回技能 ID、技能名称、技能分类、优先级和简化学习建议;响应 MUST 不包含个人隐私、简历原文或模型推理内容。
#### Scenario: A learning item is generated
- **WHEN** 存在未命中技能
- **THEN** 每个路径项 MUST 同时具备技能身份、分类、连续优先级和建议文本
#### Scenario: Repeated request is made
- **WHEN** 学生资料、词库数据和筛选条件均未变化
- **THEN** 接口 MUST 返回相同顺序、相同优先级和相同建议
#### Scenario: Response contains no private profile data
- **WHEN** 学习路径成功返回
- **THEN** 响应 MUST 不含手机号、邮箱、简历文件、原始 `skills_json` 或其他无关个人数据

98
changes/n03-skill-cloud/specs/skill-cloud/spec.md

@ -0,0 +1,98 @@
# Skill Cloud Specification
## ADDED Requirements
### Requirement: STUDENT-only skill cloud access
系统 MUST 只允许已登录 STUDENT 调用技能词云接口,并从 JWT 解析当前用户,不接受客户端传入学生身份参数。
#### Scenario: Anonymous request is rejected
- **WHEN** 未携带有效 JWT 请求 `GET /api/v1/skills/word-cloud`
- **THEN** 系统 MUST 返回 401,且不得返回任何词库数据
#### Scenario: Company or administrator request is rejected
- **WHEN** COMPANY 或 ADMIN 用户携带有效 JWT 请求技能词云
- **THEN** 系统 MUST 返回 403,且不得返回任何词库数据
#### Scenario: Student request is accepted
- **WHEN** STUDENT 用户携带有效 JWT 请求技能词云
- **THEN** 系统 MUST 按筛选条件返回词库数据
### Requirement: Skill cloud filter validation
接口 MUST 要求 `industryId` 和 `occupationId` 为必填正整数,`categoryId` 为可选正整数;行业与职位必须存在且互为关联,技能分类存在时必须属于该职位。
#### Scenario: Required filters are missing
- **WHEN** 请求未传 `industryId` 或 `occupationId`
- **THEN** 系统 MUST 返回 400,并说明缺失的筛选参数
#### Scenario: Occupation does not belong to industry
- **WHEN** 请求传入存在但不属于目标行业的职位
- **THEN** 系统 MUST 返回 400,且不得返回技能数据
#### Scenario: Category does not belong to occupation
- **WHEN** 请求传入存在但不属于目标职位的技能分类
- **THEN** 系统 MUST 返回 400,且不得返回技能数据
#### Scenario: Category is omitted
- **WHEN** 请求只传行业和职位
- **THEN** 系统 MUST 返回该职位下所有技能分类的技能
### Requirement: Complete ranked skill cloud
系统 MUST 返回当前筛选范围内全部技能,并按 `heat` 降序排列;`heat` 为空时排在非空值之后。同 `heat` 或同为空值时,系统 MUST 使用稳定的中文技能名称和技能 ID 作为次级排序条件。
#### Scenario: Skills are ordered by heat
- **WHEN** 筛选结果存在多个带 O*NET Importance 的技能
- **THEN** 返回列表 MUST 按数值从高到低排列
#### Scenario: Skills without heat are retained
- **WHEN** 筛选结果包含无 O*NET Importance 的 ESCO 技能
- **THEN** 这些技能 MUST 保留在列表中,`heat` 为 null,并排在所有非 null 热度技能之后
#### Scenario: Equal heat has deterministic order
- **WHEN** 两个技能具有相同 `heat`
- **THEN** 系统 MUST 按稳定次级规则返回一致顺序,重复请求不得随机变化
#### Scenario: Empty scope returns empty list
- **WHEN** 合法筛选范围内没有技能
- **THEN** 系统 MUST 返回成功响应、空技能列表和总数 0
### Requirement: TOP10 is derived from the skill cloud
系统 MUST NOT 为 TOP10 维护独立排序或独立数据源;前端必须能够直接使用词云返回列表的前十条得到 TOP10。
#### Scenario: Scope has at least ten skills
- **WHEN** 词云结果包含至少十条技能
- **THEN** 前十条 MUST 与同一筛选条件下的热门技能 TOP10 一致
#### Scenario: Scope has fewer than ten skills
- **WHEN** 词云结果少于十条技能
- **THEN** 全部结果 MUST 构成实际 TOP10 集合
### Requirement: Skill cloud response exposes source-linked metadata
每个技能项 MUST 至少返回技能 ID、中文名称、技能分类 ID、技能分类名称、`heat` 和稳定排名。技能仍可通过 ID 追溯至 ESCO/O*NET 来源。
#### Scenario: Skill item is returned
- **WHEN** 查询返回任一技能
- **THEN** 响应 MUST 包含该技能的身份、分类、热度和排名字段
#### Scenario: Response is wrapped consistently
- **WHEN** 请求成功
- **THEN** 响应 MUST 使用项目统一 `ApiResponse` 结构,并在 `data` 中返回筛选条件和技能列表

98
changes/n03-skill-cloud/specs/skill-match/spec.md

@ -0,0 +1,98 @@
# Skill Match Specification
## ADDED Requirements
### Requirement: Personal matching uses the current ranked TOP10
个人技能匹配接口 MUST 复用技能词云相同的筛选参数、数据范围和热度排序;匹配集合 MUST 是当前筛选结果的前 `min(10, 实际技能数)` 项。
#### Scenario: Filtered result has at least ten skills
- **WHEN** 当前筛选返回十条或更多技能
- **THEN** 匹配集合 MUST 恰好包含热度最高的十条技能
#### Scenario: Filtered result has fewer than ten skills
- **WHEN** 当前筛选返回六条技能
- **THEN** 匹配集合 MUST 包含全部六条技能,分母 MUST 为 6
#### Scenario: Filtered result is empty
- **WHEN** 当前合法筛选范围内没有技能
- **THEN** 匹配度 MUST 返回 0,命中和缺失列表 MUST 均为空
### Requirement: Student skills are read from the authenticated profile
系统 MUST 从当前登录 STUDENT 的 `student_profiles.skills_json` 读取技能;不得接受客户端另行提交的技能列表作为个人匹配依据。
#### Scenario: Student profile has valid skills
- **WHEN** 当前学生存在合法技能数组
- **THEN** 系统 MUST 使用该数组计算匹配结果
#### Scenario: Student profile is absent or has no skills
- **WHEN** 当前学生没有资料记录、技能字段为空或技能数组为空
- **THEN** 匹配度 MUST 返回 0,命中列表为空,缺失列表为当前 TOP10
#### Scenario: Skills JSON is malformed
- **WHEN** 当前学生技能字段无法解析为字符串数组
- **THEN** 系统 MUST 按无技能处理,不得导致 500 响应或泄露原始字段内容
### Requirement: Skill matching normalizes aliases
系统 MUST 使用技能标准和别名进行匹配,归一化至少覆盖首尾空白、大小写、连续空格和常见标点差异;每个标准技能命中一次即可。
#### Scenario: Alias matches canonical skill
- **WHEN** 学生技能为 `SpringBoot`,TOP10 标准技能为 `Spring Boot` 且存在对应别名
- **THEN** 系统 MUST 将该技能计为命中
#### Scenario: Case and spacing differ
- **WHEN** 学生技能与标准技能的字母大小写或空格数量不同
- **THEN** 系统 MUST 在归一化后判断为同一技能
#### Scenario: Duplicate student skills exist
- **WHEN** 学生技能数组包含多个指向同一标准技能的别名
- **THEN** 系统 MUST 只计一次命中
### Requirement: Match score uses the actual TOP10 size
匹配度 MUST 使用公式 `round(命中数 × 100 / min(10, 实际技能数))` 计算,并返回 0 到 100 的整数。实际技能数为 0 时,匹配度 MUST 为 0。
#### Scenario: All skills match
- **WHEN** 当前 TOP10 有 8 项,学生命中全部 8 项
- **THEN** 匹配度 MUST 返回 100
#### Scenario: Partial match is rounded
- **WHEN** 当前 TOP10 有 3 项,学生命中 1 项
- **THEN** 匹配度 MUST 返回 33
#### Scenario: No skill matches
- **WHEN** 当前 TOP10 非空且学生没有命中任何技能
- **THEN** 匹配度 MUST 返回 0
### Requirement: Match response separates matched and missing skills
响应 MUST 返回匹配度、命中数量、实际匹配集合大小、命中技能列表和缺失技能列表;缺失技能 MUST 保持 TOP10 的热度排序。
#### Scenario: Some skills are matched
- **WHEN** 学生命中 TOP10 的部分技能
- **THEN** 命中列表和缺失列表 MUST 分别返回对应技能,且并集等于 TOP10
#### Scenario: All skills are matched
- **WHEN** 学生命中 TOP10 的全部技能
- **THEN** 缺失技能列表 MUST 为空,匹配度 MUST 为 100
#### Scenario: No skills are matched
- **WHEN** 学生没有命中 TOP10 的任何技能
- **THEN** 命中技能列表 MUST 为空,缺失列表 MUST 等于 TOP10
Loading…
Cancel
Save