# Holland Career Assessment

> 当用户提到霍兰德测试、RIASEC、职业兴趣或兴趣类型时调用本Skill，用于评估职业兴趣偏好并解读测评结果。

- Skill: `ahang1598/holland-career-assessment` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add ahang1598/holland-career-assessment`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ahang1598/holland-career-assessment/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: ahang1598 (https://skillmd.com/u/ahang1598)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/ahang1598/holland-career-assessment

---


# 霍兰德职业兴趣测评 Skill

## Overview

生成霍兰德职业兴趣测评题目并计算结果，覆盖六大职业兴趣维度：

- R：现实型（Realistic）
- I：研究型（Investigative）
- A：艺术型（Artistic）
- S：社会型（Social）
- E：企业型（Enterprising）
- C：常规型（Conventional）

本版本扩展为 60 道题的简版测评，保持六维度平衡设计，每个维度 10 题，适合做早期演示、训练和平台上传使用。

## When to Use This Skill

在以下场景触发本 skill：

- 用户希望做职业兴趣评测或职业适配测试；
- 用户问“我适合什么职业”“我适合做什么”；
- 用户希望复用该题库做职业测评页面；
- 用户已经给出一组作答结果，要求快速生成职业兴趣报告。

## 题目元数据

```yaml
assessment_id: HOLLAND-60-001
assessment_name: "霍兰德职业兴趣测评（60题版）"
question_total: 60
dimension_count: 6
dimensions: ["R", "I", "A", "S", "E", "C"]
question_bank_path: "holland-career-assessment/references/questions.json"
algorithm_path: "holland-career-assessment/references/algorithm.md"
score_calculation_path: "holland-career-assessment/scripts/calculate_holland.py"
score_function: "calculate_scores"
entrypoint: "python holland-career-assessment/scripts/calculate_holland.py --answers 'YYNNYY...'"
```

## 工作流程

1. 判断用户是否需要“开始测评”或“已有答案直接算分”。
2. 如为新测评，则展示题目并收集作答。
3. 对答案进行校验，确保题目编号、答案类型、维度映射正确。
4. 调用评分脚本计算各维度分数与职业倾向。
5. 返回用户报告：顶层职业类型、维度分数、推荐职业方向。

## WorkBuddy 视觉化交互卡片约束（核心执行规范）
1. **唯一渲染通道**：
   生成测评页面的 HTML/CSS/JS 代码后，**必须且只能**调用内置的 `show_widget` 工具，将代码作为参数传入，以触发聊天窗口的原生卡片渲染。
   - `show_widget` 是 WorkBuddy 平台**原生渲染工具**，直接以 `tool_use` 发起调用即可；**严禁在思考阶段用 `ToolSearch` 或任何工具发现机制去”确认 `show_widget` 是否存在”**——原生工具不通过 `ToolSearch` 暴露，检索不到 ≠ 工具不存在 ≠ 环境不支持。
   - 正确流程：直接发起 `show_widget` 调用 → 成功即渲染；仅当调用被实际执行后返回错误或被拒绝，才按下条「异常处理」处理。

2. **绝对禁止项**：
   - **严禁**直接将 HTML/CSS/JS 代码以 Markdown 代码块的形式输出。
   - **严禁**生成或提供独立的 `.html` 文件下载链接。
   - **严禁**在不调用 `show_widget` 的情况下直接输出组件代码。

3. **异常处理**：
   仅当 `show_widget` 调用**被实际发起并返回错误、或被用户/平台拒绝**时，才视为渲染失败。此时仅向用户输出**一句话**纯文本提示（如：”当前环境暂不支持内联渲染，请稍后重试或检查配置”），**绝对不允许**：
   - 退化为生成独立 HTML 页面；
   - 退化为把问卷/题目以纯文本逐题列出（**禁止输出”文本版 60 题”等替代问卷**——那等于让用户在聊天里逐题手答，体验崩坏，且仍非卡片渲染）；
   - 把思考过程、工具检索结果暴露给用户。
   - **关键**：思考阶段用 `ToolSearch` 找不到 `show_widget`，**不构成**渲染失败，不得据此放弃渲染——必须仍直接发起 `show_widget` 调用。

4. **视觉稳定性要求**：
   传入 `show_widget` 的代码需保持结构稳定，避免使用可能导致聊天窗口高度剧烈跳动的复杂外部依赖。

### 1. 渲染目标

当用户触发“开始测评”或“霍兰德职业兴趣测评”时，必须返回一个可渲染的内联卡片，而不是纯文本说明。

该内联卡片必须包含：

- 顶部标题：`霍兰德职业兴趣测评`
- 二级说明：`共 60 题， 请根据真实感受选择“是”或“否”`
- 顶部进度条：显示已答题数/总题数，例如 `已答 17 / 60`（驱动提交按钮置灰/高亮，**用已答题数而非当前题号**，见 §4 联动逻辑）
- 题目列表：每一题显示编号 + 题干
- 每道题的答题区域：左右两侧各有一个单选按钮，分别对应“是”和“否”
- 底部交互：可前后翻题，并在最后一题提供提交入口
- 允许用户在同一对话流中直接答题，不需要跳转到其他页面

### 2. 视觉结构要求

视觉结构必须遵循以下稳定版布局：

- 整体是一个白色/浅灰背景的卡片容器，边框柔和、圆角适中
- 标题采用大字号、黑色/深灰字体
- 子标题采用中等字号、灰色字体，位置在标题下方
- 顶部右侧显示进度文本，如 `已答 17 / 60`（初始为 `已答 0 / 60`，随答题实时同步，见 §4 联动逻辑）
- 进度条位于标题区下方，长度为卡片宽度的主内容区域
- 题目行高统一，题干左对齐，选项按钮右对齐
- “是 / 否”是两个二选一按钮，按钮背景色按选项区分，**不允许出现无底色（白底/透明）状态**：
  - 未选中：两个按钮均为灰底（`#E5E7EB` 底 / **`#4B5563` 深灰字**，确保灰底上文字清晰可读）
  - 选中"是"：该按钮变为绿底（`#22C55E` / `#FFFFFF` 白字）；"否"按钮恢复灰底（`#E5E7EB` / `#4B5563`）
  - 选中"否"：该按钮变为红底（`#DC2626` / `#FFFFFF` 白字）；"是"按钮恢复灰底（`#E5E7EB` / `#4B5563`）
  - 同一题只能有一个按钮带底色（绿或红），互斥；点击未选中项时，原带色按钮必须**背景与字色同步**立即变回灰底深灰字，避免出现双带色或白底残态
  - **字色恢复（关键，防止文字消失）**：按钮从选中态切回未选中灰底态时，**字色必须同步从白字恢复为深灰字（`#4B5563`）**，绝不允许背景已变灰但字色仍停留在白字——白字配灰底会导致文字几乎不可见（用户反馈"按钮字体变白消失"）。即每个按钮的 `(背景, 字色)` 必须成对切换：未选 = `(#E5E7EB, #4B5563)`，选中 = `(绿/红, #FFFFFF)`，二者不得错位。
  - 切换必须即时、无延迟；hover 可有轻微高亮但不得与上述选中色混淆
