利息计算-终稿.py

下载原文件

# -*- coding: utf-8 -*-
"""
利息计算脚本(终稿版)

设计原则(按本轮要求):
  1. 参数读取实际文书:本金、起息日等自同目录《起诉状-终稿.txt》匹配并回显。
  2. 遇未核参数先停:起诉日、利率口径、抵充口径、3,000元性质、展期效力、LPR数值等
     只要有一项未在参数区核定,"正式计算"即中止并逐项列出缺什么,不产出可填入诉请
     的金额。
  3. 演示算例单独成节,逐条标明假设,仅供核对算法与量级,不得填入诉请。
  4. 互引检查:按实际文件名核对三份文稿的《文件》引用及"清单第X项"编号是否存在。
  5. 结果另存本目录《利息计算结果-终稿.txt》,不改动任何其他文件。
用法:python3 利息计算-终稿.py
依赖:仅Python标准库。
"""
import os
import re
import sys
from datetime import date
from decimal import Decimal, ROUND_HALF_UP

# ───────── 参数区(默认全部未核;承办律师核定后逐项填定,方可进入正式计算) ─────────
计息方案 = None   # 待核定:"方案一"(主张约定利息)或 "方案二"(LPR备选)——起诉状诉请二
起诉日 = None     # 待核定:填 date(2026, 11, 2) 这类
利率口径 = None   # 方案一待核定:"月1%" 或 "年10%"——清单第4项
抵充口径 = None   # 方案一待核定:"抵息" 或 "抵本"——清单第3项
                  # ("抵本"还须另行核定5,000元在三笔本金间的分配,未定则利息仍中止)
三千元性质 = None # 待核定:"茶叶货款" 或 "还款"——清单第8项
                  # ("还款"还须核定抵减口径,未定则中止)
展期效力已核 = False  # 方案二待核定:2025-01-03往来是否构成展期——清单第17项
LPR起算日 = date(2025, 7, 1)  # 展期届满次日(以展期效力核定为前提)——清单第5项、第17项
LPR年利率 = None  # 方案二待核定:起诉日适用的一年期LPR数值,如 Decimal("0.030")
                  # (原第5轮脚本所列3.0%亦未经核定;本脚本不联网代查)
测算截止日 = None # 演示算例截止日;None=取运行日(仅演示用,不是起诉日)
# ──────────────────────────────────────────────────────────────

BASE = os.path.dirname(os.path.abspath(__file__))
诉状名 = "起诉状-终稿.txt"
证据名 = "证据目录-终稿.txt"
清单名 = "待核清单-终稿.txt"
结果名 = "利息计算结果-终稿.txt"

行 = []


def 输出(s=""):
    行.append(s)
    print(s)


def 读文本(p):
    with open(p, "r", encoding="utf-8") as f:
        return f.read()


def 紧(t):
    return re.sub(r"\s+", "", t)


D2 = Decimal("0.01")


def 舍(x):
    return x.quantize(D2, rounding=ROUND_HALF_UP)


def 天数(起, 止):
    return (止 - 起).days


def 计息(本金, 起, 止, 日利率):
    d = 天数(起, 止)
    return d, 舍(本金 * 日利率 * d)


def 中(日):
    return f"{日.isoformat()}({日.year}年{日.month}月{日.day}日)"


def 打明细(截止, 名义利率, 式样):
    日利率 = 名义利率 / 30 if 式样 == "月" else 名义利率 / 365
    除数 = "30" if 式样 == "月" else "365"
    总 = Decimal("0")
    for 序, (本金, 起, 说明) in enumerate(借款, 1):
        d, i = 计息(本金, 起, 截止, 日利率)
        总 += i
        输出(f"      第{序}笔 本金{本金:,.0f}元({说明})")
        输出(f"        起止:{中(起)} 至 {中(截止)},算头不算尾共 {d} 天")
        输出(f"        算式:{本金:,.0f} × {名义利率:%} ÷ {除数} × {d} = {i:,.2f} 元")
    return 总


