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,52 @@
|
||||
"""Shared vocabulary regressions; no generated engine facts in these unit inputs."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from scripts.reader_appendix_language import clean_reader_appendix_markdown
|
||||
from scripts import skill_release_package
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
RULE_PATH = "scripts/reader_appendix_language.rules.json"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("source,expected", [
|
||||
("PL9 第 42 页", "外部参照资料"),
|
||||
("PL9第42页", "外部参照资料"),
|
||||
("PL9.pdf 第 42 页", "外部参照资料"),
|
||||
("PL9.pdf第42页后文", "外部参照资料后文"),
|
||||
("参见PL9第43–44页中的字段", "参见外部参照资料中的字段"),
|
||||
("PL9 第 40 / 48 页", "外部参照资料"),
|
||||
("PL9 pages 99-100", "外部参照资料"),
|
||||
])
|
||||
def test_chinese_page_boundary(source, expected):
|
||||
assert clean_reader_appendix_markdown(source) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("edition", ["basic_git", "premium_cloud_drive"])
|
||||
def test_shared_rules_are_included_by_skill_package_selection(monkeypatch, edition):
|
||||
# Simulate the tracked file inventory without mutating this worktree's index.
|
||||
# Exercise the real release selector: scripts assets must not become .py-only.
|
||||
inventory = ["scripts/reader_appendix_language.py", RULE_PATH]
|
||||
# Release manifest references are relative to the repository, not the caller.
|
||||
monkeypatch.chdir(ROOT)
|
||||
monkeypatch.setattr(skill_release_package, "_git_files", lambda: inventory)
|
||||
selected = skill_release_package._edition_files(edition)
|
||||
assert all(path in selected for path in inventory)
|
||||
assert (ROOT / RULE_PATH).is_file()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dockerfile", ["railway-api.Dockerfile", "railway-web.Dockerfile"])
|
||||
def test_shared_rules_live_inside_existing_image_copy_boundary(dockerfile):
|
||||
source = (ROOT / "deploy" / dockerfile).read_text(encoding="utf-8")
|
||||
assert any(line.startswith("COPY scripts ") for line in source.splitlines())
|
||||
|
||||
|
||||
def test_crlf_chart_fence_is_byte_preserved():
|
||||
fence = ' ```jyotish-chart\r\n{"title":"PL9第42页 parameter_sensitive","id":"D1"}\r\n ```\r\n'
|
||||
source = "PL9第42页\r\n" + fence + "| parameter_sensitive |\r\n"
|
||||
assert clean_reader_appendix_markdown(source).encode("utf-8") == (
|
||||
"外部参照资料\r\n" + fence + "| 参数敏感 |\r\n"
|
||||
).encode("utf-8")
|
||||
@@ -192,12 +192,49 @@ def _collision_spans(path: str, source: str, rules: tuple[Rule, ...]) -> list[tu
|
||||
return []
|
||||
service_path = "tests/test_rectification_v5_services.py"
|
||||
replay_path = "references/oracle/pyjhora_multi_case_panchanga_gulika_replay_2026_07_23.json"
|
||||
if path not in {service_path, replay_path}:
|
||||
report_path = "frontend/tests/fixtures/report-density-fictional-reader.json"
|
||||
if path not in {service_path, replay_path, report_path}:
|
||||
return []
|
||||
try:
|
||||
tree = ast.parse(source)
|
||||
except SyntaxError:
|
||||
return []
|
||||
if path == report_path:
|
||||
# BUG-1010: engine timestamp from fictional input, independently replayed
|
||||
# during acceptance (TASK-report-density-fix-20260923, P1-B; BUG-1008).
|
||||
# Pin provenance, JSON node, source line and complete computed row; allow
|
||||
# only the colliding timestamp span, not the large markdown string.
|
||||
try:
|
||||
data = json.loads(source)
|
||||
if data["fixtureProvenance"] != {
|
||||
"fictional": True,
|
||||
"generator": "cmd_full_reading -> build_professional_report_reference_packet",
|
||||
"input": {
|
||||
"year": 2000, "month": 1, "day": 1, "hour": 12,
|
||||
"minute": 0, "second": 0, "lat": 0.0, "lon": 0.0,
|
||||
"tz": 0, "ayanamsa": "lahiri", "node_mode": "mean",
|
||||
"house_system": "whole_sign", "today": "2026-09-22",
|
||||
"target_year": 2026,
|
||||
},
|
||||
} or data["fixtureProvenance"]["fictional"] is not True:
|
||||
return []
|
||||
nodes = [v for k, v in zip(tree.body[0].value.keys, tree.body[0].value.values)
|
||||
if isinstance(k, ast.Constant) and k.value == "markdown"]
|
||||
if len(nodes) != 1 or not isinstance(nodes[0], ast.Constant) or nodes[0].lineno != 22:
|
||||
return []
|
||||
row = (f"| 5 | 4 | 5 | 2028-01-24 {marker}:39 (unresolved_external_tuple_boundary) "
|
||||
"| 0.019276268255984996 | pyjhora_behavior_only / not_multiengine_parity |")
|
||||
if data["markdown"].splitlines().count(row) != 1:
|
||||
return []
|
||||
start, end = _node_span(source, nodes[0])
|
||||
literal = source[start:end]
|
||||
encoded_row = r"\n" + json.dumps(row, ensure_ascii=False)[1:-1] + r"\n"
|
||||
if literal.count(encoded_row) != 1:
|
||||
return []
|
||||
offset = start + literal.index(encoded_row) + encoded_row.index(marker)
|
||||
return [(offset, offset + len(marker))]
|
||||
except (ValueError, KeyError, IndexError, AttributeError, TypeError):
|
||||
return []
|
||||
if path == service_path:
|
||||
name = "test_hour_window_keeps_all_seventeen_signature_clusters_including_late_tail"
|
||||
functions = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef) and n.name == name]
|
||||
@@ -559,6 +596,42 @@ def test_reviewed_numerical_collisions_are_exact_nodes_only(path: str, count: in
|
||||
assert len(scan_text(path, source.replace(marker + ":18", marker + ":19"), rules)[0]) == count
|
||||
|
||||
|
||||
def test_report_fixture_collision_is_one_reviewed_timestamp_only() -> None:
|
||||
__tracebackhide__ = True
|
||||
path = "frontend/tests/fixtures/report-density-fictional-reader.json"
|
||||
rules = build_rules((ROOT / GOLDEN_PATH).read_text(encoding="utf8"))
|
||||
source = (ROOT / path).read_text(encoding="utf8")
|
||||
marker = next(r.value for r in rules if r.code == "R003")
|
||||
findings, permitted = scan_text(path, source, rules)
|
||||
assert len(findings) == 0 and permitted == 1
|
||||
for dirty_path, dirty in [
|
||||
("new/" + path, source),
|
||||
(path, "\n" + source),
|
||||
(path, source.replace('"fictional": true', '"fictional": false', 1)),
|
||||
(path, source.replace('"year": 2000', '"year": 2001', 1)),
|
||||
(path, source.replace('"generator": "cmd_full_reading', '"generator": "unreviewed', 1)),
|
||||
(path, source.replace('"markdown":', '"other":', 1)),
|
||||
(path, source.replace(marker + ":39", marker + ":40", 1)),
|
||||
(path, source.replace("| 5 | 4 | 5 | 2028-01-24", "| 6 | 4 | 5 | 2028-01-24", 1)),
|
||||
]:
|
||||
findings, permitted = scan_text(dirty_path, dirty, rules)
|
||||
assert len(findings) == 1 and permitted == 0
|
||||
# Extra markers in the same markdown node or another field remain findings.
|
||||
for dirty in [
|
||||
source.replace('"markdown": "', '"markdown": "' + marker + r"\n", 1),
|
||||
source.replace('"markdown":', f'"extra": {json.dumps(marker)}, "markdown":', 1),
|
||||
]:
|
||||
findings, permitted = scan_text(path, dirty, rules)
|
||||
assert len(findings) == 1 and permitted == 1
|
||||
row = next(line for line in json.loads(source)["markdown"].splitlines() if marker in line)
|
||||
encoded_row = json.dumps(row, ensure_ascii=False)[1:-1]
|
||||
dirty = source.replace(encoded_row, encoded_row + r"\n" + encoded_row, 1)
|
||||
findings, permitted = scan_text(path, dirty, rules)
|
||||
assert len(findings) == 2 and permitted == 0
|
||||
findings, permitted = scan_text(path, source, rules + (Rule("LOCAL_001", marker),))
|
||||
assert [finding.rule for finding in findings] == ["LOCAL_001"] and permitted == 1
|
||||
|
||||
|
||||
def test_local_rule_is_not_exempted_by_golden_declaration() -> None:
|
||||
source = 'FORBIDDEN_PRIVACY_MARKERS = ("synthetic",)'
|
||||
rules = build_rules(source, "synthetic")
|
||||
|
||||
@@ -87,3 +87,36 @@ def test_optional_table_validator_fails_closed_on_bad_shape():
|
||||
errors.clear()
|
||||
validate_fact_tables([], lambda *error: errors.append(error))
|
||||
assert not errors
|
||||
|
||||
|
||||
def test_fact_subtable_contract_uses_golden_periods_and_keeps_legacy_snapshots():
|
||||
import jsonschema
|
||||
|
||||
packet = json.loads((ROOT / "frontend/tests/fixtures/report-density-fictional-engine.json").read_text(encoding="utf8"))
|
||||
source = "worksheets.timing_and_predictive_systems.dasha"
|
||||
timeline = packet["worksheets"]["timing_and_predictive_systems"]["dasha"]["timeline"]
|
||||
rows = [{"sourcePath": f"{source}.timeline[{index}]", "cells": [period["lord_cn"], period["start"], period["end"], f'{period["full_years"]:.2f}', "是" if period["is_current"] else "否"]} for index, period in enumerate(timeline)]
|
||||
table = {"id": "vimshottari", "title": "Vimshottari 主运", "claimStatus": "parameter_sensitive", "sourcePath": source, "note": "原始计算供核对。", "columns": ["主运", "起", "止", "年数", "当前"], "rows": rows}
|
||||
current_index, current = next((index, period) for index, period in enumerate(timeline) if period["is_current"])
|
||||
child_source = f"{source}.timeline[{current_index}]"
|
||||
child = {"id": "antardasha", "title": "当前主运下分运", "sourcePath": child_source, "note": "原始计算供核对。", "columns": ["分运", "起", "止", "当前"], "rows": [{"sourcePath": f"{child_source}.antardasha_timeline[{index}]", "cells": [period["lord_cn"], period["start"], period["end"], "是" if period["is_current"] else "否"]} for index, period in enumerate(current["antardasha_timeline"])]}
|
||||
schema = json.loads((ROOT / "contracts/personal-report/report-document.v2.schema.json").read_text(encoding="utf8"))
|
||||
contract = {"$ref": "#/definitions/factTable", "definitions": schema["definitions"]}
|
||||
for candidate in [table, {**table, "subtables": [child]}]:
|
||||
errors = []
|
||||
validate_fact_tables([candidate], lambda *error: errors.append(error))
|
||||
assert not errors
|
||||
jsonschema.validate(candidate, contract)
|
||||
for mutation in ("row_width", "origin", "duplicate", "unknown_key"):
|
||||
invalid = copy.deepcopy({**table, "subtables": [child]})
|
||||
if mutation == "row_width":
|
||||
invalid["subtables"][0]["rows"][0]["cells"].pop()
|
||||
elif mutation == "origin":
|
||||
invalid["subtables"][0]["sourcePath"] = "worksheets.other"
|
||||
elif mutation == "duplicate":
|
||||
invalid["subtables"].append(copy.deepcopy(child))
|
||||
else:
|
||||
invalid["subtables"][0]["raw"] = True
|
||||
errors = []
|
||||
validate_fact_tables([invalid], lambda *error: errors.append(error))
|
||||
assert errors, mutation
|
||||
|
||||
Reference in New Issue
Block a user