name: skill-case-get description: "案例采集 / 拆解 / 归档。把各种素材(连续多图 / 九宫格、Word .docx、PPT .pptx、图文混合、裸图、视频)拆解为「案例信息」与「素材数组 materialAssets[]」,做合规判断(能否入库 / 客诉负面 / 隐私发现)与打标(学校别名归一 + 跟进状态七段 + 亮点/场景/异议),最后经云函数 caseSubmit 落到待审清单。适用场景:(1) 同事连发 6/9 张图其实是同一个素材,需要按九宫格阅读顺序编号归档;(2) 一份 Word/PPT 案例稿要抽出文字说明并拆出内嵌配图;(3) 判断这堆素材能不能进案例库、是不是客诉负面事件;(4) 按案例库标签体系打标并归一学校别名(IC → 帝国理工)。本 Skill 只处理已获授权素材。" description_en: "Case intake, decomposition and archiving. Turns raw material (image grids, .docx, .pptx, mixed text+image, bare images, video) into case fields plus an ordered materialAssets[] array, runs compliance triage and tagging, then submits to the review queue via the caseSubmit cloud function. Use for: (1) a run of 6/9 images that is ONE material needing 3x3 reading-order numbering, (2) extracting text and embedded media from Word/PPT, (3) deciding whether material may enter the case library or is a complaint/negative event, (4) tagging against the case-library tag system and normalising school aliases (IC -> Imperial College London). This skill only processes authorised material." version: 1.0.1 author: Yuyang001 (FmodeAgent) license: MPL-2.0 copyright: "Copyright (c) 2026 未来飞马 Fmode"
本 Skill 只处理已获授权素材。
把素材变成可入库的案例:
各种素材 → 拆解分析 → 能否进案例库判断 → 打标 → 上传归档(待审清单)
| 来源类型 | 处理方式 |
|---|---|
| ① 连续多张图片(朋友圈九宫格) | skill-vision OCR + 语义理解;按尺寸/时间/内容相似度判为同一组,按九宫格阅读顺序给显式 order |
② Word .docx |
python3 标准库 zipfile + ElementTree 解包:文字段落、表格、内嵌媒体、超链接 |
③ PPT .pptx |
同上:按放映顺序抽每页标题/正文/备注/图片/表格 |
| ④ 图文混合 | 文档文字走「案例说明」,内嵌图并进素材组一起编号 |
| ⑤ 裸图片无文字说明 | 靠视觉理解补 label / description / usageSuggestion;分组回落到尺寸 + 时间信号 |
| ⑥ 视频(附加) | ffmpeg 抽帧 → 交给图片链路;音轨走 skill-listen 转写 |
关键区分(必须分开,不能混)
title / summary / targetCustomer / usageSuggestion / resultEvidence / outcomematerialAssets[](order 1 起,同组内按左上 → 右下)authorizationStatus != authorized 一律不入库、不产生案例对象。默认值是 pending,必须显式传 --authorization authorized。reviewStatus='pending' + readyForUse=false,由负责人用 caseApprove 放行后才进检索结果。privacyFindings[].snippet 本身就是掩码后的(138****5678),不打码不落库。warnings 里明说,不编造 OCR 或转写内容。POST https://server.lumistedu.com/api/functions,请求体 {"path":"/caseSubmit","params":{...},"token":"..."}。
session token 来源:CASE_PARSE_SESSION_TOKEN → PARSE_SESSION_TOKEN。skill-vision 的 analyze()(~/.claude/skills/skill-vision/scripts/vision-client.mjs)。
该函数在宿主自带多模态时(FmodeCode / Claude Code)会返回读图指令而非调 API,此时由宿主 Agent 用自己的 Read 工具读图后补全。
可用 --vision-model <name> 覆盖,或 --skip-vision 跳过。skill-listen 的 listen-runner.mjs(走 Fmode 网关,凭据仅服务端)。fmode-storage 的 uploader.mjs(第 0 级云函数免密钥通道)。
找不到时 uploadMaterial 抛 StorageUnavailableError,ingest 会降级(url 留空 + 告警)而不崩。
可用 CASE_STORAGE_SCRIPT 指定其路径;本部署该技能在平台技能目录(/opt/data/skills/fmode-storage),
resolveSiblingScript 已覆盖 CASE_SKILLS_DIR / $HERMES_HOME/skills / /opt/data/skills。.docx / .pptx,只用标准库(本机没有 python-docx / python-pptx)。缺 token 是「缺配置」,不是「用不了」——请勿点任何付费/充值弹窗。
# 只有图片(九宫格):先看分组与顺序,不调模型
npx skill-case-get@latest plan -- --image g1.jpg --image g2.jpg ... --image g9.jpg
# 完整采集:拆解 + 图片理解 + 打标,产出案例包(不提交)
npx skill-case-get@latest ingest -- \
--image g1.jpg --image g9.jpg \
--doc 案例稿.docx --deck 复盘.pptx \
--authorization authorized \
--source-ref wx-group-2026-10 \
--asset-base-url https://cdn.example.com/cases
# 只给一个目录,自动按扩展名分流
npx skill-case-get@latest plan -- --input ./素材目录
# 调顺序(把第 3 张挪到第 1 位)
npx skill-case-get@latest reorder -- --material-assets <out>/material-assets.json --move "3:1"
# 提交到待审清单
npx skill-case-get@latest submit -- --package <out>/case-package.json --submitted-by user123
# 自检(含 office 解包、九宫格分组、打标、合规、顺序调整)
npx skill-case-get@latest selftest
ingest / plan 之后必须加 --,其后参数透传给运行器。
技能目录被复制进 .claude/skills/ 时不含 node_modules,运行器是零依赖 ESM:
import { runPlan, runIngest, runSubmit } from './scripts/case-intake.mjs';
const report = await runIngest({
inputs: ['g1.jpg', 'g2.jpg', '案例稿.docx'],
outDir: './case-get-outputs/run-001',
authorizationStatus: 'authorized',
assetBaseUrl: 'https://cdn.example.com/cases',
});
console.log(report.casePackage.materialAssets); // order 已按九宫格排好
console.log(report.casePackage.riskFlags); // 合规判断
if (report.canSubmit) await runSubmit({ package: report.artifacts.casePackage });
| 文件 | 内容 |
|---|---|
extracted.json |
文档文字段落 / 表格 / 超链接 / 内嵌媒体 / 视频转写 |
group-plan.json |
分组(groupId / layout / count / orderingRule)与组内顺序 |
vision.json |
每张图的 ocrText / description / label / usageSuggestion / piiHints |
material-assets.json |
materialAssets[](可手工调顺序后回灌) |
case-package.json |
云函数 caseSubmit 入参,逐字对齐共享契约 |
report.md |
人类可读报告(顺序表 / 标签 / 合规 / 下一步) |
同事连发 6 张、9 张图不是多个素材,而是同一个素材(朋友圈素材按九宫格排)。
三种信号判同组,至少 2 种成立才并组:
| 信号 | 判据 | 阈值 |
|---|---|---|
| 尺寸 | 宽高比相似度 | ≥ 0.88 |
| 时间 | 文件时间差 | ≤ 15 分钟(窗口内线性衰减) |
| 内容 | OCR/描述 字符二元组 Jaccard | ≥ 0.25 |
reasons;orderingRule = left-to-right,top-to-bottom,order 1 起连续编号;
原始排版位置丢失时用「收图时间升序 + 文件名自然序」近似发送顺序;reorder --move "3:1" 或 --order "3,1,2",也可在输入 manifest 里写 groups[].items[].order。九宫格 = layout: "3x3";6 张 = 3x2;4 张 = 2x2。
schoolCanonical / country / major / stage / subjectSchoolAlias 表(IC → 帝国理工、UCL → 伦敦大学学院)。
别名表存在 references/tag-dictionary.json 的 schoolAlias.entries;命中的写法写入 schoolAliases[]。
英文别名按词边界匹配(IC 不会命中 MAGIC)。新进线 / 挖需中 / 方案推荐中 / 异议处理中 / 待决策 / 沉默待跟进 / 已成交
按 docs/prd/dashboard/10-案例推荐策略.md 的策略表反推,写进 fitStatus[]。highlightTypes / scenarioTags / objectionTagsreferences/tag-dictionary.json
(--no-learn 可关;本技能只改文件、不自动 push)。企微会话里系统提示「对方默认同意存档会话内容」时,该截图可作为 --authorization authorized 的依据(会话内双方均已收到该告知)。没有这行提示的截图/记录一律按 pending 处理,不得擅自升为 authorized。授权状态是硬门:未授权 submit 必拒。
--upload,默认开)案例检索侧只看 materialAssets[].url,本地路径不算数。ingest 现在会真正上传本地素材,
拿到公网 URL 再写进案例包——不再依赖人工先传一遍。
URL 优先级(resolveAssetUrl):
| 顺序 | 条件 | 结果 |
|---|---|---|
| ① | item.url 已存在(文档/清单里带来的远程 URL) |
原样使用,不触发上传 |
| ② | 本地文件且 --upload 开(默认) |
调 uploadMaterial() 真上传,拿公网 URL |
| ③ | ② 失败 / 关闭时的显式回退 | --asset-base-url <前缀> 派生(旧行为,标记 isFallbackUrl) |
| ④ | 都没有 | url 留空 + warnings 明确提示(绝不伪造 URL) |
通道:走兄弟技能 fmode-storage 的第 0 级通道(云函数预签名直传,免密钥)。
容器只需 FMODE_SESSION_TOKEN,真实 AK/SK 始终留在云函数侧;技能代码里绝不内联任何对象存储凭据,
也不回落本地 AK/SK 通道(宁可在 ingest 里降级留空 URL)。
object key 规范(相对 key,云函数会自动补 user/<调用者id>/ 前缀,别自己加):
case-library/<YYYYMMDD>/<caseSlug>-<order>.<ext>
# 例:case-library/20261010/imperial-aero50006-1.png
# → https://s3.fmode.cn/user/<uid>/case-library/20261010/imperial-aero50006-1.png
caseSlug 取 --source-ref,无则取 --title,再无则 case;order 与素材 order 一致(1 起)。
幂等:同一 (localPath, key) 在本进程内只传一次(Map 缓存),重复调用直接复用回执。
失败降级(绝不拖垮整批 ingest):单张上传失败 → 该素材 url 留空、
push 一条 上传失败 <file>:<原因>;该素材将无公网 URL,其余素材照常上传,
最后再汇总一条 N 张上传失败。若原因是兄弟技能缺失/不可用,汇总里会点明「fmode-storage 不可用」。
上传失败不会让 ingest 崩,案例包照常产出(合规红线字段不变:pending / readyForUse=false)。
什么时候用 --no-upload:
--asset-base-url 派生;环境确实装不了 fmode-storage,又接受「url 留空、稍后由归档方补齐」。
# 默认:真上传(推荐)
node scripts/case-intake.mjs ingest --image ./img.jpg --authorization authorized
# 离线:不上传,只按前缀派生
node scripts/case-intake.mjs ingest --image ./img.jpg --no-upload \
--asset-base-url https://s3.fmode.cn/user/<uid>/case-library/20261010
单独传一张排障(也是官方 CLI):
node scripts/storage-bridge.mjs --file ./img.png --case-slug imperial-aero50006 --order 1
# → { "url": "https://s3.fmode.cn/user/<uid>/case-library/<YYYYMMDD>/imperial-aero50006-1.png", "key": "...", "bytes": 12345 }
上传后务必用匿名 GET 复核公网可读(200 image/png 且字节数一致)——签名与 Content-Type 绑定,只看上传回执不够。
case-package.json 里 materialAssets[].uploadKey / uploadBytes / uploadVia 记录了上传回执,便于追溯。
fmode-storage未安装时,uploadMaterial抛StorageUnavailableError(明确错误码STORAGE_UNAVAILABLE), 不会静默失败。安装:npx fmode-storage@latest install;或用环境变量CASE_STORAGE_SCRIPT指向其uploader.mjs。
references/tag-dictionary.json 的 dimensions.schoolCanonical.values 是有限白名单(约 33 所,多为 QS 百强英/澳/美/港/新名校),不含西交利物浦大学等中外合作办学主体;模型也不会把「西浦」直接归一到未收录的书写名。
所以遇到白名单外的学校(如西浦 XJTLU、宁波诺丁汉、昆山杜克),采集时显式传 --school-canonical <标准中文书写名>,并同时补 --country;否则 schoolCanonical 会留空、检索必然漏检。事后建议把该校补进字典白名单。
| 判定 | 落到哪 |
|---|---|
| 授权缺失 / pending / denied | riskFlags: UNAUTHORIZED + blocker,不入库 |
| 客诉 / 负面事件(投诉、退费纠纷、挂科、维权…) | riskFlags: COMPLAINT / NEGATIVE_EVENT + blocker,不得对外展示 |
| 隐私片段(手机号 / 邮箱 / 微信号 / 姓名…) | privacyFindings[{field, rule, snippet}],snippet 已掩码 |
| 与留学/课程无关 | riskFlags: OFF_TOPIC |
| 无任何素材 | blocker,无法构成案例 |
complianceBlockers 非空 → submit 拒绝提交(即使授权通过)。
submit 会先试云函数,不可达时自动回落 Parse 直写,两条通道落库字段逐字一致(都只进待审清单):
| 通道 | 何时用 | 说明 |
|---|---|---|
云函数 caseSubmit |
部署环境挂载了函数运行时 | POST /api/functions,{path,params,token};返回 {ok:true,result:{...}} |
| Parse 直写(本部署实际通道) | 云函数端点不可用(本部署:客户自有云未挂载函数运行时) | 直接写 CaseAsset,字段与 caseSubmit.js 对齐;--no-direct 可禁用回落 |
server.lumistedu.com)没有可用的函数运行时:/api/functions 只是定位器端点、/parse/functions/* 只有 Parse 原生注册表。项目的真实数据架构就是浏览器端 Parse 直连(见 web/src/app/core/data.service.ts),归档走同一路径。reviewStatus='pending'、readyForUse=false、tenantId + idempotencyKey 幂等 —— 新案例只进待审清单,不进公共素材库。submit 必然以 fallback: 'cloud-unavailable -> parse-direct' 返回,这是本部署的正常路径、不是故障:不要为了"修好云函数"去反复调 /api/functions(任何 path 都 500,连通性探针也一样)。node scripts/case-intake.mjs probe(只读,应返回 ok:true, hasMasterKey:true)。CASE_PARSE_MASTER_KEY 或 0600 的 ~/.fmode/config/enterprise/current.json 读取,只进请求头,不落盘、不打印。CaseAsset 字段类型:targetCustomer / resultEvidence / outcome / groupInfo 是 Object(不是字符串),本通道已按此对齐。| 函数 | 用途 |
|---|---|
caseSubmit |
提交案例;写 reviewStatus='pending'、readyForUse=false;tenantId + idempotencyKey 幂等 |
casePendingList |
查待审清单 |
caseApprove |
负责人审核放行(approve → readyForUse=true) |
caseSearch |
检索(只返回 reviewStatus='approved' 且 readyForUse=true) |
tenantId 由云函数从服务端会话上下文解析,客户端不传。
npx --yes skill-case-get@latest workspace # 项目级 → ./.claude/skills/skill-case-get
npx --yes skill-case-get@latest install # 用户级 → ~/.claude/skills/skill-case-get
安装后可直接提示:这 9 张图是同一个案例素材,帮我拆解、打标,然后归档到待审清单。
.docx / .pptx 解包走 python3 标准库,不要为此引入 python-docx / python-pptx / 任何 npm 包。--upload 开,走 fmode-storage 第 0 级免密钥通道)拿公网 URL;--no-upload 才回落到 --asset-base-url 派生。两种情况下都不伪造 URL,拿不到就留空并在 warnings 里说清楚。--frame-count 调整);转写走网关按音频时长计费。pending / authorized),展示层再做中文化。