- 题目行之间用浅边框分隔
- 在页面底部可以显示辅助说明，例如 `今天帮你做些什么？@引用对话文件 / 调用技能与指令`
- 60 道题必须按三段显示，每一段前方都有明确的分段标题和提示词，不得混合成一大段连续题目

### 3. 分段提示词要求（必须严格遵守）

60 道题必须按真实题库顺序存储为 3 个连续区段，并且每个区段上方必须显示固定标题和提示词，不得改写语义、删减内容或自由替换：

1. 第一部分标题：`第一部分 你所感兴趣的活动`
   - 提示词：`下面列举了若干种活动，请根据你自己的感觉，对这些活动进行好恶判断。若是喜欢的，请在“是”栏里选“是”；若是不喜欢的，请选“否”。请按顺序回答全部问题。`

2. 第二部分标题：`第二部分 你所擅长或胜任的活动`
   - 提示词：`下面列举了若干种活动，其中凡是你能做或大概能做的事，请在“是”栏里填“是”；反之，则选“否”。请回答全部问题。`

3. 第三部分标题：`第三部分 你所喜欢的职业`
   - 提示词：`下面列举了多种职业，请逐一认真考虑，如果是你有兴趣的工作，请在栏里选“是”；如果是你不太喜欢、不关心的工作，请选“否” 。请回答全部问题。`

