# Career Anchor

> 当用户提到职业价值观、职业锚、工作长期发展取向时调用本Skill，用于识别其核心职业价值取向并解读测评结果。

- Skill: `ahang1598/career-anchor` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add ahang1598/career-anchor`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ahang1598/career-anchor/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/career-anchor

---


# 职业锚测评 Skill

## Overview

生成职业锚测评题目并计算结果，覆盖 8 个维度：

- TF：技术职能型
- GM：管理型
- AU：自主型
- SE：安全型
- EC：创造型
- SV：服务型
- CH：挑战型
- LS：生活型

本测评共 40 题，每题评分 1-6 分，每个维度均匀分布 5 道题目。最终结果为得分最高的前三个维度组合（如 TF+GM+AU）。

## When to Use This Skill

在以下场景触发本 skill：

- 用户明确说出"测职业锚""做职业锚测试""帮我测一下职业锚""启动职业锚测评"这类完整指令
- 用户直接询问"我是什么职业锚""生成我的职业锚报告""帮我算我的职业锚类型"
- 用户主动提及"打开职业锚测评工具""开始职业锚测评"
- 用户说"我要做职业倾向测试，指定是职业锚的""我想测职业锚的完整维度"
- 用户历史对话3轮内明确提过要做职业锚测评，当前轮次说"继续""开始吧""下一步"
- 用户直接发送"职业锚测评""职业锚测试"作为唯一指令，无其他无关内容

在以下场景需要二次确认后才触发职业锚测评 Skill（命中后先弹出确认话术，用户同意再启动）：

- 用户只单独发送"职业锚"三个字，没有后续补充任何内容
- 用户讨论某类职业锚特征后，说"我好像就是这种人""我感觉我符合这个类型"
- 用户说"我想了解自己的职业倾向""帮我做个职业测试"，没有明确指定是职业锚
- 用户说"我想看看我的职业价值观是什么样的""分析一下我的职业倾向"，没有限定测评类型
- 用户提到"我朋友说我是XX型职业锚，想验证一下"，没有直接说要启动测评
- 用户在讨论职业锚相关话题后，说"帮我测测看"，没有明确指向其他测评工具
- 用户说"我想做个测试看看我适合什么职业锚类型"，表述模糊未直接唤起测评

## When NOT to Use This Skill

以下场景**不触发**本 skill：

- 用户仅询问职业锚基础科普："职业锚有多少种类型""职业锚的八个维度是什么""职业锚的起源是什么"
- 用户仅查询某类职业锚的特征："技术职能型的特点是什么""管理型适合什么职业""自主型的优缺点"
- 用户讨论职业锚的非测评应用场景："职业锚面试技巧""用职业锚做职业规划""职业锚职场应用"
- 用户在讨论其他完全无关的话题时，偶然提到职业锚："我昨天和朋友聊到职业锚""我同事是管理型"
- 用户明确要求其他类型的测试："我要做MBTI测试""帮我测霍兰德职业兴趣""生成九型人格测试"
- 用户的需求是内容生成类："帮我写一篇职业锚主题的小红书文案""生成职业锚相关的短视频脚本"
- 用户同时提出多个混合需求，且没有明确表示要做测评："帮我查职业锚类型，再写一份职业规划方案"
- 用户明确表示"我不想做测评""我只是想了解职业锚的知识"，直接拦截所有测评唤起

## 题目元数据

```yaml
assessment_id: CAREER-ANCHOR-40-001
assessment_name: "职业锚测评"
question_total: 40
dimension_count: 8   # TF/GM/AU/SE/EC/SV/CH/LS
dimension_distribution: 每个维度5题
result_dimensions: 3  # 取得分最高的前三个维度
references_path: "references/questions.md"
algorithm_path: "references/algorithm.md"
score_calculation_path: "scripts/calculate_career_anchor.py"
score_function: "calculate_scores"
entrypoint: "python scripts/calculate_career_anchor.py --answers '{\"1\":5,\"2\":4}'"
card_spec: "内嵌于本文件 §0（答题卡片渲染规范，含完整代码块）"
report_spec: "内嵌于本文件 §R（报告渲染规范，含完整代码块）"
```

## references 数据文件结构

测评数据（题库 / 8 维度详情）以 **2 个 H2 段** 组织，每段必须包含一段散文说明 + 一个 ```json 代码块，段顺序固定如下（**H2 标题改名会破坏脚本解析，禁止改名**）：

1. `## 题库` → 40 题题库数组
2. `## 维度详情` → 8 维度详情数组

**文件形态**：
- `references/questions.md`（含 `## 题库`）
- `references/dimensions.md`（含 `## 维度详情`）
- 每个文件内**仍必须保留对应的 H2 标题与 ```json 代码块**（解析锚点不变）

## 题目加载硬约束（关键）

1. 本 skill **仅有唯一测评流程**：展示 40 题 → 用户作答 → 输出完整测评报告。不存在其他题数或版本，不询问用户"要做多少题"、不提供任何"版本二选一"入口。
2. 触发测评后必须直接展示全部 40 题进入答题流程，不得插入任何"选择题目数量""选择版本"的中间步骤。
3. 题目必须从 `references/questions.md` 文件中读取 `questions` 数组，按 `questionIndex` 1→40 原序展示；**禁止模型自行编造或凭印象生成题目**。
4. 题库共 40 题，每题必须包含 `questionIndex` / `questionText` / `dimensionCode` / `dimensionName` 四要素，渲染时不得遗漏任一字段。
5. 若题库读取失败或不足 40 题，必须直接报错说明，不得用"部分题目"凑数、不得用模型自拟题补齐。
6. 一次性展示全部 40 道题，不得分页、不得"先展示前几题"、不得逐题加载。

### 题目数据加载流程

模型在渲染交互卡片前，**必须先读取题库文件**，不得凭训练数据回忆题目：

1. 通过 Read 工具读取题库文件 `references/questions.md`，解析其 ```json 代码块为数组。
2. 按数组顺序（questionIndex 1→40）渲染全部 40 题，不得打乱、不得省略、不得改写题干/维度归属；**渲染动作必须遵循"题目卡片渲染规范"（§0 内联约束），禁止自绘样式**。
3. 若 Read 工具调用失败、返回内容不足 40 题、或解析报错，模型**必须**向用户返回明确的错误提示（如"题库文件读取失败，请检查 references/questions.md 文件是否存在且包含 40 题"），**不得**：
   - 用模型自拟/凭印象/从训练数据回忆的题目补齐
   - 用"部分题目"凑数渲染卡片
   - 返回空卡片或不做任何响应
   - 静默跳过题目渲染只输出其他元素（标题、进度条、维度标签等）

