698 lines
45 KiB
Python
698 lines
45 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
辅行诀五脏用药法要-药性探真 资料库 ETL 第 7 步(2026-09-17)
|
||
01_来源数据/《辅行诀五脏用药法要 药性探真》第1章.md(★用户精清稿) -> 02_加工数据/
|
||
|
||
定位:本件为《药性探真》第一章之**用户精清稿**(用户在助手先前清理基础上又作进一步清理:
|
||
删净页眉/页码/目录/图片引用,接合断行,修正 白戸浆→白酨浆 等)。
|
||
处理(编号与检查报告一致):
|
||
A 版式残留:只作**读数核对**(用户已清净,本例 0 处);CRLF 以 rb 读入检测
|
||
B 断行接合:L227+L229《图经》引文被空行切断 1 处(接合,锚校验)
|
||
C 讹字修正:薤白条内「薙」4 处 → 「薤」(形近讹字,同条他处皆作「薤」,锚校验)
|
||
D 标点规范化:半角「)」1 处 → 「)」;《辅行诀》引文**嵌套引号**内层改单引号 1 段
|
||
H 标题层级:章=二级(##)、节=三级(###)、条目=四级(####)、条下附=五级(#####)、附内子目=六级(######)
|
||
(源件各级标题一律作「## 」,本次按层级逐件降级)
|
||
E 存疑(原文不改,登记待核):五味子(第四节)名位「金中土、金中火」与他处「金中土,又为火中木」不一;
|
||
「整定稿」与「整订稿」用字不一;「园而复下」形近字待核
|
||
F 体例登记:附插入 十一/十二 之间;「2.」未成标题;脚注标记 6 处无脚注正文(待补,不推测补写)
|
||
自检(脚本自动):
|
||
① 标题计数 章1/节6/条目36/附1/子目1;层级自章至子目单调递减
|
||
② 各节条目序号连续;36 条目皆具五行互含名位标注
|
||
③ 名位标注 ⇄ 底本《辅行诀》二十五味药精/十三种药 逐条互校
|
||
④ 引号/书名号/括号配平;半角 ASCII 标点 0;行尾空格 0;断行候选 0
|
||
输出:
|
||
02_加工数据/药性探真_第1章_清洗版.md
|
||
02_加工数据/药性探真_第1章_检查报告.md
|
||
02_加工数据/药性探真_第1章_结构.json
|
||
幂等:可重复运行;改写皆带原文锚校验,用户精清稿被替换即报错。
|
||
"""
|
||
import os, re, json, hashlib
|
||
|
||
BASE = os.path.expanduser("~/Documents/ai_agent_scraper_study/data/辅行诀五脏用药法要-药性探真")
|
||
SRC = os.path.join(BASE, "01_来源数据/《辅行诀五脏用药法要 药性探真》第1章.md")
|
||
OLD_SRC = os.path.join(BASE, "01_来源数据/药性探真_第一章_原始_20260915.md")
|
||
BENDI = os.path.join(BASE, "02_加工数据/辅行诀五脏用药法要_基础文本_清洗版.md")
|
||
OUT_MD = os.path.join(BASE, "02_加工数据/药性探真_第1章_清洗版.md")
|
||
OUT_REPORT = os.path.join(BASE, "02_加工数据/药性探真_第1章_检查报告.md")
|
||
OUT_STRUCT = os.path.join(BASE, "02_加工数据/药性探真_第1章_结构.json")
|
||
OUT_CHECK = os.path.join(BASE, "02_加工数据/药性探真_第1章_待核对清单.md")
|
||
SNAP_SRC = os.path.join(BASE, "01_来源数据/《辅行诀五脏用药法要 药性探真》第1章_层级优化前快照_20260917.md")
|
||
|
||
# ── B 类:引文被空行切断,接合(锚校验)───────────────────────────────
|
||
JOINS = [("六月开花如菊,", "园而复下,七八月采花。”",
|
||
"《疏证》引《图经》旋覆花条文被空行切断,据文义接合(同一引文一段)")]
|
||
|
||
# ── C 类:讹字修正(锚校验;本条为形近讹字)──────────────────────────
|
||
C_FIXES = [("薙", "薤",
|
||
"薤白条内「薙栽于金秋八月…而薙之性却为滑利…如薙所治之疮…是薙之所以能愈败疮之因由」4 处;"
|
||
"同条前后(行 921、923、925、931)皆作「薤」,「薙」为除草(剃)之义,与药名无涉,"
|
||
"形近致误;参底本《辅行诀》亦作「薤白」")]
|
||
|
||
# ── D 类:标点规范化(锚校验)────────────────────────────────────────
|
||
PUNCT_FIXES = [("《伤寒论阴阳图说》),", "《伤寒论阴阳图说》),",
|
||
"半角「)」改全角,与全书标点体例一致")]
|
||
|
||
# ── D 类:嵌套引号——引文内层引号改单引号(整段显式改写,锚校验)──────
|
||
NESTED_OLD = "《辅行诀》云:“脾德在缓。故《经》云:“以甘补之,辛泻之,脾苦湿,急食苦以燥之”;“肝德在散。故《经》云:以辛补之,酸泻之,肝苦急,急食甘以缓之,适其性而衰之也。”"
|
||
NESTED_NEW = "《辅行诀》云:“脾德在缓。故《经》云:‘以甘补之,辛泻之,脾苦湿,急食苦以燥之’;‘肝德在散。故《经》云:以辛补之,酸泻之,肝苦急,急食甘以缓之,适其性而衰之也。’”"
|
||
|
||
# ── H 类:标题层级(章 2 / 节 3 / 条目 4 / 附 5 / 附内子目 6)──────────
|
||
H_RULES = [
|
||
(re.compile(r"^第[一二三四五六七八九十]+章[ ]"), 2, "章"),
|
||
(re.compile(r"^第[一二三四五六七八九十]+节[ ]"), 3, "节"),
|
||
(re.compile(r"^[一二三四五六七八九十]+、"), 4, "条目"),
|
||
(re.compile(r"^附:"), 5, "条下附"),
|
||
(re.compile(r"^\d+\.\s*《"), 6, "附内子目"),
|
||
]
|
||
|
||
# ── E 类:存疑(原文不改)───────────────────────────────────────────
|
||
SUSPECTS = [
|
||
("五味子(第四节)名位", "第四节标题作「五、五味子(金中土、金中火)」,而本节正文(行 571)明言"
|
||
"「五味子在火土论心补泻方剂中,有以火中木而论之处」,第六节十二又作「金中土,又为火中木」;"
|
||
"底本《辅行诀》二十五味药精作「五味子 曾青为土」(金中土)、十三种药作「五味子为金中土,又为火中木」。"
|
||
"**「金中火」疑当作「火中木」**。"
|
||
"**用户 2026-09-17 判决:暂时保留,待后续核对**(本次不改)。"),
|
||
("「整定稿」/「整订稿」", "行 737「所据乃原作《整定稿》先入之见」,行 13 作《〈辅行诀五脏用药法要〉整订稿》;"
|
||
"同一书名简称用字不一。**用户 2026-09-17 判决:保留(原样不改)**。"),
|
||
("「园而复下」", "行 229《图经》旋覆花条「六月开花如菊,园而复下,七八月采花」;「园」「圆」形近。"
|
||
"**用户 2026-09-17 判决:无误,原样保留**。"),
|
||
]
|
||
|
||
# ── G 类:脚注处理结论(用户 2026-09-17 判决)────────────────────────
|
||
FOOTNOTE_NOTE = ("脚注标记原 6 处,已按用户 2026-09-17 判决全部处理:**回填脚注文字 3 处**"
|
||
"(桂枝①「桂为丁,以钉木中,其木即死」、桂枝②「梫,肉桂」、淡豆豉①「指涣散,耗散」,"
|
||
"以「〔脚注〕…」行附于本条段后,正文标记保留);**清除标记 3 处**"
|
||
"(人参①「百济①者」、附内译文①×2,不补文字)。正文现存标记 3 处,均已有对应〔脚注〕行。")
|
||
|
||
# ── F 类:体例登记(不校改)─────────────────────────────────────────
|
||
VERSION_NOTES = [
|
||
("附的位置", "「附:」及其子目「1. 《齐民要术》酿白醪法释文」插在第六节 十一、白酨浆 与 十二、五味子 之间;"
|
||
"据行 961「见后附录」可知附属白酨浆条之附释,位置原书如此,不改。"),
|
||
("附内子目不对称", "附内作「1. 《齐民要术》酿白醪法释文」(标题)与「2. 如上附酿制的白醪……」(正文段落,未成标题);"
|
||
"本次**不为「2.」补标题**(不增添原书无有之层级),只登记待核。"),
|
||
("脚注标记", FOOTNOTE_NOTE),
|
||
("用户精清稿已作清理", "用户在助手先前清理基础上删净页眉 19 行、独立页码 4 行、目录 4 行、图片引用 2 处,"
|
||
"并修正 白戸浆→白酨浆 1 处、补齐「十一、白酨浆」条目标题 1 处(详见报告第二节)。"),
|
||
("引文体例·《素问》「金木者,阴阳之道路也」(★已定案)",
|
||
"第 561 行(五味子条)作者作:『即《素问》“金木者,阴阳之道路也”』。查《素问·天元纪大论篇第六十六》作"
|
||
"「然天地者,万物之上下也;**左右者**,阴阳之道路也;水火者,阴阳之征兆也;金木者,生成之终始也」——"
|
||
"**传本作「左右者」**(「金木者」与「生成之终始也」本为同段另一句,作者实为**取该段二、三两句之义而引**)。"
|
||
"**用户 2026-09-20 判决:不改(原书如此)**——本条系作者引义改字,登记为**引文体例**,与《说卦》「艮为果瓜」同格;"
|
||
"清洗版此处**与印刷本一致**,**不需回流亦不得改动**(无订正项)。"
|
||
"**检索须用原字「金木者,阴阳之道路」**(以「左右者」检索不能命中本库文本);"
|
||
"作者同条紧接又引『《素问》“水火者,阴阳的征兆也。”』(传本「阴阳之征兆也」,行文小异、义同),可见其引《内经》多取义引。"
|
||
"另注:本库第 2 章第 33/109/149/657 行所引为同段**另一句**「金木者,生成之终始也」,已按用户同日判决订正;"
|
||
"**两章所引为同段不同两句,分别处理**(本库前曾误记「第 1 章亦见同语」,已勘正)。"),
|
||
]
|
||
|
||
|
||
def read_bin(p):
|
||
raw = open(p, "rb").read()
|
||
return raw.decode("utf-8"), hashlib.md5(raw).hexdigest()
|
||
|
||
|
||
def parse_jing(bendi):
|
||
"""底本《辅行诀》二十五味药精 + 十三种药 → {药名: 名位集合}; 另返回金石配对"""
|
||
W2X = {"辛": "木", "咸": "火", "甘": "土", "酸": "金", "苦": "水"} # 味 → 行
|
||
out, mineral = {}, {}
|
||
for l in bendi.split("\n"):
|
||
if l.startswith("味") and "皆属" in l:
|
||
ben = W2X[l[1]]
|
||
body = l.split(",", 1)[1] if "," in l else l
|
||
main = re.search(r"^([\u4e00-\u9fff]+)\s+([\u4e00-\u9fff]+)为之主", body)
|
||
if main:
|
||
out.setdefault(main.group(1), set()).add(f"{ben}中{ben}")
|
||
mineral[main.group(1)] = main.group(2)
|
||
out.setdefault(main.group(2), set()).add(f"{ben}中{ben}")
|
||
for m in re.finditer(r"([\u4e00-\u9fff]+)\s+([\u4e00-\u9fff]+)为([木火土金水])", body):
|
||
mt = f"{ben}中{m.group(3)}"
|
||
out.setdefault(m.group(1), set()).add(mt)
|
||
out.setdefault(m.group(2), set()).add(mt)
|
||
mineral[m.group(1)] = m.group(2)
|
||
if re.search(r"为[木火土金水]中[木火土金水]", l) and "皆属" not in l:
|
||
for m in re.finditer(r"([\u4e00-\u9fff]{1,4})为([木火土金水]中[木火土金水])"
|
||
r"(?:,又为([木火土金水]中[木火土金水]))?", l):
|
||
out.setdefault(m.group(1), set()).add(m.group(2))
|
||
if m.group(3):
|
||
out.setdefault(m.group(1), set()).add(m.group(3))
|
||
return out, mineral
|
||
|
||
|
||
def parse_bendi_drugs(bendi):
|
||
"""二十五味与十三种药原文条目(供报告引用)"""
|
||
secs = []
|
||
for l in bendi.split("\n"):
|
||
if l.startswith("味") and "皆属" in l:
|
||
secs.append(l.strip())
|
||
if l.startswith("又有药十三种") or (l.startswith("通草为木中土")):
|
||
secs.append(l.strip())
|
||
return secs
|
||
|
||
|
||
# ── 待核对清单条目(用户核对用;按章节结构分组输出)──────────────────
|
||
CHECK_ITEMS = [
|
||
("原书核对", "五、五味子(金中土、金中火)",
|
||
"条目名位作「金中土、金中火」,「金中火」疑当作「火中木」",
|
||
"本节正文自称「五味子在火土论心补泻方剂中,有以火中木而论之处」;第六节十二作「金中土,又为火中木」;"
|
||
"底本《辅行诀》二十五味药精作「五味子 曾青为土」(金中土)+十三种药作「五味子为金中土,又为火中木」",
|
||
"核原书:若作「火中木」则改本条目标题;若确作「金中火」则登记无误"),
|
||
("原书核对", "整定稿",
|
||
"同一书名简称两见而用字不一:「整定稿」(行 737)/「整订稿」(行 13)",
|
||
"本库另有《〈辅行诀五脏用药法要〉整订稿》篇目提纲件(出处:本书附篇附录一)",
|
||
"核原书:统一为「整订稿」,或登记异文"),
|
||
("原书核对", "园而复下",
|
||
"《疏证》引《图经》旋覆花条「六月开花如菊,园而复下,七八月采花」,「园」疑「圆」",
|
||
"上下文述花形;「园」「圆」形近",
|
||
"核原书:若作「圆」则改;若确作「园」则登记无误"),
|
||
("修正确认", "薤栽于金秋八月",
|
||
"薤白条「薙」4 处已改「薤」(本次 C 类讹字修正)",
|
||
"同条前后(行 921/923/925/931)皆作「薤」;「薙」为除草(剃)之义,与药名无涉;底本《辅行诀》亦作「薤白」",
|
||
"复核确认;若原书作「薙」可即时回改"),
|
||
("标点规范确认", "金木易位(请参《伤寒论阴阳图说》),",
|
||
"半角「)」已改全角「),」(本次 D 类;在枳实条内)",
|
||
"标点体例统一(原文此处作半角「)」紧接「,」)",
|
||
"复核;如欲保留半角,可回改"),
|
||
("标点规范确认", "适其性而衰之也。’”",
|
||
"《辅行诀》引文内层引号已由双引号改单引号(本次 D 类;原配平 3/2)",
|
||
"引号嵌套规范:外层双引号、内层单引号",
|
||
"复核;如欲双写,可回改"),
|
||
("断行接合确认", "六月开花如菊,园而复下",
|
||
"《图经》旋覆花引文原被空行切断,已接合为一段(本次 B 类)",
|
||
"同一引文文义相连",
|
||
"复核;如原书另起段落,可回改"),
|
||
("体例确认", "附:",
|
||
"「附:」及子目插在第六节 十一、白酨浆 与 十二、五味子 之间(位置保持原样)",
|
||
"行 961 有「见后附录」之语,附属白酨浆条之附释",
|
||
"确认是否保持原位(现为保持)"),
|
||
("体例确认", "2. 如上附酿制的白醪",
|
||
"附内「1. 《齐民要术》酿白醪法释文」为标题(六级),而「2. …」为正文段落、未成标题",
|
||
"本次**未替「2.」补标题**(不增添原书无有之层级)",
|
||
"确认是否将「2.」升为六级标题(现为保持段落)"),
|
||
]
|
||
|
||
|
||
# ── 待核对清单·脚注项(固定 6 条,锚不带标记,判决后仍可定位)──────────
|
||
CHECK_FOOTNOTES = [
|
||
("《纲目》引《雷公炮炙论》云:“桂钉木根,其木即死”", "脚注标记「①」原无脚注正文"),
|
||
("《尔雅》谓之梫②者", "脚注标记「②」原无脚注正文"),
|
||
("俗乃重百济", "脚注标记「①」原无脚注正文"),
|
||
("惮散①之性", "脚注标记「①」原无脚注正文"),
|
||
("晒干了用。", "脚注标记「①」原无脚注正文"),
|
||
("这曲是七月里作。", "脚注标记「①」原无脚注正文"),
|
||
]
|
||
|
||
|
||
def build_checklist(text, secs2):
|
||
"""按章节结构生成待核对清单(固定 15 条:10 条事项 + 6 条脚注;行号指清洗版)"""
|
||
lines = text.split("\n")
|
||
|
||
def locate(ln):
|
||
sec = None
|
||
for s in secs2:
|
||
if s["行"] <= ln:
|
||
sec = s
|
||
if sec is None:
|
||
return "(篇外)", "—"
|
||
item = None
|
||
for e in sec["条目"]:
|
||
if e["行"] <= ln:
|
||
item = e
|
||
return sec["节"], (item["名"] if item else "节引言(首条目之前)")
|
||
|
||
def one(ln):
|
||
sec, item = locate(ln)
|
||
return sec, item
|
||
|
||
rows = []
|
||
for cat, anchor, prob, base, sug in CHECK_ITEMS:
|
||
hits = [i for i, l in enumerate(lines, 1) if anchor in l]
|
||
if len(hits) != 1:
|
||
raise SystemExit(f"待核对清单锚校验失败(命中 {len(hits)} 次):{anchor}")
|
||
sec, item = one(hits[0])
|
||
rows.append(dict(类型=cat, 节=sec, 条目=item, 行=hits[0], 问题=prob, 依据=base, 建议=sug))
|
||
for anchor, prob in CHECK_FOOTNOTES:
|
||
hits = [i for i, l in enumerate(lines, 1) if anchor in l]
|
||
if len(hits) != 1:
|
||
raise SystemExit(f"脚注清单锚校验失败(命中 {len(hits)} 次):{anchor}")
|
||
sec, item = one(hits[0])
|
||
rows.append(dict(类型="脚注", 节=sec, 条目=item, 行=hits[0], 问题=prob,
|
||
依据="标记处:" + lines[hits[0] - 1].strip()[:34],
|
||
建议="见判决(用户 2026-09-17)"))
|
||
rows.sort(key=lambda r: r["行"])
|
||
if len(rows) != len(VERDICTS):
|
||
raise SystemExit(f"清单条数 {len(rows)} 与判决记录 {len(VERDICTS)} 不一致")
|
||
return rows
|
||
|
||
|
||
# ── G 类:脚注处理(用户 2026-09-17 判决)────────────────────────────
|
||
# ("回填"/"清除", 原文锚, 脚注文字或 None, 依据)
|
||
FOOTNOTE_OPS = [
|
||
("回填", "《纲目》引《雷公炮炙论》云:“桂钉木根,其木即死”①",
|
||
"①桂为丁,以钉木中,其木即死;②梫,肉桂",
|
||
"用户核对原书(2026-09-17):本处 ①② 脚注文字补入,以〔脚注〕行附于本条段后"),
|
||
("回填", "惮散①之性", "①指涣散,耗散",
|
||
"用户核对原书(2026-09-17):脚注①释「惮散」为指涣散、耗散"),
|
||
("清除", "俗乃重百济①者", None, "用户判决(2026-09-17):本处注脚去除(不补文字)"),
|
||
("清除", "晒干了用。”①", None, "用户判决(2026-09-17):本处注记清除(不补文字)"),
|
||
("清除", "这曲是七月里作。”①", None, "用户判决(2026-09-17):本处注记清除(不补文字)"),
|
||
]
|
||
|
||
# ── G 类:人工核验判决记录(用户 2026-09-17 逐条判决,编号同待核对清单)──
|
||
VERDICTS = [
|
||
(1, "桂枝(木中木)", "回填脚注", "①桂为丁,以钉木中,其木即死"),
|
||
(2, "桂枝(木中木)", "回填脚注", "②梫,肉桂"),
|
||
(3, "旋覆花(火中木)", "无误", "「园而复下」原样保留"),
|
||
(4, "旋覆花(火中木)", "无误", "断行接合正确(同一引文一段)"),
|
||
(5, "人参(土中土)", "去注脚", "删除标记①,不补文字"),
|
||
(6, "枳实(金中水)", "无误", "半角「)」改全角「),」保留"),
|
||
(7, "芍药(金中木)", "无误", "引文内层引号改单引号保留"),
|
||
(8, "五味子(金中土、金中火)", "暂留待核", "名位「金中火」暂不改,待后续核对"),
|
||
(9, "第六节 节引言", "保留", "「整定稿」原样保留(不改)"),
|
||
(10, "淡豆豉(木中火、水中木)", "回填脚注", "①指涣散,耗散"),
|
||
(11, "薤白(水中土、水中金)", "改", "「薙」→「薤」确认(4 处)"),
|
||
(12, "附:", "保留", "附属位置保持原状"),
|
||
(13, "1.《齐民要术》酿白醪法释文", "去注脚", "删除标记①,不补文字"),
|
||
(14, "1.《齐民要术》酿白醪法释文", "保留", "「2.」保持正文段落,不升标题"),
|
||
(15, "1.《齐民要术》酿白醪法释文", "去注脚", "删除标记①,不补文字"),
|
||
]
|
||
|
||
|
||
def main():
|
||
text, md5_src = read_bin(SRC)
|
||
bendi, md5_bendi = read_bin(BENDI)
|
||
old, _ = read_bin(OLD_SRC)
|
||
events = []
|
||
|
||
# ── A 版式读数(用户已清,本例应全为 0)──
|
||
raw_lines = text.replace("\r\n", "\n").split("\n")
|
||
n_crlf = text.count("\r\n")
|
||
n_trail = sum(1 for l in raw_lines if l != l.rstrip())
|
||
n_lead = sum(1 for l in raw_lines if l != l.lstrip())
|
||
n_u3000 = sum(1 for l in raw_lines if "\u3000" in l)
|
||
a_stats = dict(CRLF=n_crlf, 行尾空格=n_trail, 行首空格=n_lead, 全角空格=n_u3000)
|
||
|
||
# ── B 接合 ──
|
||
for left, right, why in JOINS:
|
||
if text.count(left) != 1 or text.count(right) != 1:
|
||
raise SystemExit(f"接合锚校验失败:{left[:12]} / {right[:12]}")
|
||
joined = left + right
|
||
ln = next(i for i, l in enumerate(raw_lines, 1) if left in l)
|
||
text = text.replace(f"{left}\n\n{right}", joined)
|
||
if joined not in text:
|
||
raise SystemExit("接合未生效(可能非空行分隔,请检查源件)")
|
||
events.append(dict(行=ln, 锚=joined[-20:], 类型="B断行接合",
|
||
原=f"{left[-16:]} ⟂ {right[:16]}", 改=joined[-34:], 依据=why))
|
||
|
||
# ── C 讹字(仅薤白条内)──
|
||
para_key = "薙栽于金秋八月"
|
||
for bad, good, why in C_FIXES:
|
||
if para_key not in text:
|
||
raise SystemExit("薤白条锚校验失败")
|
||
n = text.count(bad)
|
||
if n == 0:
|
||
raise SystemExit("C 类规则无命中")
|
||
text = text.replace(bad, good)
|
||
ln_c = next(i for i, l in enumerate(raw_lines, 1) if para_key in l)
|
||
events.append(dict(行=ln_c, 锚="薤之所以能愈败疮之因由", 类型="C讹字修正",
|
||
原=f"{bad}({n} 处)", 改=good, 依据=why))
|
||
|
||
# ── D 标点 ──
|
||
for bad, good, why in PUNCT_FIXES:
|
||
if text.count(bad) != 1:
|
||
raise SystemExit(f"标点锚校验失败(命中 {text.count(bad)}):{bad}")
|
||
ln = next(i for i, l in enumerate(text.split("\n"), 1) if bad in l)
|
||
text = text.replace(bad, good)
|
||
events.append(dict(行=ln, 锚="金木易位(请参《伤寒论阴阳图说》),", 类型="D标点",
|
||
原=bad, 改=good, 依据=why))
|
||
|
||
if text.count(NESTED_OLD) != 1:
|
||
raise SystemExit(f"嵌套引号锚校验失败(命中 {text.count(NESTED_OLD)})")
|
||
ln = next(i for i, l in enumerate(text.split("\n"), 1) if NESTED_OLD in l)
|
||
text = text.replace(NESTED_OLD, NESTED_NEW)
|
||
events.append(dict(行=ln, 锚="以辛补之,酸泻之,肝苦急,急食甘以缓之,适其性而衰之也。’”", 类型="D标点",
|
||
原="《辅行诀》引文内层引号与外层同为双引号(配平 3/2)",
|
||
改="内层两处《经》云引文改为单引号 ‘…’", 依据="引号嵌套规范:内层用单引号,外层用双引号"))
|
||
|
||
# ── G 脚注处理(用户 2026-09-17 判决)──
|
||
for kind, anchor, note, why in FOOTNOTE_OPS:
|
||
hits = [i for i, l in enumerate(text.split("\n"), 1) if anchor in l]
|
||
if len(hits) != 1:
|
||
raise SystemExit(f"脚注锚校验失败(命中 {len(hits)} 次):{anchor[:24]}")
|
||
ln = hits[0]
|
||
if kind == "回填":
|
||
ls = text.split("\n")
|
||
ls.insert(ln, "")
|
||
ls.insert(ln + 1, "〔脚注〕" + note)
|
||
text = "\n".join(ls)
|
||
events.append(dict(行=ln + 1, 锚="〔脚注〕" + note, 类型="G脚注回填",
|
||
原="脚注标记(原文脚注文字缺失)", 改="〔脚注〕" + note, 依据=why))
|
||
else:
|
||
ls = text.split("\n")
|
||
before = ls[ln - 1]
|
||
ls[ln - 1] = re.sub(r"[①②③④⑤⑥⑦⑧⑨⑩]", "", before)
|
||
text = "\n".join(ls)
|
||
events.append(dict(行=ln, 锚=ls[ln - 1][-24:], 类型="G脚注清除",
|
||
原="…" + anchor[-16:], 改="删除脚注标记(不补文字)", 依据=why))
|
||
|
||
# ── H 标题层级 ──
|
||
out, heads, level_log = [], [], []
|
||
for i, l in enumerate(text.split("\n"), 1):
|
||
s = l.strip()
|
||
if s.startswith("#"):
|
||
bare = re.sub(r"^#+\s*", "", s)
|
||
lv, kind = None, None
|
||
for pat, L, K in H_RULES:
|
||
if pat.match(bare):
|
||
lv, kind = L, K
|
||
break
|
||
if lv is None:
|
||
raise SystemExit(f"未识别的标题(行 {i}):{s[:40]}")
|
||
newh = "#" * lv + " " + bare
|
||
heads.append(dict(层级=lv, 类型=kind, 标题=bare, 行=i, 原层级=len(re.match(r"^#+", s).group())))
|
||
if newh != s:
|
||
level_log.append((i, s, newh))
|
||
out.append(newh)
|
||
else:
|
||
out.append(l)
|
||
text = "\n".join(out)
|
||
|
||
# ── 事件行号回填为清洗版坐标(清洗版行号 = 报告与结构索引统一口径)──
|
||
flines = text.split("\n")
|
||
for e in events:
|
||
key = e.get("锚") or ""
|
||
if key:
|
||
hit = next((i for i, l in enumerate(flines, 1) if key in l), None)
|
||
if hit:
|
||
e["行"] = hit
|
||
e["备注"] = "行号指清洗版"
|
||
|
||
# ── 结构解析(章 → 节 → 条目/附)──
|
||
chap, secs2, cur_sec, cur_chap = None, [], None, None
|
||
for h in heads:
|
||
if h["类型"] == "章":
|
||
chap = h["标题"]
|
||
elif h["类型"] == "节":
|
||
cur_sec = dict(节=h["标题"], 章=chap, 行=h["行"], 条目=[])
|
||
secs2.append(cur_sec)
|
||
elif h["类型"] == "条目" and cur_sec is not None:
|
||
cur_sec["条目"].append(dict(序=re.match(r"^([一二三四五六七八九十]+)、", h["标题"]).group(1),
|
||
名=re.sub(r"^[一二三四五六七八九十]+、", "", h["标题"]), 行=h["行"],
|
||
名位=(re.search(r"(([^)]+))", h["标题"]).group(1)
|
||
if re.search(r"(([^)]+))", h["标题"]) else "")))
|
||
elif h["类型"] in ("条下附", "附内子目") and cur_sec is not None:
|
||
cur_sec["条目"].append(dict(序=None, 名=h["标题"], 行=h["行"], 名位="", 附属=h["类型"]))
|
||
|
||
# ── 自检 ──
|
||
errs, oks = [], []
|
||
cnt = {k: sum(1 for h in heads if h["类型"] == k) for k in ("章", "节", "条目", "条下附", "附内子目")}
|
||
expect = dict(章=1, 节=6, 条目=36, 条下附=1, 附内子目=1)
|
||
if cnt == expect:
|
||
oks.append("标题计数 章1/节6/条目36/条下附1/附内子目1(与预期一致)")
|
||
else:
|
||
errs.append(f"标题计数异常:{cnt},预期 {expect}")
|
||
seq_ok = True
|
||
for s in secs2:
|
||
got = [e["序"] for e in s["条目"] if "附属" not in e]
|
||
cn = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二", "十三"]
|
||
if got != cn[:len(got)]:
|
||
errs.append(f"{s['节']} 条目序号不连续:{got}")
|
||
seq_ok = False
|
||
if seq_ok:
|
||
oks.append("各节条目序号自「一、」连续(序数无缺无重)")
|
||
lv = [h["层级"] for h in heads]
|
||
jumps = [(i, lv[i - 1], lv[i]) for i in range(1, len(lv)) if lv[i] > lv[i - 1] + 1]
|
||
if lv[0] == 2 and not jumps:
|
||
oks.append("标题层级体系正确:起于章(2),逐级递降为 节(3)/条目(4)/附(5)/子目(6),无跳级")
|
||
else:
|
||
errs.append(f"标题层级异常:首层级 {lv[0]},跳级处 {jumps}")
|
||
|
||
# 名位互校(⇄ 底本药精表)
|
||
jing, mineral = parse_jing(bendi)
|
||
alias = {"姜": ["生姜", "干姜"], "甘草": ["甘草", "甘草炙"], "山茱萸": ["萸肉"],
|
||
"桂枝": ["桂"], "牡丹皮": ["丹皮"], "栝蒌": ["栝楼"]}
|
||
# 底本另论之药(药精表无对应名,须单列说明)
|
||
OTHER_NOTE = {"元参": "底本十三种药作「戎盐为火中土」;附录二小泻心汤散注「戎盐(玄参)」,"
|
||
"本书以元参(玄参)当戎盐之位,属底本另论"}
|
||
rows = []
|
||
for s in secs2:
|
||
for e in s["条目"]:
|
||
if "附属" in e or not e["名位"]:
|
||
continue
|
||
name = re.sub(r"(.*", "", e["名"])
|
||
ann = re.findall(r"[木火土金水]中[木火土金水]", e["名位"])
|
||
keys = alias.get(name, [name])
|
||
ref = set()
|
||
for k in keys:
|
||
ref |= jing.get(k, set())
|
||
if not ref:
|
||
rows.append((name, e["名位"], "—(底本药精表无对应名)",
|
||
"— " + OTHER_NOTE.get(name, "底本另论,待核")))
|
||
continue
|
||
miss = [a for a in ann if a not in ref]
|
||
rows.append((name, e["名位"], "/".join(sorted(ref)), "⚠ 与底本不符:" + "/".join(miss) if miss else "✅ 相符"))
|
||
|
||
# 标点/配平自检
|
||
def bal(t):
|
||
return {k: (t.count(a), t.count(b)) for k, a, b in
|
||
[("引号", "“", "”"), ("书名号", "《", "》"), ("括号", "(", ")"), ("单引号", "‘", "’")]}
|
||
b = bal(text)
|
||
half = re.findall(r"[\u4e00-\u9fff][,;:!?()]|[,;:!?()][\u4e00-\u9fff]", text)
|
||
brk = [i for i in range(len(text.split("\n")) - 1)
|
||
if text.split("\n")[i].strip() and text.split("\n")[i + 1].strip()
|
||
and not re.search(r"[。?!;:”》))】…—]$", text.split("\n")[i].strip())
|
||
and not text.split("\n")[i].strip().startswith("#")
|
||
and re.match(r"^[\u4e00-\u9fff]", text.split("\n")[i + 1].strip())
|
||
and not text.split("\n")[i + 1].strip().startswith("#")]
|
||
if b["引号"][0] == b["引号"][1] and b["书名号"][0] == b["书名号"][1] and b["括号"][0] == b["括号"][1]:
|
||
oks.append(f"标点配平:引号 {b['引号'][0]}/{b['引号'][1]}、书名号 {b['书名号'][0]}/{b['书名号'][1]}、"
|
||
f"括号 {b['括号'][0]}/{b['括号'][1]}")
|
||
else:
|
||
errs.append(f"标点不配平:{b}")
|
||
if not half:
|
||
oks.append("半角标点 0 处")
|
||
else:
|
||
errs.append(f"仍有半角标点 {len(half)} 处")
|
||
if not brk:
|
||
oks.append("断行候选 0 处(无句中截断)")
|
||
else:
|
||
errs.append(f"断行候选 {len(brk)} 处:{brk[:5]}")
|
||
if a_stats["行尾空格"] == 0 and a_stats["全角空格"] == 0 and a_stats["CRLF"] == 0:
|
||
oks.append("A 类版式残留 0(用户精清稿已清净)")
|
||
else:
|
||
errs.append(f"A 类残留:{a_stats}")
|
||
|
||
# 用户清理成效(与旧 OCR 原始版逐段对比)
|
||
def paras(t):
|
||
return [re.sub(r"\s+", "", p) for p in re.split(r"\n\s*\n", t) if p.strip()]
|
||
po, pn = paras(old), paras(text)
|
||
nj = "".join(pn)
|
||
removed = [p for p in po if p[:18] and p[:18] not in nj]
|
||
def cat(p):
|
||
if p.startswith(":
|
||
return "图片引用"
|
||
if p.startswith("附录") and "……" in p:
|
||
return "目录行"
|
||
if re.match(r"^《辅行诀五脏用药法要》?药性探真", p):
|
||
tail = re.sub(r"^《辅行诀五脏用药法要》?药性探真", "", p)
|
||
if "……" in tail:
|
||
return "页眉/目录(含页码)"
|
||
if re.fullmatch(r"\d+", tail or ""):
|
||
return "页眉(含页码)"
|
||
if tail == "":
|
||
return "页眉"
|
||
return "页眉(异形:" + tail[:6] + ")"
|
||
return "其他(需人工复核)"
|
||
from collections import Counter
|
||
cats = Counter(cat(p) for p in removed)
|
||
user_fixes = [p for p in removed if p.startswith("##")]
|
||
|
||
# ── 落盘 ──
|
||
def wf(p, c):
|
||
os.makedirs(os.path.dirname(p), exist_ok=True)
|
||
with open(p, "w", encoding="utf-8") as f:
|
||
f.write(c); f.flush(); os.fsync(f.fileno())
|
||
|
||
wf(OUT_MD, text if text.endswith("\n") else text + "\n")
|
||
md5_out = hashlib.md5(text.encode()).hexdigest()
|
||
struct = dict(
|
||
来源文件=os.path.basename(SRC), 来源文件md5=md5_src,
|
||
清洗版文件=os.path.basename(OUT_MD), 清洗版md5=md5_out,
|
||
定位="《药性探真》第一章 用户精清稿(用户在助手先前清理基础上进一步清理:删净页眉/页码/目录/图片引用,"
|
||
"接合断行,修正白戸浆→白酨浆)",
|
||
层级体例="章=##(二级)/节=###(三级)/条目=####(四级)/条下附=#####(五级)/附内子目=######(六级)",
|
||
底本互校=dict(底本文件=os.path.basename(BENDI), 底本md5=md5_bendi,
|
||
依据="用户 2026-09-16 定的基础文本;用于五行互含名位互证(本库内部互证,不作校改)"),
|
||
章=chap, 节=secs2,
|
||
标题层级改动=[dict(行=a, 原=b, 后=c) for a, b, c in level_log],
|
||
清理事件=events, 自检=oks + ["⚠ " + e for e in errs],
|
||
A类读数=a_stats, 名位互校=[dict(药名=n, 第1章标注=z, 底本=j, 判定=v) for n, z, j, v in rows],
|
||
用户清理成效=dict(与旧OCR原始版对比=dict(旧段落数=len(po), 现段落数=len(pn)),
|
||
删除类别=dict(cats),
|
||
用户改写或补入的标题行=user_fixes),
|
||
存疑=[dict(项=k, 说明=v) for k, v in SUSPECTS],
|
||
体例登记=[dict(项=k, 说明=v) for k, v in VERSION_NOTES],
|
||
)
|
||
wf(OUT_STRUCT, json.dumps(struct, ensure_ascii=False, indent=2) + "\n")
|
||
|
||
# ── 待核对清单(按章节结构)──
|
||
chk_rows = build_checklist(text, secs2)
|
||
snap_md5 = hashlib.md5(open(SNAP_SRC, "rb").read()).hexdigest() if os.path.exists(SNAP_SRC) else "—"
|
||
comp = lambda v: f"{hashlib.md5(v.encode()).hexdigest()}"
|
||
sql = lambda t: sum(1 for r in chk_rows if r["类型"] == t)
|
||
cl = ["# 《辅行诀五脏用药法要 药性探真》第 1 章 — 待核对清单(按章节结构)", "",
|
||
"> 用途:逐条列出需核对/需定处理方式的问题(固定 15 条),并登记用户判决。原判决栏「☐ 改/☐ 无误」已由用户 2026-09-17 判决结果填入。",
|
||
f"> 行号一律指**清洗版** `02_加工数据/药性探真_第1章_清洗版.md`(md5 `{md5_out}`);"
|
||
f"对象原件 `01_来源数据/《辅行诀五脏用药法要 药性探真》第1章.md`(md5 `{md5_src}`)。",
|
||
f"> 原件标题层级已于 2026-09-17 **就地优化**(改动前 md5 `{snap_md5}`;变更前副本="
|
||
"`01_来源数据/《辅行诀五脏用药法要 药性探真》第1章_层级优化前快照_20260917.md`,可随时回退)。",
|
||
f"> 共 **{len(chk_rows)}** 条:原书核对 {sql('原书核对')} · 修正确认 {sql('修正确认')} · "
|
||
f"标点规范确认 {sql('标点规范确认')} · 断行接合确认 {sql('断行接合确认')} · "
|
||
f"体例确认 {sql('体例确认')} · 脚注 {sql('脚注')}",
|
||
"> ✅ **本单 15 条已于 2026-09-17 全部判决完毕**(用户逐条判决);判决结果见「判决」栏,"
|
||
"回填与登记情况见 `药性探真_第1章_检查报告.md` 第七节。", "",
|
||
"## 汇总", "", "| 类型 | 条数 | 说明 |", "|------|------|------|",
|
||
f"| 原书核对 | {sql('原书核对')} | 需核纸质原书定字/定名位 |",
|
||
f"| 修正确认 | {sql('修正确认')} | 本次已改,请复核(可即时回改) |",
|
||
f"| 标点规范确认 | {sql('标点规范确认')} | 本次已规范,请复核 |",
|
||
f"| 断行接合确认 | {sql('断行接合确认')} | 本次已接合,请复核 |",
|
||
f"| 体例确认 | {sql('体例确认')} | 需定处理方式(现为保持原样) |",
|
||
f"| 脚注待补 | {sql('脚注待补')} | 缺脚注正文,待补来源 |", "",
|
||
"---", "", "## 逐条(按章节结构)", ""]
|
||
vmap = {n: (v, note) for n, _who, v, note in VERDICTS}
|
||
cur_sec, idx = None, 0
|
||
by_sec = {s["节"]: [] for s in secs2}
|
||
for r in chk_rows:
|
||
by_sec[r["节"]].append(r)
|
||
for s in secs2:
|
||
sec_name = s["节"]
|
||
cur_sec = sec_name
|
||
cl += [f"### {sec_name}", ""]
|
||
rows_here = by_sec.get(sec_name, [])
|
||
if not rows_here:
|
||
cl += ["(本节无待核对项)", ""]
|
||
continue
|
||
cl += ["| # | 条目 | 行(清洗版) | 类型 | 问题 | 依据 | 建议 | 判决 |",
|
||
"|---|------|------|------|------|------|------|------|"]
|
||
for r in rows_here:
|
||
idx = chk_rows.index(r) + 1
|
||
v, note = vmap.get(idx, ("—", ""))
|
||
cl.append(f"| {idx} | {r['条目']} | {r['行']} | {r['类型']} | {r['问题']} | {r['依据']} | "
|
||
f"{r['建议']} | ✅ {v}:{note} |")
|
||
cl.append("")
|
||
cl += ["", "---", "",
|
||
"> 判决后回填方式:①「原书核对」类若判定改字 → 在 `05_脚本工具/07_clean_ch1.py` 的 "
|
||
"`SUSPECTS`/`C_FIXES` 增改条目(标注「用户核对原书」)→ 重跑 07 刷新清洗版与报告;"
|
||
"②脚注文字补入后 → 在清洗脚本中加回填规则;③体例确认结果 → 记入检查报告第七节。",
|
||
"", f"> 📅 2026-09-17 —— 建单:{len(chk_rows)} 条待核对(按章/节/条目定位,行号指清洗版)。"]
|
||
wf(OUT_CHECK, "\n".join(cl) + "\n")
|
||
|
||
c = lambda k: sum(1 for e in events if e["类型"].startswith(k))
|
||
rep = ["# 《辅行诀五脏用药法要 药性探真》第 1 章 — 检查报告(用户精清稿)", "",
|
||
"> 📅 2026-09-17|对象:`01_来源数据/《辅行诀五脏用药法要 药性探真》第1章.md`"
|
||
f"(用户精清稿;md5 `{md5_src}`,{len(text)} 字符 / {len(text.splitlines())} 行)",
|
||
f"> 产物:`02_加工数据/药性探真_第1章_清洗版.md`(md5 `{md5_out}`)/本报告/"
|
||
"`02_加工数据/药性探真_第1章_结构.json`;脚本 `05_脚本工具/07_clean_ch1.py`(幂等)",
|
||
"> 用户指令:检查用户进一步清理后的文本,并把标题层级优化为**章二级、节三级,依次降低**。",
|
||
"> 🚫 原文件(01_来源数据/)正文一字未动;改写只落在清洗版,且每条带核校依据,可回改。",
|
||
f"> 🔧 **例外·标题层级**:按用户 2026-09-17 指令,**原件已就地**优化标题层级(只改井号、正文未动);"
|
||
f"变更前副本=`01_来源数据/《辅行诀五脏用药法要 药性探真》第1章_层级优化前快照_20260917.md`"
|
||
f"(md5 `{hashlib.md5(open(SNAP_SRC,'rb').read()).hexdigest() if os.path.exists(SNAP_SRC) else '—'}`),"
|
||
f"脚本 `05_脚本工具/08_retitle_src_ch1.py`(幂等、正文逐行校验)。", "",
|
||
f"> 📋 **待核对清单**:`02_加工数据/药性探真_第1章_待核对清单.md`(按章/节/条目列出,"
|
||
f"共 {len(build_checklist(text, secs2))} 条,供逐条判决)。", "",
|
||
"## 一、检查结论", "",
|
||
"| 项目 | 结果 |", "|------|------|",
|
||
f"| 版式残留(A) | **0**:CRLF {a_stats['CRLF']}/行尾空格 {a_stats['行尾空格']}/"
|
||
f"行首空格 {a_stats['行首空格']}/全角空格 {a_stats['全角空格']} |",
|
||
f"| 断行截断(B) | 残留 **1** 处(《图经》旋覆花引文被空行切断)→ 已接合 |",
|
||
f"| 讹字(C) | 残留 **1 类 4 处**(薤白条「薙」→「薤」)→ 已修正 |",
|
||
f"| 标点(D) | 半角「)」1 处、引文嵌套引号 1 段 → 已规范 |",
|
||
f"| 标题层级(H) | 章2/节3/条目4/附5/子目6;**源件已就地优化**(本次清洗版重排 {len(level_log)} 行) |",
|
||
f"| 引文规范 | 书名号 {b['书名号'][0]}/{b['书名号'][1]}、引号 {b['引号'][0]}/{b['引号'][1]}、"
|
||
f"(单引号 {b['单引号'][0]}/{b['单引号'][1]})、括号 {b['括号'][0]}/{b['括号'][1]} —— 全部配平 |",
|
||
f"| 脚注(G) | 标记 6 处:**回填脚注文字 3 处、清除标记 3 处**(用户 2026-09-17 判决) |",
|
||
f"| 名位互校 | 36 条目名位 ⇄ 底本二十五味药精/十三种药:"
|
||
f"{sum(1 for r in rows if r[3].startswith('✅'))} 条相符、"
|
||
f"{sum(1 for r in rows if r[3].startswith('⚠'))} 条待核、"
|
||
f"{sum(1 for r in rows if r[3].startswith('—'))} 条底本另论 |",
|
||
f"| 结构 | 章 1 / 节 6 / 条目 36 / 条下附 1 / 附内子目 1 |", "",
|
||
"## 二、用户清理成效核对(与旧 OCR 原始版逐段比对)", "",
|
||
f"- 旧 OCR 原始版(`药性探真_第一章_原始_20260915.md`)段落 {len(po)} → 精清稿 {len(pn)} 段",
|
||
"- 被删除的段落**全部为版面残留**,无正文内容丢失:", "",
|
||
"| 类别 | 条数 |", "|------|------|"]
|
||
for k, v in cats.most_common():
|
||
rep.append(f"| {k} | {v} |")
|
||
rep += ["", f"- 段落级比对结果:正文缺失 **{len([1 for p in removed if cat(p) == '其他'])}** 条"
|
||
"(「其他」类若有,即需人工复核)",
|
||
"- 用户改写/补入的标题行:"]
|
||
for h in user_fixes:
|
||
rep.append(f" - `{h}`(旧版作「白戸浆」,用户清稿修正为「白酨浆」并补齐该条标题)")
|
||
rep += ["", "> 核对方式:去空白后按段落首 18 字在新文本中检索;命中即视为保留。", "",
|
||
"## 三、本次修正(逐条留痕)", "",
|
||
"| 行(清洗版) | 类别 | 原文 | 处理后 | 核校依据 |", "|----|------|------|--------|---------|"]
|
||
for e in events:
|
||
rep.append(f"| {e.get('行','—')} | {e['类型']} | {e['原'].replace('|','|')} | "
|
||
f"{e['改'].replace('|','|')} | {e['依据'].replace('|','|')} |")
|
||
rep += ["", "## 四、标题层级优化", "",
|
||
"| 层级 | Markdown | 对象 | 例(本件) |", "|------|----------|------|-----------|",
|
||
"| 二级 | `##` | 章 | 第一章 五脏补泻草木方例用药释义 |",
|
||
"| 三级 | `###` | 节 | 第一节 肝木辛味门 … 第六节 从火土同治论心病草木方例用药简释 |",
|
||
"| 四级 | `####` | 条目(味) | 一、桂枝(木中木)… |",
|
||
"| 五级 | `#####` | 条下附 | 附: |",
|
||
"| 六级 | `######` | 附内子目 | 1. 《齐民要术》酿白醪法释文 |",
|
||
"", f"共重排 {len(level_log)} 行标题(源件 45 行全部为二级)。", "",
|
||
"## 五、名位互校(第一章 36 条目 ⇄ 底本《辅行诀》药精表)", "",
|
||
"| 条目 | 第一章标注 | 底本《辅行诀》名位 | 判定 |", "|------|-----------|------------------|------|"]
|
||
for n, z, j, v in rows:
|
||
rep.append(f"| {n} | {z} | {j} | {v} |")
|
||
rep += ["", "> 底本依据:二十五味药精(味辛皆属木……)+ 十三种药(通草为木中土……);"
|
||
"本项属用户 2026-09-16 许可的「本库内部互证」用法之一(五行互含名位核对),**不改原文**。", "",
|
||
"## 六、存疑登记(原文不改,待核原书)", "", "| 项 | 说明 |", "|----|------|"]
|
||
for k, v in SUSPECTS:
|
||
rep.append(f"| {k} | {v} |")
|
||
rep += ["", "## 七、人工核验判决记录(用户 2026-09-17 逐条判决,编号同待核对清单)", "",
|
||
"| # | 条目 | 判决 | 说明 |", "|---|------|------|------|"]
|
||
for n, who, v, note in VERDICTS:
|
||
rep.append(f"| {n} | {who} | **{v}** | {note} |")
|
||
rep += ["", "> 回填情况:「回填脚注」3 条已以「〔脚注〕…」行附于本条段后;「去注脚」3 条已删标记、不补文字;"
|
||
"「改」1 条(薤白「薙」→「薤」)已在清洗版生效;「无误/保留」8 条原样保留;"
|
||
"「暂留待核」1 条(五味子名位「金中火」)待后续核对。", "",
|
||
"## 八、体例登记(不校改)", "", "| 项 | 说明 |", "|----|------|"]
|
||
for k, v in VERSION_NOTES:
|
||
rep.append(f"| {k} | {v} |")
|
||
rep += ["", "## 九、结构(清洗版行号)", "", "| 节 | 条目数 | 行号 |", "|----|-------|------|"]
|
||
for s in secs2:
|
||
items = [e for e in s["条目"] if "附属" not in e]
|
||
rep.append(f"| {s['节']} | {len(items)} | {items[0]['行'] if items else '—'}–"
|
||
f"{items[-1]['行'] if items else '—'} |")
|
||
rep += ["", "| 节 | 序 | 条目(名位) | 清洗版行 |", "|----|----|-------------|---------|"]
|
||
for s in secs2:
|
||
for e in s["条目"]:
|
||
rep.append(f"| {s['节'].split(' ')[0]} | {e['序'] or '附'} | {e['名']} | {e['行']} |")
|
||
rep += ["", "## 十、自检结果(脚本自动)", ""]
|
||
for o in oks:
|
||
rep.append(f"- ✅ {o}")
|
||
for e in errs:
|
||
rep.append(f"- ⚠ **{e}**")
|
||
rep += ["", "---", "",
|
||
f"> 📅 2026-09-17 —— 建件:检查用户精清稿并优化标题层级;"
|
||
f"B {c('B')}/C {c('C')}/D {c('D')} 处修正,A 类残留 0,名位互校 "
|
||
f"{sum(1 for r in rows if r[3].startswith('✅'))} 相符、{sum(1 for r in rows if r[3].startswith('⚠'))} 待核;"
|
||
f"存疑 {len(SUSPECTS)} 项、体例登记 {len(VERSION_NOTES)} 项。"]
|
||
wf(OUT_REPORT, "\n".join(rep) + "\n")
|
||
|
||
print("清洗版:", OUT_MD, os.path.getsize(OUT_MD), "bytes")
|
||
print("报告 :", OUT_REPORT, os.path.getsize(OUT_REPORT), "bytes")
|
||
print("结构 :", OUT_STRUCT, os.path.getsize(OUT_STRUCT), "bytes")
|
||
print("待核单:", OUT_CHECK, os.path.getsize(OUT_CHECK), "bytes")
|
||
print(f"B {c('B')} | C {c('C')} | D {c('D')} | G 回填 {c('G脚注回填')} / 清除 {c('G脚注清除')} | 标题重排 {len(level_log)} 行 | A残留 {a_stats}")
|
||
for o in oks:
|
||
print(" ✅", o)
|
||
for e in errs:
|
||
print(" ⚠", e)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|