题库真实顺序如下：

- 第一部分：1–30 题，全部为“感兴趣的活动”题
- 第二部分：31–48 题，全部为“擅长或胜任的活动”题
- 第三部分：49–60 题，全部为“喜欢的职业”题

要求：

- 三段必须依次连续出现，且题目区段与标题一一对应
- 每段上方都必须显示标题与提示词
- 标题和提示词必须原样保留，不得改成中文简写、删减或替换“是/否”表述
- 每段内部题目都必须属于对应分段题目内容，不得跨段混排
- 题号必须保留为真实题库编号，不能重排成 1–30 之外的非真实编号

### 4. 交互约束

- 一次性展示全部 60 道题，不能分页、不能逐题加载、不能”先展示前几题再继续”
- 题目必须按真实题库区段顺序展示：第一部分 1–30，第二部分 31–48，第三部分 49–60
- 每题必须只有一个有效答案：`是` 或 `否`
- 用户点击”是”或”否”时，必须只切换当前题在该题目的选中状态，不应同时多选
- 当前题号必须和题目编号一一对应，且每道题都有明确的题目编号
- 进度条长度和文本必须依据已答题/总题数自动更新，但整个评测仍然保持 60 题全量展示
- 当用户完成全部 60 道题后，必须出现明确的”提交”按钮，并且点击后触发最终评测结果计算
- 提交按钮必须在整个卡片的最下方可见，且只有在所有题目已选择答题状态后才允许提交；未完成时可提示未答题项
- **提交按钮交互状态（关键）**：
  - 未答完60题前：提交按钮必须置灰（disabled状态），不可点击，配色为 `(#E5E7EB` 灰底 / `#4B5563` 深灰字`)`——**disabled 态字色必须为深灰 `#4B5563`，严禁用白字**（白字配灰底会看不见，这是"按钮字体变白消失"的高频原因）。
  - 已答完60题后：提交按钮高亮可点击，配色为 `(#15803D` 深绿底 / `#FFFFFF` 白字`)`——可点态才用白字，且必须配深色底，保证对比度。
  - 置灰状态下应显示提示文案，如"请完成所有题目后再提交（已答 X/60）"——文案须带已答数，与进度文本同源
  - 按钮状态必须实时响应答题进度，不允许延迟更新
  - **答题进度与提交按钮联动逻辑（关键，必须实现，不得省略）**：进度文本、进度条宽度、提交按钮的 `disabled / 文案 / (背景,字色)` 三者必须由**同一套答题状态**驱动、实时同步，不得各自为政。渲染模型须将下列契约落实为卡片内 `<script>`，**不得只写静态 HTML**（静态写死 `17 / 60` 或 `disabled` 即视为未实现）：
    ```js
    // 状态：题号(1-60) → 'Y'|'N'；同题重选覆盖旧值，保证一题一答、天然互斥
    const answered = {};
    function recalc() {
      const count = Object.keys(answered).length;        // 已答题数（0-60）
      const pct = (count / 60 * 100);                     // 进度百分比
      // 1) 进度文本（标题区右侧）
      progressText.textContent = `已答 ${count} / 60`;
      // 2) 进度条宽度：fill 必须有显式 background（与得分条同理，见结果渲染约束）
      progressBarFill.style.width = pct + '%';
      // 3) 提交按钮：未答完置灰 disabled，答完高亮可点
      if (count === 60) {
        submitBtn.disabled = false;
        submitBtn.textContent = '提交并生成兴趣画像';
        submitBtn.style.background = '#15803D';           // 深绿底
        submitBtn.style.color = '#FFFFFF';                // 白字（仅可点态用白字）
      } else {
        submitBtn.disabled = true;
        submitBtn.textContent = `请完成所有题目后再提交（已答 ${count}/60）`;
        submitBtn.style.background = '#E5E7EB';            // 灰底
        submitBtn.style.color = '#4B5563';                // 深灰字（disabled 严禁白字）
      }
    }
    // 每个"是/否"按钮：点击即互斥记录该题答案，随后 recalc() 同步三处
    answerBtn.addEventListener('click', () => {
      answered[id] = (option === 'yes') ? 'Y' : 'N';   // 同题另一按钮点击会覆盖，天然互斥
      // 同题另一按钮成对切回灰底深灰字、被选按钮切语义色白字（见 §2）
      recalc();
    });
    // 提交：仅 count===60 时可达，拼 60 位 Y/N 紧凑序列提交（见「提交数据格式约束」）
    submitBtn.addEventListener('click', () => {
      if (submitBtn.disabled) return;                    // 双保险，拦截未答完
      const seqStr = Array.from({length: 60}, (_, i) => answered[i + 1] || '').join('');
      submit(seqStr);                                    // 60 位 Y/N 序列，严禁 JSON
    });
    ```
    - **三处必须由同一次 `recalc()` 同步驱动**：进度文本、进度条宽度、提交按钮状态；任一滞后或不更新即视为未实现。
    - **初始渲染（关键，防静态陷阱）**：页面加载时 `answered = {}`、`count = 0`，提交按钮必须为 **disabled 灰底深灰字**、文案 `请完成所有题目后再提交（已答 0/60）`、进度条宽度 `0%`、进度文本 `已答 0 / 60`。**严禁初始即为可点高亮态、严禁静态写死 `已答 17 / 60`**（§6 示意中的 `17 / 60` 仅为结构示例，不是初始值）。
    - **字色与背景成对切换**：提交按钮在 disabled↔可点切换时，`(背景, 字色)` 必须整体成对切（`灰底/深灰字` ↔ `深绿底/白字`），不得只切其一——错位会导致白字配灰底(消失)或深灰字配深绿底(看不清)。
  - **字色与背景成对切换（关键）**：提交按钮从 disabled → 可点时，`(背景, 字色)` 必须整体从 `(灰底, 深灰字)` 切到 `(深绿底, 白字)`，不得只切背景不切字色、或只切字色不切背景——错位会导致白字配灰底(消失)或深灰字配深绿底(看不清)。
