交付出件箱

← 返回出件箱

机构洞察 · 原型落代码前的物理 verify 报告

2026-08-26 · dev3 东京云机 · 长官令「先 verify(物理 grep)· 不漏证据 · 查清楚再继续」
基线:worktree 分支 feature/org-insights-simplify @ 451d3125
原型最终版:org-insights-redesign-v16.html(v16 第二十六轮)
现网前端:web/app/(dashboard)/admin/org-insights/page.tsx(1168 行)
现网后端:app/services/org_insight_service.py(37,718 B)、app/api/admin_activity.py(31,253 B)

一句话结论:原型要的东西,后端 80% 已经现成;有 3 个硬缺口必须先定怎么办,其中 1 个是我上一轮判断失误留下的。


一、后端已有、可直接用(逐项带证据)

现网 GET /api/admin/org-insights 的 OrgInsightRow(admin_activity.py:146-177)已经返回:

原型要的 后端字段 证据
九种状态标签 status / status_label / action / evidence org_insight_service.py:50-82 九个枚举 + 中文名 + 建议动作
使用频率折线图(30 天) spark30: list[int] admin_activity.py:177
「近 N 天用满 N 天」 active_days_7 admin_activity.py:169
来源构成(席位/链接/门店端) src_counts admin_activity.py:138-144,含 miniapp 第四类
三窗次数 / 在用席位 events_day|week|month、events_prev_week、seats_active_day|week|month :165-171
席位总数 / 从未激活 seats_total、seats_never_activated :158-160
到期天数 days_to_expiry、next_expiry_at、all_expired :161-163
建档天数 created_days :149
值得深挖的分子分母 reports、plans_confirmed、quoted_amount :173-175
数据不可信判定依据 has_credential、trusted :176、org_insight_service.py:486
频率分档 freq(high/mid/low/idle) :157
口径阈值 + 存得住 thresholds + GET/PUT /org-insights/config admin_activity.py:746/755
席位明细(含三类操作计数) GET /org-insights/{id}/seats admin_activity.py:793
弹窗「概览」三块 GET /org-insights/{id}/usage → overview.actions / data_generated / reports org_insight_service.py:156-171
弹窗「搜过什么(原文)」 同上 → cases.searched org_insight_service.py:181-184

🔴 最重要的一条:九状态判定,后端与原型逐条一致

后端 org_insight_service.py:290-317 的判定链:

untrusted → churned → no_seat → renew_window → cliff → never_active → low_util → worth_digging → healthy

顺序、命中即停的逻辑、九个枚举名,与原型 calc() 完全对得上。
这意味着原型里的「九状态 + 每条注解 + 四个可调阈值」不需要动后端判定逻辑,只是前端展示层重做。


二、前端自己能解决、不用动后端

原型功能 做法
五类筛选框(要立刻处理/该我们跟进/…) 后端只给 status,五类是 status → bucket 的映射,前端做一张映射表即可
分页(每页 15 条) 后端一次返回完整 ranking 数组,前端切片,无需后端分页
状态标签悬浮注解 纯前端文案(长官 8-26 给的 9 条),数字从 thresholds 取
柱子改状态色 / 数字进柱 / 长尾保底宽度 纯样式
席位「席」字徽章 + 账号胶囊 纯样式
折线图 7/30 天切换 + 每天悬浮出次数 spark30 已有 30 天数据,切 7 天 = 取数组后 7 位,前端算

三、🔴 三个硬缺口(后端给不出,必须先定)

缺口 1:弹窗「问了什么」那 5 个维度,后端一处都没有 —— 这是我上一轮判断失误

原型「席位详情 → 问了什么」保留了 5 块:患者年龄段 / 近视度数档 / 防控方法 / 疗程长度 / 关注的结果。