## 工作流程

本 skill 只有一条流程：**展示 40 题 → 用户在卡片内作答 → 点击"提交测评" → 模型直接输出完整测评报告**。具体步骤：

1. 渲染完整交互卡片展示全部 40 题（按 §0 规范渲染），用户在卡片内完成作答（每题评分 1-6 分）。
2. 用户点击"提交测评"时：
   - 存在未作答题目 → 卡片内自动滚动到第一个未答题并高亮提示，**不提交、不出报告**；
   - 已答完全部 40 题 → 卡片内 JS 通过宿主回传机制（`sendPrompt()`）将答案 JSON 自动作为用户消息发送。
3. 模型收到答案后对答案进行校验，确保题目编号、答案值（1-6）正确。
4. 调用评分脚本计算 8 个维度得分 + 排序 + 取前三个维度。
5. **直接**返回用户完整测评报告（按 §R 规范渲染）：报告头部（标签/主标题/副标题）→ 职业价值观类型（前三类型名称用 `+` 连接）→ 八维度雷达图 → 前三职业锚类型详解。

**强制约束（不允许有其他链路）**：用户点击"提交测评"且答完全部题目后，模型必须**直接输出完整测评报告**。禁止以下任何行为：要求用户复制/粘贴答案回传、要求用户手动输入答案、先输出答案 JSON 等待用户确认、把出报告推迟到用户再次追问之后、以任何形式要求用户参与答案传递。

## WorkBuddy 视觉化交互卡片约束（关键）

本 skill 在 Workbody / WorkBuddy 场景下，不仅需要返回结构化 JSON，也必须支持可视化内联交互卡片：即在对话中直接渲染一个 HTML/CSS/JS 组件，模拟真实测评页面，且在聊天窗口中保持稳定的视觉结构。

### 渲染总则（所有渲染的最高约束，必须遵守）

本 skill **不依赖任何外部模板文件**，样式与结构以**本文件内嵌的规范代码块**为唯一来源。渲染时必须遵守：

1. **代码块即规范**：§0（答题卡片）与 §R（报告）下方给出的完整代码块（骨架 + `<style>` + `<script>`）是**唯一允许的输出形式**。渲染时从本文件逐字复制这些代码块，**只替换其中唯一的数据占位符**（`__QUESTIONS__` / `__REPORT__`），其余字符（含空格、换行、缩进、类名、色值）一律不得改动。
2. **禁止凭记忆渲染**：每次渲染前必须**重新读取本文件对应章节**的代码块，禁止凭训练数据或上一次输出的印象"复述"样式；即使同一会话内第二次渲染（如"重新作答"），也必须重新复制同一份代码块，保证与第一次**逐字节一致**。
3. **禁止自绘样式**：禁止重新设计布局、更换类名/色值/字号、增删 DOM 结构、追加额外样式或脚本；任何"看起来差不多"的自定义输出都是违规。
4. **禁止静态化坐标**：雷达图坐标一律由代码块内 JS 用 `Math.cos/Math.sin` 在浏览器端精确计算，模型**禁止**预计算、硬编码、手算任何 SVG 坐标值。
5. **失败处理**：若本文件 §0/§R 代码块缺失或占位符不存在，必须报错检查，不得自绘卡片/报告凑数。
6. **输出通道硬约束（关键）**：题目卡片与测评报告**必须**通过 Visualizer（show_widget）以 `widget_code` 形式在对话中内联渲染，**禁止生成本地 HTML 文件**（含 outputs 目录、临时目录等任何落盘路径），禁止用文件路径或"打开本地页面"方式替代内联渲染；若 show_widget 等渲染工具不可用，**必须向用户明确报错并等待环境支持或用户确认**，不得自行降级为文件输出，不得自绘样式凑数。

### 派生请求禁止走捷径（关键，最常见违规）

用户提出**派生请求**时（典型话术："再来一份""朋友也要测""给同事也来一套""试试换个 UI""重置再来一遍""换个颜色/风格"），模型必须：

1. 一律重新 `Read` 数据源文件（`references/questions.md` 题库、`references/dimensions.md` 维度详情），并**重新从本文件 §0 / §R 逐字复制对应代码块**，完整走标准渲染流程（§0 答题卡片 / §R 报告，含重新跑评分脚本）；不得依赖上一轮的输出或记忆印象。
2. **禁止"拷贝+改名"充当新产物**：不允许把"上一次的卡片/报告"复制过来仅改 ID 后缀（如 `-friend` / `-v2`）、改类名、改标题、换人名/昵称就当作新测评输出；任何复用上一轮 DOM/样式/文案的"派生粘贴"行为一律视为违规。
3. 新卡片/报告必须是 `references/questions.md` + 本文件 §0/§R 代码块的 **byte-stable 实例**：所有 ID（如 `anchor-card` / `anchor-list` / `anchor-rpt` / `rType` / `rRadar` / `rDims`）、class、style、文案（标题、副标题、按钮、提示语、维度名称）与首次测评输出保持**逐字符一致**；同一会话内多次测评，唯一允许变化的只有数据占位符（`__QUESTIONS__` / `__REPORT__`）内的实际数据。
4. "试试换个 UI""换个样式"类请求**一律拒绝样式变更**：本 skill 样式唯一来源是 §0/§R 内嵌代码块，不存在"换 UI"选项；用户只能重新作答获取新结果，渲染样式必须与首发完全一致（数据可不同，外观必须相同）。

