- Functional default = PVR Table 30 (functional_profile=pvr_table_30); the former product table is bphs_exception_profile, scored only when named (rectification names it, scores unchanged). profile_divergence shows both roles side by side; movable-lagna Moon by the table's paksha note; dual-lordship note; prior rulings BUG-1570/1590/#166 Q4 checked against Table 30. - Shadbala: six open items labelled parameter_sensitive; every option combination computed; only planets whose pass/fail flips get minimum_check=parameter_sensitive. - Vimshottari AD/PD rows carry parent_period_start / visible_period_start; the consult card cuts the balance MD from its full parent and clips at birth. - KP DBA: full first parent, clipped at birth (no balance rescale). - Mudda: balance fields + balance_method (angle default, time comparison apart), nominal_year_days, her crossing wording; Harsha combined blocked reason lists items. - D30 _meta describes the unequal parts (as her r40). - Golden recaptured with TTL=0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbBXoyn7Hj3LCW3BUs5bZ8
118 lines
6.2 KiB
Python
118 lines
6.2 KiB
Python
"""Per-planet functional role table for the chart page (issue #163).
|
|
|
|
Reads ``functional_benefics.derive_functional_benefic_malefic`` (default profile
|
|
``pvr_table_30`` since 占星师 2026-11-08 第五轮裁定 3; the former product table
|
|
``bphs_ch34_general_with_sign_exceptions_v3_166q4`` is shown beside it where the
|
|
two differ) 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,
|
|
moon_paksha_from_planets, 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,
|
|
moon_paksha_from_planets, 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, planets: Any = None) -> dict[str, Any]:
|
|
# 占星师 2026-11-08 第五轮裁定 3:默认 PVR 表 30;原产品表(bphs_exception_profile)只并列对照。
|
|
base = derive_functional_benefic_malefic(ascendant, moon_paksha=moon_paksha_from_planets(planets))
|
|
asc_sign = normalize_sign(ascendant)
|
|
if base.get("status") != "used" or asc_sign is None:
|
|
return {"status": "blocked", "reason": "unknown_ascendant_sign", "functional_profile": base.get("functional_profile"), "source": SOURCE}
|
|
benefics = set(base["functional_benefics"])
|
|
malefics = set(base["functional_malefics"])
|
|
neutrals = set(base["functional_neutrals"])
|
|
divergence = base.get("profile_divergence") or {}
|
|
other_profile = divergence.get("other_profile")
|
|
other_by_planet = {cell["planet"]: cell.get(other_profile) for cell in divergence.get("cells") or []}
|
|
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" if planet in neutrals else "pending")
|
|
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,
|
|
# Both profiles shown where they differ, never merged (第五轮裁定 3).
|
|
**({"other_profile_functional": {"profile": other_profile, "functional": other_by_planet[planet]}}
|
|
if planet in other_by_planet else {}),
|
|
})
|
|
return {
|
|
"status": "computed",
|
|
"ascendant": asc_sign,
|
|
"profile": base.get("profile"),
|
|
"functional_profile": base.get("functional_profile"),
|
|
"profile_divergence": divergence,
|
|
"movable_lagna_moon": base.get("movable_lagna_moon"),
|
|
"dual_lordship_note": base.get("dual_lordship_note"),
|
|
"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,
|
|
}
|