"""Governed named-yoga draft built from shared Tajika motion evidence. This module does not promote named yogas into authority. It packages kernel candidate relations into a review-ready draft so the remaining closure can be resolved under one governed vocabulary. """ from __future__ import annotations from typing import Any try: from tajika_kernel import calculate_tajika_interactions except ImportError: # pragma: no cover - package import from scripts.tajika_kernel import calculate_tajika_interactions SCHEMA_VERSION = "jyotish.tajika_named_yoga_governed_draft.v1" _CANDIDATE_TO_NAMED = { "Ithasala_candidate": "Ithasala", "Easarapha_candidate": "Easarapha", } def build_tajika_named_yoga_governed_draft(planets: dict[str, dict[str, Any]]) -> dict[str, Any]: """Package Tajika kernel candidate relations as a review-only draft.""" if not isinstance(planets, dict): raise ValueError("planets_required") kernel = calculate_tajika_interactions(planets) if kernel.get("status") == "blocked": return { "schema_version": SCHEMA_VERSION, "status": "blocked", "authority_ready": False, "claim_boundary": "governed_named_yoga_draft_requires_complete_seven_planet_kernel_inputs", "reason": kernel.get("reason") or "tajika_kernel_blocked", "missing": list(kernel.get("missing") or []), "rows": [], "summary": { "row_count": 0, "named_yoga_counts": {}, "motion_counts": {}, }, "limitations": [ "kernel_input_missing", "no_named_yoga_authority", "draft_does_not_promote_verdict", ], } rows: list[dict[str, Any]] = [] for item in kernel.get("candidate_yogas") or []: if not isinstance(item, dict): continue named_yoga = _CANDIDATE_TO_NAMED.get(str(item.get("name") or "")) if not named_yoga: continue rows.append( { "named_yoga": named_yoga, "candidate_name": item.get("name"), "planets": list(item.get("planets") or []), "aspect": item.get("aspect"), "motion": item.get("motion"), "average_deeptamsa": item.get("average_deeptamsa"), "residual": item.get("residual"), "rule_source": item.get("rule_source"), "review_status": "governed_seed_resolution_review_required", "resolution_state": "governed_seed_resolution_draft", "claim_boundary": "candidate_relation_only_no_named_yoga_authority", } ) return { "schema_version": SCHEMA_VERSION, "status": "draft_only", "authority_ready": False, "claim_boundary": "governed_named_yoga_draft_only_no_named_yoga_verdict_or_promotion", "kernel_status": kernel.get("status"), "kernel_boundary": kernel.get("boundary"), "rows": rows, "summary": { "row_count": len(rows), "named_yoga_counts": _count(rows, "named_yoga"), "motion_counts": _count(rows, "motion"), }, "limitations": [ "candidate_relations_only", "no_named_yoga_authority", "no_final_judgment", "governed_seed_review_required_before_promotion", ], } def _count(rows: list[dict[str, Any]], key: str) -> dict[str, int]: counts: dict[str, int] = {} for row in rows: value = row.get(key) if value in (None, ""): continue value = str(value) counts[value] = counts.get(value, 0) + 1 return counts