FmodeCode ec05b37ac8 初始化:康师傅产品创新看板 v10 5 дней назад
..
css ec05b37ac8 初始化:康师傅产品创新看板 v10 5 дней назад
docs ec05b37ac8 初始化:康师傅产品创新看板 v10 5 дней назад
js ec05b37ac8 初始化:康师傅产品创新看板 v10 5 дней назад
tools ec05b37ac8 初始化:康师傅产品创新看板 v10 5 дней назад
README.md ec05b37ac8 初始化:康师傅产品创新看板 v10 5 дней назад
TASK-dashboard-v1.md ec05b37ac8 初始化:康师傅产品创新看板 v10 5 дней назад
TASK-dashboard-v2.md ec05b37ac8 初始化:康师傅产品创新看板 v10 5 дней назад
TASK-dashboard-v3.md ec05b37ac8 初始化:康师傅产品创新看板 v10 5 дней назад
TASK-dashboard-v4.md ec05b37ac8 初始化:康师傅产品创新看板 v10 5 дней назад
index.html ec05b37ac8 初始化:康师傅产品创新看板 v10 5 дней назад

README.md

康师傅产品创新看板 · v3(第二版迭代)

第一版跑通了取数与筛选,但被判定「太普通、没有达到系统性看板的要求」。 这一版在保留 v1 已通过能力的前提下,补上了分析报告层,并把它和台账层打通: 从一句结论,能一路追到原始成果。

