fix(report): make density facts readable and printable
Unify reader cleanup rules, lock writer table guards, and register the exact fictional timestamp collision. Preserve existing ordinary-report safety contracts and source-data gaps. Validation: report Node 165/165, final safety 29/29, Python 101/101, Chrome 28/28; both PDFs retain all 130 rows. Full Node 3704 tests with the same 91 baseline failures. Privacy test: 62 passed, 1 failed due to 17 protected-file READ_ERRORs; not a green gate. Build, DB, manual checklist and controlled-login gaps remain documented. User explicitly authorized staging push with these gaps disclosed. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { cleanReaderAppendixMarkdown } from "../src/lib/reader-appendix-language.ts";
|
||||
|
||||
const golden = JSON.parse(readFileSync(new URL("./fixtures/report-density-fictional-reader.json", import.meta.url), "utf8"));
|
||||
// Vocabulary-only cases, not invented calculation fixtures.
|
||||
const pageCases = [
|
||||
"PL9 第 42 页", "PL9第42页", "PL9.pdf 第 42 页", "PL9.pdf第42页后文",
|
||||
"参见PL9 第 42 页。", "PL9 第 43–44 页", "PL9 第 40 / 48 页",
|
||||
"PL9 pages 99-100", "PL9 p-42", "PL9第42页中的原始字段",
|
||||
];
|
||||
const chartFence = ' ```jyotish-chart\r\n{"title":"PL9 第 42 页 parameter_sensitive","id":"D1"}\r\n ```\r\n';
|
||||
const copyCases = [
|
||||
...pageCases,
|
||||
"中文parameter_sensitive中文 PyJHora中文 cmd_full_reading后文",
|
||||
"some_parameter_sensitive_field PL99 XPL9 PL9_suffix",
|
||||
"The blocked / executed / available / computed result is not a claim.",
|
||||
"| blocked | `executed` | available | computed | sign_cn | 12.50 |\n",
|
||||
`parameter_sensitive\r\n${chartFence}| parameter_sensitive |\r\n`,
|
||||
];
|
||||
|
||||
function pythonClean(inputs: string[]): string[] {
|
||||
const script = [
|
||||
"import json, sys",
|
||||
"from scripts.reader_appendix_language import clean_reader_appendix_markdown",
|
||||
"sys.stdout.write(json.dumps([clean_reader_appendix_markdown(value) for value in json.load(sys.stdin)], ensure_ascii=False))",
|
||||
].join("\n");
|
||||
const result = spawnSync(process.env.PYTHON ?? (process.platform === "win32" ? "python" : "python3"), ["-c", script], {
|
||||
cwd: fileURLToPath(new URL("../../", import.meta.url)),
|
||||
encoding: "utf8", input: JSON.stringify(inputs), maxBuffer: 16 * 1024 * 1024,
|
||||
env: { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONDONTWRITEBYTECODE: "1" },
|
||||
});
|
||||
assert.equal(result.status, 0, `${result.error ?? ""}\n${result.stderr}`);
|
||||
return JSON.parse(result.stdout);
|
||||
}
|
||||
|
||||
test("appendix Python and TS share byte-identical golden and Chinese-boundary output", () => {
|
||||
assert.equal(golden.fixtureProvenance.fictional, true);
|
||||
const inputs = [golden.markdown, golden.reader_dasha_applicability, ...copyCases] as string[];
|
||||
const python = pythonClean(inputs);
|
||||
for (const [index, input] of inputs.entries()) {
|
||||
const ts = cleanReaderAppendixMarkdown(input);
|
||||
assert.deepEqual(Buffer.from(python[index], "utf8"), Buffer.from(ts, "utf8"), `case ${index}`);
|
||||
assert.equal(cleanReaderAppendixMarkdown(ts), ts, `idempotent case ${index}`);
|
||||
}
|
||||
assert.doesNotMatch(python[0], /第\s*\d+(?:\s*[–—/-]\s*\d+)*\s*页/);
|
||||
assert.doesNotMatch(cleanReaderAppendixMarkdown(golden.markdown), /第\s*\d+(?:\s*[–—/-]\s*\d+)*\s*页/);
|
||||
});
|
||||
|
||||
test("appendix page references disappear without swallowing adjacent Chinese prose", () => {
|
||||
for (const source of pageCases) {
|
||||
const output = cleanReaderAppendixMarkdown(source);
|
||||
assert.doesNotMatch(output, /PL9|第\s*\d+|pages?\s*\d+|p-42/);
|
||||
}
|
||||
assert.equal(cleanReaderAppendixMarkdown("参见PL9第42页中的原始字段"), "参见外部参照资料中的原始字段");
|
||||
assert.equal(cleanReaderAppendixMarkdown("PL9.pdf第42页后文"), "外部参照资料后文");
|
||||
assert.equal(cleanReaderAppendixMarkdown("some_parameter_sensitive_field PL99 XPL9"), "some_parameter_sensitive_field PL99 外部参照资料");
|
||||
});
|
||||
|
||||
test("appendix golden preserves line and table shape, field names, numeric cells and chart bytes", () => {
|
||||
const raw = golden.markdown as string;
|
||||
const clean = cleanReaderAppendixMarkdown(raw);
|
||||
const sourceLines = raw.split("\n");
|
||||
const cleanLines = clean.split("\n");
|
||||
assert.equal(cleanLines.length, sourceLines.length);
|
||||
for (const [index, line] of sourceLines.entries()) {
|
||||
if (!line.startsWith("|")) continue;
|
||||
const cells = line.split("|");
|
||||
const outputCells = cleanLines[index].split("|");
|
||||
assert.equal(outputCells.length, cells.length, `table columns at line ${index}`);
|
||||
cells.forEach((cell, column) => {
|
||||
if (/^\s*-?\d+(?:\.\d+)?\s*$/.test(cell)) assert.equal(outputCells[column], cell);
|
||||
});
|
||||
}
|
||||
for (const field of ["sign_cn", "degree_in_sign", "nakshatra_lord"]) {
|
||||
assert.equal(clean.split(field).length, raw.split(field).length);
|
||||
}
|
||||
const fences = (text: string) => [...text.matchAll(/^[ \t]*```jyotish-chart[^\n]*\n[\s\S]*?^[ \t]*```[^\n]*(?:\n|$)/gm)].map(match => Buffer.from(match[0]));
|
||||
assert.deepEqual(fences(clean), fences(raw));
|
||||
assert.deepEqual(fences(cleanReaderAppendixMarkdown(copyCases.at(-1)!)), [Buffer.from(chartFence)]);
|
||||
});
|
||||
Reference in New Issue
Block a user