AutoRpa/docs/superpowers/specs/2026-10-02-selection-split-...

13 KiB
Raw Blame History

选品:跨平台仓序 + 拆量落表

文档属性 内容
日期 2026-10-02
状态 已锁定,本轮实施依据
范围 选品流程/ 规则、班次判定、表 2 落盘。不加购,不推接口 2
替代 选品流程/README.md 规则节;口径文档 3.1 / 3.3 中「单仓满量、两边都写满量」的作业语义
不变 接口 1 收件、表结构、查询键(条形码)、两平台登录/搜索、接口 2 报文形态

一张 omsId 一个品种。status 仍是该品种整单采购状态。


1. 要做成什么

从表 1 领一行,两个商城都搜,合并候选后统一判定:

  1. 硬过滤:自营仓、毛利 ≥ 4%、效期 ≥ 12 个月。库存不再是过滤项。
  2. 按四级仓序(同仓低价、再高库存)排序。
  3. 同一平台同一仓的多个 sku 都参与拆量,不再只留低价那条。
  4. 按序贪心拆量,直到计划采购量凑齐。
  5. 凑齐:每条拆量一行写入表 2,status=1,数量是该条拆到的件数,合计等于计划量。
  6. 凑不齐:不写成功行,表 2 status=2,表 1 deal_flag=2。

完成标准:选品流程 单测全绿;用内存仓库能演示「杭州 3 + 广东 7 = 计划 10」落两行,以及「合计 9 < 10」整单失败且成功行为 0。


2. 不做什么

  • 真加购、清空购物车、页面点击
  • 接口 2 推送(push_status 仍为 0)
  • 批准文号搜索、商家名模糊匹配
  • 中包装 / 起订量
  • 改表结构、改接口 1

3. 已锁定决策

项 决定
毛利 (salePrice - 平台价) / salePrice >= 0.04,恰好 4% 过
拆量 跨仓跨平台贪心拆。凑不齐一件都不落成功行
仓序 见第 4 节全称表。级内顺序即距离顺序
湖南壹号、甘肃壹号云 移出自营集合
同一平台同一仓 只留 1 条(低价,再高库存)。余量给下一仓
multi_platform_flag 实际拆到两个 platform_type 时为 1;同一平台拆两仓为 0
一边故障、另一边单独凑齐 按成功落,不写故障平台失败行
一边故障、剩下的凑不齐 系统故障(可把 deal_flag 改回 0 重跑),禁止写成库存不足
两边都搜到、合计不够 业务失败「库存不足」,该 omsId 不重跑选品
查询键 仍是条形码

4. 自营仓全称与顺序

商家名去掉 [自营]、压缩空白后必须等于下表某一行。对不上 = 非自营,淘汰。

序 级 平台 全称
0 1 药帮忙 杭州小药药自营旗舰店
1 1 药帮忙 长沙小药药自营旗舰店
2 1 药帮忙 湖北小药药自营旗舰店
3 2 1号药城 广东壹号药业有限公司
4 2 1号药城 昆山亿方药业有限公司
5 2 1号药城 湖北亿昊药业有限公司
6 3 药帮忙 济南小药药自营旗舰店
7 3 1号药城 天津壹号药业有限公司
8 3 1号药城 重庆亿昊药业有限公司
9 4 1号药城 辽宁亿昊药业有限公司
10 4 1号药城 陕西壹号药业有限公司
11 4 1号药城 河南壹号医药有限公司
12 4 1号药城 重庆壹号医药有限公司
13 4 药帮忙 重庆小药药自营旗舰店

删除 WAREHOUSE_ALIASES 里甘肃那条。平台字段与名单不一致(例如 1 号药城搜出杭州小药药)仍按商家全称定序,不按搜索来源改序。


5. 算法

计划量 qty = int(plan_purchase_qty)。qty <= 0 仍走现有 NO_QTY。库存按件向下取整后再拆。

5.1 过滤(单条候选)

evaluate_candidate 不再看库存。淘汰原因仅:

  • NOT_SELF_WAREHOUSE
  • PRICE_UNPARSEABLE
  • MARGIN_LT_4PCT(原 MARGIN_LT_5PCT)
  • EXPIRY_UNPARSEABLE / EXPIRY_LT_12M
  • NO_SALE_PRICE(采购行级,先于搜品)

库存为 None 或向下取整后 <= 0:可以过过滤,但拆量时当作 0,拆不到量。

5.2 同仓

不再按 (mall_name, vendor_norm) 折成一条。过过滤的 sku 全部进入拆量。同仓低价先吃,不够再吃同仓下一条;仓序仍优先于跨仓低价。不再产生 SAME_WAREHOUSE_NOT_BEST。