- **提交数据格式约束（关键）**：
  - 用户点击提交时，卡片**必须以 60 位紧凑 Y/N 字符序列**提交答案（如 `YYNNYY...`，按题号 1→60 顺序排列，`Y`=是 / `N`=否），**严禁使用 JSON 对象格式**（如 `{"1":"Y",...}`）提交。
  - 原因：JSON 格式约 530 字符，传输过程中在约 530 字符处被系统截断，导致答案丢失；紧凑序列仅 60 字符，远低于截断阈值，不会丢失。
  - 提交的字符序列长度必须等于 60；不足 60 视为未答完，由提交按钮置灰逻辑拦截。
  - 序列第 i 位对应题号 i（1-based）：第 1 位 = 第 1 题，第 60 位 = 第 60 题，顺序与题库一致，不得错位。
- 交互必须发生在当前对话流的内联组件中，而不是返回普通纯文本
- 该组件的页面流必须保持”60题一次性展示 + 全量答题 + 提交”的连续逻辑

### 5. HTML / Visualizer 输出要求

如果系统支持 WorkBuddy 的 Visualizer / 内联 HTML 渲染，返回内容必须满足：

- 以 HTML 片段或可渲染组件形式输出，而不是纯 Markdown
- 必须保留视觉层结构：标题、进度、题目列表、双选按钮
- 不能出现散乱的自然语言说明替代卡片
- 不能直接输出仅有 JSON 字段而没有交互容器
- 不能让前端自行“自由发挥”生成不同布局
- 只允许按本 skill 规定的结构渲染，不允许引入无关内容

### 6. 参考示意结构

以下内容仅用于描述结构，不应被当作自由文本输出；真正交互时，应按本 skill 的稳定规则渲染：