def 截至已生息(止, 日利率):
    return sum(计息(p, d, 止, 日利率)[1] for p, d, _ in 借款)


# ════════════════════════ 主流程 ════════════════════════
缺失 = [n for n in (诉状名, 证据名, 清单名) if not os.path.exists(os.path.join(BASE, n))]
if 缺失:
    输出("【错误】以下输入文件缺失,拒绝在无依据下计算:")
    for n in 缺失:
        输出(f"  - {n}")
    with open(os.path.join(BASE, 结果名), "w", encoding="utf-8") as f:
        f.write("\n".join(行) + "\n")
    sys.exit(1)

诉状原文 = 读文本(os.path.join(BASE, 诉状名))
证据原文 = 读文本(os.path.join(BASE, 证据名))
清单原文 = 读文本(os.path.join(BASE, 清单名))
诉紧, 证紧, 清紧 = 紧(诉状原文), 紧(证据原文), 紧(清单原文)

输出("利息计算报告(终稿版脚本自动生成)")
输出(f"运行日期:{date.today().isoformat()}")
输出("参数来源:本目录《起诉状-终稿.txt》(计算参数);《证据目录-终稿.txt》"
     "《待核清单-终稿.txt》(一致性与互引核对)。")
输出("原则:正式计算遇未核参数先停;演示算例单独成节并标明全部假设,不得填入诉请。")
输出()

# ── 一、文书参数读取与回显 ──
输出("一、从《起诉状-终稿.txt》读取的参数(自动匹配)")
本金项 = re.findall(r"以([\d,]+)元为基数自(\d{4})年(\d{1,2})月(\d{1,2})日起", 诉紧)
解析通过 = True
if len(本金项) != 3:
    输出(f"  ✗ 方案一本金项匹配到 {len(本金项)} 笔(应为3笔),中止。")
    解析通过 = False
else:
    借款 = []
    期望 = [("80,000", 2024, 3, 8), ("10,000", 2024, 3, 10), ("20,000", 2024, 3, 12)]
    说明表 = {
        "80,000": "银行转账2024-03-08,证据二",
        "10,000": "现金交付2024-03-10(当事人陈述,证据七补强),证据一",
        "20,000": "茶行账户转公司账户2024-03-12,证据五",
    }
    for 金额串, y, m, d in 本金项:
        借款.append((Decimal(金额串.replace(",", "")), date(int(y), int(m), int(d)),
                     说明表.get(金额串, 金额串)))
    for (金额串, y, m, d), (e金额, ey, em, ed) in zip(本金项, 期望):
        ok = (金额串 == e金额 and (int(y), int(m), int(d)) == (ey, em, ed))
        输出(f"  {'✓' if ok else '✗'} 以{金额串}元为基数自{y}年{m}月{d}日起"
             + ("" if ok else f"(与预期 {e金额}元/{ey}-{em}-{ed} 不符)"))
        if not ok:
            解析通过 = False

待核定标记 = [
    ("待核定:110,000元(5,000元抵息口径)", "诉请一本金留待核定"),
    ("105,000元(5,000元抵本口径)", "诉请一列明两口径"),
    ("暂计至起诉之日为人民币(待核定)元", "诉请二暂计金额留待核定"),
    ("按月利率1%还是年利率10%理解,待定", "'一分'口径未定(未写成已定)"),
    ("抵充利息还是本金【待定", "5,000元抵充口径未定"),
    ("【管辖法院待核定", "管辖未写成已定"),
    ("是否构成展期合意", "展期效力未下结论"),
    ("见清单第17项", "展期待定项已挂清单第17项"),
]
输出("  待定/待核定标记检查(文稿不得把未核事项写成已定):")
for 片, 说明 in 待核定标记:
    输出(f"  {'✓' if 片 in 诉紧 else '✗'} {说明}")

