Files
Jyotisha/scripts/flexible_birth_time_report_support.py
T
2026-09-04 02:11:57 +08:00

89 lines
3.8 KiB
Python

"""Project candidate-window evidence into bounded report support."""
from __future__ import annotations
from copy import deepcopy
from hashlib import sha256
import json
from typing import Any, Mapping
try:
from flexible_birth_time_profile import _candidate_window_authority_violation
except ImportError: # pragma: no cover - package import
from scripts.flexible_birth_time_profile import _candidate_window_authority_violation
SCHEMA_VERSION = "jyotish.flexible_birth_time_report_support.v1"
PROFILE_SCHEMA_VERSION = "jyotish.flexible_birth_time_profile.v1"
THEME_LAYERS: dict[str, tuple[str, ...]] = {
"career": ("D10.ascendant", "arudha.A10"),
"marriage": ("D7.ascendant", "D9.ascendant", "arudha.A7", "arudha.UL"),
"wealth": ("D2.ascendant", "D11.ascendant"),
"health": ("D6.ascendant", "D8.ascendant", "D30.ascendant"),
"timing": ("D1.ascendant", "D60.ascendant", "KP.cusp_observation"),
"general": (),
}
class FlexibleBirthTimeReportSupportError(ValueError):
"""Raised when an unresolved profile cannot form report support."""
def build_flexible_birth_time_report_support(profile: Mapping[str, Any]) -> dict[str, Any]:
packet = _validate_profile(profile)
stable = deepcopy(dict(packet.get("stable_evidence") or {}))
sensitive = deepcopy(dict(packet.get("sensitive_evidence") or {}))
themes = {
theme: _theme_status(theme, stable, sensitive)
for theme in THEME_LAYERS
}
support_id = _digest_id(
str(packet["flexible_profile_id"]),
str((packet.get("birth_time_window") or {}).get("start_time")),
str((packet.get("birth_time_window") or {}).get("end_time")),
)
return {
"schema_version": SCHEMA_VERSION,
"support_id": support_id,
"birth_time_window": deepcopy(dict(packet["birth_time_window"])),
"stable_report_evidence": {"evidence_keys": sorted(stable), "layers": stable},
"sensitive_report_evidence": {"evidence_keys": sorted(sensitive), "layers": sensitive},
"theme_sensitivity": themes,
"trace": deepcopy(list(packet.get("trace") or [])),
"status": "candidate_window_only",
"claim_boundary": (
"Sensitivity support only. Sensitive themes must remain conditional and this packet "
"cannot identify, rank, or confirm a birth minute."
),
}
def _theme_status(theme: str, stable: Mapping[str, Any], sensitive: Mapping[str, Any]) -> dict[str, Any]:
configured = THEME_LAYERS[theme]
keys = tuple(sorted(set(stable) | set(sensitive))) if theme == "general" else configured
sensitive_layers = [key for key in keys if key in sensitive]
stable_layers = [key for key in keys if key in stable]
return {
"status": "sensitive" if sensitive_layers else "stable",
"sensitive_layers": sensitive_layers,
"stable_layers": stable_layers,
}
def _validate_profile(value: Mapping[str, Any]) -> dict[str, Any]:
violation = _candidate_window_authority_violation(value)
if violation:
raise FlexibleBirthTimeReportSupportError(f"candidate_window_authority_forbidden:{violation}")
if not isinstance(value, Mapping) or value.get("schema_version") != PROFILE_SCHEMA_VERSION:
raise FlexibleBirthTimeReportSupportError("flexible_birth_time_profile_schema_invalid")
if value.get("status") != "candidate_window_only":
raise FlexibleBirthTimeReportSupportError("profile_must_remain_candidate_window_only")
if not isinstance(value.get("birth_time_window"), Mapping):
raise FlexibleBirthTimeReportSupportError("birth_time_window_required")
return dict(value)
def _digest_id(*parts: str) -> str:
payload = json.dumps(parts, ensure_ascii=True, separators=(",", ":"))
return f"flex-report-support://{sha256(payload.encode('utf-8')).hexdigest()[:24]}"