### 0. 题目卡片渲染规范（内联约束，强制）

**渲染流程（强制）**：

1. Read 题库 `references/questions.md`，取出其中 ```json 代码块内的 40 题 JSON 数组**原文**（含首尾方括号 `[` `]`）；
2. 从**本文件本小节下方**逐字复制三部分代码（HTML 骨架 → `<style>` → `<script>`），拼接为完整交互组件；
3. 将 `<script>` 中**唯一占位符** `__QUESTIONS__` 替换为步骤 1 的题库数组原文；**除该占位符外，其余内容必须逐字节照抄，禁止改动任何字符**；
4. 将拼接替换完成的完整内容作为交互组件（Visualizer show_widget）的 `widget_code` 输出。

**代码块 1/3 —— HTML 骨架（逐字复制）**：

```html
<div id="anchor-card">
  <div class="q-head">
    <h2 class="q-title">职业锚测评</h2>
    <span class="q-count">0 / 40</span>
  </div>
  <p class="q-sub">共 40 题，请根据真实感受从 1-6 中选择一个数字</p>
  <div class="q-track"><i></i></div>
  <div id="anchor-list"></div>
  <div class="q-foot">
    <span class="q-tip"></span>
    <button type="button" class="q-submit">提交测评 ↗</button>
  </div>
