fix(report): make density facts readable and printable
Independent Staging Quality Gate / validate (push) Failing after 11m4s
Independent Staging Quality Gate / publish (push) Skipped

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:
jesse-ux
2026-09-23 15:26:25 +08:00
co-authored by Claude Code
parent 0d37bec15a
commit a12f2c0d26
27 changed files with 1220 additions and 210 deletions
+29 -24
View File
@@ -1,27 +1,32 @@
/** Cache compatibility for the Python reader-only vocabulary projection. */
const STATUS_LABELS: Readonly<Record<string, string>> = {
parameter_sensitive: "参数敏感",
pyjhora_behavior_only: "仅单一外部参照,未做多引擎核对",
not_multiengine_parity: "仅单一外部参照,未做多引擎核对",
unresolved_external_tuple_boundary: "外部边界未对齐",
partial_verified: "部分核验",
raw_appendix_only: "仅原始附录可见",
missing_in_local: "本地暂无",
internal_reference_omitted: "内部参照已省略",
};
const CELL_LABELS: Readonly<Record<string, string>> = {
blocked: "暂不可用", executed: "已执行", available: "可用", computed: "已计算",
};
import vocabulary from "../../../scripts/reader_appendix_language.rules.json";
type VocabularyRule = Readonly<{
pattern: string;
ignoreCase?: boolean;
replacement?: string;
labels?: Readonly<Record<string, string>>;
labelGroup?: number;
prefix?: string;
beforeGroups?: readonly number[];
afterGroups?: readonly number[];
}>;
// One rule source for Python and TS; the JSON ships in both existing images.
const chartFence = new RegExp(vocabulary.chartFencePattern, "m");
const replacements = (vocabulary.rules as readonly VocabularyRule[]).map((rule) => ({
rule,
pattern: new RegExp(rule.pattern, rule.ignoreCase ? "gi" : "g"),
}));
export function cleanReaderAppendixMarkdown(markdown: string): string {
return markdown.split(/(^[ \t]*```jyotish-chart[^\n]*\n[\s\S]*?^[ \t]*```[^\n]*(?:\n|$))/m)
.map((part, index) => index % 2 ? part : part
.replace(new RegExp(`\\b(${Object.keys(STATUS_LABELS).join("|")})\\b`, "g"), (value) => STATUS_LABELS[value])
.replace(/\b(?:jyotish_engine\.)?(?:cmd_[a-z0-9_]+|render_pl9_markdown|build_professional_report_reference_packet)\b/g, "本地计算")
.replace(/\b(?:[a-z0-9_]+pl9[a-z0-9_]*|pl9_[a-z0-9_]+)(?:\.v\d+)?\b/gi, "外部参照资料")
.replace(/\bPL9\b(?:\.pdf)?(?:[ \t]*(?:第[ \t]*\d+(?:[ \t]*[–—/-][ \t]*\d+)*[ \t]*页|(?:pages?|p)[ \t-]*\d+(?:[ \t]*[–—/-][ \t]*\d+)*))?/gi, "外部参照资料")
.replace(/\b(?:PyJHora|JHora)\b/gi, "外部参照引擎")
.replace(/\|([ \t]*)(`?)(blocked|executed|available|computed)\2([ \t]*)(?=\|)/g,
(_match, before: string, quote: string, value: string, after: string) => `|${before}${quote}${CELL_LABELS[value]}${quote}${after}`))
.join("");
return markdown.split(chartFence).map((part, index) => {
if (index % 2) return part;
return replacements.reduce((value, { pattern, rule }) => value.replace(pattern, (...match) => {
if (rule.replacement !== undefined) return rule.replacement;
return (rule.prefix ?? "")
+ (rule.beforeGroups ?? []).map((group) => match[group]).join("")
+ rule.labels![match[rule.labelGroup!]]
+ (rule.afterGroups ?? []).map((group) => match[group]).join("");
}), part);
}).join("");
}