```html
<div class="assessment-card">
  <h2>霍兰德职业兴趣测评</h2>
  <div class="subtitle">共 60 题，请根据真实感受选择“是”或“否”</div>
  <div class="progress-row">
    <!-- 初始渲染：宽度 0%、文本「已答 0 / 60」，随答题 recalc() 同步更新（见 §4 联动逻辑） -->
    <div class="progress-bar"><div class="progress-bar-fill" style="width:0%"></div></div>
    <span class="progress-text">已答 0 / 60</span>
  </div>
  <div class="question-row">
    <div class="question-number">1</div>
    <div class="question-text">你喜欢修理或组装器械吗？</div>
    <div class="answer-options">
      <!-- 是/否双选按钮：配色与互斥见 §2；data-option=yes/no，提交走 60 位 Y/N 序列见 §4 -->
      <button data-option="yes">是</button>
      <button data-option="no">否</button>
    </div>
  </div>
  <!-- 提交按钮：初始即 disabled 灰底深灰字、文案「已答 0/60」；随答题 recalc() 同步（见 §4 联动逻辑），静态写死 17/60 视为未实现 -->
  <button class="submit-btn" disabled>请完成所有题目后再提交（已答 0/60）</button>
</div>
```

### 7. 禁止事项

> 按钮配色、互斥、禁白底等约束已在 **§2** 详述，本节不再重复，仅列 §2 未覆盖项。

- 不允许输出纯文本描述替代卡片
- 不允许缺失“是 / 否”双选按钮
- 不允许没有进度条和题号
- 不允许在页面中出现无关长文案或随机推理内容
- 不允许模型自行更换布局结构，必须保持 WorkBuddy 交互卡片的稳定格式

## 输出格式

## 确定性输出约束（关键）

为避免 Workbody 生成结果每次都变化，本 skill 必须执行严格的确定性规则：

1. 结果必须以脚本计算结果为唯一准绳，不允许模型自由推断分数。
2. `details` 中六个维度必须按固定顺序输出：`["R", "I", "A", "S", "E", "C"]`。
3. `top_dimensions` 必须按分数降序排列；若分数相同，按固定维度优先级排序：`R, I, A, S, E, C`。
4. `dominant_type` 必须由 `top_dimensions` 前 3 项拼接而成，不能由模型自行命名或重写。
5. `score` 必须为整数，且由公式计算产生，不允许模型自由发挥。
6. `reportTextList` 必须只包含 `top_dimensions` 对应的 3 个维度，顺序与 `top_dimensions` 一致；分数展示走顶层 `details`，详情项不带 `score`。
7. `reportTextList` 每项的字段名固定为 `type / name / commonFeatures / typicalOccupations`（驼峰），文案必须使用脚本预定义内容，不允许自由发挥。
8. 任何场景都不允许输出随机、模糊、口语化的结论；必须稳定输出统一结构。
9. 如果题目不完整，必须返回 `incomplete`，并列出缺失题号；不得在有缺失时强行生成完整结论。
10. 生成结果时必须以 JSON 对象返回，不能返回 Markdown、自然语言说明、额外说明块或解释性文本。

本 skill 只负责返回结构化的评测结果，不承担前端渲染。输出必须严格符合以下极简 JSON 结构：