</div>
```

**代码块 2/3 —— 样式（逐字复制到 `<style>` 标签内）**：

```css
#anchor-card{font-family:var(--font-sans);color:var(--color-text-primary);max-width:680px;margin:0 auto}
#anchor-card .q-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:2px}
#anchor-card .q-title{font-size:18px;font-weight:500;margin:0}
#anchor-card .q-sub{font-size:13px;color:var(--color-text-secondary);margin:2px 0 12px}
#anchor-card .q-count{font-size:13px;font-weight:500;color:var(--color-text-secondary)}
#anchor-card .q-track{height:6px;border-radius:999px;background:var(--color-background-secondary);overflow:hidden;margin-bottom:16px}
#anchor-card .q-track i{display:block;height:100%;width:0;border-radius:999px;background:var(--color-text-info);transition:width .2s}
#anchor-card .q-item{background:var(--color-background-primary);border:0.5px solid var(--color-border-tertiary);border-radius:var(--border-radius-lg);padding:14px 18px;margin-top:12px;scroll-margin-top:12px}
#anchor-card .q-item.miss{border-color:var(--color-text-danger);box-shadow:0 0 0 1px var(--color-text-danger)}
#anchor-card .q-top{display:flex;gap:10px;align-items:flex-start}
#anchor-card .q-num{flex:none;min-width:28px;height:28px;border-radius:999px;background:var(--color-background-secondary);color:var(--color-text-secondary);font-size:13px;font-weight:500;display:flex;align-items:center;justify-content:center;margin-top:1px}
#anchor-card .q-body{flex:1;min-width:0}
#anchor-card .q-text{font-size:14px;font-weight:500;line-height:1.6;margin:0}
#anchor-card .q-dim{font-size:12px;color:var(--color-text-tertiary);line-height:1.5;margin:6px 0 10px}
#anchor-card .q-opts{display:grid;grid-template-columns:repeat(6,1fr);gap:8px}
#anchor-card .q-opt{font-family:inherit;font-size:13px;font-weight:500;line-height:1;text-align:center;padding:10px 8px;border-radius:var(--border-radius-md);cursor:pointer;background:#F5F6F8;border:1px solid #E5E7EB;color:#3A3F47;transition:background .15s,border-color .15s,color .15s}
#anchor-card .q-opt:hover{border-color:var(--color-border-secondary)}
#anchor-card .q-opt.on{background:#E8F1FF;border:1px solid #4E8CFF;color:#1E4FB8;box-shadow:inset 0 -2px 0 #4E8CFF}
#anchor-card .q-foot{display:flex;align-items:center;justify-content:center;gap:12px;margin-top:18px;flex-wrap:wrap}
#anchor-card .q-tip{font-size:13px;color:var(--color-text-danger);display:none}
#anchor-card .q-submit{font-family:inherit;font-size:14px;font-weight:500;padding:10px 24px;border-radius:var(--border-radius-md);cursor:pointer;background:#4E8CFF;color:#FFFFFF;border:1px solid #4E8CFF;transition:background .15s}
#anchor-card .q-submit:hover{background:#3A78E0}
@media (max-width:560px){#anchor-card .q-opts{grid-template-columns:repeat(3,1fr)}}
```

**代码块 3/3 —— 交互脚本（逐字复制到 `<script>` 标签内，仅替换 `__QUESTIONS__`）**：

```html
<script>
(function(){
  var QUESTIONS = __QUESTIONS__;
  var total = QUESTIONS.length;
  var answers = {};
  var list = document.getElementById('anchor-list');
  list.innerHTML = QUESTIONS.map(function(q){
    var opts = [1,2,3,4,5,6].map(function(v){
      return '<button type="button" class="q-opt" data-v="'+v+'">'+v+'</button>';
    }).join('');
    return '<div class="q-item" data-i="'+q.questionIndex+'">'
      + '<div class="q-top"><span class="q-num">'+q.questionIndex+'</span>'
      + '<div class="q-body"><p class="q-text">'+q.questionText+'</p>'
      + '<p class="q-dim">'+q.dimensionName+'</p>'
      + '<div class="q-opts">'+opts+'</div></div></div></div>';
  }).join('');
  var countEl = document.querySelector('#anchor-card .q-count');
  var trackEl = document.querySelector('#anchor-card .q-track i');
  var tipEl = document.querySelector('#anchor-card .q-tip');
  var submitEl = document.querySelector('#anchor-card .q-submit');
  function refresh(){
    var n = Object.keys(answers).length;
    countEl.textContent = n + ' / ' + total;
    trackEl.style.width = (n / total * 100) + '%';
  }
  list.addEventListener('click', function(e){
    var btn = e.target.closest ? e.target.closest('.q-opt') : null;
    if(!btn) return;
    var item = btn.closest('.q-item');
    var idx = item.getAttribute('data-i');
    var opts = item.querySelectorAll('.q-opt');
    for(var j = 0; j < opts.length; j++){ opts[j].classList.remove('on'); }
    btn.classList.add('on');
    answers[idx] = parseInt(btn.getAttribute('data-v'), 10);
    refresh();
  });
  submitEl.addEventListener('click', function(){
    var miss = [];
    for(var i = 1; i <= total; i++){
      if(answers[String(i)] === undefined){ miss.push(i); }
    }
    if(miss.length){
      var first = miss[0];
      tipEl.textContent = '还有 ' + miss.length + ' 题未作答，请先完成第 ' + first + ' 题';
      tipEl.style.display = 'block';
      var target = list.querySelector('.q-item[data-i="'+first+'"]');
      if(target){
        target.classList.add('miss');
        target.scrollIntoView({behavior:'smooth', block:'center'});
        setTimeout(function(){ target.classList.remove('miss'); }, 1600);
      }
      return;
    }
    tipEl.style.display = 'none';
    if(window.sendPrompt){
      window.sendPrompt('[职业锚测评提交] 我的答案如下：' + JSON.stringify(answers));
    }
  });
})();
</script>
```

**代码块已内置的行为（验收标准，禁止另行实现或修改）**：
- 顶部标题 `职业锚测评`、副标题 `共 40 题，请根据真实感受从 1-6 中选择一个数字`、右侧进度文本 `已答 / 40`、进度条；
- 40 题全量连续展示，每题左侧顺序编号（questionIndex 1→40）+ 题干 + 维度名称 + 六个评分按钮 `1` / `2` / `3` / `4` / `5` / `6`；
- 点击评分：同题六个按钮互斥选中；选中态固定色值（未选中浅灰 `#F5F6F8`/`#E5E7EB`/`#3A3F47`，选中淡蓝 `#E8F1FF`/`#4E8CFF`/`#1E4FB8`）；进度文本与进度条实时更新；
- 提交按钮文案固定 `提交测评 ↗`，**始终可见可点**（禁止延迟出现、禁止 disabled）：存在未作答 → 给第一个未答题加高亮并 `scrollIntoView` 滚动定位，提示"还有 N 题未作答，请先完成第 X 题"，约 1.6s 后移除高亮；已答完 → 调用 `sendPrompt('[职业锚测评提交] 我的答案如下：' + JSON.stringify(answers))` 回传（key 为 questionIndex 1→40，value 为 1-6）。

**禁止事项**：
- 禁止自绘题目卡片、禁止改写代码块中任何字符、禁止更换类名/色值/布局、禁止在代码块外追加任何样式或脚本；
- 禁止分页/逐题加载/打乱题序/省略题目/改写题干；
- 禁止用纯文本或 Markdown 替代交互卡片；
- 若题库读取失败或本文件代码块缺失，必须明确报错，不得用自拟题/部分题凑数；
- **禁止生成本地 HTML 文件**（任何落盘路径）或用文件形式替代内联交互卡片；show_widget 不可用时必须报错并等待环境支持，不得自行降级；
- 禁止派生请求走捷径（"再来一份""朋友也要测""给同事也来一套""试试换个 UI""重置再来一遍"）：必须重新 Read 题库 + 重新复制本小节代码块输出，禁止复制上一张卡片改 ID 后缀（如 `-friend` / `-v2`）充当新产物，新卡片必须与首发逐字符一致（见"派生请求禁止走捷径"小节）。

## 输出格式

### 报告渲染规范（内联约束，强制）

**渲染流程（强制）**：

1. 调用评分脚本 `python scripts/calculate_career_anchor.py --answers '<答案 JSON>'`，获取纯 JSON 计算结果（脚本输出为 byte-equal 确定性 JSON，含 `dimension_scores` / `top_three_dimensions` / `anchor_type` 等全部字段）；
2. 从**本文件本小节下方**逐字复制三部分代码（HTML 骨架 → `<style>` → `<script>`），拼接为完整报告组件；
3. 将 `<script>` 中**唯一占位符** `__REPORT__` 替换为步骤 1 脚本输出的 JSON 对象**原文**（含首尾花括号 `{` `}`）；**除该占位符外，其余内容必须逐字节照抄，禁止改动任何字符**；
4. 将拼接替换完成的完整内容作为交互组件（Visualizer show_widget）的 `widget_code` 输出。

**代码块 1/3 —— HTML 骨架（逐字复制）**：

```html
<div id="anchor-rpt">
  <div class="r-hdr">
    <p class="r-tag">职业锚测评</p>
    <h2 class="r-title">职业锚测评报告</h2>
    <p class="r-sub">你的职业价值观类型</p>
    <span class="r-line"></span>
  </div>
  <div class="r-type-row" id="rType"></div>
  <div class="r-radar" id="rRadar"></div>
  <div id="rDims"></div>
</div>
```

**代码块 2/3 —— 样式（逐字复制到 `<style>` 标签内）**：

```css
#anchor-rpt{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;color:#222;max-width:680px;margin:0 auto;background:transparent}
#anchor-rpt .r-hdr{padding:6px 0 14px;text-align:left}
#anchor-rpt .r-tag{font-size:12px;font-weight:500;letter-spacing:2px;color:#3B6D11;opacity:.6;margin:0}
#anchor-rpt .r-title{font-size:28px;font-weight:700;letter-spacing:4px;color:#3B6D11;margin:6px 0 0}
#anchor-rpt .r-sub{font-size:14px;color:#3B6D11;margin:8px 0 0}
#anchor-rpt .r-line{display:block;width:56px;height:2px;background:#3B6D11;opacity:.5;margin-top:12px;border:0}
#anchor-rpt .r-type-row{font-size:34px;font-weight:700;letter-spacing:1px;color:#3B6D11;text-align:left;margin:20px 0 4px;line-height:1.3}
#anchor-rpt .r-type-row .r-plus{color:#3B6D11;opacity:.4;font-weight:400;margin:0 6px}
#anchor-rpt .r-radar{background:transparent;padding:10px 0;margin-top:8px}
#anchor-rpt .r-radar svg{display:block;width:100%;max-width:420px;height:auto;margin:0 auto}
#anchor-rpt .r-radar .r-grid{stroke:#3B6D11;stroke-opacity:.18;stroke-width:1;fill:none}
#anchor-rpt .r-radar .r-axis{stroke:#3B6D11;stroke-opacity:.35;stroke-width:1}
#anchor-rpt .r-radar .r-poly{fill:rgba(59,109,17,.12);stroke:#3B6D11;stroke-width:2;stroke-linejoin:round}
#anchor-rpt .r-radar .r-pt{fill:#3B6D11}
#anchor-rpt .r-radar .r-pt.win{fill:#EF4444}
#anchor-rpt .r-radar text{font-family:inherit}
#anchor-rpt .r-radar .r-lb{font-size:11px;font-weight:600;fill:#3B6D11}
#anchor-rpt .r-dim{padding:14px 0;margin:0}
#anchor-rpt .r-dim+.r-dim{border-top:1px solid rgba(59,109,17,.25)}
#anchor-rpt .r-dn{font-size:15px;font-weight:600;color:#3B6D11;margin:0 0 6px}
#anchor-rpt .r-desc{font-size:13px;line-height:1.7;color:#222}
```

**代码块 3/3 —— 报告渲染脚本（逐字复制到 `<script>` 标签内，仅替换 `__REPORT__`）**：

```html
<script>
(function(){
  var REPORT = __REPORT__;
  var ORDER = ['TF','GM','AU','SE','EC','SV','CH','LS'];
  var NAMES = {TF:'技术职能型',GM:'管理型',AU:'自主型',SE:'安全型',EC:'创造型',SV:'服务型',CH:'挑战型',LS:'生活型'};
  var CX = 160, CY = 160, R = 118;
  var top3 = REPORT.top_three_dimensions.map(function(d){ return d.code; });
  var scores = REPORT.dimension_scores;
  var anchors = ['middle','start','start','start','middle','end','end','end'];
  function coords(i, r){
    var a = (90 - 45 * i) * Math.PI / 180;
    return { x: CX + r * Math.cos(a), y: CY - r * Math.sin(a) };
  }
  function ptStr(i, r){
    var p = coords(i, r);
    return p.x.toFixed(1) + ',' + p.y.toFixed(1);
  }
  document.getElementById('rType').innerHTML = REPORT.top_three_dimensions.map(function(d){
    return d.name;
  }).join('<span class="r-plus">+</span>');
  var svg = '<svg viewBox="0 0 320 320" xmlns="http://www.w3.org/2000/svg">';
  [5,10,15,20,25,30].forEach(function(k){
    var pts = [];
    for(var i = 0; i < 8; i++){ pts.push(ptStr(i, R * k / 30)); }
    svg += '<polygon class="r-grid" points="' + pts.join(' ') + '"/>';
  });
  for(var i = 0; i < 8; i++){
    var p = coords(i, R);
    svg += '<line class="r-axis" x1="' + CX + '" y1="' + CY + '" x2="' + p.x.toFixed(1) + '" y2="' + p.y.toFixed(1) + '"/>';
  }
  var poly = [];
  for(var i = 0; i < 8; i++){ poly.push(ptStr(i, R * scores[ORDER[i]] / 30)); }
  svg += '<polygon class="r-poly" points="' + poly.join(' ') + '"/>';
  for(var i = 0; i < 8; i++){
    var code = ORDER[i];
    var p = coords(i, R * scores[code] / 30);
    var cls = top3.indexOf(code) > -1 ? 'r-pt win' : 'r-pt';
    svg += '<circle class="' + cls + '" cx="' + p.x.toFixed(1) + '" cy="' + p.y.toFixed(1) + '" r="3"/>';
  }
  for(var i = 0; i < 8; i++){
    var code = ORDER[i];
    var p = coords(i, R * 1.05);
    svg += '<text class="r-lb" x="' + p.x.toFixed(1) + '" y="' + (p.y.toFixed(1) + 3) + '" text-anchor="' + anchors[i] + '">' + NAMES[code] + '</text>';
  }
  svg += '</svg>';
  document.getElementById('rRadar').innerHTML = svg;
  document.getElementById('rDims').innerHTML = REPORT.top_three_dimensions.map(function(d){
    return '<div class="r-dim"><p class="r-dn">' + d.name + '</p><p class="r-desc">' + d.description + '</p></div>';
  }).join('');
})();
</script>
```

**代码块已内置的结构（验收标准，禁止另行实现或修改）**：
- **4 部分固定顺序**（缺一不可）：报告头部（`.r-tag` 标签 `职业锚测评` → `.r-title` 主标题 `职业锚测评报告` → `.r-sub` 副标题 `你的职业价值观类型` → `.r-line` 56px 短细线，整体左对齐）→ 职业价值观类型（`.r-type-row`，前三维度名称用 `<span class="r-plus">+</span>` 连接，禁止字母代码/得分/排名）→ 八维度雷达图 → 前三职业锚类型详解（`.r-dim`，维度名称 + 描述原文，无标题无序号，相邻用半透明绿细线分隔）；
- **样式基调锁定**：透明背景 + 双色（绿 `#3B6D11` + 黑 `#222`）；无渐变横幅、无卡片边框、无装饰圆点、无得分/排名/字母代码/图例/序号；唯一例外是雷达图前三名顶点用红点 `#EF4444`（用户明确要求的保留项）；
- **雷达图规格**：viewBox `0 0 320 320`，中心 (160,160)，半径 R=118；8 轴按固定顺序 `TF→GM→AU→SE→EC→SV→CH→LS` 从 12 点方向起顺时针每 45° 均布（角度公式 `90-45×i` 度）；6 圈网格（5/10/15/20/25/30 分档）；数据多边形按各维度得分连线；前三名顶点加红点；轴端标签为维度名称单行文本（不显示代码与得分）；
- **雷达图坐标由代码块内 JS 用 `Math.cos/Math.sin` 精确计算**（浏览器端确定性几何，相邻 8 顶点与圆心等距、相邻边长完全相等，数学上必然为正八边形）；模型**无需也不得**手算、硬编码或预计算任何坐标；
- **维度描述来源**：`references/dimensions.md` 原文，由评分脚本查表输出到 JSON，模型不得改写/省略/截断。

**禁止事项**：
- 禁止自绘报告、禁止改写代码块中任何字符、禁止更换类名/色值/布局；
- 禁止引入第 3 种颜色（允许的仅有绿 `#3B6D11`、黑 `#222`、雷达图前三名红点 `#EF4444`）；
- 禁止添加背景色、渐变横幅、卡片边框、圆点装饰、得分标注、图例、字母代码、数字序号、得分/排名等元素；
- 禁止改变 4 部分顺序、遗漏任何一部分；
- 禁止用 Markdown 表格、纯文本替代报告渲染；
- 禁止用图片 URL 或外部库（如 Chart.js CDN）绘制雷达图；
- **禁止生成本地 HTML 文件**（任何落盘路径）或用文件形式替代内联报告；show_widget 不可用时必须报错并等待环境支持，不得自行降级；
- 禁止"凭印象"画报告或每次输出不同样式（必须从本文件代码块逐字复制）；
- 禁止派生请求走捷径（"再来一份""朋友也要测""给同事也来一套""试试换个 UI""重置再来一遍"）：报告必须重新跑评分脚本 + 重新复制本小节代码块渲染，禁止复制上一份报告改 ID/名字充当新产物，新报告必须与首发逐字符一致（见"派生请求禁止走捷径"小节）。

## 确定性输出约束（关键）

为避免 Workbody 生成结果每次都变化，本 skill 必须执行严格的确定性规则：

1. 结果必须以计分规则计算为唯一准绳，不允许模型自由推断分数或职业锚类型。
2. 八个维度得分必须按固定顺序输出：`TF`、`GM`、`AU`、`SE`、`EC`、`SV`、`CH`、`LS`。
3. 职业锚类型为得分最高的前三个维度代码用 `+` 连接（如 `TF+GM+AU`）。
4. 排序规则：先按得分降序，分数相同时按固定维度顺序（TF > GM > AU > SE > EC > SV > CH > LS）。
5. 得分必须为整数（各题评分累加，保留原始计算结果）。
6. 维度描述必须从 `references/dimensions.md` 文件中按 `code` 查表得到，8 维度全覆盖；**字段值必须为原文，不允许模型自行撰写或改写维度描述**。
7. 任何场景都不允许输出随机、模糊、口语化的结论；必须稳定输出统一结构。
8. 如果题目不完整，必须返回 `incomplete`，并列出缺失题号；不得在有缺失时强行生成完整结论。
9. 评分脚本（`scripts/calculate_career_anchor.py`）输出计算结果时必须以纯 JSON 对象返回，不能夹杂 Markdown、自然语言说明、额外说明块或解释性文本；模型展示层在收到脚本 JSON 后，必须另行按 §R 报告渲染规范输出 HTML 报告（JSON 是"计算层"产物，HTML 是"展示层"产物，两者职责不同、均不可省略）。
10. 同一组 `answers` 必须在多次调用间产生 byte-equal 的 JSON 输出（无随机数、无时间戳、无外部网络/DB 依赖）。
11. 报告文案来源：`references/dimensions.md` 文件，**查表由评分脚本完成**——渲染报告时**只读脚本输出 JSON**，**禁止改写原文**。
12. **全量输出约束（关键）**：每一轮报告输出都必须包含完整 JSON 结构的全部字段，**严禁**在任何轮次出现"与上一轮一致""结果同上""向上翻阅"等省略式表述。

本 skill 的职责分两层：评分脚本负责在用户点击"提交测评"后**计算并输出结构化 JSON 结果**（供计分、校验与取值）；模型负责**按 §R 报告渲染规范将结果渲染为 HTML 测评报告**展示给用户。两层均不可省略——计算不得靠模型口算/推断，展示不得用纯 JSON 或 Markdown 替代 HTML 报告。脚本输出必须严格符合以下 JSON 结构：

```json
{
  "assessment_id": "CAREER-ANCHOR-40-001",
  "assessment_name": "职业锚测评",
  "status": "completed",
  "answered_count": 40,
  "total_questions": 40,
  "anchor_type": "TF+GM+AU",
  "dimension_scores": {
    "TF": 25,
    "GM": 23,
    "AU": 22,
    "SE": 18,
    "EC": 15,
    "SV": 20,
    "CH": 19,
    "LS": 17
  },
  "top_three_dimensions": [
    {
      "code": "TF",
      "name": "技术职能型",
      "score": 25,
      "description": "注重在特定技术或职能领域的专业成长与技能提升..."
    },
    {
      "code": "GM",
      "name": "管理型",
      "score": 23,
      "description": "致力于全面管理工作..."
    },
    {
      "code": "AU",
      "name": "自主型",
      "score": 22,
      "description": "强调工作方式的自由度..."
    }
  ],
  "analysis": {
    "summary": "您的职业锚类型为 TF+GM+AU（技术职能型+管理型+自主型），得分最高的三个维度分别为：技术职能型（25分）、管理型（23分）、自主型（22分）。",
    "recommendations": ["技术研发岗位", "技术专家岗位", "管理岗位", "项目经理", "自由职业"]
  }
}
```

> 注：以上 `top_three_dimensions[].description` 字段来自 `references/dimensions.md` 文件的原文，模型不得自行撰写、改写或省略。

### 输出规范

- `status` 必须为 `completed` 或 `incomplete`
- `anchor_type` 为前三个维度代码用 `+` 连接，例：`"TF+GM+AU"`
- `dimension_scores` 必须按固定顺序输出 `TF`、`GM`、`AU`、`SE`、`EC`、`SV`、`CH`、`LS` 八个键
- `top_three_dimensions` 必须按得分降序排列，包含 `code` / `name` / `score` / `description` 四个字段，**全部使用原文**
- `dimension_description` 必须从 `references/dimensions.md` 文件中按 `code` 查表，使用原文
- `analysis.summary` 必须使用固定中文模板
- `analysis.recommendations` 必须基于前三个维度给出合理的职业建议
- 若题目未完成，则返回 `incomplete`，并输出 `missing_questions`（缺失题号字符串数组）
- 输出必须为纯 JSON，不允许嵌套说明、Markdown 代码块或额外字段

### 渲染约束（关键）

本 skill 约束的是"生成结果的渲染契约"，而不是页面实现细节。具体要求如下：

- 第一部分标题固定为 `职业锚测评报告`（标签 `职业锚测评`、副标题 `你的职业价值观类型`，头部整体左对齐）
- `top_three_dimensions[].name` 用 `+` 连接（`+` 用 `.r-plus` 包裹），作为第二部分 `.r-type-row` 展示字段（如 `服务型+管理型+挑战型`），**不展示字母代码**
- `dimension_scores` 必须作为第三部分雷达图的八维度得分数据源
- `top_three_dimensions[].description` 必须作为第四部分维度描述展示字段使用，使用原文（含换行符原样渲染）
- 任何前端都不能自行生成新的字段名来替代上述结构
- 前端只能根据这几个字段进行展示，不能依赖自由文本解析
- 报告文案不得由前端或模型自行撰写，必须来自 `references/dimensions.md` 文件的原文

### 禁止事项

- 不允许返回自由文本替代 JSON
- 不允许缺少 `dimension_scores`
- 不允许缺少 `anchor_type`
- 不允许 `top_three_dimensions` 中遗漏任一字段
- 不允许在 skill 中混合前端渲染逻辑
- 不允许前端自行扩展未定义字段覆盖结果解释
- 不允许模型自行撰写维度描述，必须使用 `references/dimensions.md` 文件中的原文
- **不允许在任何轮次以"与上一轮一致""结果同上""向上翻阅"等省略式表述替代完整 JSON 输出**
- **不允许用户点击"提交"按钮时若有未答题就出报告**
- **不允许"复制答案回传"链路（关键）**
- **不允许模型自绘题目卡片或报告样式（关键）**

## 题目结构说明

该评测题目以 8 个维度为核心，采用"评分式"答题。40 题在 8 个维度间**交错分布**（每 8 题一轮，每轮每个维度出现 1 题），每个维度共 5 题：

- TF（技术职能型）：题目 1, 9, 17, 25, 33
- GM（管理型）：题目 2, 10, 18, 26, 34
- AU（自主型）：题目 3, 11, 19, 27, 35
- SE（安全型）：题目 4, 12, 20, 28, 36
- EC（创造型）：题目 5, 13, 21, 29, 37
- SV（服务型）：题目 6, 14, 22, 30, 38
- CH（挑战型）：题目 7, 15, 23, 31, 39
- LS（生活型）：题目 8, 16, 24, 32, 40

每题的评分范围为 1-6 分，分数越高表示该描述越符合用户的实际情况。

### 题目示例

```json
{
  "questionIndex": 1,
  "questionText": "我希望做我擅长的工作，这样我的内行建议可以不断被采纳。",
  "dimensionCode": "TF",
  "dimensionName": "技术职能型",
  "dimensionDescription": null
}
```

字段说明：
- `questionIndex`：题目编号（1-40）
- `questionText`：题干文本
- `dimensionCode`：该题归属的维度代码（TF/GM/AU/SE/EC/SV/CH/LS）
- `dimensionName`：该题归属的维度名称

## 评分入口

评分脚本位置：

- `scripts/calculate_career_anchor.py`
- 评分函数：`calculate_scores(answers, questions)`
- 输出：JSON 格式的分数与职业锚类型结果

示例命令：

```bash
# 根据答案计算八个维度得分并排序
python scripts/calculate_career_anchor.py \
  --answers '{"1":5,"2":4,"3":6,"4":3,"5":5}'
```

## 评分规则

### 1. 维度得分计算

每个维度的得分 = 该维度下所有题目评分的总和。

八个维度题目分布（每个维度5题）：

- TF（技术职能型）：题目 1, 9, 17, 25, 33
- GM（管理型）：题目 2, 10, 18, 26, 34
- AU（自主型）：题目 3, 11, 19, 27, 35
- SE（安全型）：题目 4, 12, 20, 28, 36
- EC（创造型）：题目 5, 13, 21, 29, 37
- SV（服务型）：题目 6, 14, 22, 30, 38
- CH（挑战型）：题目 7, 15, 23, 31, 39
- LS（生活型）：题目 8, 16, 24, 32, 40

### 2. 排序规则

1. 按维度得分降序排列
2. 分数相同时，按固定维度顺序：TF > GM > AU > SE > EC > SV > CH > LS

### 3. 确定职业锚类型

取得分最高的前三个维度代码，用 `+` 连接作为职业锚类型。

例：TF=25, GM=23, AU=22, SE=18, EC=15, SV=20, CH=19, LS=17

排序：TF(25) > GM(23) > AU(22) > SV(20) > CH(19) > SE(18) > LS(17) > EC(15)

职业锚类型：`TF+GM+AU`

### 4. 维度详情查询

从 `references/dimensions.md` 文件中按 `code` 查询维度详情，包含：

- `code`：维度代码（如 `"TF"`）
- `name`：维度名称（如 `"技术职能型"`）
- `description`：维度描述（完整版原文）
- `questionIndexes`：该维度包含的题目索引数组

### 5. 总分计算（可选）

总分 = 八个维度得分之和（范围 40-240）

## 报告示例

下面是一组真实作答经评分脚本计算后的报告片段：

```json
{
  "assessment_id": "CAREER-ANCHOR-40-001",
  "status": "completed",
  "answered_count": 40,
  "total_questions": 40,
  "anchor_type": "TF+GM+AU",
  "dimension_scores": {
    "TF": 25,
    "GM": 23,
    "AU": 22,
    "SE": 18,
    "EC": 15,
    "SV": 20,
    "CH": 19,
    "LS": 17
  },
  "top_three_dimensions": [
    {
      "code": "TF",
      "name": "技术职能型",
      "score": 25,
      "description": "注重在特定技术或职能领域的专业成长与技能提升，追求通过应用专业知识解决问题，通常不喜欢转向一般管理职位。"
    },
    {
      "code": "GM",
      "name": "管理型",
      "score": 23,
      "description": "致力于全面管理工作，偏好承担跨部门整合责任，将组织成功视为个人工作成果。"
    },
    {
      "code": "AU",
      "name": "自主型",
      "score": 22,
      "description": "强调工作方式的自由度，希望自主安排工作习惯与生活方式，可能放弃晋升机会以保持独立性。"
    }
  ],
  "analysis": {
    "summary": "您的职业锚类型为 TF+GM+AU（技术职能型+管理型+自主型），得分最高的三个维度分别为：技术职能型（25分）、管理型（23分）、自主型（22分）。",
    "recommendations": ["技术研发岗位", "技术专家岗位", "管理岗位", "项目经理", "自由职业"]
  }
}
```

## 重要原则

- 题库必须明确给出题目编号、题干和维度归属；
- 评分逻辑必须单独写在脚本文件中，不能隐含在对话里；
- 用户提交题目后，必须返回测试结果、得分和职业锚类型；
- 若题目缺失或答案格式不合法，先要求用户补全，不要直接伪造结果；
- 维度映射必须保持一一对应；
- 同一组作答必须产出 byte-equal 的 JSON（无随机性）。

## Demo 目录结构

```text
career-anchor/
├── SKILL.md                   # 含全部渲染规范（§0 答题卡片 / §R 报告），样式代码块内嵌，无外部模板依赖
├── references/
│   ├── algorithm.md
│   ├── questions.md            # ## 题库（40 题题库数组，运行必需：单一数据源）
│   └── dimensions.md           # ## 维度详情（8 维度详情数组，运行必需：单一数据源）
└── scripts/
    └── calculate_career_anchor.py
```

## 资源说明

- 题库（唯一数据源）：`references/questions.md` 的 `## 题库` 段，40 道职业锚测评题库（```json 代码块内为纯 JSON 数组，供评分脚本提取解析）
- 维度详情（唯一数据源）：`references/dimensions.md` 的 `## 维度详情` 段，8 个维度的详细信息（```json 代码块内为纯 JSON 数组）
- **运行前置条件（关键）**：`references/questions.md` 与 `references/dimensions.md` 是评分脚本的默认数据源，必须存在且各自的 `## 题库` / `## 维度详情` 段各含一个 ```json 代码块；脚本 `_load_data()` 优先按纯 JSON 解析、失败则提取 ```json 代码块内容（兼容 .json / .md 两种文件）。**数据只有 references 下一份拷贝，无同步问题**；修改题库/文案只需改 md 一处。
- **渲染规范（关键）**：答题卡片与测评报告的唯一样式来源是**本文件 §0 / §R 小节内嵌的完整代码块**（HTML 骨架 + `<style>` + `<script>`，含数据占位符 `__QUESTIONS__` / `__REPORT__`）。模型渲染时**只替换占位符，其余从本文件逐字复制**，从根本上保证"首次测评"与"重新作答"渲染结果逐字节一致。**不依赖任何外部 HTML 模板文件**；若本文件代码块缺失或占位符不存在，必须报错并检查文件，不得自行手绘卡片/报告。
- 算法说明：`references/algorithm.md`
- 评分脚本：`scripts/calculate_career_anchor.py`

## 交互输出要求

用户完成题目后，系统应返回：

1. 测评 ID 与名称
2. 状态（completed / incomplete）
3. 职业锚类型（前三个维度组合）
4. 八个维度的得分
5. 前三个维度的详情（含维度代码、名称、得分、描述）
6. 职业建议
7. 若未完成，给出缺失题号列表

本 skill 仅有唯一测评流程：展示 40 题 → 用户作答 → 输出完整测评报告。评分逻辑独立于题库内容；后续如需切换题库，只需替换 `references/questions.md` 文件，无需改动评分脚本。报告文案（维度描述）全部来自 `references/dimensions.md` 文件，如需更新文案，直接替换该文件即可，无需改动评分脚本。**题目卡片与测评报告的展示格式/样式由本文件 §0 / §R 内嵌代码块锁定，模型只做数据占位符替换，不依赖模型每次重新生成样式，也不依赖任何外部模板文件。**

