b2a528eba1
pricebot comparison_results[].rank 是纯价格排序(含缺菜店), server 派生 best 若照单全收, 会把缺菜(漏菜)店的虚低总价当 best_price → 记录页戴"最低"红框 + 算出虚假省额。 - _derive / _derive_from_results 派生 best 时按 platform_results[pid]. skipped_dish_count 排除缺菜店; 源平台永远全菜, 全目标缺菜时回落到源 (is_source_best、saved=0), 不虚报省额。 - platform_results 内层结构宽松(老客户端透传可伪造), _is_short 用 isinstance 兜底, 值非 dict 时按"不缺菜"处理, 不打 500。 - harvest_done 传入 done_params.platform_results; 不传→纯 rank/price 老行为不变。 - 新增纯函数测试: 排除缺菜 / 全缺菜回落源 / 不传保持老行为 / 内层非 dict 不崩, 覆盖 _derive 与 _derive_from_results 两条路径。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: guke <guke@autohome.com.cn> Reviewed-on: #176
103 lines
4.9 KiB
Python
103 lines
4.9 KiB
Python
"""_derive_from_results / _derive: 缺菜(漏菜)店总价虚低, 不当记录级"最低价"。
|
|
|
|
回归: pricebot comparison_results[].rank 是纯价格排序(含缺菜), server 派生 best 若照单全收,
|
|
会把缺菜店的虚低价当 best_price → 记录页戴"最低"红框 + 虚假省额。
|
|
修复: 派生 best 时按 platform_results[pid].skipped_dish_count 排除缺菜店(源平台永远全菜, 仍可当 best)。
|
|
纯函数, 不碰 DB。
|
|
"""
|
|
from app.repositories.comparison import _derive, _derive_from_results
|
|
from app.schemas.compare_record import ComparisonRecordIn, ComparisonResultIn
|
|
|
|
|
|
def test_derive_from_results_excludes_short_ordered_from_best():
|
|
# jd 缺 2 道菜 → 虚低 ¥25(rank=1); tb 全有 ¥38.5; 源美团 ¥42。best 应是 tb(干净最便宜), 不是 jd。
|
|
results = [
|
|
{"platform_id": "meituan", "platform_name": "美团", "price": 42.0, "is_source": True, "rank": 3},
|
|
{"platform_id": "jd_waimai", "platform_name": "京东外卖", "price": 25.0, "is_source": False, "rank": 1},
|
|
{"platform_id": "taobao_flash", "platform_name": "淘宝闪购", "price": 38.5, "is_source": False, "rank": 2},
|
|
]
|
|
platform_results = {
|
|
"jd_waimai": {"skipped_dish_count": 2},
|
|
"taobao_flash": {"skipped_dish_count": 0},
|
|
"meituan": {"is_source": True},
|
|
}
|
|
d = _derive_from_results(results, platform_results)
|
|
assert d["best_platform_id"] == "taobao_flash"
|
|
assert d["best_price_cents"] == 3850
|
|
assert d["saved_amount_cents"] == 4200 - 3850 # 350, 用干净店算省额, 不是缺菜虚低价 42-25=17元
|
|
assert d["is_source_best"] is False
|
|
|
|
|
|
def test_derive_from_results_all_targets_short_falls_back_to_source():
|
|
# 唯一比源便宜的都是缺菜 → 不crown缺菜店; 源全菜 → best=源, is_source_best, 不虚报省额。
|
|
results = [
|
|
{"platform_id": "meituan", "price": 42.0, "is_source": True, "rank": 2},
|
|
{"platform_id": "jd_waimai", "price": 25.0, "is_source": False, "rank": 1},
|
|
]
|
|
platform_results = {"jd_waimai": {"skipped_dish_count": 3}}
|
|
d = _derive_from_results(results, platform_results)
|
|
assert d["best_platform_id"] == "meituan"
|
|
assert d["is_source_best"] is True
|
|
assert d["saved_amount_cents"] == 0
|
|
|
|
|
|
def test_derive_from_results_no_platform_results_keeps_old_behavior():
|
|
# 不传 platform_results(老 harvest / 无缺菜信息)→ 行为不变: 纯 rank/price 选 best。
|
|
results = [
|
|
{"platform_id": "meituan", "price": 42.0, "is_source": True, "rank": 2},
|
|
{"platform_id": "jd_waimai", "price": 25.0, "is_source": False, "rank": 1},
|
|
]
|
|
d = _derive_from_results(results)
|
|
assert d["best_platform_id"] == "jd_waimai"
|
|
assert d["best_price_cents"] == 2500
|
|
|
|
|
|
def test_derive_from_results_malformed_platform_results_no_crash():
|
|
# 内层值非 dict(异常/伪造上报)→ 不抛 AttributeError, 按"不缺菜"处理, 照常选最便宜。
|
|
results = [
|
|
{"platform_id": "meituan", "price": 42.0, "is_source": True, "rank": 2},
|
|
{"platform_id": "jd_waimai", "price": 25.0, "is_source": False, "rank": 1},
|
|
]
|
|
d = _derive_from_results(results, {"jd_waimai": "oops"})
|
|
assert d["best_platform_id"] == "jd_waimai"
|
|
assert d["best_price_cents"] == 2500
|
|
|
|
|
|
def test_derive_pydantic_excludes_short():
|
|
# _derive(老客户端 POST 路径)同样排除缺菜店: jd 缺菜虚低 ¥25 不当 best, 取干净的淘宝 ¥38.5。
|
|
payload = ComparisonRecordIn(
|
|
trace_id="t-short-pyd",
|
|
source_price=42.0,
|
|
source_platform_id="meituan",
|
|
comparison_results=[
|
|
ComparisonResultIn(platform_id="meituan", platform_name="美团", price=42.0, is_source=True, rank=3),
|
|
ComparisonResultIn(platform_id="jd_waimai", platform_name="京东外卖", price=25.0, is_source=False, rank=1),
|
|
ComparisonResultIn(platform_id="taobao_flash", platform_name="淘宝闪购", price=38.5, is_source=False, rank=2),
|
|
],
|
|
platform_results={
|
|
"jd_waimai": {"skipped_dish_count": 2},
|
|
"taobao_flash": {"skipped_dish_count": 0},
|
|
},
|
|
)
|
|
d = _derive(payload)
|
|
assert d["best_platform_id"] == "taobao_flash"
|
|
assert d["best_price_cents"] == 3850
|
|
assert d["saved_amount_cents"] == 4200 - 3850
|
|
assert d["is_source_best"] is False
|
|
|
|
|
|
def test_derive_pydantic_malformed_platform_results_no_crash():
|
|
# _derive 的 platform_results 来自老客户端透传(可伪造): 内层非 dict 不应打 500。
|
|
payload = ComparisonRecordIn(
|
|
trace_id="t-malformed-pyd",
|
|
source_price=42.0,
|
|
comparison_results=[
|
|
ComparisonResultIn(platform_id="meituan", price=42.0, is_source=True, rank=2),
|
|
ComparisonResultIn(platform_id="jd_waimai", price=25.0, is_source=False, rank=1),
|
|
],
|
|
platform_results={"jd_waimai": "oops"},
|
|
)
|
|
d = _derive(payload) # 不抛 AttributeError
|
|
assert d["best_platform_id"] == "jd_waimai"
|
|
assert d["best_price_cents"] == 2500
|