不应出现 = [
    ("依法先行抵充利息", "不得把'先抵利息'写成既定"),
    ("杭州市西湖区人民法院", "受理法院不得写死"),
    ("双方达成展期合意,履行期限变更为", "展期效力不得下结论"),
    ("未超过合同成立时一年期贷款市场报价利率四倍", "LPR上限不得下结论"),
    ("均按月利率1%计算至实际清偿之日止", "月息1%不得写成唯一既定口径"),
    ("我方暂按", "不得以起草立场代律师选边"),
]
输出("  已撤回结论检查(以下表述不应再出现):")
撤回干净 = True
for 片, 说明 in 不应出现:
    ok = 片 not in 诉紧
    撤回干净 = 撤回干净 and ok
    输出(f"  {'✓' if ok else '✗'} 已无'{片}'——{说明}")
输出()

# ── 二、互引检查(按实际文件名) ──
输出("二、互引检查(按实际文件名)")
定义项 = set(int(m) for m in re.findall(r"(?m)^(\d{1,2})\.", 清单原文))
互引全过 = True
引用集 = set()
for 来源, 紧t in (("起诉状", 诉紧), ("证据目录", 证紧), ("待核清单", 清紧)):
    for name in re.findall(r"《([^》]+)》", 紧t):
        引用集.add((来源, name))
for 来源, name in sorted(引用集):
    if not re.search(r"\.(txt|py)$", name):
        输出(f"  · {来源}引用《{name}》——非文件引用(法律或文书名),不作文件核对")
        continue
    if name == 结果名:
        输出(f"  ✓ {来源}引用《{name}》——本脚本输出文件(本次运行生成)")
        continue
    存在 = os.path.exists(os.path.join(BASE, name))
    互引全过 = 互引全过 and 存在
    输出(f"  {'✓' if 存在 else '✗'} {来源}引用《{name}》" + ("" if 存在 else "——本目录无此文件"))
for 来源, 紧t in (("起诉状", 诉紧), ("证据目录", 证紧), ("待核清单", 清紧)):
    for n in sorted(set(int(x) for x in re.findall(r"第(\d{1,2})项", 紧t))):
        ok = n in 定义项
        互引全过 = 互引全过 and ok
        输出(f"  {'✓' if ok else '✗'} {来源}引用清单第{n}项" + ("" if ok else "——清单中无此编号"))
输出()

# ── 三、正式计算:未核参数检查 ──
输出("三、正式计算")
未核 = []
if 计息方案 is None:
    未核.append("计息方案(方案一/方案二)——起诉状诉请二,待承办律师核定")
if 起诉日 is None:
    未核.append("起诉日——起诉状'暂计至起诉之日',待核定")
if 计息方案 == "方案一":
    if 利率口径 not in ("月1%", "年10%"):
        未核.append("'一分'利率口径(月1%/年10%)——清单第4项,待定")
    if 抵充口径 not in ("抵息", "抵本"):
        未核.append("5,000元抵充口径(抵息/抵本)——清单第3项,待定")
    if 抵充口径 == "抵本":
        未核.append("5,000元抵本时在三笔本金间的分配口径——材料未指明,须另行核定")
    if 三千元性质 not in ("茶叶货款", "还款"):
        未核.append("3,000元(2024-12-31)性质——清单第8项,待定")
    if 三千元性质 == "还款":
        未核.append("3,000元认定还款后的抵减口径(抵息/抵本及分配)——须另行核定")
if 计息方案 == "方案二":
    if not 展期效力已核:
        未核.append("展期效力——清单第17项,LPR起算日以其为前提")
    if LPR年利率 is None:
        未核.append("起诉日适用的一年期LPR数值——待核定(本脚本不联网代查)")

if 未核:
    输出("  【中止】以下参数未经核定,正式计算不产出任何可填入诉请的金额:")
    for i, s in enumerate(未核, 1):
        输出(f"    {i}. {s}")
    输出("  承办律师在参数区逐项填定后重跑,本节将给出正式结果。")