入口:index.html(纯静态,浏览器直接打开或任意静态服务器托管均可;file:// 下也已实测可读)。

python3 -m http.server 8765        # 然后打开 http://127.0.0.1:8765/

1. 这一版做了什么

1.1 视觉:iOS Liquid Glass(客户硬性要求)

v1 被打回的直接原因是「实心白卡片 + 靠阴影分开」。这一版把视觉主线重新做成液态玻璃:

要求 落地
半透明磨砂层 面板/卡片 background: rgba(255,255,255,.26~.58) + backdrop-filter: blur(20~32px) saturate(175~190%)
玻璃为什么"透" 页面底层有真实光场 .ambient:四团色光(蓝/紫/薄荷/橙)+ 一道从左上斜下的天光,极缓漂移(46–64s),再叠一层 SVG 湍流噪点。没有底层光,backdrop-filter 无物可模糊,玻璃就成了白纸
发丝边 1px 半透明边;.glass 基元用双层背景做上亮下暗的渐变边(模拟玻璃边缘折光),面板底部另有一条更暗的极淡内边;吸顶的筛选条/目录条上方不圆角、无缝接到玻璃顶栏
分层光照 所有光源从左上:每块玻璃顶部 inset 0 1px 0 rgba(255,255,255,.7) 内高光 + 左上角径向光斑放在容器层级;阴影全部"往下偏右、极浅"(0 10px 30px -16px rgba(32,48,90,.14)),没有新拟物那种大阴影凸起/凹陷
纵深 三层透明度:L1 面板最透 → L2 卡片/指标稍实 → L3 顶栏/侧栏/抽屉更实;关键数字用强调色微光(text-shadow 柔光)而不是底色块
铁律:不糊数据 正文/数字一律落在 --ink(对比度 ≈ 14:1)与 --ink-2(≈ 8:1)上;说明文字 --ink-3(≈ 5.2:1);万字级报告正文放在更实的"纸"衬底(rgba(255,255,255,.62~.78))。模糊只存在于装饰层

配色:客户说"配色无所谓",这里选了浅色(与 v1 的"浅色底"方向一致、更适合长文阅读),并明确定义了对比度层级。

1.2 内容层:报告能被翻开读

新增 报告层:

  • 报告目录页 #/reportIndex:每份报告一张卡片(几章、几条结论、几条支撑成果、几条原文)。
  • 报告阅读页 #/report/<成果id>:
    • 吸顶章节导航,点击平滑跳转;滚动时高亮当前章(?ch= 会写回地址,刷新可还原)
    • 封面区:项目名、方法论、数据源、数据管线(2,727 → 1,190 → 730,过滤率 73%)、生成日期、证据链完整度侧栏
    • 报告结构总览(6 章,每张可点跳到对应章节)
    • 执行摘要(敢下判断的四句话)
    • KPI 卡片组(大数字 + 小单位 + 一行解读)
    • 章首导语(每章一句话说清"这章在回答什么")
    • 公式化标题(5 大场景 × 现有产品 × 未满足需求)、二级编号(1.1 / 1.2 …)
    • 交叉分析表(场景 × 产品占比条 × 未满足需求 × VOC 数 × 机会)、漏斗、机会评分、心智阶梯、一句话定位、行动清单、验证里程碑、数据缺口
    • 结论块带态度("「软塌」的根因是工艺,不是口味"),旁边就是证据锚点
    • 证据锚点:点开抽屉,列出支撑这条结论的每条成果(谁、哪层、证据、在库里是否可追),以及原文片段;每条成果可跳回台账
    • 附录:所有结论 × 证据对照总表
    • 反面证据:把一条「没有原文的推演」原样留在证据链里,说明"我们排除了什么"

1.3 闭环:从结论一路追到原始成果

归纳(沉淀层) ──展开──▶ 归纳自哪几份报告 ──▶ 报告
     ▲                                     
     │                                     
报告结论 ──证据锚点──▶ 支撑成果(过程层) ──▶ 台账定位(?focus=)
     ▲                                     
     └──────── 成果抽屉:「作为证据,被这些结论引用」 ◀── 成果
台账行标题「报告」标记 ──▶ 报告 ──▶ 顶部「成果台账」──▶ 台账
  • 台账 → 报告:带报告的成果,标题本身就是报告链接("报告"徽标),也可在抽屉点「打开完整报告」
  • 报告 → 台账:证据锚点抽屉里每条成果都可跳转;跳过去时台账用 ?focus= 高亮并置顶那一行,给出"正在定位"的说明和"清除定位"
  • 报告结论(反向)→ 成果:成果抽屉里「作为证据,被这些结论引用 N」,点开直接看证据链
  • 归纳 → 报告:归纳卡上「归纳自 N 份报告(M 条结论)」,展开看报告、对应结论、证据链,并可进报告
  • 往返不乱:所有状态都在 hash 里,往返时抽屉关闭、筛选与定位参数互不污染

1.4 保留 v1 已通过的能力(没有推翻)

真实取数(Parse REST,只带 appId)· 6 维筛选(优先级/阶段/品类/证据/层/成员)+ 组合叠加 · 筛选写 URL 刷新保留 · hash 路由直链落位 · 四层区分 · 五阶段人数分布 · 15 秒轮询 · 搜索。


2. 报告正文怎么存:KfArtifact.report(而不是新建 KfReport)

结论:挂在 KfArtifact 上新增 report 字段(Object/JSON),不新建 class。 理由:

  1. 身份唯一。报告不是独立对象,它就是"某个学员在某个阶段交的某条成果"的完整形态:同一份出身(ownerName/ownerSession)/ 同一阶段 / 同一层 / 同一项目。另建 class 就得复制或引用这套出身,而"没有出身的记录不许进沉淀层"的规则就会有两处实现,迟早不一致。
  2. 四层逻辑不需要改。报告随成果一起是过程层;它的结论是证据锚点,不是沉淀层归纳。沉淀层仍然是 KfInsight。一条报告被回灌时,是它里面的结论被写成 KfInsight,并引用报告支撑的成果——这正好是现有规则。
  3. 闭环天然成立。derivedFrom 一直都是 KfArtifact.objectId;报告的 evidence 里 derivedFrom 也指向同一批 id,不需要跨表映射。
  4. 写入简单。上传 skill 只要多写一个字段;没有报告的老成果不写即可,前端显示"无报告",不需要迁移。
  5. 代价:一份成果只有一个报告字段。后续若需要"一份成果多版报告/多语言",再拆 KfReport 并加 artifact 指针;届时 report 字段可以原样迁过去(结构不变)。

报告数据结构(KfArtifact.report)

{
  "version": 1,
  "title": "…", "subtitle": "…", "method": "…", "sources": "…", "structure": "…",
  "pipeline": [{"label":"原始 VOC 采集","value":2727}, {"label":"去噪去重","value":1190}, {"label":"精准 VOC","value":730}],
  "filterRate": "73%",
  "generatedAt": "ISO 时间",
  "kpis":   [{"value":2727,"unit":"条","label":"原始 VOC","note":"一行解读"}],
  "executive": ["四句话"],
  "chapters": [
    {"id":"ch1","no":1,"stage":"S1","en":"MARKET FUNNEL","title":"全景漏斗","subtitle":"…",
     "lead":"这一章在回答什么问题",
     "sections":[
       {"id":"s1-1","no":"1.1","title":"…","kind":"funnel","data":[…]},
       {"id":"s2-1","no":"2.1","title":"…","kind":"matrix","data":{"cols":[…],"rows":[…]}},
       {"id":"s2-2","no":"2.2","title":"…","kind":"findings","data":[{"id":"f-1","conclusion":"…","attitude":"…","evidence":"c-texture"}]}
     ]}
  ],
  "evidence": {
    "c-texture": {"claim":"…","ownerCount":4,
                  "derivedFrom":["<KfArtifact.objectId>", …],     // 支撑它的过程层成果
                  "quotes":[{"artId":"<objectId|null>","author":"…","text":"…","ref":"…","likes":412}]}
  }
}
  • chapters[].stage 只能取 S1–S5(沿用五阶段,没有发明阶段)。示例报告 6 章对应:Ch1 S1、Ch2/Ch3 S2、Ch4 S4、Ch5/Ch6 S5。
  • kind 是渲染器的键:para / longpara / funnel / matrix / findings / table / two / fit / score / ladder / statement / milestones。遇到未知 kind 会原样列出键值并标黄,不会静默吞内容。
  • 正文一律是数据,不是 HTML:渲染端统一 esc()。避免把 HTML 塞进库里(注入、样式漂移、分身不好读)。将来要做富文本,再加 format:"md",由渲染端解析。
  • 证据优先级:derivedFrom 指向的成果是"事实来源";quotes 是原文片段。quotes[].artId 为空表示"原文来自外部采集、没有对应成果记录",抽屉里单独列出,不冒充成果。

闭环用到的其他字段

  • KfInsight.citedInsightIds(Array,可选):这条归纳对应报告里的哪几条结论。没有时前端用"归纳支撑成果 ∩ 报告证据 derivedFrom"取交集推断。
  • 原有 KfInsight.derivedFrom / derivedArtifacts:不变。

3. 四层的含义(与 v1 一致)

层 layer 谁写 页面上的样子
原材料 raw 调研 Agent 原材料页;每条必须带出处
过程 process 学员 + 分身 台账主体;报告挂在过程层成果上
沉淀 sediment 归纳 + 证据筛选 归纳页;必须能回溯到支撑成果
记录 record 自动落入 记录页;只留档,也保留在台账里以便看出"排除了什么"

同一个库,靠字段区分。前端所有列表都能看到层标。


4. 目录结构

index.html          入口
css/tokens.css      设计变量(光场色、三层玻璃、发丝边、光照、文字对比度层级)
css/base.css        光场/噪点、侧栏顶栏、应用骨架、玻璃基元
css/components.css  指标、筛选、表格、归纳、抽屉、空态、骨架
css/report.css      报告阅读页与目录页
css/responsive.css  1280 / 1024 / 768
js/config.js        ★ 唯一需要改数据源的地方
js/taxonomy.js      阶段/层/优先级/证据枚举
js/model.js         原始行 → 页面对象(含 report 规整、容错)
js/query.js         筛选(纯函数)
js/router.js        hash 路由(report / reportIndex / focus / ev / ch / back)
js/views/*.js       各视图(report.js = 报告阅读、目录、证据链抽屉、滚动高亮)
js/app.js           入口:取数 → 建模 → 按 hash 渲染 → 事件委托 → 轮询
tools/seed_report.py       为 1 条成果写入示例报告(可重复执行)
tools/seed_long_report.py  把示例报告扩到约 2 万字(压测第 13 项;再跑 seed_report.py 可还原)
tools/seed_insight_link.py 把 1 条归纳与示例报告打通(让"归纳自哪几份报告"有样本)
tools/enrich.py / reset_demo.py / setup_cloud.py   v1 遗留的数据准备脚本
docs/verify.js      浏览器点击验收脚本(16 项 + 3 项视觉核验)
docs/smoke.js       jsdom 冒烟(快速看各页能否渲染)
tools/check_frontend_secrets.sh   红线自检:按 .js/.html/.css 与按"文本内容"两种口径扫前端
docs/verify-run.log 最后一次验收的完整输出
docs/screenshots/   截图

数据准备(需要 PARSE_MASTER_KEY,仅限本地脚本)

python3 tools/seed_report.py          # 写入示例报告(康师傅拌面 · 江中骨架,6 章)
python3 tools/seed_insight_link.py    # 打通 1 条归纳 → 报告
python3 tools/seed_long_report.py     # 可选:扩到 ~2 万字做压测

masterKey 只从环境变量读取,不进入任何前端文件。红线自检:./tools/check_frontend_secrets.sh。


5. 验收:第 5 节 16 项逐条结论

验证方式:真实 Chromium(headless Chrome 153)+ puppeteer-core,在 http://127.0.0.1:8765 上真实点击/滚动/断网,脚本 docs/verify.js,完整输出 docs/verify-run.log。最后一次运行 19/19 PASS(16 项 + 3 项视觉核验)。屏幕上还逐张看过了 docs/screenshots/ 里的截图。

# 项 结论 怎么验的 / 结果
1 真实取数,条数与库一致 ✅ 已验证 浏览器内以公开读方式直连 Parse 取 5 张表,与导航计数比对:session 10 / 归纳 4 / 成果 26 / 项目 10 / 原材料 4,全部一致
2 单选筛选生效 ✅ 已验证 在台账页点「优先级 S」:库中 S 级成果 7 条 → 页面 7 行、提示 7 条(未筛 26 行)
3 组合筛选(交集正确) ✅ 已验证 再点「S1」:独立计算库中 S∩S1 = 0 → 页面 0 条并显示空态;已选条件回显「优先级 S / S1」。注意:验收脚本只断言了空交集;非空的 S∩S3 = 4 条是我在调试脚本里单独点过、人工核对与库一致的,不在 16 项自动断言里
4 筛选状态写入 URL,刷新保留 ✅ 已验证 #/artifacts?priority=S&stage=S1:新标签打开结果一致、选中 chip 保留;页面 reload 后仍一致
5 搜索命中 ✅ 已验证 搜「张伟」:页面 4 条 = 库中 4 条,4 行都命中;其中 1 行成员是陈静(标题写着"与张伟重复"),属按标题/成员/项目联合搜索的预期。清空后恢复 26 行
6 四层区分可见 ✅ 已验证 台账层列出现 process / sediment / record 三种层标;总览「分层」区块 4 个层标(raw/process/sediment/record)。总览「成果总数」卡把被归纳引用的成果改计入沉淀层,所以 raw=0;原文要看原材料页(KfSource,4 条,其中 3 条是早期探针占位)
7 五阶段人数分布正确 ✅ 已验证 库中 10 条会话恰好每阶段 2 人,但页面不是平均分配出来的:页面按「会话阶段 + 成果阶段取最远者」统计,结果 2/2/2/2/2,与独立按 KfSession 统计一致,合计 10
8 前端无凭据泄露(红线) ✅ 已验证 ① 浏览器内读取全部 24 个前端文件(index + 13 个 js + 5 个 css ≈ 210 KB)匹配 masterKey / X-Parse-Master-Key / cc84… / PARSE_MASTER_KEY:0 处;② 对交付目录的 index.html / js / css grep -rniE:无;③ 另用环境变量的实际值全文搜索整个交付目录(含 tools、docs、README):无命中
9 成果详情页是报告形态 ✅ 已验证 在台账真实点击带「报告」标记的行标题 → 报告页:目录 6 章、管线 3 步、章首导语 6、KPI 4、表格 9、结论块 6、证据锚点 18
10 章节导航跳转 + 滚动高亮 ✅ 已验证 初始高亮 ch1;点目录「4 产品适配」→ 滚动收敛到第 4 章顶部距视口 96px、高亮切到 ch4;合成滚动到第 6 章/第 2 章,高亮分别跟到 ch6 / ch2
11 报告结论反查支撑成果 ✅ 已验证 点 Ch2 结论旁「看原文」→ 抽屉列出 3 条支撑成果(张伟/李娜/王芳,均可在库中找到)、3 条原文,提示"证据链完整";每条成果有跳转
12 台账→报告→台账往返不乱 ✅ 已验证 证据抽屉点成果 → 台账 26 行、定位行高亮、抽屉已关、导航停在「成果」;清除定位后 URL 干净;再进同一报告在顶部;报告顶部「成果台账」→ 台账 26 行
13 长报告可读(万字级) ✅ 已验证 库中真实版本(seed_long_report.py 生成)= 6 章、可见正文 21,440 字、DOM 1,196 节点、页高 24,773px。平滑滚到 33% → 高亮 ch2 → 跳底 → 高亮 ch6 且到底 → 跳第 4 章 → ch4;无横向溢出
14 1280 / 1024 / 768 三档不错乱 ✅ 已验证 三档下报告页与台账页:横向溢出 0px、无越界元素;正文始终 15px;KPI 4 列/2 列/2 列;目录条保持单行。768 的截图人工看过
15 空态 / 加载态 / 错误态有设计 ✅ 已验证 空态:搜不存在的词 →「当前条件下没有成果」+ 逐步放宽提示;报告目录 →「没有匹配的报告」。加载态:人为把 Parse 延迟 1.5s + reload → 骨架 38 块。错误态:人为断开 Parse →「报告没有加载出来」+ 原因 + 重试(侧栏红点「读取失败」);恢复网络点重试 → 报告正常渲染
16 无 console 报错(有一处保留说明) ✅ 已验证 走完 10 个页面:pageerror 0;console.error 共 5 条,全部来自第 15 项人为断网测试的 net::ERR_FAILED,非网络类错误 0 条。正常使用(不断网)时控制台无报错

补充(不计入 16 项,说明闭环的另外两条路):

项 结论 说明
视觉核验 V1/V2 ✅ 已验证 归纳卡「归纳自 1 份报告(2 条结论)」展开 → 出现报告链接 → 点击进入该报告
视觉核验 V3 ✅ 已验证 成果抽屉「作为证据,被这些结论引用 1:『太软没嚼劲』… 来自报告…」
file:// 直接打开 ✅ 已验证 用 Chrome 以 file:///…/index.html#/report/… 打开,报告正常渲染、0 报错

6. 没验证到的、没做到的、需要你知道的

明确未验证(不做"已实现"的模糊表述):

  1. 真机 / 真实浏览器交互的滚动惯性:验收跑在 headless Chrome 里。长距离平滑滚动在无头环境下是"阶梯式"的(测试因此用了逐帧推进 + 等待收敛)。真实桌面浏览器里的手感(macOS Safari 尤其,backdrop-filter 性能与观感都会不同)未验证。
  2. Safari / Firefox 兼容:只测了 Chromium 内核。backdrop-filter 已加 -webkit- 前缀,并有 @supports not 降级(不支持时面板回到高亮度实底);但Safari 上的玻璃观感、Firefox 上的降级效果未实测。
  3. 主色对比度是计算得来的,未用测量工具做全页扫描:--ink 对最亮玻璃 ≈14:1、--ink-2 ≈8:1、--ink-3 ≈5.2:1,均按"玻璃背景与最亮光斑叠加"的最坏情况估算;没有逐元素跑 axe / 取色器扫描。极小字号(10.5–11px)的浅色元信息(如 gauge-l、r-toc-s)属于装饰性文字,没有保证 4.5:1。
  4. 768 以下(手机竖屏) 只做了样式上的兜底(≤420px 断点),未验收。 另:768 宽时报告封面右侧"证据链完整度"卡片因为被压扁,行间分隔线会略微外伸(纯观感,不影响数据,未修)。
  5. 40 人同时在线、15 秒轮询下的压力:未测。
  6. 上传 skill 写报告:本版没有做上传链路(不在本版范围)。示例报告由 tools/seed_report.py 直接写库。
  7. 归纳 → 报告的自动归集:本版用 citedInsightIds + 交集推断,没有自动生成归纳。

没做到的 / 已知限制:

  • 报告正文是"数据 + 渲染器"模式,kind 之外的富文本(表格合并、图片、脚注)暂不支持;未知 kind 会原样陈列而不是报错。
  • 同一份报告里的结论 id 需在报告内唯一(例如 c-texture)。不同报告可以用相同 id,前端按"第一份含该结论的报告"打开证据链——目前只有一份报告,不受影响;多报告后建议上传方用 <成果id>/<结论id> 命名。
  • 示例报告里的"原文"是为演示结构而写的内容(seed_report.py 里逐条列出),不是真实采集到的评论;看板不会把它们伪装成学员成果(无 artId 的原文单独列为"未挂到成果上的原文")。
  • 库里有 4 条 KfSource 是早先的探针占位(sourceType=probe),不是真实原材料,页面如实显示。
  • 库里 26 条成果 sources 全为空——报告的证据链因此是"挂到成果 + 原文片段"的组合,而不是"成果自带原文"。这是数据现状,不是前端丢失。

环境事件(需告知):验收过程中 Parse 服务端(kangshifu.fmode.cn/parse)出现过约 10 分钟 503 Service Unavailable(连 /parse/health 也 503,且 CORS 预检失败),期间前端正确显示错误态;服务恢复后重跑,全部通过。


7. 歧义与我的处理(需求里不自洽的地方,先说明)

  1. "台账列表里点一条成果 → 进报告详情页":不是所有成果都有报告。我的处理:有报告的成果,标题本身就是报告入口(带「报告」徽标);没有报告的成果,点行仍打开原有详情抽屉(保留 v1 的台账能力),抽屉里明确写"这条成果没有附带报告"。若要求"点任何一条都必须进报告页",需要先约定"无报告成果"的占位页形态。
  2. "章节能对应 S1–S5":示例报告 6 章,其中 Ch2/Ch3 同属 S2、Ch5/Ch6 同属 S5。chapters[].stage 只允许 S1–S5,不设"第六阶段"。
  3. "证据锚点能点开看支撑它的原始 VOC 条数/原文片段":结论的 ownerCount 和原文片段是报告作者写进报告的数据;看板能做的是"把它们挂到真实成果上、并检查成果在库里是否还存在",而不是替作者重新统计。抽屉里同时显示"提到人数 / 原文条数 / 支撑成果数 / 库里可追数",可追数与引用数不一致时会标黄提示。
  4. 归纳 → 报告:归纳和报告分别属于沉淀层和过程层,库中没有现成的外键。本版用 citedInsightIds(可选)+ 交集推断;如果客户希望归纳直接引用报告里的结论 id,需要在上传 skill 约定字段(见第 8 节)。

8. 下一版要接什么

  1. 上传 skill 写 report:按第 2 节结构产出;上传时校验:chapters[].stage ∈ S1..S5、evidence[*].derivedFrom 必须是本批成果 id、kind 在渲染器白名单内,否则落记录层并给出原因。
  2. 结论 → 归纳:报告结论回灌时自动写成 KfInsight(带 derivedFrom 与 citedInsightIds),并接"证据够不够格回灌"的判定接口(v1/v2 已留位)。
  3. 分身查库入口:report 是结构化数据,分身可以直接按 kind/evidence 检索;建议为结论加 embedding 字段做语义召回。
  4. 业务字段定稿:品类/竞品/工艺/人群的具体字段仍等调研回传,在 KfSource.payload 上定稿,不动表结构;报告的 matrix 表可以按新字段配置列。
  5. 可访问性扫描:接入 axe / 取色器,把第 6 节第 3 条的"估算"换成实测。
  6. 若需要多版报告:新增 KfReport,artifact 指针回指成果,KfArtifact.report 原样迁移。