89 lines
3.8 KiB
Python
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]}"
|