```json
{
  "status": "completed",
  "score": 55,
  "top_dimensions": ["A", "C", "E"],
  "dominant_type": "ACE",
  "details": {
    "R": 4,
    "I": 3,
    "A": 8,
    "S": 5,
    "E": 7,
    "C": 6
  },
  "reportTextList": [
    {
      "type": "A",
      "name": "艺术型",
      "commonFeatures": "有创造力，乐于创造新颖、与众不同的成果，渴望表现自己的个性，实现自身的价值。做事理想化，追求完美，不重实际。具有一定的艺术才能和个性。善于表达、怀旧、心态较为复杂。",
      "typicalOccupations": "喜欢的工作要求具备艺术修养、创造力、表达能力和直觉，并将其用于语言、行为、声音、颜色和形式的审美、思索和感受，具备相应的能力。不善于事务性工作。如艺术方面(演员、导演、艺术设计师、雕刻家、建筑师、摄影家、广告制作人)，音乐方面(歌唱家、作曲家、乐队指挥)，文学方面(小说家、诗人、剧作家)。"
    },
    {
      "type": "C",
      "name": "常规型",
      "commonFeatures": "尊重权威和规章制度，喜欢按计划办事，细心、有条理，习惯接受他人的指挥和领导，自己不谋求领导职务。喜欢关注实际和细节情况，通常较为谨慎和保守，缺乏创造性，不喜欢冒险和竞争，富有自我牺牲精神。",
      "typicalOccupations": "喜欢要求注意细节、精确度、有系统有条理，具有记录、归档、据特定要求或程序组织数据和文字信息的职业，并具备相应能力。如:秘书、办公室人员、记事员、会计、行政助理、图书馆管理员、出纳员、打字员、投资分析员。"
    },
    {
      "type": "E",
      "name": "企业型",
      "commonFeatures": "追求权力、权威和物质财富，具有领导才能。喜欢竞争、敢冒风险、有野心、抱负。为人务实，习惯以利益得失，权利、地位、金钱等来衡量做事的价值；做事有较强的目的性。",
      "typicalOccupations": "喜欢要求具备经营、管理、劝服、监督和领导才能，以实现机构、政治、社会及经济目标的工作，并具备相应的能力。如项目经理、销售人员、营销管理人员、政府官员、企业领导、法官、律师。"
    }
  ],
  "qrCodePath": "pages/hollander/index"
}
```

### 输出规范

- `status` 必须为 `completed` 或 `incomplete`
- `score` 为 0-100 的整数，且必须来自脚本计算结果
- `top_dimensions` 返回前 3 个维度，必须按分数降序排列，例：`["A", "C", "E"]`
- `dominant_type` 为前 3 维度拼接结果，例：`ACE`，不能自行更改为其他文本
- `details` 必须按固定顺序输出 `R`、`I`、`A`、`S`、`E`、`C` 六个键，包含所有维度的分数
- `reportTextList` 为数组，**只包含 top 3 维度的详情**，顺序与 `top_dimensions` 一致
- 当多个维度分数相同时，按固定维度优先级 `R, I, A, S, E, C` 取靠前者，确保 reportTextList 中最多只有三个维度
- `reportTextList` 每个元素必须包含且仅包含：
  - `type`：维度代号（如 `A`），与 `top_dimensions` 对应
  - `name`：维度名称
  - `commonFeatures`：共同特点文案（字符串类型，驼峰命名）
  - `typicalOccupations`：典型职业文案（字符串类型，驼峰命名）
- `commonFeatures` 和 `typicalOccupations` 必须使用预定义文案，不允许自由发挥
- `qrCodePath` 为固定值 `"pages/hollander/index"`，前端用于跳转，不允许自行更改
- 若题目未完成，则返回 `incomplete`，并说明缺失题目编号
- 输出必须为纯 JSON，不允许嵌套说明、Markdown 代码块或额外字段

### 渲染约束（关键）

本 skill 约束的是”生成结果的渲染契约”，而不是页面实现细节。具体要求如下：

- `dominant_type` 必须作为页面主标题字段使用。
- `score` 必须作为总分展示字段使用。
- `top_dimensions` 必须按降序排列，并用于展示 Top 3 维度。
- `details` 必须作为各维度分数表格/柱状图数据源，展示所有六个维度的分数。
- `reportTextList` **只包含分数最高的3个维度的详情**，必须作为最下方维度说明区域的数据源：
  - 顺序与 `top_dimensions` 一致；当多个维度分数相同时，按固定维度优先级 `R, I, A, S, E, C` 取靠前者，确保最多只展示三个维度
  - 每个维度必须展示"共同特点"（`commonFeatures`）和"典型职业"（`typicalOccupations`）两个部分
  - 文案必须使用脚本返回的预定义内容，不允许前端自由发挥
