fix: 允许颜色缺价后继续采集 (#42)

This commit is contained in:
chengma
2026-08-08 11:25:06 +08:00
parent ccc146cfbe
commit 5aaf0a937e
5 changed files with 52 additions and 25 deletions
+10 -25
View File
@@ -699,18 +699,6 @@ class PddCollectService:
self._wait_spec_panel(device)
color_dimension, color_samples = self._collect_color_prices(device)
missing_prices = [
color.text
for color in color_dimension.values
if color.available
and color_samples[color.text].price_cent is None
]
if missing_prices:
raise PddCollectError(
"PDD_DATA_PRICE_MISSING",
"以下可用颜色没有采集到稳定价格:"
+ "、".join(missing_prices),
)
size_dimension = self._collect_size_dimension(device)
dimensions = (color_dimension,)
if size_dimension is not None:
@@ -723,11 +711,11 @@ class PddCollectService:
skus = self._build_color_price_skus(
dimensions, color_samples
)
has_available_price = any(
item.price_cent is not None for item in skus if item.available
)
if not skus or not has_available_price:
raise PddCollectError("PDD_DATA_PRICE_MISSING", "没有采集到可用 SKU 的价格")
if not skus:
raise PddCollectError(
"PDD_DATA_SPEC_INCOMPLETE",
"规格面板没有生成可提交的规格组合",
)
except PddCollectError as exc:
if not exc.diagnostics and self._last_goods_xml:
artifact = self._save_xml("collect-failed", self._last_goods_xml)
@@ -1207,21 +1195,18 @@ class PddCollectService:
options = {"color": color.text}
if size is not None:
options["size"] = size.text
available = (
color.available
and (size is None or size.available)
and sample.price_cent is not None
)
# 是否可用来自规格控件状态,不能用“是否采到价格”代替。
available = color.available and (size is None or size.available)
results.append(
SkuResult(
options,
sample.price_cent if available else None,
sample.price_cent,
available,
sample.raw_price if available else None,
sample.raw_price,
{"color": color.text}
if sample.price_cent is not None
else {},
sample.list_price_cent if available else None,
sample.list_price_cent,
)
)
return tuple(results)
+33
View File
@@ -232,6 +232,13 @@ class IgnoredColorClickDevice(SnakeColorDevice):
self.clicks.append((x, y))
class AllMissingColorPriceDevice(IgnoredColorClickDevice):
"""所有颜色点击都未生效,模拟全部颜色采价为空。"""
def _spec_xml(self):
return super()._spec_xml().replace('selected="true"', 'selected="false"')
def keep_only_one_sku(xml_data: str) -> str:
"""从脱敏固件中删除蓝色和 L,只保留一个组合。"""
@@ -562,6 +569,32 @@ class PddCollectParserTest(unittest.TestCase):
self.assertIsNone(sample.price_cent)
def test_all_missing_color_prices_still_collects_sizes(self):
device = AllMissingColorPriceDevice(self.home_xml)
service = PddCollectService(
PddDeviceService(lambda _serial: device),
"USB-001",
"client-001",
sleeper=lambda _seconds: None,
)
data = service.collect(
FakeTask("https://mobile.yangkeduo.com/goods.html?goods_id=123")
).to_pdd_data()
self.assertEqual(
[item["text"] for item in data["dimensions"][1]["values"]],
["M", "L"],
)
self.assertTrue(device.sizes_visible)
self.assertTrue(data["skus"])
self.assertTrue(all(item["price_cent"] is None for item in data["skus"]))
self.assertTrue(all(item["raw_price"] is None for item in data["skus"]))
self.assertTrue(
all(item["price_observed_at"] == {} for item in data["skus"])
)
self.assertTrue(all(item["available"] for item in data["skus"]))
def test_missing_goods_id_is_structured_error(self):
service = PddCollectService(
PddDeviceService(lambda _serial: object()),
+4
View File
@@ -59,6 +59,10 @@ Client 顶级导航仅包含:
`price_observed_at` 只记录实际选中的颜色,不得虚构已选尺码。以后需要按尺码
区分价格或组合库存时,必须另行确认并改用 `price_granularity=sku`。
颜色已售罄、无法选中或未采到稳定价格时,不重试采价,也不阻断后续流程;该
颜色的价格相关字段留空。所有颜色处理完成后仍须采集尺码、保存本地结果并提交
Admin。规格 `available` 只反映页面控件状态,不能根据价格是否为空推断。
对于“1.2 万+”等近似数值,同时保存规范化数值、原始文字和近似标记。
### 4.2 采购任务
+4
View File
@@ -525,6 +525,10 @@ CREATE TABLE app_settings (
这里的 `available` 来自当前规格节点显示状态,不表示 Client 已逐个验证了每个
颜色与尺码组合的实时库存。
颜色无法选中或没有采到稳定价格时,该颜色对应组合的 `price_cent`、`raw_price`
和 `list_price_cent` 使用 `null`,`price_observed_at` 使用空对象。缺价不改变
`available`,也不阻止尺码采集、结果入库和提交。
### 8.2 采购结果
采购任务在同一结构中增加:
+1
View File
@@ -310,6 +310,7 @@ Idempotency-Key: task-id:attempt-id:result-v1
- `[必须]` Client 只有收到 `accepted: true` 后才能把本地任务标为 `succeeded`。
- `[必须]` `price_cent` 是实际支付价的整数分;划线价可放在 `list_price_cent`。
- `[必须]` `price_granularity` 只能是 `color` 或 `sku`。当前 Client 按颜色采样时填 `color`;同一颜色的尺码组合可以共享颜色价格,但 `price_observed_at` 只填写实际选中的颜色,不得虚构尺码。以后逐个选择完整组合采价时才使用 `sku`。
- `[必须]` 采集任务中,颜色无法选中或未采到稳定价格时,相关 `price_cent`、`raw_price` 和 `list_price_cent` 允许为 `null`,`price_observed_at` 允许为空对象;Admin 不得仅因部分或全部颜色缺价拒绝结果。
- `[必须]` `shop_name` 采不到时允许省略或留空;Admin 不得因此拒绝老版本 Client,也不得用空值覆盖已保存的店铺名。
### 6.1 Admin 必须无条件接受