"""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]}"