feat(upstream): merge a6f47abd engine, MCP, and orchestrator
Three-way-merge calculation modules and pl9-export into the product fork while keeping commercial API routes, Raman ayanamsa, and the consultation contract as a keypath superset. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
"""Shared report-pack contract normalizer.
|
||||
|
||||
This module intentionally stays thin: it does not compute astrology results and
|
||||
does not adjudicate pack truth. It converts existing pack-shaped dictionaries
|
||||
into one stable envelope that the final PL9-style renderer can consume.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from canonical_jyotish_profile import (
|
||||
build_canonical_jyotish_profile,
|
||||
build_disputed_method_policy,
|
||||
build_reader_engine_boundary_notice,
|
||||
)
|
||||
except Exception: # pragma: no cover - research helpers are not vendored here
|
||||
try:
|
||||
from scripts.canonical_jyotish_profile import (
|
||||
build_canonical_jyotish_profile,
|
||||
build_disputed_method_policy,
|
||||
build_reader_engine_boundary_notice,
|
||||
)
|
||||
except Exception:
|
||||
def build_canonical_jyotish_profile():
|
||||
return {"profile_id": "product_canonical", "status": "local_fallback"}
|
||||
|
||||
def build_disputed_method_policy():
|
||||
return {}
|
||||
|
||||
def build_reader_engine_boundary_notice():
|
||||
return {"status": "local_fallback"}
|
||||
try:
|
||||
from profile_aware_benchmark_boundary_dashboard import load_dashboard_payload
|
||||
except Exception: # pragma: no cover - research helpers are not vendored here
|
||||
try:
|
||||
from scripts.profile_aware_benchmark_boundary_dashboard import load_dashboard_payload
|
||||
except Exception:
|
||||
def load_dashboard_payload():
|
||||
return {"status": "blocked", "reason": "profile_aware_dashboard_absent"}
|
||||
|
||||
|
||||
SCHEMA = "pl9.unified_report_pack_contract.v1"
|
||||
|
||||
|
||||
def normalize_report_pack_contract(pack: Mapping[str, Any], *, pack_id: str) -> dict[str, Any]:
|
||||
"""Return a uniform report-ready envelope for an existing pack."""
|
||||
|
||||
report_sections = _normalize_report_sections(pack.get("report_sections"))
|
||||
audit = _normalize_audit(pack)
|
||||
pl9_pages = _as_list(pack.get("pl9_pages") or audit.get("pl9_pages"))
|
||||
blocked_reasons = _blocked_reasons(pack, report_sections)
|
||||
|
||||
contract = {
|
||||
"schema": SCHEMA,
|
||||
"pack_id": pack_id,
|
||||
"source_schema": pack.get("schema"),
|
||||
"status": _status(pack, audit),
|
||||
"profile": dict(pack.get("profile") or {}),
|
||||
"pl9_pages": pl9_pages,
|
||||
"summary": dict(pack.get("summary") or {}),
|
||||
"canonical_jyotish_profile": dict(pack.get("canonical_jyotish_profile") or build_canonical_jyotish_profile()),
|
||||
"reader_engine_boundary_notice": dict(
|
||||
pack.get("reader_engine_boundary_notice") or build_reader_engine_boundary_notice()
|
||||
),
|
||||
"multi_engine_difference_notice": _multi_engine_difference_notice(pack),
|
||||
"profile_aware_dashboard": dict(pack.get("profile_aware_dashboard") or load_dashboard_payload()),
|
||||
"raw_data": _raw_data(pack),
|
||||
"normalized_data": _normalized_data(pack),
|
||||
"report_sections": report_sections,
|
||||
"exports": _normalize_exports(pack.get("exports")),
|
||||
"audit": audit,
|
||||
"blocked_reasons": blocked_reasons,
|
||||
"contract_audit": {
|
||||
"missing_fields": _missing_fields(report_sections),
|
||||
"source_pack_keys": list(pack.keys()),
|
||||
"normalization_boundary": "contract_only_no_astrological_recalculation",
|
||||
},
|
||||
}
|
||||
return contract
|
||||
|
||||
|
||||
def _status(pack: Mapping[str, Any], audit: Mapping[str, Any]) -> str:
|
||||
explicit = pack.get("status") or audit.get("status")
|
||||
if explicit:
|
||||
return str(explicit)
|
||||
|
||||
statuses: list[str] = []
|
||||
report_sections = pack.get("report_sections") if isinstance(pack.get("report_sections"), Mapping) else {}
|
||||
for key in ("executive_summary", "thematic_narrative", "evidence_appendix", "pdf_sections"):
|
||||
section = report_sections.get(key)
|
||||
if isinstance(section, Mapping) and section.get("status"):
|
||||
statuses.append(str(section.get("status")))
|
||||
for key, value in pack.items():
|
||||
if key in {"schema", "profile", "pl9_pages", "summary", "report_sections", "exports", "audit"}:
|
||||
continue
|
||||
if isinstance(value, Mapping) and value.get("status"):
|
||||
statuses.append(str(value.get("status")))
|
||||
|
||||
if not statuses:
|
||||
return "blocked"
|
||||
if any(status in {"conflict", "parameter_sensitive"} for status in statuses):
|
||||
return "parameter_sensitive"
|
||||
if any(status in {"partial_verified", "verified"} for status in statuses):
|
||||
return "partial_verified"
|
||||
if any(status == "not_applicable" for status in statuses):
|
||||
return "not_applicable"
|
||||
return "blocked"
|
||||
|
||||
|
||||
def _normalize_report_sections(value: Any) -> dict[str, Any]:
|
||||
sections = dict(value or {}) if isinstance(value, Mapping) else {}
|
||||
executive = sections.get("executive_summary")
|
||||
thematic = sections.get("thematic_narrative")
|
||||
evidence = sections.get("evidence_appendix")
|
||||
pdf_sections = sections.get("pdf_sections")
|
||||
|
||||
if isinstance(executive, Mapping):
|
||||
executive = [str(executive.get("headline") or executive.get("status") or "")]
|
||||
elif isinstance(executive, str):
|
||||
executive = [executive]
|
||||
elif executive is None:
|
||||
executive = []
|
||||
else:
|
||||
executive = list(executive)
|
||||
|
||||
if thematic is None:
|
||||
thematic = sections.get("trigger_seed_narrative") or []
|
||||
if evidence is None:
|
||||
evidence = []
|
||||
for key in ("visual_chart_audit", "audit_appendix"):
|
||||
if key in sections:
|
||||
evidence.append(sections[key])
|
||||
|
||||
return {
|
||||
"executive_summary": executive,
|
||||
"thematic_narrative": list(thematic) if isinstance(thematic, list) else _as_list(thematic),
|
||||
"evidence_appendix": list(evidence) if isinstance(evidence, list) else _as_list(evidence),
|
||||
"pdf_sections": list(pdf_sections) if isinstance(pdf_sections, list) else _as_list(pdf_sections),
|
||||
}
|
||||
|
||||
|
||||
def _normalize_exports(value: Any) -> dict[str, Any]:
|
||||
exports = dict(value or {}) if isinstance(value, Mapping) else {}
|
||||
return {
|
||||
"json": exports.get("json"),
|
||||
"markdown": exports.get("markdown"),
|
||||
"pdf_sections": exports.get("pdf_sections"),
|
||||
"ai_evidence_bundle": dict(exports.get("ai_evidence_bundle") or {}),
|
||||
}
|
||||
|
||||
|
||||
def _multi_engine_difference_notice(pack: Mapping[str, Any]) -> dict[str, Any]:
|
||||
if isinstance(pack.get("multi_engine_difference_notice"), Mapping):
|
||||
return dict(pack["multi_engine_difference_notice"])
|
||||
policy = build_disputed_method_policy()
|
||||
lanes = []
|
||||
for lane_id, lane in policy.items():
|
||||
lanes.append(
|
||||
{
|
||||
"lane_id": lane_id,
|
||||
"canonical_standard": lane["canonical_standard"],
|
||||
"external_difference_display": "external_observation_conflict",
|
||||
"report_assertion_ceiling": lane["report_assertion_ceiling"],
|
||||
"reader_text_zh": (
|
||||
f"{lane_id} 主口径按 {lane['canonical_standard']};"
|
||||
"其他引擎若不同,会显示为外部观察冲突,不直接改写主结论。"
|
||||
),
|
||||
}
|
||||
)
|
||||
return {
|
||||
"status": "active",
|
||||
"purpose": "reader_safe_multi_engine_difference_display",
|
||||
"canonical_profile_id": build_canonical_jyotish_profile()["profile_id"],
|
||||
"lanes": lanes,
|
||||
"must_not_claim": [
|
||||
"external_engine_conflict_overrides_canonical_result",
|
||||
"internal_consistency_is_global_truth_closure",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _normalize_audit(pack: Mapping[str, Any]) -> dict[str, Any]:
|
||||
audit = dict(pack.get("audit") or {})
|
||||
must_not_claim = list(audit.get("must_not_claim") or [])
|
||||
sections = pack.get("report_sections") if isinstance(pack.get("report_sections"), Mapping) else {}
|
||||
for key in ("visual_chart_audit", "evidence_audit"):
|
||||
section = sections.get(key) if isinstance(sections, Mapping) else None
|
||||
if isinstance(section, Mapping):
|
||||
must_not_claim.extend(item for item in section.get("must_not_claim") or [] if item not in must_not_claim)
|
||||
if must_not_claim:
|
||||
audit["must_not_claim"] = must_not_claim
|
||||
return audit
|
||||
|
||||
|
||||
def _blocked_reasons(pack: Mapping[str, Any], report_sections: Mapping[str, Any]) -> list[str]:
|
||||
reasons: list[str] = []
|
||||
for key in ("blocked_reasons", "blocked_fields"):
|
||||
reasons.extend(str(item) for item in pack.get(key) or [])
|
||||
original_sections = pack.get("report_sections") if isinstance(pack.get("report_sections"), Mapping) else {}
|
||||
executive = original_sections.get("executive_summary") if isinstance(original_sections, Mapping) else None
|
||||
if isinstance(executive, Mapping):
|
||||
reasons.extend(str(item) for item in executive.get("blocked_fields") or [])
|
||||
if not reasons and _status(pack, pack.get("audit") or {}) == "blocked":
|
||||
for item in report_sections.get("evidence_appendix") or []:
|
||||
if isinstance(item, str):
|
||||
reasons.append(item)
|
||||
return list(dict.fromkeys(reasons))
|
||||
|
||||
|
||||
def _raw_data(pack: Mapping[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
key: value
|
||||
for key, value in pack.items()
|
||||
if key
|
||||
not in {
|
||||
"schema",
|
||||
"status",
|
||||
"profile",
|
||||
"pl9_pages",
|
||||
"summary",
|
||||
"report_sections",
|
||||
"exports",
|
||||
"audit",
|
||||
"profile_aware_dashboard",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def _normalized_data(pack: Mapping[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"source_schema": pack.get("schema"),
|
||||
"status": pack.get("status") or (pack.get("audit") or {}).get("status") if isinstance(pack.get("audit"), Mapping) else pack.get("status"),
|
||||
}
|
||||
|
||||
|
||||
def _missing_fields(report_sections: Mapping[str, Any]) -> list[str]:
|
||||
missing = []
|
||||
for key in ("executive_summary", "thematic_narrative", "evidence_appendix"):
|
||||
if not report_sections.get(key):
|
||||
missing.append(key)
|
||||
return missing
|
||||
|
||||
|
||||
def _as_list(value: Any) -> list[Any]:
|
||||
if value is None:
|
||||
return []
|
||||
if isinstance(value, list):
|
||||
return value
|
||||
if isinstance(value, tuple):
|
||||
return list(value)
|
||||
return [value]
|
||||
Reference in New Issue
Block a user