- `qrCodePath` 为固定跳转路径，前端按原值使用，不允许自行改写。
- 任何前端都不能自行生成新的字段名来替代上述结构。
- 前端只能根据这几个字段进行展示，不能依赖自由文本解析。

### 禁止事项

- 不允许返回自由文本替代 JSON
- 不允许缺少 `details`
- 不允许缺少 `dominant_type`
- 不允许`details`中遗漏任一维度
- 不允许 `reportTextList` 中包含超过三个维度
- 不允许 `reportTextList` 中出现 `score` 字段（分数走顶层 `details`）
- 不允许在 skill 中混合前端渲染逻辑
- 不允许前端自行扩展未定义字段覆盖结果解释
- 不允许前端自由发挥"共同特点"和"典型职业"文案
## 题目结构说明

该评测题目以职业兴趣维度为核心，采用“真值判断”式答题。

- 第一部分：你所感兴趣的活动（1–30 题，各维度 5 题）
- 第二部分：你所擅长或胜任的活动（31–48 题，各维度 3 题）
- 第三部分：你所喜欢的职业（49–60 题，各维度 2 题）

本版本保持 60 题简版结构，结果页面在最下方必须按各维度展开展示：共同特点、典型职业与分数说明。

### 题目示例

```json
{
  "id": 1,
  "dimension": "R",
  "text": "你喜欢修理或组装物品吗？",
  "answer_type": "yes_no"
}
```

## 评分入口

评分脚本位置：

- `holland-career-assessment/scripts/calculate_holland.py`
- 评分函数：`calculate_scores`
- 输出：JSON 格式的分数与排序结果

示例命令：

```bash
# 推荐：紧凑序列（60 位 Y/N 字符串，约 60 字符，不触发传输截断）
python holland-career-assessment/scripts/calculate_holland.py \
  --answers 'YYNNYYNNYYNNYYNNYYNNYYNNYYNNYYNNYYNNYYNNYYNNYYNNYYNNYYNN'

# 兼容：旧 JSON 对象格式（约 530 字符，不推荐，存在被截断风险）
python holland-career-assessment/scripts/calculate_holland.py \
  --answers '{"1":"Y","2":"N","3":"Y","4":"N","5":"Y","6":"N"}'
```

## 评分规则

### 1. 维度分数

每道题对应一个职业兴趣维度，用户回答“是/否”或“Y/N”将累加到对应维度分数中：

- 当答案为 “Y” 或 “是” 时，维度分数 +1
- 当答案为 “N” 或 “否” 时，维度分数 +0
- 最终输出六个维度的得分

### 2. 结果排序

程序依据各维度分数排序，给出用户偏好的职业兴趣类型：

```json
{
  "top_dimensions": ["R", "S", "E"],
  "dominant_type": "RSE"
}
```

### 3. 报告示例

```json
{
  "assessment_id": "HOLLAND-60-001",
  "status": "completed",
  "score": 45,
  "max_score": 100,
  "top_dimensions": ["R", "S", "E"],
  "dominant_type": "RSE",
  "details": {
    "R": 7,
    "I": 3,
    "A": 2,
    "S": 6,
    "E": 5,
    "C": 4
  }
}
```

## 重要原则

- 题库必须明确给出题目编号、题干和维度归属；
- 评分逻辑必须单独写在脚本文件中，不能隐含在对话里；
- 用户提交题目后，必须返回测试结果、得分和排序；
- 若题目缺失或答案格式不合法，先要求用户补全，不要直接伪造结果；
- 题目内容与职业兴趣维度保持一一映射，便于后续扩展到 181 题全量版本。

## Demo 目录结构

```text
holland-career-assessment/
├── SKILL.md
├── references/
│   ├── algorithm.md
│   └── questions.json
└── scripts/
    └── calculate_holland.py
```

## 资源说明

- 题库参考：`references/questions.json`
- 算法说明：`references/algorithm.md`
- 评分脚本：`scripts/calculate_holland.py`

## 交互输出要求

用户完成题目后，系统应返回：

1. 题目 ID
2. 题目标题
3. 各维度分数
4. 排名结果
5. 最可能匹配的职业类型
6. 若未通过/未完成，给出缺失题信息


