Add Rangacharya comparison variant

This commit is contained in:
732642856
2026-07-17 01:03:50 +08:00
parent 1d61fbe535
commit 9bb054b0ae
13 changed files with 702 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
{
"schema_version": 1,
"created": "2026-07-16",
"cards": [
{
"id": "rangacharya_core_arudha",
"title": "Rangacharya Arudha core counting",
"status": "transcribed",
"adjudication_enabled": false,
"evidence": {
"source_ids": ["uploaded_screenshots_20260716"],
"formula_text": "Pending formula-level transcription and source verification.",
"notes": "Covers AL, A7, A10, UL, and A1-A12 variant counting."
}
},
{
"id": "active_effective_lagna",
"title": "Active Lagna and Effective Lagna",
"status": "transcribed",
"adjudication_enabled": false,
"evidence": {
"source_ids": ["uploaded_screenshots_20260716"],
"formula_text": "Pending formula-level transcription and source verification.",
"notes": "Runtime may report placeholder metadata only."
}
},
{
"id": "prakriti_sanmukha",
"title": "Prakriti Chakra and Sanmukha",
"status": "blocked",
"adjudication_enabled": false,
"blocked_reason": "formula direction and exception rules need source-card verification",
"evidence": {
"source_ids": ["uploaded_screenshots_20260716"],
"formula_text": "Blocked until rule text is verified.",
"notes": "No runtime calculation allowed yet."
}
},
{
"id": "rangacharya_special_mappings",
"title": "Special divisional and graha mappings",
"status": "blocked",
"adjudication_enabled": false,
"blocked_reason": "Krishnamisra/Somanatha Navamsa, Parivritti/Somanatha Drekkana, Pancansa, and Graha Chakra need separate fixtures",
"evidence": {
"source_ids": ["uploaded_screenshots_20260716"],
"formula_text": "Blocked until mapping tables are verified.",
"notes": "Raw descriptive output only after fixtures exist."
}
},
{
"id": "rangacharya_named_yogas",
"title": "Named Rangacharya yogas",
"status": "blocked",
"adjudication_enabled": false,
"blocked_reason": "named yogas need formula-level source cards and golden fixtures",
"evidence": {
"source_ids": ["uploaded_screenshots_20260716"],
"formula_text": "Blocked until each yoga has its own source card.",
"notes": "Includes Dhana, Nirdhana, Kemadruma, Vahana, Bandhana, Dustamarana, Saukhya, Buddhi, Raja, Manipravala, Amatya, Senadhipatya, Chandradhi/Lagnadhi, Karakamsa, Yogada, Kevala, and Aspecting Graha."
}
},
{
"id": "rangacharya_ul_family_rules",
"title": "UL relationship and family rules",
"status": "blocked",
"adjudication_enabled": false,
"blocked_reason": "relationship, child, miscarriage, and adoption claims need real-case calibration",
"evidence": {
"source_ids": ["uploaded_screenshots_20260716"],
"formula_text": "Blocked until source and case validation.",
"notes": "May only become rule-hit reporting before calibration."
}
},
{
"id": "article_warehouse_future_tracks",
"title": "Future article-warehouse tracks",
"status": "blocked",
"adjudication_enabled": false,
"blocked_reason": "registered only; out of Rangacharya runtime scope",
"evidence": {
"source_ids": ["local_article_warehouse_20260716"],
"formula_text": "Blocked until hash, license, and technique-level extraction.",
"notes": "Includes Tithi Lord, Panchapakshi, Rashi Tulya Navamsa, Bhrigu Pada Dasha, Tajika, Darakaraka, and spouse rules."
}
}
]
}
+127
View File
@@ -0,0 +1,127 @@
{
"schema_version": 1,
"created": "2026-07-16",
"sources": [
{
"id": "uploaded_screenshots_20260716",
"kind": "user_uploaded_screenshots",
"paths": [
"/Users/wuyongnaren/文件仓库/印度占星文章/260716/IMG_3502.PNG",
"/Users/wuyongnaren/文件仓库/印度占星文章/260716/IMG_3503.PNG",
"/Users/wuyongnaren/文件仓库/印度占星文章/260716/IMG_3504.PNG",
"/Users/wuyongnaren/文件仓库/印度占星文章/260716/IMG_3505.PNG",
"/Users/wuyongnaren/文件仓库/印度占星文章/260716/IMG_3506.PNG",
"/Users/wuyongnaren/文件仓库/印度占星文章/260716/IMG_3507.PNG"
],
"sha256": {
"IMG_3502.PNG": "1cf628c3f5dfac372c719eba442f0cc8325114fabea4cc01855e00247bc9c031",
"IMG_3503.PNG": "f3c1f3c67a3add9c6c586e60e18186e86549ca4a5b5a1e6a6c07cc4fe223e1de",
"IMG_3504.PNG": "6c884011b1fe458da2f23a4e3e5acaf48c6ea72cd402b51b2942b3a28cee5de1",
"IMG_3505.PNG": "16af004f553fde2c1ab5d0bc7968deba46040a02e7726f735f174523e6bdccee",
"IMG_3506.PNG": "a8732bc73767983130683152079eb44697d4686435452e71d2e742f8b16ed888",
"IMG_3507.PNG": "1ff2bbc69a3fe1dab17d742437000d9938d9b5bb5e818831dafc9bf0e05a152e"
},
"license": "user_private_reference",
"privacy": "private",
"runtime_use": "reference_only_until_formula_verified",
"extraction_status": "located_and_hashed_ocr_blocked_tesseract_missing"
},
{
"id": "local_article_warehouse_20260716",
"kind": "local_research_archive",
"path": "/Users/wuyongnaren/文件仓库/印度占星文章",
"license": "unknown",
"privacy": "local_research",
"runtime_use": "manifest_only_until_hash_and_license_review"
},
{
"id": "kimi_agent_archive_20260716",
"kind": "local_training_archive",
"path": "/Users/wuyongnaren/Downloads/_整理候选/安装包与压缩包/Kimi_Agent_高维印度占星师.zip",
"license": "unknown",
"privacy": "local_research",
"runtime_use": "reference_only_until_license_review"
},
{
"id": "vedastro_official",
"kind": "external_oracle",
"url": "https://github.com/VedAstro/VedAstro",
"license": "MIT",
"privacy": "public",
"runtime_use": "oracle_raw_reference"
},
{
"id": "pyjhora_official",
"kind": "external_oracle",
"url": "https://github.com/naturalstupid/PyJHora",
"license": "AGPL-3.0",
"privacy": "public",
"runtime_use": "isolated_external_process_only"
},
{
"id": "jyotishganit_official",
"kind": "external_oracle",
"url": "https://github.com/northtara/jyotishganit",
"license": "MIT",
"privacy": "public",
"runtime_use": "oracle_raw_reference"
},
{
"id": "dashaflow_official",
"kind": "external_formula_reference",
"url": "https://github.com/adarshj322/dashaflow",
"license": "MIT",
"privacy": "public",
"runtime_use": "formula_reference_only"
}
],
"validation_ladder": [
"transcribed",
"source_verified",
"golden_verified",
"engine_cross_checked",
"case_calibrated",
"adjudication_enabled",
"blocked"
],
"rules": [
{
"id": "rangacharya_core_arudha",
"label": "Rangacharya Arudha core counting",
"source_ids": ["uploaded_screenshots_20260716"],
"status": "transcribed",
"adjudication_enabled": false
},
{
"id": "active_effective_lagna",
"label": "Active Lagna and Effective Lagna",
"source_ids": ["uploaded_screenshots_20260716"],
"status": "transcribed",
"adjudication_enabled": false
},
{
"id": "rangacharya_special_mappings",
"label": "Krishnamisra/Somanatha Navamsa, Parivritti/Somanatha Drekkana, Pancansa Graha, Graha Chakra",
"source_ids": ["uploaded_screenshots_20260716"],
"status": "blocked",
"adjudication_enabled": false,
"blocked_reason": "needs formula-level source cards before runtime use"
},
{
"id": "rangacharya_named_yogas",
"label": "Dhana/Nirdhana/Kemadruma and other named yogas",
"source_ids": ["uploaded_screenshots_20260716"],
"status": "blocked",
"adjudication_enabled": false,
"blocked_reason": "needs formula-level source cards before runtime use"
},
{
"id": "article_warehouse_future_tracks",
"label": "Tithi Lord, Panchapakshi, Rashi Tulya Navamsa, Bhrigu Pada Dasha, Tajika, Darakaraka, spouse rules",
"source_ids": ["local_article_warehouse_20260716"],
"status": "blocked",
"adjudication_enabled": false,
"blocked_reason": "registered for later source governance; out of Rangacharya Phase 1 runtime scope"
}
]
}
+34
View File
@@ -1695,6 +1695,40 @@
},
"conclusion_policy": "Supporting evidence only; it can raise/lower confidence but cannot by itself decide an event or timing claim."
},
"rangacharya_jaimini_variant": {
"audit_label": "Rangacharya Jaimini Variant",
"commands": [
"jaimini"
],
"conclusion_policy": "Display current-vs-variant differences only; do not use for verdicts or timing.",
"domains": [
"jaimini",
"arudha",
"experimental",
"audit"
],
"entry_type": "experimental_variant",
"evidence_role": "comparison_only",
"knowledge_refs": [
"references/rangacharya_source_manifest.json",
"references/rangacharya_source_cards.json",
"docs/superpowers/specs/2026-07-16-rangacharya-vedastro-design.md"
],
"missing_impact": "Rangacharya-specific Arudha and named-yoga rules remain unavailable for adjudication until source cards, golden fixtures, oracle comparison, and case calibration pass.",
"name": "Rangacharya / Iranganti Jaimini Variant",
"note": "Rangacharya variant has a tested current-vs-variant diff path and API variant output, but adjudication remains disabled until source-card validation and golden fixtures pass.",
"output_paths": [
"result.rangacharya",
"result.rangacharya_diff"
],
"status": "comparison-only",
"user_visibility": "expert_audit",
"verification_level": {
"calculation": "experimental",
"prediction": "blocked",
"rule": "blocked"
}
},
"rashi_tulya_navamsa": {
"name": "Rashi Tulya Navamsa / 本命对分盘分析",
"domains": [
+13
View File
@@ -5947,6 +5947,13 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
allowed_modes = {'all', 'karaka', 'dasha', 'karakamsha', 'arudha', 'special'}
if mode not in allowed_modes:
raise BadRequest(f'mode must be one of: {", ".join(sorted(allowed_modes))}')
variant = body.get('variant', 'current')
if not isinstance(variant, str):
raise BadRequest('variant must be a string')
variant = variant.strip().lower() or 'current'
allowed_variants = {'current', 'rangacharya', 'all'}
if variant not in allowed_variants:
raise BadRequest(f'variant must be one of: {", ".join(sorted(allowed_variants))}')
antardasha = bool(body.get('antardasha', False))
year = self._get_int(body, 'year', datetime.now().year, 1800, 2400)
month = self._get_int(body, 'month', 1, 1, 12)
@@ -5976,6 +5983,12 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
if mode in ('all', 'arudha'):
result['arudha_padas'] = jaimini.calc_arudha_padas(asc_sign_idx, planet_lons)
result['graha_padas'] = jaimini.calc_graha_padas(planet_lons)
if variant in ('rangacharya', 'all'):
rangacharya = _load_local_module('rangacharya')
rangacharya_result = rangacharya.calc_rangacharya_variant(asc_sign_idx, planet_lons)
result['rangacharya'] = rangacharya_result
current_arudha = result.get('arudha_padas') or jaimini.calc_arudha_padas(asc_sign_idx, planet_lons)
result['rangacharya_diff'] = rangacharya.diff_current_vs_rangacharya(current_arudha, rangacharya_result)
if mode in ('all', 'special'):
result['special_lagnas'] = jaimini.calc_special_lagnas(asc_sign_idx, hour, minute + second / 60.0)
return {
+143
View File
@@ -0,0 +1,143 @@
"""Experimental Rangacharya/Jaimini variant.
All outputs are blocked from adjudication until formula-level validation passes.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any, Dict, Mapping
SIGNS = [
"Aries",
"Taurus",
"Gemini",
"Cancer",
"Leo",
"Virgo",
"Libra",
"Scorpio",
"Sagittarius",
"Capricorn",
"Aquarius",
"Pisces",
]
SOURCE_CARDS_PATH = Path(__file__).resolve().parent.parent / "references" / "rangacharya_source_cards.json"
class RangacharyaValidationError(RuntimeError):
pass
def _source_cards() -> Dict[str, Dict[str, Any]]:
try:
data = json.loads(SOURCE_CARDS_PATH.read_text(encoding="utf-8"))
except (FileNotFoundError, json.JSONDecodeError):
return {}
return {str(card.get("id")): dict(card) for card in data.get("cards", []) if card.get("id")}
def _card_meta(card_id: str) -> Dict[str, Any]:
card = _source_cards().get(card_id, {})
status = str(card.get("status") or "blocked")
meta = {
"source_card_id": card_id,
"source_card_status": status,
"validation_status": status,
"adjudication_enabled": False,
}
if status != "source_verified":
meta["blocked_reason"] = card.get("blocked_reason") or "source card is not verified for adjudication"
return meta
def _sign_name(index: int) -> str:
return SIGNS[index % 12]
def _placeholder_pada(label: str, asc_sign_idx: int, source_house: int) -> Dict[str, Any]:
sign_idx = (asc_sign_idx + source_house - 1) % 12
return {
"label": label,
"sign": _sign_name(sign_idx),
"sign_index": sign_idx,
"source_house": source_house,
"note": "Rangacharya formula pending source-card implementation",
**_card_meta("rangacharya_core_arudha"),
}
def calc_rangacharya_variant(asc_sign_idx: int, planet_longitudes: Mapping[str, float]) -> Dict[str, Any]:
asc_sign_idx %= 12
arudha_padas = {
"AL": _placeholder_pada("AL", asc_sign_idx, 1),
"A7": _placeholder_pada("A7", asc_sign_idx, 7),
"A10": _placeholder_pada("A10", asc_sign_idx, 10),
"UL": _placeholder_pada("UL", asc_sign_idx, 12),
}
return {
"variant": "rangacharya",
"status": "experimental_not_for_adjudication",
"adjudication_enabled": False,
"source_status": "transcribed",
"active_lagna": {
"sign": _sign_name(asc_sign_idx),
**_card_meta("active_effective_lagna"),
},
"effective_lagna": {
"sign": _sign_name(asc_sign_idx),
**_card_meta("active_effective_lagna"),
},
"arudha_padas": arudha_padas,
"input_planets_present": sorted(planet_longitudes),
}
def _flatten(prefix: str, value: Any) -> Dict[str, Any]:
if not isinstance(value, dict):
return {prefix: value}
rows: Dict[str, Any] = {}
for key, child in value.items():
child_key = f"{prefix}.{key}" if prefix else str(key)
rows.update(_flatten(child_key, child))
return rows
def diff_current_vs_rangacharya(current: Mapping[str, Any], variant: Mapping[str, Any]) -> Dict[str, Any]:
current_flat = _flatten("", dict(current))
variant_flat = _flatten("", dict(variant.get("arudha_padas", variant)))
differences = []
for key in sorted(set(current_flat) | set(variant_flat)):
current_value = current_flat.get(key)
variant_value = variant_flat.get(key)
if current_value != variant_value:
differences.append({"key": key, "current": current_value, "rangacharya": variant_value})
return {
"current_algorithm": "current_jaimini",
"variant_algorithm": "rangacharya",
"adjudication_enabled": False,
"differences": differences,
}
def validation_summary(result: Mapping[str, Any]) -> Dict[str, Any]:
statuses = []
for key, value in _flatten("", dict(result)).items():
if key.endswith("validation_status"):
statuses.append(str(value))
blocking = sorted({status for status in statuses if status != "adjudication_enabled"})
return {
"adjudication_enabled": bool(result.get("adjudication_enabled")) and not blocking,
"blocking_statuses": blocking,
}
def assert_adjudication_allowed(result: Mapping[str, Any]) -> None:
summary = validation_summary(result)
if not summary["adjudication_enabled"]:
raise RangacharyaValidationError(
"Rangacharya variant is not adjudication-enabled; validation gates are incomplete"
)
+58
View File
@@ -0,0 +1,58 @@
"""Readiness report for the experimental Rangacharya variant."""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any, Dict
ROOT = Path(__file__).resolve().parent.parent
CARDS_PATH = ROOT / "references" / "rangacharya_source_cards.json"
MANIFEST_PATH = ROOT / "references" / "rangacharya_source_manifest.json"
def _load_json(path: Path) -> Dict[str, Any]:
try:
return json.loads(path.read_text(encoding="utf-8"))
except (FileNotFoundError, json.JSONDecodeError):
return {}
def build_report() -> Dict[str, Any]:
cards_payload = _load_json(CARDS_PATH)
manifest_payload = _load_json(MANIFEST_PATH)
cards = {}
blocked = []
transcribed = []
for card in cards_payload.get("cards", []):
card_id = str(card.get("id") or "")
if not card_id:
continue
status = str(card.get("status") or "blocked")
adjudication_enabled = bool(card.get("adjudication_enabled"))
cards[card_id] = {
"status": status,
"adjudication_enabled": adjudication_enabled,
"blocked_reason": card.get("blocked_reason") or "",
}
if status == "blocked" or not adjudication_enabled:
blocked.append(card_id)
if status == "transcribed":
transcribed.append(card_id)
return {
"scope": "rangacharya_readiness",
"manifest_available": bool(manifest_payload),
"source_cards_available": bool(cards_payload),
"adjudication_enabled": bool(cards) and not blocked,
"card_count": len(cards),
"blocked_count": len(blocked),
"transcribed_count": len(transcribed),
"blocked_cards": blocked,
"transcribed_cards": transcribed,
"cards": cards,
}
if __name__ == "__main__":
print(json.dumps(build_report(), ensure_ascii=False, indent=2, sort_keys=True))
+50
View File
@@ -0,0 +1,50 @@
import os
import sys
SCRIPTS = os.path.join(os.path.dirname(__file__), "..", "scripts")
if SCRIPTS not in sys.path:
sys.path.insert(0, SCRIPTS)
from jyotish_api_server import JyotishAPIHandler # noqa: E402
def _handler() -> JyotishAPIHandler:
return JyotishAPIHandler.__new__(JyotishAPIHandler)
def _planets():
return {
"Sun": {"lon": 10.0},
"Moon": {"lon": 45.0},
"Mars": {"lon": 80.0},
"Mercury": {"lon": 110.0},
"Jupiter": {"lon": 145.0},
"Venus": {"lon": 200.0},
"Saturn": {"lon": 250.0},
"Rahu": {"lon": 300.0},
"Ketu": {"lon": 120.0},
}
def test_jaimini_default_does_not_include_rangacharya():
result = _handler()._compute_jaimini({
"mode": "arudha",
"ascendant": {"lon": 0.0},
"planets": _planets(),
})
assert "rangacharya" not in result["result"]
assert "rangacharya_diff" not in result["result"]
def test_jaimini_variant_all_includes_current_variant_and_diff():
result = _handler()._compute_jaimini({
"mode": "arudha",
"variant": "all",
"ascendant": {"lon": 0.0},
"planets": _planets(),
})
assert result["result"]["rangacharya"]["adjudication_enabled"] is False
assert result["result"]["rangacharya_diff"]["adjudication_enabled"] is False
assert result["result"]["rangacharya"]["arudha_padas"]["AL"]["source_card_status"] == "transcribed"
assert result["result"]["rangacharya"]["active_lagna"]["source_card_id"] == "active_effective_lagna"
@@ -0,0 +1,16 @@
import pytest
from scripts import rangacharya
def test_assert_adjudication_allowed_rejects_default_variant():
result = rangacharya.calc_rangacharya_variant(0, {"Sun": 10.0})
with pytest.raises(rangacharya.RangacharyaValidationError):
rangacharya.assert_adjudication_allowed(result)
def test_validation_summary_lists_blocking_rules():
result = rangacharya.calc_rangacharya_variant(0, {"Sun": 10.0})
summary = rangacharya.validation_summary(result)
assert summary["adjudication_enabled"] is False
assert summary["blocking_statuses"]
+14
View File
@@ -0,0 +1,14 @@
from scripts import rangacharya_readiness
def test_readiness_reports_blocked_until_no_adjudication_cards():
report = rangacharya_readiness.build_report()
assert report["scope"] == "rangacharya_readiness"
assert report["adjudication_enabled"] is False
assert report["blocked_count"] >= 1
assert "rangacharya_core_arudha" in report["cards"]
def test_readiness_does_not_expose_secrets():
report = rangacharya_readiness.build_report()
assert "sk_live_" not in str(report)
+17
View File
@@ -0,0 +1,17 @@
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REGISTRY = ROOT / "references" / "technique_registry.json"
def test_rangacharya_variant_registered_as_comparison_only():
techniques = json.loads(REGISTRY.read_text(encoding="utf-8"))["techniques"]
entry = techniques["rangacharya_jaimini_variant"]
assert entry["status"] == "comparison-only"
assert entry["verification_level"]["calculation"] == "experimental"
assert entry["verification_level"]["prediction"] == "blocked"
assert entry["conclusion_policy"] == "Display current-vs-variant differences only; do not use for verdicts or timing."
assert entry["evidence_role"] == "comparison_only"
assert "references/rangacharya_source_cards.json" in entry["knowledge_refs"]
+50
View File
@@ -0,0 +1,50 @@
import json
from pathlib import Path
CARDS = Path("references/rangacharya_source_cards.json")
REQUIRED_CARD_IDS = {
"rangacharya_core_arudha",
"active_effective_lagna",
"prakriti_sanmukha",
"rangacharya_special_mappings",
"rangacharya_named_yogas",
"rangacharya_ul_family_rules",
"article_warehouse_future_tracks",
}
def _cards():
return json.loads(CARDS.read_text(encoding="utf-8"))
def test_source_cards_exist_with_required_schema():
data = _cards()
assert data["schema_version"] == 1
assert isinstance(data["cards"], list)
for card in data["cards"]:
assert card["id"]
assert card["status"] in {
"transcribed",
"source_verified",
"golden_verified",
"engine_cross_checked",
"case_calibrated",
"blocked",
}
assert card["adjudication_enabled"] is False
assert "evidence" in card
def test_source_cards_cover_phase2_rule_groups():
data = _cards()
found = {card["id"] for card in data["cards"]}
assert REQUIRED_CARD_IDS <= found
def test_source_cards_do_not_contain_secrets():
text = CARDS.read_text(encoding="utf-8")
assert "sk_live_" not in text
assert "VEDASTRO_API_KEY" not in text
+50
View File
@@ -0,0 +1,50 @@
import json
from pathlib import Path
MANIFEST = Path("references/rangacharya_source_manifest.json")
def test_manifest_exists_and_has_required_sections():
data = json.loads(MANIFEST.read_text(encoding="utf-8"))
assert data["schema_version"] == 1
assert "sources" in data
assert "rules" in data
assert "validation_ladder" in data
def test_manifest_does_not_contain_secrets():
text = MANIFEST.read_text(encoding="utf-8")
assert "sk_live_" not in text
assert "api_key" not in text.lower()
def test_rules_default_below_adjudication():
data = json.loads(MANIFEST.read_text(encoding="utf-8"))
for rule in data["rules"]:
assert rule["status"] in {
"transcribed",
"source_verified",
"golden_verified",
"engine_cross_checked",
"case_calibrated",
"blocked",
}
assert rule["status"] != "adjudication_enabled"
assert rule["adjudication_enabled"] is False
def test_screenshot_source_paths_are_located_and_hashed():
data = json.loads(MANIFEST.read_text(encoding="utf-8"))
source = next(item for item in data["sources"] if item["id"] == "uploaded_screenshots_20260716")
assert source["extraction_status"] == "located_and_hashed_ocr_blocked_tesseract_missing"
assert len(source["paths"]) == 6
assert all("/文件仓库/印度占星文章/260716/" in path for path in source["paths"])
assert set(source["sha256"]) == {
"IMG_3502.PNG",
"IMG_3503.PNG",
"IMG_3504.PNG",
"IMG_3505.PNG",
"IMG_3506.PNG",
"IMG_3507.PNG",
}
+42
View File
@@ -0,0 +1,42 @@
from scripts import rangacharya
SAMPLE_LONGS = {
"Sun": 10.0,
"Moon": 45.0,
"Mars": 80.0,
"Mercury": 110.0,
"Jupiter": 145.0,
"Venus": 200.0,
"Saturn": 250.0,
"Rahu": 300.0,
"Ketu": 120.0,
}
def test_variant_result_is_experimental_and_not_for_adjudication():
result = rangacharya.calc_rangacharya_variant(0, SAMPLE_LONGS)
assert result["variant"] == "rangacharya"
assert result["adjudication_enabled"] is False
assert result["status"] == "experimental_not_for_adjudication"
def test_variant_includes_core_sections():
result = rangacharya.calc_rangacharya_variant(0, SAMPLE_LONGS)
assert "source_status" in result
assert "arudha_padas" in result
assert "active_lagna" in result
assert "effective_lagna" in result
assert result["arudha_padas"]["AL"]["source_card_id"] == "rangacharya_core_arudha"
assert result["arudha_padas"]["AL"]["source_card_status"] == "transcribed"
assert result["active_lagna"]["source_card_id"] == "active_effective_lagna"
assert result["effective_lagna"]["source_card_id"] == "active_effective_lagna"
def test_diff_marks_algorithm_names():
current = {"AL": {"sign": "Aries"}}
variant = {"arudha_padas": {"AL": {"sign": "Taurus"}}}
diff = rangacharya.diff_current_vs_rangacharya(current, variant)
assert diff["current_algorithm"] == "current_jaimini"
assert diff["variant_algorithm"] == "rangacharya"
assert diff["differences"][0]["key"] == "AL.sign"