else:
    输出("  【执行】参数已核定,正式计算如下:")
    # 展期效力、起诉日、口径均已被参数区确认;此处仅实现方案一抵息与方案二的完整路径
    if 计息方案 == "方案一" and 抵充口径 == "抵息" and 三千元性质 == "茶叶货款":
        毛利 = 打明细(起诉日, Decimal("0.01") if 利率口径 == "月1%" else Decimal("0.10"),
                     "月" if 利率口径 == "月1%" else "年")
        净利 = 毛利 - Decimal("5000")
        输出(f"    利息合计:{毛利:,.2f} 元;抵5,000元后净利息:{净利:,.2f} 元")
        本金 = Decimal("110000")
        输出(f"    诉请填列:本金 {本金:,.0f} 元+利息暂计 {净利:,.2f} 元"
             f"={本金 + 净利:,.2f} 元(截至 {中(起诉日)})")
    elif 计息方案 == "方案二":
        d = 天数(LPR起算日, 起诉日)
        i = 舍(Decimal("105000") * LPR年利率 / 365 * d)
        输出(f"    诉请填列:本金 105,000 元+逾期利息 {i:,.2f} 元"
             f"({中(LPR起算日)} 起按 LPR {LPR年利率:%},共 {d} 天)")
    else:
        输出("    该核定组合的自动计算路径未实现(如抵本分配、3,000元抵减),"
             "请按核定口径人工复算或扩展本脚本。")
输出()

# ── 四、演示算例(非诉请金额) ──
截止 = 测算截止日 if 测算截止日 is not None else date.today()
LPR演示值 = Decimal("0.030")
输出(f"四、演示算例(非诉请金额,不得填入文书;截止日 {中(截止)},"
     "仅为展示算式的运行日)")
输出("  全部假设(均未核定,逐条列明):")
输出("    假设①:'一分'按月利率1%理解,日利率=月利率÷30(清单第4项待定)")
输出("    假设②:5,000元抵利息(清单第3项待定)")
输出("    假设③:3,000元按茶叶款、不作抵减(清单第8项待定)")
输出("    假设④:截止日非起诉日,仅演示用")
输出("    假设⑤:'一分'按年10%理解的情景按年利率÷365折算(折算口径未统一,亦为假设)")
输出(f"    假设⑥:LPR演示值沿用原第5轮脚本所列 {LPR演示值:%}(未经核定,不联网更新)")
输出("    假设⑦:天数=两日期之差(算头不算尾);各笔利息四舍五入至分后加总")
输出()
输出("  演示A 月利率1%+5,000元抵利息(假设①②③④)")
毛利A = 打明细(截止, Decimal("0.01"), "月")
净利A = 毛利A - Decimal("5000")
输出(f"      利息合计:{毛利A:,.2f} 元;抵息后净利息:{净利A:,.2f} 元;"
     f"本金+净利息={Decimal('110000') + 净利A:,.2f} 元")
截至抵充日 = 截至已生息(date(2024, 9, 2), Decimal("0.01") / 30)
输出(f"      (算术注:截至{中(date(2024, 9, 2))}三笔已生息 {截至抵充日:,.2f} 元 ≥ 5,000 元,"
     "抵充时点选择不影响本演示合计。)")
输出()
输出("  演示B 月利率1%+5,000元抵本金(假设①③④,另:抵本分配未定)")
输出("      本金:105,000 元;5,000元从哪一笔扣减未指明——不给利息数,"
     f"仅给上限参考(演示A抵充前利息 {毛利A:,.2f} 元,实际应低于此,待分配口径核定后重算)。")
输出()
输出("  演示C '一分'按年利率10%理解+5,000元抵利息(假设④⑤)")
毛利C = 打明细(截止, Decimal("0.10"), "年")
输出(f"      利息合计:{毛利C:,.2f} 元;抵息后净利息:{毛利C - Decimal('5000'):,.2f} 元")
输出()
输出("  演示D LPR备选口径(假设④⑥,另以展期效力核定为前提)")
dD = 天数(date(2025, 7, 1), 截止)
iD = 舍(Decimal("105000") * LPR演示值 / 365 * dD)
输出(f"      算式:105,000 × {LPR演示值:%} ÷ 365 × {dD} = {iD:,.2f} 元"
     f"({中(date(2025, 7, 1))} 起算)")