物理 grep(带对照组):

                   org_insight_service.py    全 app/*.py
input_types                3                      3      ← 对照组:后端确实有的词
top_questions              2                      2      ← 对照组
searched                   2                      2      ← 对照组
patient_type               0                     34
myopia_degree              0                     31
outcome_tag                0                     33

对照组证明查法有效。结论:这套 facet 词表在系统里确实存在
(b3_ingest_service.py / find_similar_service.py / b3_sticker.py 等 30+ 处),
但机构洞察的 usage 接口一处都没用到。

后端 build_usage_detail 的 questions 实际只返回:
total / input_types / top(高频词)/ samples(问题原文样本)—— 没有任何 facet 聚合。

为什么会留下这块:原型里这 5 块自带 ready:1(现成)标记,上一轮长官令「只保留现成的」时我照标记留了。
但那个标记的原意是「b3 的词表现成」,不等于「这个接口能返回」——
要做出来得先给每条提问打上 facet 标签再 GROUP BY,是新功能,不是改一行 select。
这是我的判断失误,如实报。

缺口 2:席位账号(users.username)没被取出来

org_insight_service.py:610:

select(User.id, User.experience_store_name, User.seat_expires_at)

只取了门店名,返回 seat_name = 门店名 or f"席位 {seat_id}"(:641)。
原型第九轮起席位那列显示的是登录账号(10 位数字,格式见 seat_service.py:122)。
users.username 对 org_seat 角色强制非空(user.py CheckConstraint),给得出,加一个字段即可。

缺口 3:自定义时间范围,后端只认三个固定值

admin_activity.py:720 / 796 / 807 三处都写死:

range: str = Query("7d", pattern="^(7d|30d|90d)$")

原型第十四轮做的「自定义起止日期」需要后端接受任意区间(或至少 from/to 两个参数)。
要改 3 个端点的入参 + 下游取数逻辑。


四、前端改动清单(page.tsx 1168 行)

删:副标题 :307 · 顶部橙色警示条 :345-363 · 时间下拉 :312-323 ·
卡片 hint 与环比行 :368-377 · 排行副标题 :389-393 · 「共 N 家 · 正在看 N 家」:422 ·
使用频率分档整块 :459+

改:排行行加状态标签列(悬浮注解)· 柱子改状态单色 + 数字进柱 + 长尾保底 ·
展开层重排(来源构成条 → 折线图 → 席位行)· 席位账号胶囊 + 「席」徽章 ·
抽屉重构为九状态 + 注解 + 四滑块 · 弹窗按「只留现成」精简 · 「用来干什么」→「席位详情」

新增:数据分类筛选框 · 时间范围框 · 分页(每页 15)· 折线图交互(7/30 切换 + 每天悬浮)

接口调用点无需新增:现有 5 处(主数据 / config 读写 / seats / usage)已覆盖,
除非缺口 2、3 要动后端。


五、需要长官定的三件事

  1. 「问了什么」那 5 个维度:砍掉(弹窗只剩概览 + 搜过什么)/保留但显示「待后端支持」/立项做后端打标签聚合?
  2. 席位账号:要不要现在就改后端多取一个 username?(改动小,1 行 select + 1 行返回)
  3. 自定义时间范围:要不要现在就扩后端 range 入参?还是先只做 7/30/90 三档(前端下拉给三个选项)?

六、本次 verify 用到的命令(可复现)

# 后端契约
sed -n '138,235p' app/api/admin_activity.py          # 全部响应模型字段
grep -n '"untrusted"\|"churned"\|…' app/services/org_insight_service.py   # 九状态
sed -n '/^async def build_usage_detail/,/^async def /p' app/services/org_insight_service.py

# 缺口对照组
for k in input_types top_questions searched patient_type myopia_degree outcome_tag; do
  printf "%-16s service=%s 全app=%s\n" "$k" \
    "$(grep -c "$k" app/services/org_insight_service.py)" \
    "$(grep -rc "$k" app/ --include=*.py | awk -F: '{s+=$2}END{print s}')"
done

# 前端现状
grep -n "useState" "web/app/(dashboard)/admin/org-insights/page.tsx"
grep -n "apiFetch(" "web/app/(dashboard)/admin/org-insights/page.tsx"

没做的:云机没装 web/node_modules,tsc / vitest 跑不了;
后端 pytest 依赖链要到 faiss,本机也跑不了。这两条到 test 环境验。