核心事实先钉住:Back Market 官方成色是 Premium / Excellent / Good / Fair,且“所有等级都必须 100% 功能正常、电池至少 80%(Premium 90%)”;成色只描述外观,功能不一致就是违反 Quality Charter。 行业里 A/B/C 只是内部口径,“A 标成 B 卖”叫 undergrade(亏钱),“B 标成 A 卖”叫 overgrading(退货+搜索降权+匿名测试单翻车)。 所以 ERP 要做的不是“传个性别字段”,而是入库质检 → 内部等级 → 平台等级 → 文案/图片/电池/维修记录一致性 → 发布前 A/B 校验 → 售后退货回流再校准的闭环。
🔁《跨境二手商品等级描述不符触发自动下架:ERP 上传数据的 A/B 级校验闭环》(附 Python 源码)
一、惨案公式(为什么 A/B 级会搞死店铺)
内部QC说: "屏幕有发丝划痕, 算 B" 运营图省事: "标 A 好卖, 价格+8%" 买家收到: "A 应该有几乎无划痕" 平台机制: 描述外观 ≠ 实物 → 退货/客诉 → 退货率↑ → 算法降权/下架/匿名测试单 → 账户风险
Back Market 自己放匿名测试单(anonymous test orders),卖家自己都分不清是不是真买家;A 级设备如果屏幕有划痕,本身就是违约。
行业术语:
Overgrading = 等级往高标(最烧钱:退货+逆向物流+搜索降权)
Grading drift = 技师久了越来越松(不是故意骗,但结果一样)
Undergrading = 往低标(亏毛利,不违规)
ERP 的活:让“标出去的等级”永远 ≤ “实物检出来的等级”。
二、统一等级模型(内部 A/B/C ↔ 平台 Premium/Excellent/Good/Fair)
内部 外观标准 电池 平台(Back Market) 抖音/闲鱼 A / LikeNew 无划痕/无磕碰/近乎新 ≥90% Premium/Excellent 99新/准新 B / Good 微划痕/轻微磨损/不影响功能 ≥80% Good 95新/正常使用 C / Fair 可见划痕/磕碰/掉漆/轻凹 ≥80% Fair 9新/明显使用 D / Parts 功能缺陷/屏裂/无法正常使用 - 不允许多数翻新平台 For parts
关键点:功能不通过 = 不能当 A/B/C 卖;电池不达标 = 不能标 Premium;屏幕有划痕 = 不能标 Excellent/Premium。
这就是“A/B 级校验”的本质——多字段联合判定,不是单个下拉框。
三、完整源码:Grade Integrity Loop
# grade_integrity_loop.py
"""
跨境二手 A/B级描述不符防御闭环
- QcRecord: 入库质检(划痕/磕碰/电池/功能/维修)
- InternalGrade: 由QC推导内部等级 (A/B/C/D)
- PlatformGradeMapper: 内部等级 -> BackMarket/Mercari/Vinted/eBay 平台等级
- ListingAssertion: 发布前校验 (标称等级 <= 实物等级, 文案/图片/电池一致)
- ReturnSignalLoop: 退货原因码回流 -> 发现 overgrading / grading drift
- AutoTakedownGuard: 高危SKU禁止发布 / 已发布触发降级或下架申请
"""
from typing import Dict, List, Optional
from dataclasses import dataclass, field
from enum import Enum
# ---------------- 等级枚举 ----------------
class InternalGrade(Enum):
A = "A" # like new
B = "B" # good / light wear
C = "C" # fair / visible wear
D = "D" # parts / not functional
class BMGrade(Enum):
PREMIUM = "Premium"
EXCELLENT = "Excellent"
GOOD = "Good"
FAIR = "Fair"
# ---------------- 入库质检 ----------------
@dataclass
class QcRecord:
sku: str
screen_scratch: bool = False # 屏幕划痕
body_micro_scratch: bool = False # 机身发丝划痕
visible_scratches: bool = False # 肉眼可见划痕
dents: bool = False # 凹痕
battery_health: int = 100 # %
powers_on: bool = True
camera_ok: bool = True
wifi_ok: bool = True
repaired_parts: List[str] = field(default_factory=list) # ["screen","battery"]
qc_operator: str = ""
photos: int = 0 # 实拍角度数
def infer_internal_grade(qc: QcRecord) -> InternalGrade:
"""功能不正常 -> D; 电池/外观联合判定 A/B/C"""
if not (qc.powers_on and qc.camera_ok and qc.wifi_ok):
return InternalGrade.D
if qc.screen_scratch:
return InternalGrade.C
if qc.visible_scratches or qc.dents:
return InternalGrade.C
if qc.battery_health >= 90 and not qc.body_micro_scratch:
return InternalGrade.A
if qc.battery_health >= 80:
return InternalGrade.B
return InternalGrade.C
# ---------------- 平台映射 ----------------
class PlatformGradeMapper:
BM_MAP = {
InternalGrade.A: BMGrade.PREMIUM,
InternalGrade.B: BMGrade.GOOD,
InternalGrade.C: BMGrade.FAIR,
InternalGrade.D: None, # 不允许作为翻新可售
}
def to_backmarket(self, g: InternalGrade) -> Optional[BMGrade]:
return self.BM_MAP.get(g)
def to_label(self, platform: str, g: InternalGrade) -> str:
if platform == "backmarket":
bm = self.to_backmarket(g)
return bm.value if bm else "NOT_LISTABLE"
if platform == "generic":
return {InternalGrade.A: "99新/准新",
InternalGrade.B: "95新/正常使用",
InternalGrade.C: "9新/明显使用",
InternalGrade.D: "故障/配件机"}[g]
return g.value
# ---------------- 发布前 A/B 校验 ----------------
@dataclass
class ListingDraft:
sku: str
platform: str
claimed_grade: str # 运营填的: Premium / A / 99新 ...
title: str = ""
desc: str = ""
battery_in_desc: Optional[int] = None
photo_count: int = 0
qc: QcRecord = None
class GradeViolation(Exception):
def __init__(self, issues: List[str]):
self.issues = issues
super().__init__("; ".join(issues))
class ListingAssertion:
def __init__(self, mapper: PlatformGradeMapper):
self.mapper = mapper
def assert_listing(self, draft: ListingDraft):
qc = draft.qc
if qc is None:
raise GradeViolation(["无QC记录, 禁止发布"])
real = infer_internal_grade(qc)
real_bm = self.mapper.to_backmarket(real)
issues = []
# 1. 功能不达标却想卖可售等级
if real == InternalGrade.D:
issues.append("功能不达标(InternalGrade=D), 不允许作为翻新可售")
# 2. 标称等级高于实物等级 = overgrading
claimed_internal = self._claimed_to_internal(draft.platform, draft.claimed_grade)
if claimed_internal is not None and self._rank(claimed_internal) > self._rank(real):
issues.append(
f"overgrading: 标称 {draft.claimed_grade}({claimed_internal.value}) "
f"> 实物 {real.value}")
# 3. Back Market: Premium 必须电池>=90 且无可辨划痕
if draft.platform == "backmarket" and draft.claimed_grade == "Premium":
if qc.battery_health < 90:
issues.append(f"Premium 电池需>=90%, 实际 {qc.battery_health}%")
if qc.body_micro_scratch or qc.screen_scratch:
issues.append("Premium 不允许任何可见/发丝划痕")
# 4. 文案与电池不一致
if draft.battery_in_desc is not None and draft.battery_in_desc != qc.battery_health:
issues.append(
f"描述写电池 {draft.battery_in_desc}%, QC实测 {qc.battery_health}%")
# 5. 实拍图不足 -> 外观争议无证据
if qc.photos < 3 or draft.photo_count < 3:
issues.append("实拍图<3张, 外观争议时无法自证")
# 6. 维修记录未披露
if qc.repaired_parts and "换" not in draft.desc and "维修" not in draft.desc:
issues.append(f"换过部件 {qc.repaired_parts} 但未在描述披露")
if issues:
raise GradeViolation(issues)
return real_bm
def _claimed_to_internal(self, platform: str, claimed: str) -> Optional[InternalGrade]:
claimed = claimed.lower()
if claimed in ("premium", "a", "99新", "准新"):
return InternalGrade.A
if claimed in ("excellent", "good", "b", "95新", "正常使用"):
return InternalGrade.B
if claimed in ("fair", "c", "9新", "明显使用"):
return InternalGrade.C
if claimed in ("parts", "d", "for parts", "配件机"):
return InternalGrade.D
return None
def _rank(self, g: InternalGrade) -> int:
return {InternalGrade.A: 3, InternalGrade.B: 2,
InternalGrade.C: 1, InternalGrade.D: 0}[g]
# ---------------- 售后回流:发现 overgrading ----------------
@dataclass
class ReturnCase:
sku: str
claimed_grade: str
return_reason: str # "not_as_described", "scratch", "battery", "dead_pixel"...
qc: QcRecord = None
class ReturnSignalLoop:
def __init__(self):
self.by_sku_grade: Dict[tuple, int] = {}
def ingest(self, case: ReturnCase):
key = (case.sku, case.claimed_grade)
self.by_sku_grade[key] = self.by_sku_grade.get(key, 0) + 1
def overgrading_alert(self, threshold: int = 3) -> List[Dict]:
alerts = []
for (sku, claimed), cnt in self.by_sku_grade.items():
if cnt >= threshold:
alerts.append({
"sku": sku, "claimed": claimed,
"return_cases": cnt,
"suspect": "overgrading / grading drift",
"action": "暂停该SKU发布 + 盲检复评 + 已上架降级",
})
return alerts
# ---------------- 自动下架防御 ----------------
class AutoTakedownGuard:
def __init__(self, assertion: ListingAssertion, returns: ReturnSignalLoop):
self.assertion = assertion
self.returns = returns
def pre_publish(self, draft: ListingDraft) -> Dict:
try:
bm = self.assertion.assert_listing(draft)
return {"ok": True, "backmarket_grade": bm.value if bm else None}
except GradeViolation as e:
return {"ok": False, "block": True,
"issues": e.issues, "action": "禁止发布"}
def post_return_heal(self, draft: ListingDraft) -> Dict:
"""退货聚集后: 自动降级到实物等级重新发, 不硬撑A"""
real = infer_internal_grade(draft.qc)
bm = PlatformGradeMapper().to_backmarket(real)
return {
"action": "relist_as_real_grade",
"new_claimed_grade": real.value,
"backmarket_grade": bm.value if bm else "NOT_LISTABLE",
}
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
# ==================== DEMO ====================
if __name__ == "__main__":
mapper = PlatformGradeMapper()
assertion = ListingAssertion(mapper)
returns = ReturnSignalLoop()
guard = AutoTakedownGuard(assertion, returns)
print("=== 1. B级实物被运营标成 Premium (overgrading) ===")
qc_b = QcRecord(sku="IP13-001", body_micro_scratch=True,
battery_health=85, photos=4, repaired_parts=["battery"])
draft = ListingDraft(sku="IP13-001", platform="backmarket",
claimed_grade="Premium",
desc="准新机 电池健康 95%",
battery_in_desc=95, photo_count=4, qc=qc_b)
print(" ", guard.pre_publish(draft))
# -> block: overgrading + Premium电池不足 + 维修未披露
print("\n=== 2. 真实A级 (无划痕/电池93) 标 Premium ===")
qc_a = QcRecord(sku="IP13-002", battery_health=93, photos=5)
draft_a = ListingDraft(sku="IP13-002", platform="backmarket",
claimed_grade="Premium",
desc="无划痕 电池93% 官方级翻新",
battery_in_desc=93, photo_count=5, qc=qc_a)
print(" ", guard.pre_publish(draft_a))
# -> ok: Premium
print("\n=== 3. 售后回流: 同一SKU标A被退3次 ===")
for _ in range(3):
returns.ingest(ReturnCase("IP13-003", "A", "not_as_described",
QcRecord(screen_scratch=True, battery_health=82)))
print(" alerts:", returns.overgrading_alert())
print("\n=== 4. 自动愈合: 不再硬标A, 按实物降级 ===")
qc_c = QcRecord(sku="IP13-003", screen_scratch=True, battery_health=82, photos=3)
draft_c = ListingDraft(sku="IP13-003", platform="backmarket",
claimed_grade="A", desc="屏幕有轻微划痕 电池82%",
battery_in_desc=82, photo_count=3, qc=qc_c)
print(" ", guard.post_return_heal(draft_c))
# -> relist as C / Fair
print("\n=== 5. 功能坏机想当B卖 ===")
qc_dead = QcRecord(sku="IP13-004", powers_on=False, camera_ok=False)
draft_dead = ListingDraft(sku="IP13-004", platform="backmarket",
claimed_grade="Good", qc=qc_dead)
print(" ", guard.pre_publish(draft_dead))
# -> block: InternalGrade=D 不允许可售跑出来核心就是:
1. {'ok': False, 'block': True, 'issues': [
'overgrading: 标称 Premium(A) > 实物 B',
'Premium 电池需>=90%, 实际 85%',
'描述写电池 95%, QC实测 85%',
'换过部件 [...]/电池... 但未在描述披露'], 'action': '禁止发布'}
2. {'ok': True, 'backmarket_grade': 'Premium'}
3. alerts: [{'sku':'IP13-003','claimed':'A','return_cases':3,
'suspect':'overgrading / grading drift','action':'暂停该SKU发布+盲检复评'}]
4. {'action':'relist_as_real_grade','new_claimed_grade':'C','backmarket_grade':'Fair'}
5. {'ok': False, 'block': True, 'issues':['功能不达标(InternalGrade=D)...']}四、A/B 级校验闭环(架构图)
入库QC ──► InternalGrade(A/B/C/D) │ ├─ PlatformGradeMapper ──► BackMarket(Premium/Excellent/Good/Fair) │ └─ 抖音/闲鱼/Mercari/Vinted 本地成色词 │ ▼ ListingAssertion (发布闸门) • claimed <= real • 电池/划痕/维修/实拍图 一致性 • 平台宪章对齐 (BM: 功能全过/电池80%/Premium90%) │ ├─ 不通过 ──► 禁止发布 + 告警 + 留审计日志 └─ 通过 ──► 上架 │ ▼ ReturnCases 回流 │ ▼ ReturnSignalLoop (同SKU+同标称等级退货>=N → overgrading警报) │ ▼ AutoTakedownGuard.post_return_heal() 硬标A的 → 按实物降级为B/C 严重漂移 → 停发 + 盲检复评(反向打分)
盲检复评(reverse blind grading) 是行业里防 grading drift 最稳的办法:让另一个技师在不看原等级的情况下重新检,和原结果比对。
五、6 条铁律
标称等级永远 ≤ 实物等级:宁可 undergrade 亏毛利,别 overgrading 丢账户。
A/B/C 不是形容词,是字段组合:划痕、磕碰、电池、功能、维修记录、实拍张数,全进规则引擎。
Back Market 成色只管外观,功能必须全过:把“A 级但触屏偶尔失灵”发出去 = 违反 Quality Charter。
电池数字是高危字段:描述写 95%、QC 写 85%,平台/买家一投诉就实锤“描述不符”。
退货原因码是唯一真相源:按
SKU × 标称等级聚合,退货集中 = 等级标准漂了,不是“买家事多”。自动下架防御要双向:发布前拦错标 + 售后后自动降级;别等平台算法把店铺流量关了再救。
六、和前几篇串起来
前篇 Back Market Compliance Gate:主体/VAT/OAuth/Quality Charter 是“能不能卖”;
本篇 Grade Integrity Loop:成色 A/B 一致性是“卖得久不久”;
前篇 Vinted GPSR / Mercari 限流 / eBay ConditionGuard:都是“平台替你守买家体验,ERP 必须先把规则内建”;
前篇 ModeSelector:认证服务商模式值不值——看这种闭环要养多少质检/SOP 就懂了。
二手跨境电商的底层公式:可信等级 = QC 证据 × 平台宪章 × 描述一致性三者任一断,A 级也会变“描述不符”。
要不要我把
grade_integrity_loop.py 合进 commerce-mesh/core/:做成 GradeEngine + QcChecklistRegistry(按手机/平板/服饰/奢品挂不同清单)+ ReturnDriftDetector,并和前篇 ObservabilityMiddleware 出一张 Overgrading 风险面板(按 SKU / 技师 / 班次 / 类目拆分)?