| name | fbo-retry |
| description | Ozon FBO 未发清单查询 + 生成重试 plan (list_pending.py). 当用户说"查未发"、"看 pending"、"哪些集群还没发"、"把未发的改派到其他集群"、"重试失败的"、"生成 retry plan"、"未发到哪些集群了"、"矩阵放开了重试"时触发。支持 by-cluster/by-sku/by-reason 分组, 过滤, 改派, 一键生成新 plan.json 灌回 `/fbo-plan`. |
FBO 未发清单查询 + 重试
从 ledger 台账提炼当前未发清单, 支持按集群/SKU/原因分组查看, 可直接改派或生成新 plan.json 让 /fbo-plan 重试.
脚本: /Users/mac/Documents/ozns/丝绸生活/海外仓发货表/2026-04-23海外仓发货/list_pending.py (~180 行)
触发场景
- "看一下还有哪些未发"
- "哪些 SKU 还没发 / 哪些集群还没发完"
- "把远东的未发改派到莫斯科"
- "把新西伯利亚的 Q_MeiGongDao 1800 件生成个 retry plan"
- "按原因看 — 矩阵过滤的 vs 业务拒绝的"
- "矩阵应该松了, 重试那批"
工作原理
两个数据源 (2026-04-25 起默认从 4181 plans 算):
--from-approved (默认 ON): 4181 service /plans?status=approved/dispatched 的 cluster→SKU→件数 cap_total, 减去 Ozon /v1/supply-order/list+bundle 的 active_total (该 SKU 全集群已发件数)
--from-ledger: 老逻辑 — 扫 ledger/shipment_ledger_*.json skipped 记录
per-SKU 总量算法 (2026-04-25 fix, 处理改派抵扣):
对每个 offer_id:
cap_total = sum(4181 plans 该 SKU 各 cluster 件数)
active_total = sum(Ozon 实发该 SKU 各 cluster 件数, 含 4181 计划外的 "改派目标" cluster)
if active_total >= cap_total: 整 SKU 跳过 (含改派抵扣)
else: per-cluster gap, 上限 remaining=cap_total-active_total
旧 per-cluster 算法不识别改派 (远东 600 → Москва 后, 远东 cap 600 - active 0 = 误报 600 仍 pending). 算法落地后 7 集群 6300 件 → 6 集群 5700 件 (远东 Q_ChongQiZui 自动消解).
bundle retry-with-backoff (2026-04-25): _post() 包 3 次 backoff (2/4/6s) 防止 cron 偶发 /v1/supply-order/bundle timeout 漏算 active.
输出:
- 平铺列表 / 按集群 / 按 SKU / 按原因分组
- 可选: 改派 (源集群→新集群) + 生成新 plan.json 喂回
create_fbo_plan.py
常用用法
工作目录统一:
cd /Users/mac/Documents/ozns/丝绸生活/海外仓发货表/2026-04-23海外仓发货
1. 看所有未发 (平铺)
python3 list_pending.py
2. 按集群分组 (最常用)
python3 list_pending.py --by-cluster
3. 按 SKU 分组 (看哪个 SKU 发不动)
python3 list_pending.py --by-sku
4. 按原因分组 (MATRIX_DROPPED_FROM_SUPPLY / NO_AVAILABLE_WAREHOUSE / DRAFT_OR_SUPPLY_FAILED)
python3 list_pending.py --by-reason
5. 过滤: 只看某原因
python3 list_pending.py --filter-reason MATRIX_DROPPED_FROM_SUPPLY
python3 list_pending.py --filter-reason NO_AVAILABLE_WAREHOUSE
6. 过滤: 只看某集群 (substring 匹配)
python3 list_pending.py --filter-cluster Новосибирск
python3 list_pending.py --filter-cluster "Дальний"
7. 改派: 把某集群的未发改发到其他集群
python3 list_pending.py --reallocate "Дальний Восток=Москва,Новосибирск=Казань"
语法: 源集群=新集群,源2=新2. 会改 cluster 字段但不影响 ledger.
⚠️ --reallocate 参数 bug: 集群名含 , (例如 "Москва, МО и Дальние регионы") 会被 split 截断成 "Москва". 解决: 直接手工写 plan.json 不走 --reallocate, 或换其他不含逗号的集群名 (如 Москва).
改派 Москва 是矩阵拒/限流挡集群件数的安全出口 (2026-04-25 实测):
- 远东 Q_ChongQiZui 600 →
Москва, МО и Дальние регионы 全 600 件实接 (无 trim)
- 跟 Саратов/Казань
Q_MeiGongDao 跨多轮 hard cap ~52-58 件强烈对比 — Москва 头部集群 reserved 多
- 改派后跑算法: per-SKU 总量自动抵扣, 远东 cap 600 vs active 0 但 Москва active 600 → 整 SKU
cap_total == active_total 跳过
8. 生成重试 plan.json (核心场景)
python3 list_pending.py --emit-plan retry.json
python3 create_fbo_plan.py --config retry.json --box-size 300 --account 丝绸生活
9. 改派 + 生成 plan (一气呵成)
python3 list_pending.py \
--reallocate "Дальний Восток=Москва" \
--emit-plan retry_moscow.json
10. 只重试矩阵过滤的那一批
python3 list_pending.py \
--filter-reason MATRIX_DROPPED_FROM_SUPPLY \
--emit-plan retry_matrix.json
11. 自定义 drop-off / source (默认 ЩЕРБИНКА / ЖУКОВСКИЙ_РФЦ)
python3 list_pending.py \
--emit-plan retry.json \
--drop-off-keyword "КАЗАНЬ_ХАБ" \
--source-warehouse-keyword "ЖУКОВСКИЙ_РФЦ"
典型工作流
A. 本次 run 完, 看剩多少没发 → 决定等等还是改派
python3 list_pending.py --by-reason
B. 某集群几天后解锁, 重发那批
python3 list_pending.py --filter-cluster Новосибирск --emit-plan retry_nsk.json
python3 create_fbo_plan.py --config retry_nsk.json --box-size 300 --account 丝绸生活
C. 业务决定某集群死活发不动 → 改到其他集群
python3 list_pending.py --reallocate "Дальний Восток=Москва" --emit-plan retry_moscow.json
python3 create_fbo_plan.py --config retry_moscow.json --box-size 300 --account 丝绸生活
输出示例 (by-cluster)
=== pending 3 条 (过滤后) ===
[Дальний Восток] (2 条)
Q_ChongQiZui-HuangSe-Free (3214934161) × 300 — NO_AVAILABLE_WAREHOUSE
Q_MeiGongDao-HeiSe-Free (3214793665) × 900 — NO_AVAILABLE_WAREHOUSE
[Новосибирск] (1 条)
Q_MeiGongDao-HeiSe-Free (3214793665) × 1800 — NO_AVAILABLE_WAREHOUSE
reason_code 枚举 (ledger)
MATRIX_DROPPED_FROM_SUPPLY — Ozon 建 supply 时静默过滤 SKU, supply 实际不含
NO_AVAILABLE_WAREHOUSE — timeslot/info NotFound "can't find available warehouse scoring result" (业务矩阵全拒)
DRAFT_OR_SUPPLY_FAILED — 其他 draft/supply 建单失败 (伴原始错误)
注意
--emit-plan 产物可直接喂 create_fbo_plan.py, 无需手改.
- 重试前可以先 dry-run 看可发性:
create_fbo_plan.py --config retry.json ... --dry-run.
- pending 不是"永远发不掉", 只是该 run 当时发不掉. 业务矩阵松开可重发, 海外仓清了也可重发.
- 改派后原 ledger.skipped 记录不动, 仅当次 emit 的 plan.json 里集群被替换.
每小时 cron retry 工作流 (本 session 验证)
矩阵拒/route 拒集群没法手工干预, 但 Ozon 矩阵动态调整, 24h+ 后可能放开. 配 cron 每小时 retry, 新放开的集群自动建单 + 填箱 + 拉 PDF.
/loop 1h "每小时 retry FBO 失败集群. 步骤: 1) 健康检查 4182+4181; 2) python3 list_pending.py --emit-plan /tmp/retry_hourly.json; 3) 空就 push '清空' 退出; 4) 后台跑 create_fbo_plan; 5) Monitor 直到 PID 退出; 6) 解析 ledger, shipped>0 push, 全 skipped 矩阵拒不 push (避免半夜骚扰); 7) 不要问用户, 直接执行."
实测节奏 (本 session 跨 16+ 轮):
- 第 6 轮 Саратов 突破 (52 件 → 600 件全接) — capacity 是动态的
- 第 N 轮 Казань 矩阵开 → 600 件实接 (FULL_AVAILABLE)
- Махачкала/Ярославль/Воронеж 持续 NOT_AVAILABLE_MATRIX (24+ 小时仍拒, 等更久)
- Новосибирск NOT_AVAILABLE_ROUTE (路由禁, 比矩阵更顽固)
12h 短间隔实测无效, 至少 24h+ 才有意义 (feedback_multicluster_matrix_short_circuit.md).
同步本 skill 的 .py 文件
工作目录是真理之源. 修改 list_pending.py / cancel_supply.py 后:
bash /Users/mac/.claude/skills/sync_fbo_scripts.sh
bash /Users/mac/.claude/skills/sync_fbo_scripts.sh --check
相关 skill / memory
/fbo-plan — 跑 plan.json (emit 完直接喂它)
/fbo-status — 看已发/未发全景, 不只是 pending
- memory
reference_shipment_ledger.md — ledger + pending 去重规则详解
- memory
reference_create_fbo_plan_sop.md — SOP 6 步, 理解为什么会产生哪种 reason_code
- memory
feedback_reallocate_to_moscow_capacity.md — 改派 Москва 安全出口 + per-SKU 总量算法
- memory
feedback_list_pending_bundle_retry.md — bundle 必须 retry-with-backoff
- memory
feedback_multicluster_matrix_short_circuit.md — 矩阵拒不 fallback + 24h+ retry 才有效