5.3 排序

(WAREHOUSE_RANK[vendor_norm], price, -stock)。price 缺失视为 +inf(过滤阶段已淘汰,排序不应再见到)。

5.4 拆量

remaining = qty
lines = []
for candidate in sorted_passed:
    take = min(int(stock), remaining)
    if take <= 0:
        continue
    lines.append(Allocation(candidate, take))
    remaining -= take
    if remaining == 0:
        break

库存 None 时 take 当 0,不要对 None 做 int()。

remaining == 0 → 成功,lines 即为表 2 成功行。
remaining > 0 → 见第 6 节,丢弃 lines,不落成功行。

禁止:先落部分成功行再标失败。


6. 班次:先搜齐再判定

job.run_batch 对一行先跑完本次启用的全部平台搜索,再调用一次统一判定。禁止再「每平台独立选仓、独立满量写入」。

每个平台的搜索结果是三种之一:

结果 含义
candidates 搜到 0..n 条(空列表 = 无命中,不是故障)
LOGIN_FAILED:... 本平台本班不可用
QUERY_FAILED:... 本行本平台查询异常

无条形码:两个平台都不搜,业务失败 NO_BAR_CODE(与现在相同)。

统一判定:

  1. 采购行缺销售价 / 数量:SKIPPED,不搜也行(保持现有,在搜之前拦)。
  2. 把所有成功搜到的 candidates 合并,走第 5 节。
  3. 凑齐:成功。故障平台的错误忽略,不写该平台失败行。
  4. 凑不齐,且存在任一平台 LOGIN_FAILED / QUERY_FAILED:整单 系统故障。
  5. 凑不齐,且无系统故障:业务失败。

只跑 --platform yaoex 时,可用候选只有 1 号药城。凑不齐且该平台无故障 → 业务失败;该平台故障 → 系统故障。


7. 落表

成功行(每条 Allocation 一行):

列 值
operate_type 1
oms_id 本行
platform_type 48 药帮忙 / 66 1号药城
supplier_name vendor_raw
bar_code / org_code 原样
purchase_price 该候选平台价,四位小数
purchase_qty 该条 take,不是计划全量
purchase_total 单价 × 该条数量,四位小数
status 1
fail_reason null
push_status 0
multi_platform_flag 本单成功行里出现 ≥2 个不同 platform_type 则为 1,否则 0

表 1:deal_flag=1,deal_result 形如 选品成功:药帮忙、1号药城(按成功行出现的商城去重,顺序:药帮忙、1号药城)。

业务失败:每个已启用且已判定的平台落一行 status=2,purchase_qty 仍用计划量(造数校验:数量 > 0),purchase_price 仍用销售价兜底。supplier_name 仍为「无符合规则供应商」。multi_platform_flag 按失败行平台数(≥2 个平台则为 1)。

业务失败原因(fail_reason / deal_result)。先看过滤后是否留下可拆候选(含库存为 0。以过过滤的集合为准):

  1. NO_BAR_CODE / NO_SALE_PRICE / NO_QTY:沿用现有中文
  2. 过过滤集合非空,但拆不满:一律 无符合规则商品,库存不足。rejected 里即使同时有非自营、效期、毛利淘汰,也不改这条。
  3. 过过滤集合为空时,才按候选清单(含淘汰原因)往下看,取第一条能成立的:
    • 含 MARGIN_LT_4PCT:无符合规则商品,毛利不足 4%
    • 含 EXPIRY_LT_12M / EXPIRY_UNPARSEABLE:无符合规则商品,效期不足 12 个月
    • 含 NOT_SELF_WAREHOUSE:无符合规则商品,非自营仓
    • 全部无命中:无符合规则商品,平台无命中
    • 其它:无符合规则商品

第 9 节必测补一条:杭州自营库存 4 + 若干非自营/效期短淘汰、计划 10 → 原因必须是库存不足,不能是非自营或效期。

系统故障:每个启用平台一行 status=2,原因带 系统故障:。有故障的平台写该平台错误;无故障但被连带失败的平台写 系统故障:对端平台不可用,合计库存不足。表 1 deal_result 以 系统故障: 开头。SelectionResult.mall_errors 按平台给出原始错误串,persist 据此写各行,禁止所有失败行共用一句。

JobSummary 按表 2 行计:added = status=1 行数,skipped = 业务失败行数,failed = 系统故障行数。


8. 模块怎么改

