「比起前端 UI 样式,门店检测的逻辑更需要调整。现在的门店诊断结果并不严谨, 诊断建议也不是根据门店实际问题给出的,而是一股脑全给。」 要求:先做思维导图统一梳理「每项指标没达到的问题 + 整改建议」,确认后再写进代码。
对 1409 家门店实测,找出 5 个硬伤:
| 问题 | 实测 | 结论 |
|---|---|---|
| 核销金额基准 10000 当不达标线 | 全品牌中位 165 元,仅 8% 达标 | 命中 1023 家 = 噪音,不是诊断 |
| 回复率 <50% | 命中 1305 家(93%) | 没有区分度 |
| 评分 0 分 | 91 家被判「低于 4.3」 | 0 分是未开分,不是评分低 |
| 差评率小样本 | 22 家「1-2 条评价」就判 | 一条差评 = 非常严重 |
| 整改动作 | 9 条固定清单,每家全给 | 用户说的「一股脑全给」 |
补充发现(重要,推翻了之前的判断):
产出:
项目记录/规则设计_v2_思维导图.html(自包含,可打印)项目记录/规则设计_v2_明细.md(同一份规格的表格版)结构:4 层漏斗(流量/转化/履约/口碑)× 16 条规则,5 档分级,1 棵诊断树,≤3 条动作。
DEFAULT_THRESHOLDS 新增 7 个可调门槛:visit_low / cvr_low / pend_high /
hx_min_deal / amt_min_coupons / cp_min_new / cp_min_total / reply_min_newdiagnose() 返回 problems / primary / secondary / actions / sev / tier / headline / hd_keybad_rate() 改为返回 (ratio, 口径, 是否小样本),小样本在 diagnose 里封顶 R1detect() 兼容旧调用V1_TEXTS:v1 默认文案快照,用于迁移get_thresholds() 用默认值补全库里没有的新键(老库零迁移)get_texts() 迁移:存档值 == v1 默认值 → 用新默认;否则视为用户自定义保留
(实测保留了用户的 3 处自定义:概览行模板、访问行模板、传素材动作文案)aggregate_period() 补输出 reviews_new/bad_new/reviews_total/bad_total/lit
—— 差评率判定需要原始计数,之前只算了一个 cp_rate 出来diagnosisOf(s, X):用当前(可能未保存的)模板重新渲染问题与动作renderFeedback() 重写:概览 → 结论 → 主因+次要 → 待核销 → 流失 → 洞察 → ≤3 动作previewFlags()(前端复刻的第二套判定规则),
改为直接用后端 problems + 模板键重渲染 → 规则唯一源真正落到 rules.pyreply_min_new 可在面板调到 5,命中会降到 414 家。用户在 规则全景图_可编辑.html 里改了 11 条规则、新增 1 条、删除 2 条,
导出 规则修改_2026-11-01.json。已用浏览器实时状态核对,导出的就是最终版。
| 类型 | 内容 |
|---|---|
| 删除 | VISIT0(并入 VISIT,判定改为「访问 < 100」含 0)、SCORE0(并入 SCORE) |
| 新增 | 新增评价低(新增评价 < 10 条,T 级) |
| 阈值 | 视频 0 → <10、点亮 0 → <10、转化率 2% → 10% |
| 重命名 | 客单价偏低 → 核销金额偏低;退单率超线 → 商责退单率超线 |
| 重写判定 | 核销金额去掉「核销率已达标」前置条件 |
| 文案 | 12 条问题的动作全部改写为更具体的门店执行动作 |
rules.py
DIAGNOSIS_ORDER 去掉 VISIT0/SCORE0,加入 RVOLAMT 移出链外独立判定SCORE 一条规则内区分 0 分(走 t_SCORE0「未开分」文案)_clean_action() 去掉动作行首的 ✅,避免与「✅ {动作}」模板叠加video_low / lit_low / new_review_low,cvr_low 默认 2 → 10hd_key 回退链:规则 key → 模板 key(区分未开分)→ 层 → OKdb.py
set_texts() 改为只存与默认值不同的键 —— 以后改默认文案不会再被旧存档盖住,也不再需要 V1 快照比对(保留 V1 兼容历史存档)diagnosisOf 的动作覆盖全部问题;风险段、抽屉、报告全部改为「问题全列」str.replace 批量改文案时误伤了 V1_TEXTS 快照(t_VISIT / t_VIDEO 两处字符串完全相同),
导致迁移判断失效、旧值被当成用户自定义。已按 V1 真实值修复,并改为「只存差异」从根上避免这类问题。cvr_low 还是 2% —— 条件文本和参数是两处,已按 10% 统一。| 规则 | 命中 | 规则 | 命中 |
|---|---|---|---|
| 点亮偏低 | 1305 (93%) | 评分不达标 | 323 (23%) |
| 核销金额偏低 | 1299 (92%) | 未核销积压 | 253 (18%) |
| 转化率低 | 1213 (86%) | 成交未核销 | 221 (16%) |
| 新增评价少 | 1170 (83%) | 商责退单率 | 165 (12%) |
| 回复率偏低 | 822 (58%) | 访问过低 | 147 (10%) |
| 核销率不达标 | 695 (49%) | 零成交 | 56 (4%) |
| 视频量少 | 437 (31%) | 投诉率 | 11 (1%) |
| 差评率超线 | 349 (25%) |
平均每家 6.01 条问题 / 6.01 条动作,单店最多 11 条。
用户要求:每天从抖音来客「报表集市」导出近30天数据并入库,分析口径围绕近30天滚动窗口。
把「按统计周期」的导出文件直接丢进旧版导入,会静默写错:
统计日期是 2026-10-03-2026-11-01 这种区间字符串,旧代码只截取开头 2026-10-03,
结果 30 天的汇总被当成「10 月 3 日一天」的日报,窗口结束日丢失。
(测试时误把 2 家测试门店写进真实库,已清理,KPI 复核无异常。)
metrics 新增 window_start / window_end,granularity 增加 snapshotingest.parse_range() 识别 2026-10-03-2026-11-01 / ~ / 至 / / 分隔等写法period 记「截至日」、每店一行、granularity='snapshot'
get_periods() 返回快照标签「近30天 · 截至 11-01」并带窗口区间aggregate_period() 快照的「数据天数」按窗口长度算验证:隔离副本连续导入 11-01 / 11-02 两份快照,周期列表、聚合、窗口字段全部正确。
已确认的事实:
卡点:日期区间控件(byted-date-picker)点选不生效。
试过合成 click、真实 click、标记后 click,日格能拿到 byted-date-selected 样式但输入框值不变;
面板里存在多个 popper,疑点是我抓到的不是输入框对应的那一个。下一步需要重新定位 popper 与输入框的绑定关系。
尚未创建任何下载任务,未消耗配额。
放弃点 UI,改为抓接口。找到的统一网关:
POST https://www.life-data.cn/api/lowcode_api/query
?projectKey=shengyijing&pageId=15352&interface_id=0ac0bacd
必带的请求头(少了会返回 code -1):
content-type: application/json; charset=utf-8
root-life-account-id: 7376197379193669659
life-account-id: 7376197379193669659
x-tt-trace-log: 01
请求体(创建「按日 × 日期区间 × 全部门店」的导出任务):
{"interface_id":"0ac0bacd","data_query":{
"data_query_type":"data_query",
"indicators":[ 41 个字段,poi_id / enter_poi_cnt / verify_cert_cnt_1d / reply_rate_ratio ... ],
"filters":{"operator":"and","filters":[
{"field":{"name":"date"},"filter_operator":"between","range_values":["20261003","20261101"]},
{"field":{"name":"poi_selector_json"},"filter_operator":"equal","value":"{全部门店选择器}"},
{"field":{"name":"ignore_zero_rows"},"filter_operator":"equal","value":1}]},
"group_by":["day"],
"order_by":[{"order_field":"day","order_type":"desc"}],
"extra":{}}}
返回:{"code":0,"status":"submit_success","task_id":"431b88fb-..."}
意义:日期区间完全由我们控制,彻底绕过了点不动的日历控件。
已捕获的其他接口:
interface_id=d715cc19 —— 按 query_task_name 查任务(可用于轮询状态)interface_id=9e029004 —— 查 total / available(当日剩余建任务次数)interface_id=0ac0bacd + data_query_type=meta_query —— 拉字段元数据(64 个字段)data/inbox/测试期间创建过 2 个任务(「同步测试_1101」「报表集市_门店」),均已撤销, 下载列表恢复为原来的 2 条已完成任务。
download_link 给出,是签名 URL,有效期约 1 天sync/run_sync.sh
→ ego 打开生意经(复用登录态)
→ POST interface_id=0ac0bacd 建任务(group_by=[],日期=今天-29~今天)
→ 轮询 interface_id=d715cc19 拿 download_link
→ page.fetch 下载 xlsx 到 data/inbox/门店数据_<start>_<end>.xlsx
→ 服务端 watcher 20 秒内导入
→ 文件名里的日期区间被识别 → 按快照入库 → 周期下拉出现「近30天 · 截至 11-01」
sync/life_sync.mjs —— 同步主脚本(建任务/轮询/下载)sync/run_sync.sh —— 定时调用入口(带日志与失败标记)sync/com.yhm.lifesync.plist —— launchd 每日定时模板ingest.range_in_filename() —— 文件名带日期区间时自动按快照入库ingest 支持显式 window=(start,end) 参数第一次同步时服务还在跑旧代码,没加载窗口识别,结果:
门店数据_10月.csv 恢复月度 → 再把 xlsx 按快照重导
修复后:2026-10 月度 1409 家 + 2026-11-01 快照 1413 家,两者共存
教训:改完 ingest 必须重启服务再触发收件箱导入。配好 ~/Library/LaunchAgents/com.yhm.lifesync.plist(每天 01:00),launchctl load 成功、日历项注册成功,
但触发后报 /bin/bash: …/run_sync.sh: Operation not permitted。
逐步排查:
env -i 直接跑脚本 → 正常,不是 PATH 问题launchctl submit 探针 cd + ls → 成功,不是目录不可达head 脚本路径 → 也被拒,head=1结论:macOS TCC 保护 ~/Documents,系统级 launchd 任务读不到这个目录里的文件内容。
要绕过必须在「系统设置 → 隐私与安全性 → 完全磁盘访问」里手动把 /bin/bash 加进去。
已卸载并移除该 LaunchAgent,plist 留在 sync/ 下作参考。
工作台服务本身是从终端启动的,具备该目录的读权限,所以把定时器放进服务里:
server.daily_sync_loop() 线程:算到下一个 01:00,睡到点再跑POST /api/sync 手动触发,GET /api/sync/status 查状态last_sync 时间戳存 settings 表2026-11-01(之前误记成 2026-11)-u,服务窗口能实时看到同步进度用户:「我想要的是在【指标配置】面板改数字,就会直接同步把规则逻辑一起修改」 —— 不要「代码默认值」和「库里自定义值」两套数字打架。
backend/codesync.py
write_defaults(T):把新阈值写回 rules.py 的 DEFAULT_THRESHOLDS
只替换已有键的「值」,保留键名/缩进/行尾注释/其它代码;原子写入 + 生成 .bakimportlib.reload(rules),立即生效get_factory():首次运行时把出厂值快照到 data/factory_thresholds.jsonPUT /api/thresholds → 写库 + 写回代码POST /api/thresholds/reset → 回到出厂值 + 同样写回代码gen_editable_map.py 里所有判定条件中的数字改为从 rules.DEFAULT_THRESHOLDS 读取,
改阈值后重新生成,图上文字自动同步(不再写死「≥30 张」)。
"score_base": 4.34.3、"pend_high": 3030,
直接把 rules.py 写坏。靠事前的 .bak 备份恢复。
教训:改源码的函数必须先在隔离副本里跑一遍,并且必须校验语法(ast.parse)再落盘。~/Library/Caches/com.apple.python,
不在项目里的 __pycache__。删 __pycache__ 无效,同尺寸同秒的改动会被旧字节码顶掉
(现象:源码是 10,导入却是 15)。已在 write_defaults 里主动清理该缓存路径。连续 PUT 12 → 10 → 8 → 10,每次都用一个新进程从磁盘重新导入核对: 代码默认值、服务内默认值、源码三者完全一致。
用户反馈「排版太紧凑,字体也小」,同时问「滚动加载会不会每次刷新渲染太多」。
量了一下发现真正的问题不是渲染,是每次刷新要传 6.4 MB: 同一份问题数据被下发了 4 遍(problems / flags / primary / secondary / actions)。
{k,t,l,v,c,p,a,x},文案由前端按当前模板渲染flags / primary / secondary / actions / cats(都可从前端派生)decorateStore():加载后一次性还原成原来的结构,其余代码零改动/api/export.csv 改为按模板现场渲染(内容与原来一致)tier / problems 派生.wrap 最大宽 1320 → 1400,两侧留白加大FIRST_ROWS=20、STEP_ROWS=20、MAX_ROWS=300用户反馈滚动加载不合适,改回分页:
PAGE_SIZE = 10,恢复上一页/下一页,状态行「第 1 / 142 页」loadMore / nearBottom / 滚动监听 / 300 条 DOM 上限#fsort 只选字段(严重度/核销金额/评分/核销率/访问量),#forder 选方向(倒序/正序)原来只有 评分/核销率/核销金额/退单率,漏了 R 级违规里的投诉率、差评率,也看不到流量和履约。
新增 6 列:访问 / 客资(意向成交券)/ 待核销 / 投诉率 / 差评率 / 回复率
配套调整:
#b5563a,未达标金 #c8952c),扫一眼就能定位.tablewrap{overflow-x:auto} 兜底,当前 1274px < 容器 1292px,不出现横向滚动条用户:「门店页成交券数,现在统一改成这个。包括核销率」 —— 原来看板上的「客资」列其实是门店意向成交券数(intend_deal),用户要的是门店页成交券数(deal)。
| 位置 | 原来 | 现在 |
|---|---|---|
| 核销率 | 核销券 ÷ 意向成交券 | 核销券 ÷ 门店页成交券数 |
| 待核销 | 意向成交券 − 核销券 | 成交券数 − 核销券 |
| 零成交判定 | 意向成交券 = 0 | 成交券数 = 0 |
| 成交未核销 | 意向成交券 > 0 且 核销 = 0 | 成交券数 > 0 且 核销 = 0 |
| 核销率样本门槛 | 意向成交券 ≥ 5 | 成交券数 ≥ 5 |
| 转化率分子 | 成交券数 | 不变(本来就是这个) |
| 返单/投诉率加权 | 按意向成交券加权 | 按成交券数加权 |
| 文案变量 {客资} {成交券数} | 意向成交券数 | 门店页成交券数 |
| 表格「客资」列 | 意向成交券数 | 改名「成交券」,取成交券数 |
| 报告数据卡 | 门店意向成交券数 | 门店页成交券数 |
{门店意向成交券数} 通过别名仍然有效,但取的是成交券数的值