# -*- coding: utf-8 -*-
"""
五组副本验证(测试专用,不属于业务文稿;验证脚本与日志保留、不删除)
在《利息计算-复核终稿.py》的独立副本上验证五种情况的停算/执行状态与数字一致性
(所有参数填充均系"测试假设",仅验证脚本门槛逻辑,不构成任何真实核定或法律授权):
组A 缺依据——值与核定状态齐备、但"本金基数"依据空白 → 停算,命中1项;
组B 仅填值——只填值、核定状态与依据留默认 → 仍停算,命中7项(仅填值不视为已核);
组C 三项齐备——值+"已核定"+依据均填 → 正式计算【执行】,数字与独立复算一致;
组D 截止早于借款日——测算截止日=2024-03-09(早于后两笔起息日)→ 日期违例停算2项,
演示算例不出任何数字;
组E 截止早于还款日——测算截止日=2024-06-30(三笔借款后、2024-09-02付款日之前)
→ 不扣减、不倒算该款;其正式计算状态与默认实跑完全一致(同为中止3项)。
组0 另对交接完成版根目录默认实跑结果做数字独立复算(复现运行1)。
每组同时核验:第四节/第七节/报告尾三处状态口径一致;无✗;互引、一致性、撤回检查通过。
用法:python3 复核验证_五组副本测试.py (在本目录运行)
依赖:仅Python标准库。
"""
import hashlib
import os
import re
import shutil
import subprocess
import sys
from datetime import date
from decimal import Decimal, ROUND_HALF_UP
TEST = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(TEST) # 交接完成版/
SCRIPT = "利息计算-复核终稿.py"
COPY_FILES = [SCRIPT, "起诉状-复核终稿.txt", "证据目录-复核终稿.txt",
"待核清单-复核终稿.txt", "通话转写-原第2轮.txt"]
REPORT = "利息计算结果-复核终稿_运行1.txt"
LOG_PATH = os.path.join(TEST, "复核验证日志.txt")
D2 = Decimal("0.01")
借款 = [(Decimal("80000"), date(2024, 3, 8)),
(Decimal("10000"), date(2024, 3, 10)),
(Decimal("20000"), date(2024, 3, 12))]
def 舍(x):
return x.quantize(D2, rounding=ROUND_HALF_UP)
def 独立复算(止, 名义, 式样):
"""与脚本同口径逐笔复算:算头不算尾;月÷30、年÷365;逐笔舍入后加总。"""
日利率 = 名义 / Decimal(30) if 式样 == "月" else 名义 / Decimal(365)
总 = Decimal("0")
明细 = []
for p, d0 in 借款:
n = (止 - d0).days
i = 舍(p * 日利率 * n)
明细.append((n, i))
总 += i
return 明细, 总
def d(s):
return Decimal(s.replace(",", ""))
日志行 = []
总检查 = 通过数 = 0
def 检查(组, 条件, ok, 备注=""):
global 总检查, 通过数
总检查 += 1
通过数 += 1 if ok else 0
s = f"[{'通过' if ok else '未过'}] {组}|{条件}" + (f"|{备注}" if 备注 else "")
日志行.append(s)
print(s)
def 节取(t, 起, 止):
i = t.index(起) + len(起)
j = t.index(止, i)
return t[i:j]
def 三处状态(组, t):
四 = 节取(t, "四、正式计算", "五、演示算例")
七 = 节取(t, "七、口径与结果保存说明", "—— 报告完")
尾 = t[t.index("—— 报告完"):]
s4 = "【执行】" if "【执行】" in 四 else ("【中止】" if "【中止】" in 四 else "?")
s7 = "已执行" if "正式计算本轮已执行" in 七 else ("中止" if "正式计算本轮中止" in 七 else "?")
sf = "已执行" if "正式计算:已执行" in 尾 else ("中止" if "正式计算:中止" in 尾 else "?")
检查(组, "第四节状态", s4 in ("【执行】", "【中止】"), s4)
检查(组, "第七节状态", s7 in ("已执行", "中止"), s7)
检查(组, "报告尾状态", sf in ("已执行", "中止"), sf)
s4n = {"【执行】": "已执行", "【中止】": "中止"}.get(s4, s4) # 第四节标记与第七节措辞归一
检查(组, "三处状态口径一致", s4n == s7 == sf, f"{s4}/{s7}/{sf}")
检查(组, "无✗行", "✗" not in t)
检查(组, "互引检查全部通过", "互引检查:全部通过" in 尾)
检查(组, "一致性抽查全部通过", "一致性抽查:全部通过" in 尾)
检查(组, "撤回检查干净", "撤回检查:干净" in 尾)
return 四, 七, 尾, s4
def 截止自报告(t):
m = re.search(r"截止日 (\d{4}-\d{2}-\d{2})", 节取(t, "五、演示算例", "前置声明"))
y, mo, dy = map(int, m.group(1).split("-"))
return date(y, mo, dy), m.group(1)
def 命中数(四):
m = re.search(r"停算条件命中 (\d+) 项", 四)
七 = None
return int(m.group(1)) if m else None
# ── 参数区替换对(旧串在脚本中应恰出现1次,替换时断言) ──
O_截止 = '测算截止日 = 参数(状态="演示用", 依据="未填=取运行日(仅演示用,不是起诉日)")'
def 三联全填(基数依据):
return [
("计息本金表 = 参数()",
'计息本金表 = 参数(状态="已核定", 依据="测试假设:起诉状自动读取三笔本金及起息日,副本视为已核定(非真实核定)")'),
("计息方案 = 参数()",
'计息方案 = 参数(值="方案一", 状态="已核定", 依据="测试假设:采方案一(非真实核定)")'),
("起诉日 = 参数()",
'起诉日 = 参数(值=date(2026, 11, 2), 状态="已核定", 依据="测试假设:暂定起诉日(非真实核定)")'),
("利率口径 = 参数()",
'利率口径 = 参数(值="月1%", 状态="已核定", 依据="测试假设:按月1%读法(非真实核定)")'),
("抵充口径 = 参数()",
'抵充口径 = 参数(值="抵息", 状态="已核定", 依据="测试假设:5,000元抵息(非真实核定)")'),
("三千元性质 = 参数()",
'三千元性质 = 参数(值="茶叶货款", 状态="已核定", 依据="测试假设:3,000元系茶叶货款(非真实核定)")'),
("本金基数 = 参数()",
'本金基数 = 参数(值=Decimal("110000"), 状态="已核定", 依据="' + 基数依据 + '")'),
]
仅填值 = [
("计息方案 = 参数()", '计息方案 = 参数(值="方案一")'),
("起诉日 = 参数()", '起诉日 = 参数(值=date(2026, 11, 2))'),
("利率口径 = 参数()", '利率口径 = 参数(值="月1%")'),
("抵充口径 = 参数()", '抵充口径 = 参数(值="抵息")'),
("三千元性质 = 参数()", '三千元性质 = 参数(值="茶叶货款")'),
("本金基数 = 参数()", '本金基数 = 参数(值=Decimal("110000"))'),
]
用例 = {
"A_缺依据": 三联全填(""),
"B_仅填值": 仅填值,
"C_三项齐备": 三联全填("测试假设:抵息口径110,000元(非真实核定)"),
"D_截止早于借款日": 三联全填("测试假设:抵息口径110,000元(非真实核定)")
+ [(O_截止, '测算截止日 = 参数(值=date(2024, 3, 9), 状态="演示用", '
'依据="测试假设:截止日早于后两笔借款起息日(非真实核定)")')],
"E_截止早于还款日": [(O_截止, '测算截止日 = 参数(值=date(2024, 6, 30), 状态="演示用", '
'依据="测试假设:截止日早于2024-09-02付款日(非真实核定)")')],
}
日志行.append("五组副本验证(测试假设参数,仅验证门槛逻辑,非真实核定)")
日志行.append(f"基脚本SHA256:{hashlib.sha256(open(os.path.join(ROOT, SCRIPT),'rb').read()).hexdigest()}")
日志行.append(f"运行目录:{TEST}")
日志行.append("")
组报告 = {}
for 组名, 补丁 in 用例.items():
print(f"—— 组{组名} ——")
日志行.append(f"—— 组{组名} ——")
目录 = os.path.join(TEST, 组名)
shutil.rmtree(目录, ignore_errors=True) # 重跑前清空旧测试目录
os.makedirs(目录)
for f in COPY_FILES:
目标 = os.path.join(目录, f)
shutil.copyfile(os.path.join(ROOT, f), 目标) # 仅拷内容,不继承只读权限位
if f == SCRIPT:
os.chmod(目标, 0o644) # 副本需可写以便填入测试参数
脚本路径 = os.path.join(目录, SCRIPT)
原文 = open(脚本路径, encoding="utf-8").read()
改后 = 原文
for 旧, 新 in 补丁:
n = 改后.count(旧)
检查(组名, f"替换目标恰出现1次:{旧[:14]}…", n == 1, f"实际{n}次")
改后 = 改后.replace(旧, 新)
open(脚本路径, "w", encoding="utf-8").write(改后)
cmd = [sys.executable, SCRIPT]
r = subprocess.run(cmd, cwd=目录, capture_output=True, encoding="utf-8", errors="replace")
open(os.path.join(目录, "运行日志_标准输出.txt"), "w", encoding="utf-8").write(r.stdout + r.stderr)
日志行.append(f"命令:python3 {SCRIPT} (目录 {目录}) exit={r.returncode}")
检查(组名, "退出码为0", r.returncode == 0, f"exit={r.returncode}")
t = open(os.path.join(目录, REPORT), encoding="utf-8").read()
组报告[组名] = t
四, 七, 尾, s4 = 三处状态(组名, t)
if 组名 == "A_缺依据":
检查(组名, "状态为中止", s4 == "【中止】")
检查(组名, "命中1项", 命中数(四) == 1, f"实际{命中数(四)}")
检查(组名, "唯一命中项为本金基数·依据空白",
"本金基数" in 四 and "依据空白" in 四 and "缺值" not in 四
and "核定状态为“未核”" not in 四)
检查(组名, "不产出诉请金额", "诉请填列" not in 四)
elif 组名 == "B_仅填值":
检查(组名, "状态为中止", s4 == "【中止】")
检查(组名, "命中7项(每个已填值参数各1项)", 命中数(四) == 7, f"实际{命中数(四)}")
检查(组名, "无缺值命中(值已填)", "缺值" not in 四)
检查(组名, "逐项注明仅填值不视为已核", 四.count("仅填值不视为已核") >= 7,
f"出现{四.count('仅填值不视为已核')}处")
检查(组名, "不产出诉请金额", "诉请填列" not in 四)
elif 组名 == "C_三项齐备":
检查(组名, "状态为执行", s4 == "【执行】")
起诉 = date(2026, 11, 2)
明细, 毛利 = 独立复算(起诉, Decimal("0.01"), "月")
净 = 毛利 - Decimal("5000")
m1 = re.search(r"利息合计:([\d,]+\.\d{2}) 元;抵5,000元后净利息:([\d,]+\.\d{2}) 元", 四)
m2 = re.search(r"诉请填列:本金 ([\d,]+) 元+利息暂计 ([\d,]+\.\d{2}) 元"
r"=([\d,]+\.\d{2}) 元(截至 (\d{4}-\d{2}-\d{2})", 四)
检查(组名, "利息合计与独立复算一致", m1 and d(m1.group(1)) == 毛利,
f"报告{m1.group(1) if m1 else '—'}/复算{毛利:,.2f}")
检查(组名, "净利息=毛利−5,000且与独立复算一致", m1 and d(m1.group(2)) == 净,
f"报告{m1.group(2) if m1 else '—'}/复算{净:,.2f}")
检查(组名, "诉请填列本金110,000元(抵息口径假设)", m2 and d(m2.group(1)) == Decimal("110000"))
检查(组名, "诉请暂计与合计与独立复算一致",
m2 and d(m2.group(2)) == 净 and d(m2.group(3)) == Decimal("110000") + 净,
f"报告{m2.group(2)}+{m2.group(3)}" if m2 else "—")
检查(组名, "暂计截至日=填定起诉日", m2 and m2.group(4) == "2026-11-02", m2.group(4) if m2 else "—")
天 = [int(x) for x in re.findall(r"共 (\d+) 天", 四)]
检查(组名, "三笔天数与独立复算一致", 天 == [n for n, _ in 明细], f"{天}")
elif 组名 == "D_截止早于借款日":
检查(组名, "状态为中止", s4 == "【中止】")
检查(组名, "命中2项且均为日期违例", 命中数(四) == 2
and "测算截止日 2024-03-09 早于起息日 2024-03-10" in 四
and "测算截止日 2024-03-09 早于起息日 2024-03-12" in 四
and 四.count("日期顺序错误(停算)") == 2, f"实际{命中数(四)}")
检查(组名, "演示算例不出任何数字", "演示算例不出任何数字" in t)
检查(组名, "全文无算式行", "算式:" not in t)
检查(组名, "全文无利息合计数", "利息合计" not in t)
检查(组名, "不产出诉请金额", "诉请填列" not in 四)
elif 组名 == "E_截止早于还款日":
t0 = open(os.path.join(ROOT, REPORT), encoding="utf-8").read()
四0 = 节取(t0, "四、正式计算", "五、演示算例")
检查(组名, "状态为中止", s4 == "【中止】")
检查(组名, "正式计算状态与默认实跑逐字一致(同为中止3项)", 四 == 四0)
检查(组名, "窗口规则行:早于付款日、不扣减不倒算",
"窗口规则:测算截止日早于5,000元付款日(2024-09-02)——该款于截止日尚未支付:"
"涉及组合不扣减该款、不倒算付款日前后数据。" in t)
截止E = date(2024, 6, 30)
_, 毛利A = 独立复算(截止E, Decimal("0.01"), "月")
_, 毛利C = 独立复算(截止E, Decimal("0.10"), "年")
m1 = re.search(r"利息合计:([\d,]+\.\d{2}) 元(不扣减);本金+利息=([\d,]+\.\d{2}) 元", t)
m4 = re.search(r"利息合计:([\d,]+\.\d{2}) 元(5,000元付款日在截止日之后,不扣减、不倒算)", t)
检查(组名, "组合一利息与独立复算一致且不扣减", m1 and d(m1.group(1)) == 毛利A,
f"报告{m1.group(1) if m1 else '—'}/复算{毛利A:,.2f}")
检查(组名, "组合一演示合计=110,000+利息", m1 and d(m1.group(2)) == Decimal("110000") + 毛利A)
检查(组名, "组合四利息与独立复算一致且不扣减", m4 and d(m4.group(1)) == 毛利C,
f"报告{m4.group(1) if m4 else '—'}/复算{毛利C:,.2f}")
检查(组名, "第五节无抵息后净利息(未扣5,000)", "抵息后净利息" not in 节取(t, "五、演示算例", "六、三份文稿一致性抽查"))
检查(组名, "无抵充时点算术注(不倒算)", "算术注" not in t)
检查(组名, "第七节注明窗口规则已触发", "已触发" in 七)
检查(组名, "附注不出年末参考数据(不倒算)", "不出该参考数据(不倒算)" in t)
检查(组名, "组合六因截止早于LPR起算日不出数", "早于LPR备选起算日 2025-07-01,该组合不出数" in t)
日志行.append("")
# ── 组0:默认实跑结果数字独立复算 ──
print("—— 组0 默认实跑结果复算 ——")
日志行.append("—— 组0 默认实跑结果复算 ——")
t0 = open(os.path.join(ROOT, REPORT), encoding="utf-8").read()
四0, 七0, 尾0, s40 = 三处状态("组0", t0)
检查("组0", "状态为中止", s40 == "【中止】")
检查("组0", "命中3项(本金三笔/计息方案/起诉日)", 命中数(四0) == 3, f"实际{命中数(四0)}")
截止0, 截止串 = 截止自报告(t0)
明细1, 毛利1 = 独立复算(截止0, Decimal("0.01"), "月")
明细4, 毛利4 = 独立复算(截止0, Decimal("0.10"), "年")
五0 = 节取(t0, "五、演示算例", "六、三份文稿一致性抽查")
组一块 = 节取(五0, "组合一", "组合二")
组四块 = 节取(五0, "组合四", "组合五")
m1 = re.search(r"利息合计:([\d,]+\.\d{2}) 元;抵息后净利息:([\d,]+\.\d{2}) 元;"
r"本金+净利息=([\d,]+\.\d{2}) 元", 组一块)
m4 = re.search(r"利息合计:([\d,]+\.\d{2}) 元;抵息后净利息:([\d,]+\.\d{2}) 元", 组四块)
检查("组0", "组合一利息合计与独立复算一致", m1 and d(m1.group(1)) == 毛利1,
f"报告{m1.group(1) if m1 else '—'}/复算{毛利1:,.2f}")
检查("组0", "组合一净利息与合计一致", m1 and d(m1.group(2)) == 毛利1 - Decimal("5000")
and d(m1.group(3)) == Decimal("110000") + 毛利1 - Decimal("5000"))
天1 = [int(x) for x in re.findall(r"共 (\d+) 天", 组一块)]
检查("组0", "组合一三笔天数与独立复算一致", 天1 == [n for n, _ in 明细1], f"{天1}")
检查("组0", "组合四利息合计与独立复算一致", m4 and d(m4.group(1)) == 毛利4,
f"报告{m4.group(1) if m4 else '—'}/复算{毛利4:,.2f}")
dD = (截止0 - date(2025, 7, 1)).days
iD = 舍(Decimal("105000") * Decimal("0.030") / Decimal(365) * dD)
m6 = re.search(r"算式:105,000 × 3\.0% ÷ 365 × (\d+) = ([\d,]+\.\d{2}) 元", 五0)
检查("组0", "组合六天数与金额与独立复算一致", m6 and int(m6.group(1)) == dD and d(m6.group(2)) == iD,
f"报告{m6.group(2) if m6 else '—'}/复算{iD:,.2f}/{dD}天")
截至年末 = sum(舍(p * (Decimal("0.01") / Decimal(30)) * (date(2024, 12, 31) - d0).days)
for p, d0 in 借款)
mf = re.search(r"已生息 ([\d,]+\.\d{2}) 元(月利率1%口径)", 五0)
检查("组0", "附注年末参考数据与独立复算一致", mf and d(mf.group(1)) == 截至年末,
f"报告{mf.group(1) if mf else '—'}/复算{截至年末:,.2f}")
算式1 = re.findall(r"= ([\d,]+\.\d{2}) 元", 组一块)[:3]
检查("组0", "组合一逐笔算式与独立复算一致", [d(x) for x in 算式1] == [i for _, i in 明细1],
f"{算式1}")
日志行.append("")
print()
结论 = f"总结:{通过数}/{总检查} 项检查通过。" + ("全部通过。" if 通过数 == 总检查 else "存在未过项,见上。")
print(结论)
日志行.append(结论)
日志行.append("说明:组A—E 参数均为测试假设,仅验证脚本门槛与口径;不构成真实核定或法律授权。")
open(LOG_PATH, "w", encoding="utf-8").write("\n".join(日志行) + "\n")
print(f"日志已保留:{LOG_PATH}")
sys.exit(0 if 通过数 == 总检查 else 1)