输出()
截至年末 = 截至已生息(date(2024, 12, 31), Decimal("0.01") / 30)
输出("  附注:3,000元(2024-12-31)若被认定为还款,各演示均需另行抵减重算,抵减口径未定;"
     f"参考数据:截至{中(date(2024, 12, 31))}三笔已生息 {截至年末:,.2f} 元(月利率1%口径)。")
输出()

# ── 五、三份文稿一致性抽查 ──
输出("五、三份文稿一致性抽查")
一致性 = [
    ("6214****3317", 诉紧, "起诉状载8万元收款账号尾号"),
    ("6214****3317", 证紧, "证据目录证据二载同一账号尾号"),
    ("2024年9月2日", 诉紧, "起诉状载5,000元还款日"),
    ("2024年9月2日", 证紧, "证据目录证据四载同一还款日"),
    ("19时32分", 证紧, "证据四载5,000元收款时刻"),
    ("22时47分", 诉紧, "起诉状载3,000元发起时刻"),
    ("22时47分", 证紧, "证据目录载同一发起时刻"),
    ("22时50分", 诉紧, "起诉状载3,000元收款时刻"),
    ("22时50分", 证紧, "证据目录载同一收款时刻"),
    ("2024年3月12日", 诉紧, "起诉状载2万元交付日"),
    ("2024年3月12日9时47分", 证紧, "证据五载同一日及时刻"),
    ("110,000元/105,000元", 清紧, "清单第3项列明两口径本金"),
    ("能否认定为自认", 证紧, "证据三第5项保留自认待定"),
    ("能否认定为自认", 清紧, "清单第12项与两稿口径一致"),
    ("的自认,待定", 诉紧, "起诉状对账段保留自认待定"),
    ("个体工商户", 诉紧, "起诉状主体类型待核"),
    ("个体工商户", 清紧, "清单第1项主体类型待核"),
    ("手指遮挡", 清紧, "清单第2项手指遮挡表述"),
    ("原告转述", 清紧, "清单第2项文三路系原告转述"),
    ("拿不准", 清紧, "清单第8项保留原告'拿不准'说法"),
    ("好像有一笔,又好像没有", 清紧, "清单第11项保留原告原话"),
    ("好像有一笔,又好像没有", 诉紧, "起诉状保留原告原话"),
]
一致全过 = True
for 片, 文, 说明 in 一致性:
    ok = 片 in 文
    一致全过 = 一致全过 and ok
    输出(f"  {'✓' if ok else '✗'} {说明}")
输出()

# ── 六、口径与假设汇总 ──
输出("六、口径与假设汇总")
输出("  1. 正式计算仅在参数区全部核定后执行;本轮运行所有参数均未核定,正式计算中止。")
输出("  2. 演示算例的全部假设见第四节开头,逐一标注,不得填入诉请。")
输出("  3. 天数=两日期之差(算头不算尾);月利率÷30、年利率÷365两套折算并存,均为假设。")
输出("  4. 本脚本只做算术、互引与一致性核对,不构成法律判断;所有待定事项以"
     "《待核清单-终稿.txt》及承办律师决定为准。")
输出()
状态 = "正式计算:中止(存在未核参数)" if 未核 else "正式计算:已执行"
互引状态 = "互引检查:全部通过" if 互引全过 else "互引检查:存在未通过项"
一致状态 = "一致性抽查:全部通过" if 一致全过 else "一致性抽查:存在未通过项"
输出(f"—— 报告完(生成:{date.today().isoformat()};{状态};{互引状态};{一致状态})——")

with open(os.path.join(BASE, 结果名), "w", encoding="utf-8") as f:
    f.write("\n".join(行) + "\n")

if not (解析通过 and 互引全过 and 一致全过 and 撤回干净):
    print("\n[存在未通过项,请核对上方 ✗ 行后修正文稿或脚本,再重跑]")
    sys.exit(1)
print(f"\n[已另存 {os.path.join(BASE, 结果名)}]")