textmachine/eval/exp15/build_s2prime_big.py

65 lines
3 KiB
Python

#!/usr/bin/env python3
"""exp15 — build the EXPANDED S2' (full-power, owner decision 2026-07-18) from the first N 节 of 蛊真人.
Produces TWO aligned versions of the same content (only the seam separator differs), + seam ledger:
S2prime_big_flat.txt seams = single '\\n' -> greedy A0 is BLIND (blind-budget cuts)
S2prime_big_blanklines.txt seams = '\\n\\n' -> greedy ORACLE cuts AT every seam (paragraph flush)
Within a 节 the body is normalized to single '\\n' (collapse internal blank lines) so the ONLY '\\n\\n'
are at seams — that is what makes the oracle cut exactly at scene boundaries.
Labels for seams are NOT hand-classified here (too many); Q1b uses the oracle's cut-at-all-seams behavior
and the covariate is per-seam. Deterministic straddle-trap finding happens in straddle_traps.py.
Run: python build_s2prime_big.py [N] # default N=45
"""
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import build_s2prime as B
OUT = Path("/home/ubuntu/books/gu-zhenren/exp15")
def main():
n = int(sys.argv[1]) if len(sys.argv) > 1 else 45
secs = B.sections(B.load_text())[:n]
bodies = []
for idx, header, body in secs:
b = body.replace("\r\n", "\n").replace("\r", "\n")
b = re.sub(r"\n{2,}", "\n", b).strip("\n") # single-\n within 节
bodies.append((idx, b))
flat = "\n".join(b for _, b in bodies)
blank = "\n\n".join(b for _, b in bodies)
# seam offsets in each version (start of body i, i>0)
def seam_offsets(sep):
offs, cur = [], 0
for i, (idx, b) in enumerate(bodies):
if i > 0:
cur += len(sep)
if i > 0:
offs.append({"seam_id": i, "prev_sec": bodies[i - 1][0], "next_sec": idx, "rune_offset": cur})
cur += len(b)
return offs
(OUT / "S2prime_big_flat.txt").write_text(flat, encoding="utf-8")
(OUT / "S2prime_big_blanklines.txt").write_text(blank, encoding="utf-8")
(OUT / "S2prime_big_seams_flat.json").write_text(json.dumps(
{"version": "flat", "n_sections": n, "section_ids": [b[0] for b in bodies],
"total_runes": len(flat), "seams": seam_offsets("\n")}, ensure_ascii=False, indent=2), encoding="utf-8")
(OUT / "S2prime_big_seams_blank.json").write_text(json.dumps(
{"version": "blanklines", "n_sections": n, "section_ids": [b[0] for b in bodies],
"total_runes": len(blank), "seams": seam_offsets("\n\n")}, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"S2'-big: {n}节 flat={len(flat)} runes blank={len(blank)} runes seams={n-1}")
# verify seam alignment
fs = seam_offsets("\n"); bs = seam_offsets("\n\n")
ok = all(flat[s["rune_offset"]:s["rune_offset"]+3] == bodies[i+1][1][:3] for i, s in enumerate(fs))
okb = all(blank[s["rune_offset"]:s["rune_offset"]+3] == bodies[i+1][1][:3] for i, s in enumerate(bs))
print(f"seam alignment: flat={ok} blank={okb}")
if __name__ == "__main__":
main()