6 changed files with 355 additions and 6 deletions
@ -0,0 +1,55 @@ |
|||
# N03 Decision Point Audit |
|||
|
|||
审计时间:2026-09-15T08:40:05Z |
|||
|
|||
## Decision Point Status |
|||
|
|||
| DP | Status | Evidence | |
|||
| --- | --- | --- | |
|||
| DP-0 | confirmed | 需求、范围、非目标、语言与 Full workflow 已确认 | |
|||
| DP-1 | confirmed | 三接口需求口径已确认 | |
|||
| DP-2 | approved | proposal、三份 delta specs、design、tasks 已审阅 | |
|||
| DP-3 | approved | execution contract revision 2 已批准 | |
|||
| DP-4 | approved | SDD;W1/W2/W3 串行 wave,依赖关系已固化 | |
|||
| DP-5 | adjudicated | T3 来源与可证伪性修复获人工批准 | |
|||
| DP-6 | pass with formal waiver | 功能范围内验证通过;全量测试中的既有无关失败由用户正式豁免 | |
|||
| DP-7 | pending | 等待归档确认 | |
|||
|
|||
## Contract Coverage |
|||
|
|||
- W1:来源清单、V35 词库结构、V36 代表性种子数据、可追溯 O*NET provenance。 |
|||
- W2:Repository、词云排序、个人匹配、学习路径,50 项 wave 测试通过。 |
|||
- W3:三个 STUDENT 接口、真实权限测试、端到端一致性、接口文档和 OpenAPI。 |
|||
- 14 条 MUST/SHALL 行为均有实现与测试证据。 |
|||
- delta specs 已发布到根 baseline:specs/skill-cloud、specs/skill-match、specs/learning-path,共 14 条 Requirements,无冲突。 |
|||
|
|||
## Verification Evidence |
|||
|
|||
- Skill Cloud 指定测试组:74 tests,0 failures,0 errors,BUILD SUCCESS。 |
|||
- mvn -DskipTests package:BUILD SUCCESS。 |
|||
- python docs\verify_openapi.py:paths 129,All checks passed。 |
|||
- 启动缺陷修复:V37 将 importance_is_proxy 规范化为跨方言 BOOLEAN;H2 MODE=MySQL 与 MySQL 8.0.43 冒烟通过。 |
|||
- 完整 mvn test:1396 tests,2 failures,1 error,BUILD FAILURE。 |
|||
- 本次完整测试失败: |
|||
- CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries:既有时间依赖。 |
|||
- JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores:既有 Mock 签名漂移。 |
|||
- JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1:既有 Repository 方法签名漂移。 |
|||
- 这些失败位于本变更未修改的模块;Skill Cloud 测试在全量套件中全部通过。 |
|||
|
|||
## Formal Waiver |
|||
|
|||
用户于 2026-09-15 明确批准:将与本功能无关的既有失败作为正式豁免记录,并进入 release-archivist。当前 fresh run 只剩上述 3 条历史阻塞;更早一轮曾包含 LocalResumeStorageServiceTest 的 Windows 临时目录错误,后续环境运行已恢复。本豁免覆盖所有与本功能无关、且在变更前后可独立复现的既有失败。 |
|||
|
|||
## Scope and Coherence |
|||
|
|||
- 变更实现集中在 com.mydreamjob.skill、Flyway V35–V37、SecurityConfig、docs、OpenAPI 与对应测试。 |
|||
- 未修改 V1–V34 历史迁移或已有接口语义。 |
|||
- V37 是启动兼容修复,不改变 API 行为或业务公式。 |
|||
- W2/W3 独立评审均已通过;运行时启动修复也已独立复审 PASS。 |
|||
- 已知非阻断项:heatSource 设计表述与实际 heatSourceCode/heatElementId/heatProxy 字段命名不同;文档已按实际字段发布。 |
|||
|
|||
## Remaining Actions |
|||
|
|||
1. 记录 DP-7 归档确认。 |
|||
2. 将状态从 executing 转为 closing。 |
|||
3. ssf CLI 不可用;物理归档/合并需在获得用户确认后手工执行,且必须排除仓库中已有的无关未提交文件。 |
|||
@ -0,0 +1,88 @@ |
|||
# learning path Specification |
|||
|
|||
## Purpose |
|||
|
|||
定义基于缺失 TOP10 技能的确定性学习路径生成行为。 |
|||
|
|||
## 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` 或其他无关个人数据 |
|||
@ -0,0 +1,102 @@ |
|||
# skill cloud Specification |
|||
|
|||
## Purpose |
|||
|
|||
定义行业技能词云查询、筛选、热度排序与来源追溯行为。 |
|||
|
|||
## 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` 中返回筛选条件和技能列表 |
|||
@ -0,0 +1,102 @@ |
|||
# skill match Specification |
|||
|
|||
## Purpose |
|||
|
|||
定义个人技能与当前热门 TOP10 的确定性匹配行为。 |
|||
|
|||
## 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…
Reference in new issue