"""Per-planet functional role table for the chart page (issue #163). Reads ``functional_benefics.derive_functional_benefic_malefic`` (profile ``bphs_ch34_general_with_sign_exceptions_v2``) and lays it out one row per body, with the natural nature and two independent identities beside it. Nothing here reclassifies a planet: - ``functional`` is the profile's class for the seven. ``neutral`` in that profile means mixed / association-dependent; associations are not evaluated there, so it is never a "cancelled out" verdict. - Rahu and Ketu are not in the seven-lord table. Their role depends on placement, dispositor and associations, which nothing here evaluates, so ``functional`` is ``pending``. - ``yogakaraka`` is the profile's strict field: distinct lordships, one of 4/7/10 and one of 5/9. - ``badhaka`` is the lord of ``yoga_engine.badhaka_house`` (movable 11, fixed 9, dual 7). It is a separate label; it never changes ``functional``. - ``kendradhipati`` marks a natural benefic that owns 4, 7 or 10 and none of 1, 5, 9. The dosha's conditions (associations, placement, maraka role) are not evaluated anywhere in the engine, so the label is always ``conditions_not_evaluated``. ``functional_benefics.py`` is part of the rectification production identity, so this layout lives in the chart page package. """ from __future__ import annotations from typing import Any try: from functional_benefics import PROFILE, SIGN_LORDS, SIGNS, SOURCE, derive_functional_benefic_malefic, normalize_sign from yoga_engine import BENEFICS, MALEFICS, badhaka_house except ImportError: # package import (scripts.chart_view_points.functional_roles) from scripts.functional_benefics import PROFILE, SIGN_LORDS, SIGNS, SOURCE, derive_functional_benefic_malefic, normalize_sign from scripts.yoga_engine import BENEFICS, MALEFICS, badhaka_house BODIES = ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu") NODES = ("Rahu", "Ketu") # The engine's natural lists treat Moon and Mercury as benefics without # checking the Moon's phase or Mercury's company; say so instead of judging it. NATURAL_CONDITION = {"Moon": "lunar_phase_not_evaluated", "Mercury": "association_not_evaluated"} TRANSLATION = "BPHS ch.34, public English translation https://jyotishvidya.com/ch34.htm (inspected 2026-10-02)" def functional_role_table(ascendant: Any) -> dict[str, Any]: base = derive_functional_benefic_malefic(ascendant) asc_sign = normalize_sign(ascendant) if base.get("status") != "used" or asc_sign is None: return {"status": "blocked", "reason": "unknown_ascendant_sign", "profile": PROFILE, "source": SOURCE} benefics = set(base["functional_benefics"]) malefics = set(base["functional_malefics"]) yogakarakas = set(base["yogakarakas"]) owned = base["owned_houses"] b_house = badhaka_house(asc_sign) b_lord = SIGN_LORDS[SIGNS[(SIGNS.index(asc_sign) + b_house - 1) % 12]] if b_house else None rows = [] for planet in BODIES: natural = "benefic" if planet in BENEFICS else "malefic" if planet in MALEFICS else None houses = list(owned.get(planet, [])) identities: list[dict[str, Any]] = [] if planet in NODES: functional = "pending" basis = "node_placement_dispositor_association_not_evaluated" else: functional = "benefic" if planet in benefics else "malefic" if planet in malefics else "neutral" basis = base["role_basis"].get(planet) if planet in yogakarakas: identities.append({"key": "yogakaraka"}) if planet == b_lord: identities.append({"key": "badhaka", "house": b_house}) if (natural == "benefic" and set(houses) & {4, 7, 10} and not set(houses) & {1, 5, 9}): identities.append({"key": "kendradhipati", "status": "conditions_not_evaluated"}) conflict = functional in ("benefic", "malefic") and natural is not None and functional != natural rows.append({ "planet": planet, "natural": natural, "natural_condition": NATURAL_CONDITION.get(planet), "owned_houses": houses, "functional": functional, "role_basis": basis, "identities": identities, "natural_functional_conflict": conflict, }) return { "status": "computed", "ascendant": asc_sign, "profile": PROFILE, "source": SOURCE, "translation": TRANSLATION, "rule_reference": base.get("rule_reference"), "yogakaraka_definition": base.get("yogakaraka_definition"), "badhaka_rule": "movable 11 / fixed 9 / dual 7 (yoga_engine.badhaka_house)", "badhaka_house": b_house, "badhaka_lord": b_lord, "kendradhipati_rule": "natural benefic owning 4/7/10 and none of 1/5/9; dosha conditions not evaluated", "neutral_meaning": "mixed / association-dependent; associations not evaluated", "rows": rows, }