文件 改什么
rules.py 新名单;MIN_MARGIN=0.04;evaluate_candidate 去掉库存;新增 allocate()(过滤+同仓全留+排序+拆量);select_warehouse 删除或改为调用 allocate 后取第一仓——禁止再作为班次入口
pipeline.py 新增 run_selection(purchase, mall_outcomes),一次产出整单结果。run_one_platform 只留给单测过渡,班次不再调用
job.py 先收集各平台搜索结果,再 run_selection
persist.py 吃整单结果:成功按拆量多行;multi_platform_flag 按平台种类数;失败原因 5% → 4%,并增加库存不足
README.md 规则节换成本文摘要
资料/RPA订单中心接口开发口径_20260930.md 3.1 改为毛利 4%、库存改为拆量;3.2 示例「毛利不足 5%」改为 4%;3.3 「只凑到一部分」改为整单失败、不落部分成功
资料/采购平台选品条件优先项_20260902.txt 文首加「已被 2026-10-02 spec 替代」;不改历史原文

搜索、登录、repo.py、表 DDL 不动。

建议的数据结构(名称可改,语义不要改):

@dataclass
class MallOutcome:
    mall_name: str
    candidates: list[Candidate] | None = None
    failed_reason: str | None = None  # LOGIN_FAILED: / QUERY_FAILED:

@dataclass
class Allocation:
    candidate: Candidate
    qty: int

@dataclass
class SelectionResult:
    kind: str  # SUCCESS / BUSINESS_FAIL / SYSTEM_FAIL
    reason: str  # SELECTED / STOCK_INSUFFICIENT / NO_HIT / LOGIN_FAILED:... 等
    allocations: list[Allocation]
    rejected: list[Rejected]
    malls: list[str]  # 本行启用过的平台,落失败行用
    mall_errors: dict[str, str]  # 平台 → LOGIN_FAILED:... / QUERY_FAILED:...

reason 在 run_selection / allocate 里用过过滤集合算完。persist 只做中文映射,禁止从 rejected 反推库存不足(库存为 0 的过过滤候选不在 rejected 里)。

kind 决定落表,不要用「有没有 FAILED 决策」去猜:

  • SUCCESS:allocations 合计数量必须等于 int(qty);表 1 deal_flag=1,表 2 只写成功行
  • BUSINESS_FAIL:allocations 必须为空;表 1 deal_flag=2,原因无「系统故障」前缀,该 omsId 不重跑
  • SYSTEM_FAIL:allocations 必须为空;表 1 deal_flag=2,deal_result 以 系统故障: 开头

pipeline 的展示字段若仍用 结论=ADDED/SKIPPED/FAILED,映射为 SUCCESS→ADDED、BUSINESS_FAIL→SKIPPED、SYSTEM_FAIL→FAILED。persist 只认 kind。

成功行 platform_type 按该候选的 mall_name(搜索来源)映射 48/66,仓序只按商家全称。第 4 节「不按搜索来源改序」只约束排序,不改 platform_type。


9. 必须覆盖的行为(单测)

规则:

  • 毛利恰好 4% 过;3.9% 淘汰(销售价 100、平台价 96 过,96.1 淘汰)
  • 杭州小药药优先于广东壹号
  • 湖南壹号、甘肃壹号云视为非自营
  • 近仓 3、次仓 20、计划 10 → 近仓 3 + 次仓 7
  • 合计 9、计划 10 → 无 allocation
  • 同一仓两 SKU(价 90 库存 5、价 92 库存 100)、计划 10 → 价 90 拿 5,价 92 拿 5,不把余量给下一仓
  • 库存 3.9 按 3 件拆

班次 / 落表:

  • 两平台拆开:药帮忙 3 + 1 号药城 7,两行 status=1,数量分别为 3 和 7,multi_platform_flag=1,表 1 deal_flag=1
  • 只在药帮忙拆两仓:两行、同一 platform_type=48、multi_platform_flag=0
  • 药帮忙登录失败、广东壹号库存 20、计划 10 → 只落 1 号药城成功一行
  • 药帮忙登录失败、1 号药城库存 4、计划 10 → 系统故障,无成功行
  • 两边都搜到、合计库存 4、计划 10 → 业务失败「库存不足」,无成功行
  • 杭州自营库存 4 + 路边店/效期短淘汰、计划 10 → 业务失败「库存不足」(不是非自营、不是效期)
  • 无条形码仍不搜

10. 风险

  • 搜到落表之间库存会变。这期只落判定,真加购时再处理。
  • 中包装可能导致拆出的件数加不进车。真加购时再对。
  • 订单中心若假定「一个 omsId + 一个 platform_type 只有一行」,同一平台拆两仓会撞上。表结构允许多行;联调时若对方拒,再改成同平台合一行(本期不合)。