报告前面加一段模型写的解读(issue #113 R1)

引擎在同一份 packet 上给出与对话同源卡相同的事实(include_reading_facts),
报告任务交付前用一次结构化模型调用写八节解读,校验不过或失败就不出解读、
报告照常交付。解读存在计算快照里,读接口一起返回,页面放在引擎正文之前,
后面标「计算依据」。用量按实际 token 记账(BUG-1460、BUG-1461)。

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LutgzyJqNhPpZy1nFsJtN3
This commit is contained in:
Jesse_Chen
2026-10-27 11:17:56 +08:00
co-authored by Claude Opus 5.5
parent e5d768f16f
commit cad28ddc04
26 changed files with 1188 additions and 22 deletions
+38 -3
View File
@@ -195,6 +195,18 @@ def _export_args(birth: dict[str, Any]) -> SimpleNamespace:
return SimpleNamespace(**payload)
def _reading_facts(packet: dict, full_reading: dict, minute_confirmed: bool, today: str | None) -> dict[str, Any]:
"""Facts for the model-written reading (issue #113 R1). Never fails the report."""
try:
try:
from scripts.report_render.reading_facts import build_report_reading_facts
except ModuleNotFoundError: # pragma: no cover - direct scripts/ execution path
from report_render.reading_facts import build_report_reading_facts
return build_report_reading_facts(packet, full_reading, minute_confirmed=minute_confirmed, today=today)
except Exception as exc: # noqa: BLE001 - the reading is optional, the report is not
return {"schema": "report-reading-facts-v1", "gaps": ["reading_facts_unavailable"], "error_type": type(exc).__name__}
def _load_report_cache():
try:
from scripts import consult_full_data_cache as cache
@@ -215,6 +227,7 @@ def _report_cache_identity(
languages: list[str],
include_fact_tables: bool,
sha: str,
reading: dict[str, Any] | None = None,
) -> dict[str, Any]:
return {
"birth": birth,
@@ -225,6 +238,8 @@ def _report_cache_identity(
"include_fact_tables": include_fact_tables,
"report_version": REPORT_VERSION_FULL,
"github_sha": sha,
"include_reading_facts": reading is not None,
**({"reading": reading} if reading is not None else {}),
}
@@ -237,6 +252,7 @@ def _assemble_professional_report_reference(
languages: list[str],
include_fact_tables: bool,
engine,
reading: dict[str, Any] | None = None,
) -> dict[str, Any]:
full_reading = handler._compute_full_reading_for_thematic(birth)
resolved_engine = engine or _load_engine()
@@ -270,6 +286,9 @@ def _assemble_professional_report_reference(
"reader_dasha_applicability": clean_reader_appendix_markdown(render_dasha_applicability(packet))}
if include_fact_tables else {}),
**(english if edition == FULL_DATA_EDITION and "en" in languages else {}),
**({"reading_facts": _reading_facts(
packet, full_reading, reading["minute_confirmed"], reading.get("today"),
)} if reading is not None and edition == FULL_DATA_EDITION else {}),
}
return {
"format": "json",
@@ -278,6 +297,21 @@ def _assemble_professional_report_reference(
}
def _reading_request(body: dict[str, Any]) -> dict[str, Any] | None:
"""`include_reading_facts: true` asks for the reading facts; off by default."""
if body.get("include_reading_facts") is not True:
return None
try:
from scripts.consult_card_domain_facts import minute_confirmed_from_body
except ModuleNotFoundError: # pragma: no cover - direct scripts/ execution path
from consult_card_domain_facts import minute_confirmed_from_body
today = body.get("today")
return {
"minute_confirmed": minute_confirmed_from_body(body),
"today": today if isinstance(today, str) else None,
}
def build_professional_report_reference(handler, body: dict[str, Any], *, engine=None) -> dict[str, Any]:
"""Build one JSON or Markdown response from one reused full-reading result."""
if not isinstance(body, dict):
@@ -291,6 +325,7 @@ def build_professional_report_reference(handler, body: dict[str, Any], *, engine
birth = handler._high_rigor_birth_payload(body)
include_fact_tables = body.get("include_fact_tables") is True
reading = _reading_request(body) if edition == FULL_DATA_EDITION and output_format == "markdown" else None
sha = _report_cache_sha()
use_cache = (
report_reference_cache_enabled()
@@ -300,16 +335,16 @@ def build_professional_report_reference(handler, body: dict[str, Any], *, engine
)
if not use_cache:
return _assemble_professional_report_reference(
handler, birth, output_format, edition, packs, languages, include_fact_tables, engine,
handler, birth, output_format, edition, packs, languages, include_fact_tables, engine, reading,
)
identity = _report_cache_identity(
birth, output_format, edition, packs, languages, include_fact_tables, sha,
birth, output_format, edition, packs, languages, include_fact_tables, sha, reading,
)
started = time.perf_counter()
def builder(_identity: dict[str, Any]) -> dict[str, Any]:
return _assemble_professional_report_reference(
handler, birth, output_format, edition, packs, languages, include_fact_tables, engine,
handler, birth, output_format, edition, packs, languages, include_fact_tables, engine, reading,
)
cached = _load_report_cache().get_or_build(