1. 食品批次追溯编码规则设计与校验

批次编码的字段构成与解析逻辑

乳制品批次追溯要求编码在离线、打印、人工抄录等场景下仍能唯一识别一批产品。本例采用定长分段编码:总长 18 位,由生产日期、产线、班次、产品代码、当日流水号和校验位组成。定长分段的好处是无需分隔符即可按位置切片解析,适合贴在箱体标签上由扫码枪或人工录入。

各字段定义如下(示例企业约定,实际落地时需与企业编码管理制度对齐):

字段位数位置取值规则示例
生产日期81–8YYYYMMDD20240517
产线29–1001–99,灌装线编号03
班次111A 早班 / B 中班 / C 夜班B
产品代码412–15企业产品主数据中的 4 位码1027
流水号216–17同日期+产线+班次+产品下从 01 递增05
校验位118前 17 位加权模 36 校验K

校验位采用加权模 36:字符集为 0-9A-Z(共 36 个字符),对前 17 位按位置权重 1、3、7、9 循环加权求和,取模 36 后映射为字符。这样能拦截抄录错位、单字符错写等常见错误,同时保持纯字母数字、便于打印。

批次编码 20240517 03 B 1027 05 K 的字段切分与校验链 20240517 03 B 1027 05 K 生产日期 8 位 产线 2 位 班次 1 位 产品代码 4 位 流水号 2 位 校验位 1 位 取前 17 位字符 逐位映射 0-9A-Z → 0-35 加权求和 权重 1,3,7,9 循环 S mod 36 映射回字符 校验位 K 与第 18 位比对 解析输出(结构化对象) date=2024-05-17 line=03 shift=B product=1027 seq=05 check=K 唯一性键:date + line + shift + product + seq(校验位不参与唯一性判定) 校验失败处理:拒绝入库并记录原始串,不自动纠错,避免错误批次被追溯链接受

编码生成函数实现

生成函数需要解决两个问题:一是流水号在同键下递增,二是校验位计算。下面用 Python 实现,字符集与权重与上表一致,可直接运行验证。

CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
WEIGHTS = [1, 3, 7, 9]

def char_value(ch: str) -> int:
    return CHARSET.index(ch)

def check_char(payload: str) -> str:
    """payload 为前 17 位,返回 1 位校验字符"""
    total = 0
    for i, ch in enumerate(payload):
        total += char_value(ch) * WEIGHTS[i % 4]
    return CHARSET[total % 36]

def build_batch_code(date: str, line: str, shift: str,
                     product: str, seq: int) -> str:
    # 基础格式校验
    assert len(date) == 8 and date.isdigit(), "日期须为 YYYYMMDD"
    assert len(line) == 2 and line.isdigit(), "产线须为 2 位数字"
    assert shift in ("A", "B", "C"), "班次须为 A/B/C"
    assert len(product) == 4 and product.isalnum(), "产品代码须为 4 位字母数字"
    assert 1 <= seq <= 99, "流水号范围 01-99"
    payload = f"{date}{line}{shift}{product}{seq:02d}"
    assert len(payload) == 17
    return payload + check_char(payload)

调用示例:build_batch_code("20240517", "03", "B", "1027", 5) 返回 2024051703B102705K。流水号上限 99 是刻意的约束——单产线单班次单产品超过 99 批时,应改用更细的时段或扩展流水位,而不是让编码溢出。

校验与解析函数

校验函数要区分三类失败:长度/字符集不合法、校验位不匹配、字段语义越界。分开报错便于现场快速定位是抄录错误还是规则误用。

def parse_batch_code(code: str) -> dict:
    if len(code) != 18:
        raise ValueError(f"长度错误:期望 18 位,实际 {len(code)} 位")
    if any(c not in CHARSET for c in code):
        raise ValueError("含非法字符,仅允许 0-9 与 A-Z")

    payload, given = code[:17], code[17]
    expect = check_char(payload)
    if given != expect:
        raise ValueError(f"校验位不匹配:期望 {expect},实际 {given}")

    date, line, shift = payload[0:8], payload[8:10], payload[10]
    product, seq = payload[11:15], payload[15:17]

    if shift not in ("A", "B", "C"):
        raise ValueError(f"班次非法:{shift}")
    if not (1 <= int(seq) <= 99):
        raise ValueError(f"流水号越界:{seq}")

    return {
        "date": f"{date[0:4]}-{date[4:6]}-{date[6:8]}",
        "line": line, "shift": shift,
        "product": product, "seq": int(seq), "check": given,
    }

验证用例(示例预期结果,由上述规则直接推出):

输入预期说明
2024051703B102705K解析成功合法编码
2024051703B102705X校验位不匹配末位被抄错
2024051703B102705长度错误漏抄校验位
2024051703D102705?含非法字符班次与符号均非法,先报字符集

唯一性保障与落地要点

  • 流水号分配:由产线终端在提交时向批次服务申请,服务端按 date+line+shift+product 加锁自增,避免多终端并发取到同一号。
  • 校验位不参与唯一性:唯一性键只取前 17 位,校验位是冗余校验,不能作为区分批次的依据。
  • 规则版本化:编码规则一旦上线,历史批次编码不可重算。建议在批次主数据中记录 rule_version,未来扩展位数时按版本解析。
  • 人工录入兜底:标签打印模糊时,校验位能拦截大部分错录;校验失败一律拒绝入库,不做自动纠错,防止错误批次混入追溯链。
设计阶段先用 20–30 个真实产品代码和产线编号跑一遍生成与解析,确认字符集不含易混淆字符(如 O 与 0、I 与 1)。若企业标签规范要求排除,可在 CHARSET 中剔除并同步调整模数。