- affliction_evidence / career_semantic_profile / career_analysis (functional roles, affliction signals, user-industry gate) - sade_sati.build_saturn_cycle_timeline with bisected ingress moments (FLG_SIDEREAL frame, BUG-1275) - Chesta profile candidates and PL9 AV cell profile as sidecars (shadbala.py / ashtakavarga.py stay frozen, ERR-110) - Raman Sahams in the annual pack; book rule packets + admission gate - consultation_native_layers: affliction_evidence, career_analysis, sade_sati_timeline, shadbala_chesta_profiles, book_rules, annual_tajika.raman_sahams Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbBXoyn7Hj3LCW3BUs5bZ8
84 lines
3.8 KiB
Python
84 lines
3.8 KiB
Python
"""Evidence-only Parashari affliction signals.
|
|
|
|
This module deliberately does not assign a universal weight or verdict. It
|
|
records which signals were observed so readers can keep pressure/distance
|
|
language conditional and apply an explicit domain profile when needed.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
|
|
MALefic_NATURAL = {'Saturn', 'Mars', 'Sun', 'Rahu', 'Ketu'}
|
|
AFFLICTION_SIGNAL_TYPES = (
|
|
'conjunction', 'graha_aspect', 'functional_malefic', 'debilitated',
|
|
'combust', 'dusthana_placement', 'varga_pressure',
|
|
)
|
|
|
|
|
|
def validate_affliction_weight_profile(weights: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
"""Validate one shared signal-weight profile for every reading domain."""
|
|
supplied = weights or {}
|
|
missing = [name for name in AFFLICTION_SIGNAL_TYPES if name not in supplied]
|
|
invalid = [name for name, value in supplied.items() if name in AFFLICTION_SIGNAL_TYPES and (not isinstance(value, (int, float)) or value < 0)]
|
|
return {
|
|
'status': 'ready' if not missing and not invalid else 'parameter_sensitive',
|
|
'weights': {name: supplied.get(name) for name in AFFLICTION_SIGNAL_TYPES},
|
|
'missing': missing,
|
|
'invalid': invalid,
|
|
'domains': ['career', 'parents', 'marriage', 'health', 'education'],
|
|
'claim_boundary': 'A shared profile is required before comparing pressure across domains; signal count alone is not a verdict.',
|
|
}
|
|
|
|
|
|
def _house_distance(source: int, target: int) -> int:
|
|
return (target - source) % 12 + 1
|
|
|
|
|
|
def collect_affliction_evidence(
|
|
*,
|
|
target_house: int,
|
|
target_lord: str | None,
|
|
planets: dict[str, dict[str, Any]],
|
|
functional_malefics: set[str] | list[str] | None = None,
|
|
varga_pressure: bool = False,
|
|
) -> dict[str, Any]:
|
|
"""Collect raw signals for a house/lord/karaka line.
|
|
|
|
``planets`` uses 1-based houses and optional ``debilitated``/``combust``
|
|
flags. Missing data is retained as a limitation rather than inferred.
|
|
"""
|
|
functional_malefics = set(functional_malefics or ())
|
|
target = planets.get(target_lord or '', {})
|
|
target_house_no = target.get('house')
|
|
signals: list[dict[str, Any]] = []
|
|
|
|
if target_lord and target_house_no is not None:
|
|
for name in ('Rahu', 'Ketu', 'Saturn', 'Mars'):
|
|
if name in planets and planets[name].get('house') == target_house_no:
|
|
signals.append({'type': 'conjunction', 'planet': name, 'basis': 'same_house'})
|
|
for name in ('Saturn', 'Mars'):
|
|
source_house = planets.get(name, {}).get('house')
|
|
if source_house is not None and _house_distance(source_house, target_house_no) in ({'Saturn': {3, 7, 10}, 'Mars': {4, 7, 8}}[name]):
|
|
signals.append({'type': 'graha_aspect', 'planet': name, 'basis': 'classical_special_aspect'})
|
|
if target_lord and target_lord in functional_malefics:
|
|
signals.append({'type': 'functional_malefic', 'planet': target_lord, 'basis': 'functional_profile'})
|
|
for flag in ('debilitated', 'combust'):
|
|
if target.get(flag) is True:
|
|
signals.append({'type': flag, 'planet': target_lord, 'basis': 'planet_state'})
|
|
if target_house_no is not None and _house_distance(target_house, target_house_no) in (6, 8, 12):
|
|
signals.append({'type': 'dusthana_placement', 'planet': target_lord, 'basis': 'house_distance'})
|
|
if varga_pressure:
|
|
signals.append({'type': 'varga_pressure', 'planet': target_lord, 'basis': 'explicit_varga_input'})
|
|
|
|
return {
|
|
'status': 'parameter_sensitive',
|
|
'target_house': target_house,
|
|
'target_lord': target_lord,
|
|
'signals': signals,
|
|
'signal_count': len(signals),
|
|
'pressure_language_allowed': bool(signals),
|
|
'claim_boundary': 'Signals indicate possible pressure or distance only; no universal weight, threshold, presence/absence, diagnosis, or event certainty is inferred.',
|
|
}
|