# -*- coding: utf-8 -*-
"""按《评审报告》修复用例文件:2 个阻断项 + 9 条建议项 + 1 处笔误。
每条替换均断言命中次数,任一不命中即抛错并中止,保证不误伤其它行。
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.stdout.reconfigure(encoding="utf-8")
CASES = REPO / "output" / "test_cases" / "物流公司管理需求_测试用例.md"
# ---------- GB 32100 校验位 ----------
CHARSET = "0123456789ABCDEFGHJKLMNPQRTUWXY"
WEIGHTS = [1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28]
def uscc_check_char(body17: str) -> str:
assert len(body17) == 17, body17
total = sum(CHARSET.index(ch) * WEIGHTS[i] for i, ch in enumerate(body17))
rem = 31 - (total % 31)
return CHARSET[0 if rem == 31 else rem]
def verify_uscc(uscc: str) -> bool:
return len(uscc) == 18 and uscc_check_char(uscc[:17]) == uscc[17]
# 自检:对已知有效样本验证算法
assert verify_uscc("91530102MA6N4K7X52"), "算法自检失败(1)"
assert verify_uscc("91530103MA6P8Q2D7F"), "算法自检失败(2)"
NEW_BODY = "91530102MA6BA5C31"
NEW_USCC = NEW_BODY + uscc_check_char(NEW_BODY)
assert verify_uscc(NEW_USCC), "新样本自检失败"
print(f"061 重建样本新信用代码: {NEW_USCC}")
# ---------- 修复项:(用例编号, 列定位说明, 旧文本, 新文本) ----------
FIXES: list[tuple[str, str, str, str]] = [
# B-001
("PLT_BSC_LC_028", "预期结果",
"3. 纯空格提交按未填处理被阻断(trim 后为空口径)",
"3. 纯空格提交的实际行为记录(按空拦截 或 通过),与测试数据 §10 补充-6 口径比对后回归(评审确认前不得静默按单一口径判定)"),
("PLT_BSC_LC_028", "备注",
"对应TP-B-010;REQ-007 |",
"对应TP-B-010;REQ-007;[待确认] 纯空格/空白字符在必填字段中是否按空处理(见测试数据 §10 补充-6,与 PLT_BSC_LC_022 同口径) |"),
("PLT_BSC_LC_032", "预期结果",
"3. 纯空格按未填处理被阻断",
"3. 纯空格提交的实际行为记录(按空拦截 或 通过),与 PLT_BSC_LC_022 口径比对后回归"),
("PLT_BSC_LC_032", "备注",
"对应TP-B-014;REQ-007(漏测清单 §1 超长字符) |",
"对应TP-B-014;REQ-007(漏测清单 §1 超长字符);[待确认] 纯空格/空白字符是否按空处理(见测试数据 §10 补充-6) |"),
# B-002
("PLT_BSC_LC_073", "测试步骤",
"4. 核对上传后文件名展示",
"4. 核对上传后文件名展示
5. 上传大写扩展名样本(合同.PDF、合同.Jpg,AT-03)并记录实际口径"),
("PLT_BSC_LC_073", "预期结果",
"4. 无扩展名文件(\"合同附件\")被拒绝;拒绝后不产生上传残留",
"4. 无扩展名文件(\"合同附件\")被拒绝;拒绝后不产生上传残留
5. 大写扩展名的实际处理行为记录(大小写不敏感放行 或 仅接受小写并提示),与评审结论对齐后回归,不得静默按单一口径断言"),
("PLT_BSC_LC_073", "备注",
"对应TP-E-012;REQ-019/BR-03 |",
"对应TP-E-012;REQ-019/BR-03;[待确认] 合同附件扩展名大小写口径(.PDF/.Jpg 是否放行,见测试数据 §10 补充-1) |"),
("PLT_BSC_LC_074", "备注",
"对应TP-E-013;REQ-019(漏测清单 §1;0 字节为观察项) |",
"对应TP-E-013;REQ-019(漏测清单 §1;0 字节为观察项);[待确认] 合同附件扩展名大小写口径(.PDF/.Jpg 是否放行,见测试数据 §10 补充-1) |"),
# S-001
("PLT_BSC_LC_017", "测试步骤",
"3. 上传营业执照_测试甲.jpg 后再次提交
4. 删除已上传的营业执照后再次提交",
"3. 上传营业执照_测试甲.jpg 后再次提交(验证提交不再因营业执照被阻断;完整成功链路由 PLT_BSC_LC_038 覆盖)
4. 在新增页重新打开表单(名称改用\"云南测试营业执照校验物流有限公司2\"等新唯一值),上传营业执照后删除该证件,再次提交"),
("PLT_BSC_LC_017", "预期结果",
"3. 上传后提交不再因营业执照阻断并保存成功
4. 删除已上传营业执照后提交再次被阻断(删除动作同步清空暂存值)",
"3. 提交不再因营业执照被阻断(阻断原因不再指向营业执照;表单可按 038 用例完成保存)
4. 删除已上传营业执照后提交再次被阻断(删除动作同步清空暂存值)"),
# S-002
("PLT_BSC_LC_049", "测试步骤",
"4. 再次点击删除并在确认框中确认",
"4. 再次点击删除并在确认框中确认,记录删除结果(本步完成后 LC-04 即为已逻辑删除状态)"),
("PLT_BSC_LC_054", "备注",
"对应TP-D-006;REQ-013/验收要点 2/6.5 |",
"对应TP-D-006;REQ-013/验收要点 2/6.5;前置承接:LC-04 确认删除由本用例唯一执行(PLT_BSC_LC_049 步骤 4 仅记录结果);若 049 已执行确认删除,本用例前置改为\"LC-04 已逻辑删除\"、步骤 1 改用新造可删除公司 |"),
# S-003 + S-004
("PLT_BSC_LC_061", "测试步骤",
"1. 用\"玉溪测试丁货运代理有限公司\"+ 91530402MA6U2W5J8J 新建公司提交
2. 用简称码 YND 新建另一公司提交
3. 核对新建成功与列表展示
4. 核对导出与统计是否将新旧两家视为同一",
"1. 新造临时公司(\"云南测试重建样本物流有限公司\"+" + NEW_USCC + ",简称码 DL6)并删除该临时公司
2. 用与临时公司完全相同的三元组(名称+" + NEW_USCC + "+简称码 DL6)新建公司提交
3. 核对新建成功与列表展示
4. 核对导出与统计是否将新旧两家视为同一"),
("PLT_BSC_LC_061", "预期结果",
"2. 新建公司与已删除行在数据库并存(deleted=0 与 deleted=1 两条记录),无唯一性误拦
3. 列表仅展示新建公司(1 条),导出/统计不混淆新旧",
"2. 新建公司与已删除行在数据库并存(deleted=0 与 deleted=1 各 1 条,共 2 条记录),无唯一性误拦
3. 列表仅展示新建公司(1 条,不含已删除行),导出/统计不混淆新旧"),
("PLT_BSC_LC_061", "备注",
"对应TP-D-013;BR-01+BR-04 组合/REQ-009 |",
"对应TP-D-013;BR-01+BR-04 组合/REQ-009;本用例为\"已删除后三元组复用\"的唯一创建执行点(PLT_BSC_LC_034/035/036 不得再创建同名/同码/同简称码公司,顺序执行时以本用例的重建记录为准) |"),
("PLT_BSC_LC_034", "备注",
"对应TP-B-015;REQ-009/BR-01 |",
"对应TP-B-015;REQ-009/BR-01;执行顺序:本用例创建的重建记录与 PLT_BSC_LC_061 互斥,二者只执行其一(推荐以 061 为唯一重建执行点,本用例改为核对 061 已创建记录) |"),
("PLT_BSC_LC_035", "备注",
"对应TP-B-016;REQ-009/BR-01(跨机构同码场景见 PLT_TMS_LC_057/061) |",
"对应TP-B-016;REQ-009/BR-01(跨机构同码场景见 PLT_TMS_LC_057/061);执行顺序:本用例的同码重建与 PLT_BSC_LC_061 互斥,二者只执行其一(推荐以 061 为唯一重建执行点) |"),
("PLT_BSC_LC_036", "备注",
"对应TP-B-017;BR-01/REQ-009;[待确认] 简称码空值是否参与全平台唯一、DB 唯一约束对 NULL/空串的处理语义(口径③,见分析 11.2 #4) |",
"对应TP-B-017;BR-01/REQ-009;[待确认] 简称码空值是否参与全平台唯一、DB 唯一约束对 NULL/空串的处理语义(口径③,见分析 11.2 #4);执行顺序:本用例的简称码重建与 PLT_BSC_LC_061 互斥,二者只执行其一(推荐以 061 为唯一重建执行点) |"),
# S-005
("PLT_BSC_LC_052", "测试步骤",
"1. 构造\"仅有待生效合同\"公司(用 LC-02 移除引用后重试或新造公司)发起删除
2. 构造\"仅有已过期合同\"公司发起删除",
"1. 新造公司 A(仅挂待生效合同、无运单/货源引用)发起删除
2. 新造公司 B(仅挂已过期合同、无运单/货源引用)发起删除"),
("PLT_BSC_LC_052", "预期结果",
"1. 仅有待生效合同时删除不因合同条件阻断(提示原因不含生效合同)
2. 仅有已过期合同时同样不因合同条件阻断",
"1. 公司 A 删除成功(deleted=1、列表消失),提示原因不含\"生效合同\";若产品口径为阻断则按双分支记录实际行为
2. 公司 B 删除成功(deleted=1、列表消失),提示原因不含\"生效合同\";若产品口径为阻断则按双分支记录实际行为"),
# S-006
("PLT_BSC_LC_067", "测试步骤",
"4. 在 LC-03 下新增另一条编号 HT-2026-0001 复核允许性",
"4. 在 LC-03 下再次新增编号 HT-2026-0001,验证同公司唯一性仍然生效"),
# S-007
("PLT_TMS_LC_037", "预期结果",
"已删除公司是否出现在下拉按快照追溯口径记录实际行为",
"已删除公司不出现在打款筛选下拉(与 PLT_BSC_LC_056/TP-D-008/SS-08 一致)"),
# S-008
("PLT_BSC_LC_069", "测试步骤",
"4. 核对每次弹窗状态与提示",
"4. 核对每次弹窗状态与提示
5. 分别留空签订时间、生效时间、过期时间各保存一次"),
("PLT_BSC_LC_069", "预期结果",
"4. 所有阻断场景弹窗不关闭、已填内容保留;数据库无非法记录落库",
"4. 所有阻断场景弹窗不关闭、已填内容保留;数据库无非法记录落库
5. 三项时间任一为空保存均被阻断、错误字段就近标红,弹窗不关闭、数据库无非法记录(样本 CD-04)"),
("PLT_BSC_LC_069", "备注",
"对应TP-E-008;BR-03/验收要点 4 |",
"对应TP-E-008;BR-03/验收要点 4;CD-04 |"),
# S-009
("PLT_BSC_LC_020", "测试步骤",
"1. 营业执照依次上传 .jpg/.jpeg/.png/.pdf 四个文件
2. 营业执照上传 .gif/.docx/.txt/无扩展名/.bmp 文件",
"1. 营业执照与道路运输经营许可证分别依次上传 .jpg/.jpeg/.png/.pdf 四个文件
2. 营业执照与道路运输经营许可证分别上传 .gif/.docx/.txt/无扩展名/.bmp 文件"),
("PLT_BSC_LC_020", "预期结果",
"1. 营业执照 jpg/jpeg/png/pdf 四格式逐一上传成功并可预览
2. 营业执照对 gif/docx/txt/无扩展名/bmp 拒绝并提示格式白名单(注意 bmp 不在证件白名单)",
"1. 营业执照与许可证 jpg/jpeg/png/pdf 四格式逐一上传成功并可预览
2. 营业执照与许可证对 gif/docx/txt/无扩展名/bmp 拒绝并提示格式白名单(注意 bmp 不在证件白名单)"),
("PLT_BSC_LC_020", "备注",
"对应TP-B-005;REQ-006(与合同附件白名单差异见 DOC-02 说明) |",
"对应TP-B-005;REQ-006(与合同附件白名单差异见 DOC-02 说明);DOC-07 许可证四格式样本 |"),
# O-001
("PLT_BSC_LC_010", "备注",
"(漏收清单 §4 分页)",
"(漏测清单 §4 分页)"),
]
# ---------- 执行 ----------
lines = CASES.read_text(encoding="utf-8").splitlines(keepends=True)
by_case: dict[str, int] = {}
for i, line in enumerate(lines):
if line.startswith("| PLT_"):
by_case[line.split("|")[1].strip()] = i
applied = 0
for case_id, col, old, new in FIXES:
li = by_case[case_id]
line = lines[li]
cnt = line.count(old)
if cnt != 1:
raise SystemExit(f"❌ {case_id} [{col}] 命中 {cnt} 次(期望 1):{old[:60]!r}")
if new in line:
raise SystemExit(f"❌ {case_id} [{col}] 目标文本已存在,跳过以免重复:{new[:60]!r}")
lines[li] = line.replace(old, new)
applied += 1
print(f"✅ {case_id} [{col}] 已修复")
CASES.write_text("".join(lines), encoding="utf-8")
print(f"\n共应用 {applied} 处修复 -> {CASES}")