sync(upstream): port astrologer branch d09feab7 (public-sync-20261107) calculation changes

Product 2026-11-07: sync the astrologer's working branch (ea2a9d65..d09feab7).

- 0266b6ad: year lord picks by the native five-fold Panchavargiya only; a
  PyJHora score (research switch) is attached per candidate as
  external_oracle_comparison (observation_only_not_selection_input) and
  never selects. panchavargiya_selection_policy on the result; candidate_list
  and the reader-report candidate table carry the five components and raw total.
  The legacy Sun/Mars/Saturn table relabel is not applicable: calc_sodhita_av
  was already removed here (BUG-1209).
- 8c4bff8a + a5b75886: Sodhita no longer votes in finance adjudication; the
  bridge keeps raw 2/11 scores with prediction_scoring_allowed false.
- ba8e36ec: finance delivery inventory (raw, no vote), wrapped producer status
  counts as blocked in machine-evidence sections, transit section, report gate
  evidence_claims bindings, finance support review readiness.
- 5962368d: dispositor chain dispositor_sign is the dispositor's own sign.
- 8813c851: BCP observed at the reading date (bcp_reference, PL9 row).
- d09feab7: nakshatra_advanced.calculate_stellar_jeeva_sareera and the
  nadi-stellar-roles CLI; specialized-jyotish SKILL.md, usage packs, ontology
  and gap scanner mirrored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyn7Hj3LCW3BUs5bZ8
This commit is contained in:
2026-11-07 20:03:44 +08:00
co-authored by Claude Opus 5.5
parent 8d56758ac9
commit c9953c05f2
22 changed files with 837 additions and 46 deletions
@@ -0,0 +1,100 @@
# Stellar Jeeva/Sareera: bounded native calculation
## Scope and current-window exploration
Repository maintenance, not a personal interpretation or a customer export.
Starting HEAD b88fdb3b was clean on codex/public-sync-20261107. Pre-work check
passed, including external-adapter diagnostics and remote visibility. Seven
worktrees were listed. Existing current-capability index, report authority,
PL9 dashboard and error ledger were consulted. Governance reports only
main_worktree_dirty during this implementation; that is not an adapter failure.
Earlier whole-machine inventory is documented in knowledge_call_chain_followup_2026_11_07.md;
it is not represented as a fresh full-content scan here. This round searched
the native scripts, tests, specialized skill, review/extraction queues and the
registered local stellar book. No mirror, frozen tree or personal report changed.
Reuse: find_nakshatra for half-open stellar boundaries; Jaimini module's
traditional sign-owner table only (not its dual-owner/strength rules);
existing Nadi review queue for discovery; native engine CLI for actual execution.
## Source record and conflict
Local corpus registration:
`Nadi_Stellar Effects -- Jyotish -- 2022 -- f8eef571147711ee4eb4479a35bd2428 -- Anna’s Archive.md`.
Text SHA256: `f30db43d842a9484316d6608f6385dfabe7232265469e67938f42459e25c4b42`.
The filename year is not a verified publication edition. Chapter 4 is the
Jeeva/Sareera chapter in this Kalamsa-Gunas/Meena source. The book cites
N. V. Raghava Chary, Practical Stellar Astrology, second edition 2004,
pp108–111; that original edition has NOT been independently checked.
| Topic | PDF page | Printed page | Corpus line locator |
|---|---:|---:|---:|
| Four starting rules | 87 | 84 | 4513 onward |
| Conflicting exalted-Venus example | 89 | 86 | 4626 onward |
| Detailed controlling-planet hierarchy and Saturn exception | 92 | 89 | 4800 onward |
| Comparative summary with different qualifications | 93–94 | 90–91 | 4882 onward |
| Anonymous structural table | 95–99 | 92–96 | 4979 onward |
Used search_jyotish_books.py with the registered filename, query Jeeva and
read-unit 92. Visually inspected original PDF pages 89 and 92. Page89 actually
names Mercury then Jupiter as Sareera in the same example; it is a printed
conflict, not merely OCR. No copyrighted source pages/text are added to Git.
The explicit runtime profile follows the detailed procedure on printed p89:
original Saturn in Anuradha retains control; otherwise sign owner, exaltation,
stellar Digbala, own-star among Digbala candidates, listed strength order,
then retain the original seed. It does not silently substitute the later
comparative list's broader own-star/debility qualifications. The own-star
Sareera fallback applies to the final Jeeva, as the printed pp93–95 table
demonstrates even when the initial bhava lord is different.
## Callable contract
`scripts.nakshatra_advanced.calculate_stellar_jeeva_sareera` accepts:
- Exactly nine already-sidereal planetary longitudes, one explicit bhava lord,
explicit ayanamsa label and mean/true node mode. It does not derive birth data.
- Optional stellar_digbala boolean map. Missing values are not false. Numeric
Shadbala scores are rejected. These flags remain caller-supplied assumptions.
- Optional nodal_exaltation_signs: explicit zero-based sign or null for each node.
No cross-school nodal exaltation is silently adopted. If this matters to an
unresolved group and is absent, that branch is blocked.
Native CLI: `python3 scripts/jyotish_engine.py nadi-stellar-roles --request <local.json>`.
The local JSON fields are the function's argument names. It prints JSON without
writing charts, profiles or exports. Roles, selected profile, source identity,
stage seeds/companions, decision reasons and missing inputs remain visible.
Status is executed or blocked; evidence is parameter_sensitive;
external_verified and prediction_scoring_allowed remain false.
The existing Nadi review queue now points to the real function. A queue listing
still says not_executed and does not approve a rule or bypass admission.
No customer frozen pin, annual score, finance score, Dasha date or interpretation
is changed. No automatic forecasting use is claimed.
## External cross-check, not new rule admission
- [Practitioner article, 2012-02-28](https://srigurucriticalcareastrologer.blogspot.com/2012/02/nadi-jyothisha-jeeva-and-sareera.html):
own-star and substitution examples overlap the existing book. Its Venus example
names Mercury without the following Jupiter sentence. Corroboration supports
an editorial-error hypothesis, not a verified erratum or predictive validation.
- [Iranganti Rangacharya article](https://saptarishisshop.com/jeeva-sareera-theory-of-prediction-i-by-iranganti-rangacharya/):
distinct same-name tradition, not admitted into this Meena profile.
No new web-only rule enters runtime; these are source-comparison/discovery URLs.
No community testimony or repeated example is treated as independent calibration.
## Verification and remaining work
269 related tests passed, including native CLI execution, seven traditional
planet roles from the anonymous source table under explicit stellar assumptions,
own-star fallback, owner/exaltation precedence, seed-specific Saturn exception,
missing inputs, invalid numbers and Nadi queue admission boundaries.
That table replay is not an independent oracle; the uncertain Mars OCR minute
is represented by a same-star/sign value and is not a precise longitude check.
Still open: source-specific automatic Nakshatra Digbala and its aspect rules;
adjudication of the comparative-list differences and nodal exaltations;
independent numerical cases; predictive calibration; optional report routing.
This is source-bounded structural execution, not global closure or permission
to turn these roles alone into financial, medical or event predictions.
+22 -10
View File
@@ -2090,18 +2090,25 @@ def _derive_sodhita_finance_support(ashtakavarga: Any, asc_sign: Optional[str])
sign_to_score[str(sign)] = float(score)
raw_scores: Dict[str, Any] = {}
numeric_scores: Dict[int, float] = {}
for house in (2, 11):
sign = _SIGNS[(asc_idx + house - 1) % 12]
if sign in sign_to_score:
numeric_scores[house] = sign_to_score[sign]
value = sign_to_score[sign]
raw_scores[str(house)] = int(value) if value.is_integer() else value
base["raw_scores"] = raw_scores
if len(numeric_scores) == 2 and max(numeric_scores.values()) <= 19:
base["level"] = "obstructive"
base["signals"] = ["sodhita_wealth_friction"]
producer = _safe_get(ashtakavarga, "sodhita") or {}
# Upstream a5b75886: no source-adjudicated finance threshold exists for this
# bridge. Caller method/permission labels cannot make the legacy <=19 penalty valid.
known_legacy = (
producer.get("method_id") == "legacy_malefic_contribution_subtraction_v1"
or producer.get("method") == "Sodhita Ashtakavarga (BPHS标准)"
)
base["status"] = producer.get("status") or (
"legacy_reference_only" if known_legacy else "method_unverified"
)
base["prediction_scoring_allowed"] = False
base["reason"] = "unadjudicated_contribution_subtraction_not_wealth_evidence"
return base
@@ -2610,9 +2617,9 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s
pav_finance_support = present.get("pav_finance_support") or {}
if pav_finance_support.get("level") == "supportive":
score += 2
sodhita_finance_support = present.get("sodhita_finance_support") or {}
if sodhita_finance_support.get("level") == "obstructive":
score -= 2
# Upstream a5b75886: Sodhita packets remain available as raw evidence,
# never a finance vote. No admitted threshold/profile exists, including
# for historical caches.
kakshya_finance_support = present.get("kakshya_finance_support") or {}
if kakshya_finance_support.get("level") == "supportive":
score += 2
@@ -2680,8 +2687,6 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s
secondary_context.append("functional_benefic_malefic_used")
if pav_finance_support.get("level") == "supportive":
secondary_context.append("pav_finance_support")
if sodhita_finance_support.get("level") == "obstructive":
secondary_context.append("sodhita_wealth_friction")
if kakshya_finance_support.get("level") == "supportive":
secondary_context.append("kakshya_finance_support")
elif kakshya_finance_support.get("level") == "obstructive":
@@ -4005,6 +4010,13 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An
"plus at least one wealth-related convergence domain."
),
}
# Upstream ba8e36ec: optional existing producers are delivered as raw
# inventory only; they never vote or fill required evidence.
try:
from scripts.finance_astrology_support_review import build_finance_delivery_inventory
except ImportError: # pragma: no cover - scripts/ on sys.path
from finance_astrology_support_review import build_finance_delivery_inventory
strict["finance_delivery_inventory"] = build_finance_delivery_inventory(modules)
strict["official_primary_evidence"] = _build_official_primary_evidence(route, present)
strict["local_supplemental_evidence"] = _build_local_supplemental_evidence(route, present)
strict["fallback_used"], strict["blocked_items"] = _build_fallback_and_blocked(
+3 -2
View File
@@ -707,12 +707,13 @@
"calculation_status": "source_bounded_wired",
"doctrine_status": "usage_pack_present",
"variant_status": "profile_inventory_present",
"variant_profile_ids": ["niryaana_shoola_goel_profile", "niryaana_shoola_raman_profile", "niryaana_shoola_pyjhora_observation_profile"],
"variant_profile_ids": ["pvr_niryaana_shoola_v1", "niryaana_shoola_pyjhora_observation_profile"],
"routing_status": "skill_linked",
"report_status": "source_bounded_wired",
"local_paths": ["scripts/niryaana_shoola_dasha.py", "scripts/niryaana_shoola_family_contract.py"],
"report_paths": ["jyotish_engine.worksheets.timing_and_predictive_systems.dasha_master_pack.families.niryaana_shoola", "references/oracle/pl9_p92_p103_field_audit_2026_09_11.json"],
"producer_contract": {"entrypoint": "scripts.niryaana_shoola_dasha.calculate_niryaana_shoola_dasha", "maturity": "source_bounded_wired", "verification_scope": "stronger Lagna/seventh selection, explicit tie-break, named Goel/Raman/PyJHora duration profiles and MD/AD/PD boundaries", "claim_boundary": "Named profile support only; worksheet path is wired but parameter-sensitive parity remains open."}
"producer_contract": {"entrypoint": "scripts.niryaana_shoola_dasha.calculate_niryaana_shoola_dasha", "maturity": "source_bounded_wired", "verification_scope": "PVR second/eighth ayur-strength selection with unresolved ties blocked; movable/fixed/dual 7/8/9 calendar-year MD; external observation separate", "claim_boundary": "Named MD profile only, longevity-domain research rather than relationship timing; AD/PD source rule and same-profile external parity remain open."},
"next_required": ["source-backed AD/PD rule and row-level replay", "independent same-profile MD case; no predictive or global admission"]
},
"kalachakra_dasha": {"calculation_status":"source_bounded_wired","doctrine_status":"usage_pack_present","variant_status":"profile_inventory_present","variant_profile_ids":["kalachakra_native_recursive_profile","kalachakra_pl9_pages_83_88_boundary_profile","kalachakra_pyjhora_observation_profile"],"routing_status":"skill_linked","report_status":"source_bounded_wired","local_paths":["scripts/kalachakra_dasha.py","scripts/kalachakra_pl9_report_boundary_packet.py"],"report_paths":["jyotish_engine.worksheets.timing_and_predictive_systems.dasha_master_pack.families.kala_chakra","kalachakra_pl9_report_boundary_packet.boundary_rows","references/oracle/kalachakra_pl9_p83_88_structural_contract_2026_08_11.json"],"producer_contract":{"entrypoint":"scripts.kalachakra_dasha.calculate_kalachakra_dasha","maturity":"source_bounded_wired","verification_scope":"savya/apasavya selection, balance, Jeeva/Deha and recursive MD/AD/PD with PL9 conflict packet","claim_boundary":"Conflict-labeled Kala Chakra output; PL9 continuation and external parity remain unclosed."}},
"lagna_kendradi_moola_dasha": {"calculation_status":"source_bounded_wired","doctrine_status":"usage_pack_present","variant_status":"profile_inventory_present","variant_profile_ids":["lagna_kendradi_explicit_seed_profile","lagna_kendradi_pyjhora_observation_profile"],"routing_status":"skill_linked","report_status":"source_bounded_wired","local_paths":["scripts/lagna_kendradi_dasha.py","scripts/lagna_kendradi_family_contract.py"],"report_paths":["jyotish_engine.worksheets.timing_and_predictive_systems.dasha_master_pack.families.lagna_kendradi","references/oracle/pl9_p92_p103_field_audit_2026_09_11.json"],"producer_contract":{"entrypoint":"scripts.lagna_kendradi_dasha.calculate_lagna_kendradi_dasha","maturity":"source_bounded_wired","verification_scope":"explicit Lagna seed, Kendra/Panaphara/Apoklima group order and profile arbitration","claim_boundary":"Explicit profile producer; automatic selector and source parity remain open."}},
+8 -2
View File
@@ -136,7 +136,7 @@ def calc_pada_dasha_marriage_timing(birth_moon_lon, birth_date_jd,
return analysis
def bhrigu_pada_dasha_full_report(birth_moon_lon, birth_date_jd,
d9_7lord_sign=None, d9_planets=None):
d9_7lord_sign=None, d9_planets=None, *, target_date_jd=None):
"""
Bhrigu Pada Dasha 完整报告 v7.0
@@ -147,6 +147,7 @@ def bhrigu_pada_dasha_full_report(birth_moon_lon, birth_date_jd,
- birth_date_jd: 出生 JD
- d9_7lord_sign: D9 7宫主星座 (0-11)
- d9_planets: D9 行星数据 {name: longitude}
- target_date_jd: BCP 观测 JD;省略时只输出明确标记的出生时点(上游 8813c851)
Returns:
- dict: 完整报告
@@ -221,7 +222,12 @@ def bhrigu_pada_dasha_full_report(birth_moon_lon, birth_date_jd,
report['d9_verification']['window_details'] = window_details
# BCP周期整合
report['bcp_cycle'] = calc_bcp_cycle(birth_moon_lon, birth_date_jd, birth_date_jd)
bcp_target_jd = birth_date_jd if target_date_jd is None else target_date_jd
report['bcp_cycle'] = calc_bcp_cycle(birth_moon_lon, birth_date_jd, bcp_target_jd)
report['bcp_reference'] = {
'target_jd': bcp_target_jd,
'scope': 'birth_epoch_default' if target_date_jd is None else 'explicit_target',
}
return report
+55 -3
View File
@@ -20,9 +20,55 @@ FINANCE_PRIORITY_GAPS = [
def _present(value: Any) -> str:
if isinstance(value, dict) and str(value.get("status", "")).lower() in {
"blocked", "missing", "error", "failed", "not_available_yet", "not_executed", "not_applicable"
}:
return "blocked"
return "present" if bool(value) else "missing"
FINANCE_EXISTING_MODULES = {
"argala": "scripts/argala.py",
"dispositor_chains": "scripts/jyotish_engine.py",
"tajika": "scripts/solar_return.py",
"bhrigu_pada_dasha": "scripts/bhrigu_pada_dasha.py",
"sudasa": "scripts/sudasa.py",
}
def build_finance_delivery_inventory(modules: dict[str, Any]) -> dict[str, Any]:
"""Preserve existing optional data without calling presence execution or a vote."""
from copy import deepcopy
rows = []
for name, producer in FINANCE_EXISTING_MODULES.items():
value = modules.get(name)
presence = _present(value)
rows.append({
"technique": name, "producer": producer,
"source_path": "modules." + name,
"delivery_status": {"present": "payload_delivered", "missing": "not_delivered",
"blocked": "producer_status_blocked"}[presence],
"source_status": value.get("status") if isinstance(value, dict) else None,
"execution_status": "not_verified_by_inventory",
"raw": deepcopy(value),
})
return {
"rows": rows,
"claim_boundary": "delivery_inventory_only_not_execution_or_prediction_validation",
"missing_means": "not_delivered_in_this_packet_not_missing_from_repository",
"presentation_order": ["executive_summary", "thematic_narrative", "evidence_appendix"],
"inference_constraints": [
"Strength is not outcome valence; SAV and Bhava Bala do not prove retention.",
"Repeated planet or house evidence is not independent confirmation.",
"Drig is not a wealth vote without a named source-specific domain rule.",
"Unknown birth precision requires sensitivity evidence for narrow windows.",
"Retain varga methods, full period boundaries and dispositor chains.",
"Repeated model agreement is not event validation or calibrated probability.",
],
}
def build_finance_astrology_support_review(
*,
route_packet: dict[str, Any] | None,
@@ -48,7 +94,7 @@ def build_finance_astrology_support_review(
"d2": _present(sections.get("D2")),
"d11": _present(machine_evidence_packet.get("D11") or sections.get("D11")),
"vimshottari": _present(sections.get("dasha_boundaries")),
"narayana": _present(runtime_evidence_log.get("cross_system_arbitration")),
"narayana": _present(sections.get("narayana") or sections.get("narayana_dasha")),
"shadbala": _present(sections.get("shadbala")),
"ashtakavarga": _present(sections.get("ashtakavarga")),
"transit": _present(sections.get("transit")),
@@ -67,13 +113,19 @@ def build_finance_astrology_support_review(
],
}
finance_surface["status"] = "used" if route in {"finance", "wealth"} else "not_applicable"
finance_surface["missing_evidence"] = [
key for key, state in finance_surface["coverage"].items() if state != "present"
]
finance_surface["readiness"] = (
"not_applicable" if route not in {"finance", "wealth"} else
"incomplete" if finance_surface["missing_evidence"] else "payloads_present_execution_unverified"
)
finance_surface["audit_rows"] = [
{
"technique": "Finance Astrology Support Review",
"status": finance_surface["status"],
"used": finance_surface["status"] == "used",
"effect_on_confidence": "finance_route_support_ready" if finance_surface["status"] == "used" else "not_applicable",
"effect_on_confidence": finance_surface["readiness"],
}
]
return finance_surface
+43
View File
@@ -140,6 +140,45 @@ def _promotion_status(blocking_reasons: list[str], review_reasons: list[str], wa
return "passed"
def evaluate_evidence_claims(packet: dict[str, Any]) -> dict[str, Any]:
"""Validate explicit claim bindings, not arbitrary free-text astrological prose.
Each claim has a path (list of dictionary keys/list indices) and a value.
Model-declared 'used' or a repeated answer never counts as a receipt.
"""
claims = packet.get("evidence_claims")
if claims is None or claims == []:
return {"status": "not_supplied", "checked": 0, "errors": [],
"boundary": "free_text_not_semantically_verified"}
if not isinstance(claims, list):
return {"status": "blocked", "checked": 0, "errors": ["claims_not_list"]}
errors = []
for i, claim in enumerate(claims):
if not isinstance(claim, dict) or "value" not in claim:
errors.append(f"claim_{i}:invalid_binding")
continue
path = claim.get("path")
if not isinstance(path, list) or not path or path[0] == "evidence_claims":
errors.append(f"claim_{i}:invalid_path")
continue
value = packet
try:
for key in path:
if isinstance(value, dict) and isinstance(key, str):
value = value[key]
elif isinstance(value, list) and type(key) is int and key >= 0:
value = value[key]
else:
raise KeyError("invalid_path_component")
except (KeyError, IndexError, TypeError):
errors.append(f"claim_{i}:source_missing")
continue
if type(value) is not type(claim["value"]) or value != claim["value"]:
errors.append(f"claim_{i}:value_mismatch")
return {"status": "blocked" if errors else "bindings_match", "checked": len(claims),
"errors": errors, "boundary": "bound_values_only_not_formula_or_prediction_validation"}
def evaluate_full_report(packet: dict[str, Any], rendered_markdown: str | None = None) -> dict[str, Any]:
"""Evaluate an assembled report without mutating calculation or report content."""
packet = _as_dict(packet)
@@ -147,6 +186,9 @@ def evaluate_full_report(packet: dict[str, Any], rendered_markdown: str | None =
blocking_reasons: list[str] = []
review_reasons: list[str] = []
warning_reasons: list[str] = []
claim_validation = evaluate_evidence_claims(packet)
if claim_validation["status"] == "blocked":
blocking_reasons.append("evidence_claim_bindings_invalid")
source_schema = str(packet.get("schema") or "")
if source_schema != REPORT_SCHEMA:
@@ -557,6 +599,7 @@ def evaluate_full_report(packet: dict[str, Any], rendered_markdown: str | None =
"status": status,
"checks": checks,
"blocking_reasons": blocking_reasons,
"evidence_claim_validation": claim_validation,
"review_reasons": review_reasons,
"warning_reasons": warning_reasons,
"professional_coverage_manifest": professional_coverage_manifest,
+23 -3
View File
@@ -592,12 +592,14 @@ def calc_dispositor_chain(planet_name, planets_data, max_depth=12):
break
sign = pdata['sign']
dispositor = SIGN_LORDS.get(sign, '')
# Upstream 5962368d: the dispositor's own location, not the sign it owns.
dispositor_data = planets_data.get(dispositor)
chain.append({
'planet': current_planet,
'sign': sign,
'sign_cn': SIGNS_CN.get(sign, ''),
'dispositor': dispositor,
'dispositor_sign': sign, # 定位星所在的星座(即当前行星所在星座)
'dispositor_sign': dispositor_data.get('sign') if isinstance(dispositor_data, dict) else None,
})
if not dispositor:
break
@@ -9934,7 +9936,7 @@ def render_pl9_markdown(packet: dict) -> str:
raw_timing_rows.append((
'Bhrigu Pada / BCP',
bhrigu_raw.get('status', 'blocked'),
f"出生月亮={birth_moon.get('longitude', '-')}°;序列行数={len(bhrigu_raw.get('dasha_sequence', []))}",
f"出生月亮={birth_moon.get('longitude', '-')}°;序列行数={len(bhrigu_raw.get('dasha_sequence', []))};BCP观测日期={bhrigu_raw.get('bcp_reference_date', '未指定;核对原始时点')}",
'raw_full_reading.modules.bhrigu_pada_dasha',
))
pac_raw = timing_sheet.get('double_transit_pac') if isinstance(timing_sheet.get('double_transit_pac'), dict) else {}
@@ -14753,6 +14755,14 @@ def cmd_nakshatra_dasha(args):
return _build_response_envelope('nakshatra-dasha', _impl(args), args=args, execution_status='executed')
def cmd_nadi_stellar_roles(args):
"""Upstream d09feab7: explicit Meena Jeeva/Sareera structure; no prediction or scoring."""
from nakshatra_advanced import calculate_stellar_jeeva_sareera
request = json.loads(Path(args.request).read_text(encoding='utf-8'))
return calculate_stellar_jeeva_sareera(**request)
def cmd_nakshatra_full(args):
from cmd_nakshatra_adv import cmd_nakshatra_full as _impl
return _build_response_envelope('nakshatra-full', _impl(args), args=args, execution_status='executed')
@@ -16246,11 +16256,17 @@ def cmd_full_reading(args):
d9_seventh_lord_data = d9_data.get(d9_seventh_lord, {}) if isinstance(d9_data, dict) else {}
if isinstance(d9_seventh_lord_data, dict) and d9_seventh_lord_data.get('sign') in SIGNS:
d9_7lord_sign = SIGNS.index(d9_seventh_lord_data['sign'])
# Upstream 8813c851: BCP is observed at the reading date, not the birth epoch.
bcp_reference_date = getattr(args, 'transit_date', None) or getattr(args, 'today', None) or datetime.now().strftime('%Y-%m-%d')
bcp_year, bcp_month, bcp_day = map(int, bcp_reference_date.split('-'))
bcp_target_jd = swe.julday(bcp_year, bcp_month, bcp_day, 12.0 - args.tz)
bpd_result = bhrigu_pada_dasha_full_report(
moon_lon, birth_jd,
d9_7lord_sign=d9_7lord_sign,
d9_planets=d9_planets,
target_date_jd=bcp_target_jd,
)
bpd_result['bcp_reference_date'] = bcp_reference_date
bpd_result['status'] = 'parameter_sensitive'
bpd_result['claim_boundary'] = (
'Bhrigu Pada / BCP is a source-limited approximate progression family. '
@@ -18796,6 +18812,10 @@ def main():
p.add_argument('--age', type=float, default=None, help='当前年龄')
p.add_argument('--transit-date', default=None, help='过境日期 YYYY-MM-DD(默认今天)')
# 18.7 nadi-stellar-roles (upstream d09feab7)
p = sub.add_parser('nadi-stellar-roles', help='显式 Meena Jeeva/Sareera 结构计算;不生成预测或评分')
p.add_argument('--request', required=True, help='本地 JSON:九星恒星经度、宫主、岁差、交点及可选星宿方向力')
# 19. argala (v3.7新增)
p = sub.add_parser('argala', help='Argala门闩系统')
_add_chart_args(p)
@@ -18980,7 +19000,7 @@ def main():
'validate': cmd_validate, 'audit': cmd_audit, 'report': cmd_report,
'varga-full': cmd_varga_full, 'aspects': cmd_aspects, 'jaimini': cmd_jaimini,
'nakshatra-adv': cmd_nakshatra_adv, 'nakshatra-dasha': cmd_nakshatra_dasha,
'nakshatra-full': cmd_nakshatra_full, 'argala': cmd_argala, 'tajika': cmd_tajika,
'nakshatra-full': cmd_nakshatra_full, 'nadi-stellar-roles': cmd_nadi_stellar_roles, 'argala': cmd_argala, 'tajika': cmd_tajika,
'marriage-match': cmd_marriage_match, 'solar-return': cmd_solar_return,
'narayana-dasha': cmd_narayana_dasha, 'muhurta': cmd_muhurta,
'full-reading': cmd_full_reading, 'pl9-export': cmd_pl9_export, 'prashna': cmd_prashna,
+109
View File
@@ -111,6 +111,115 @@ def find_nakshatra(longitude: float) -> Dict:
}
def calculate_stellar_jeeva_sareera(
positions: Dict, *, bhava_lord: str, ayanamsa: str, node_mode: str,
stellar_digbala: Optional[Dict] = None,
nodal_exaltation_signs: Optional[Dict] = None,
) -> Dict:
"""Structural Meena classification, explicit printed-p89 procedure only.
Input longitudes are already sidereal; this function does not convert them.
Stellar Digbala is a supplied boolean relationship result, NOT Shadbala.
Nodal exaltations have no implicit default. Unknown comparisons stay blocked.
See docs/research/stellar_jeeva_sareera_2026_11_07.md for source conflicts.
"""
if __package__:
from .jaimini import SIGNS, SIGN_LORDS
else:
from jaimini import SIGNS, SIGN_LORDS
planets = ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus",
"Saturn", "Rahu", "Ketu")
if not isinstance(positions, dict) or set(positions) != set(planets):
raise ValueError("Supply exactly nine planetary sidereal longitudes")
for value in positions.values():
if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):
raise ValueError("Planetary longitudes must be finite numbers")
if bhava_lord not in planets or not isinstance(ayanamsa, str) or not ayanamsa.strip():
raise ValueError("Explicit bhava_lord and ayanamsa are required")
if node_mode not in {"mean", "true"}:
raise ValueError("node_mode must be mean or true")
strengths = {} if stellar_digbala is None else stellar_digbala
if not isinstance(strengths, dict) or any(
p not in planets or type(v) is not bool for p, v in strengths.items()
):
raise ValueError("stellar_digbala requires planetary booleans, not strength scores")
nodes = {} if nodal_exaltation_signs is None else nodal_exaltation_signs
if not isinstance(nodes, dict) or any(
p not in {"Rahu", "Ketu"} or (v is not None and (type(v) is not int or not 0 <= v < 12))
for p, v in nodes.items()
):
raise ValueError("Explicit nodal exaltation: sign index 0..11 or None")
signs = {p: int(v % 360 // 30) for p, v in positions.items()}
stars = {p: find_nakshatra(v) for p, v in positions.items()}
exaltation = dict(Sun=0, Moon=1, Mars=9, Mercury=5, Jupiter=3, Venus=11, Saturn=6)
exaltation.update(nodes)
order = {p: i for i, p in enumerate(("Rahu", "Ketu", "Sun", "Moon", "Venus",
"Jupiter", "Saturn", "Mars", "Mercury"))}
trace = []
def choose(seed, stage):
group = [p for p in planets if signs[p] == signs[seed]]
row = {"stage": stage, "seed": seed, "companions": group}
trace.append(row)
def finish(selected, reason):
row.update(selected=selected, reason=reason)
return selected
if seed == "Saturn" and signs[seed] == 7 and stars[seed]["nakshatra_idx"] == 16:
return finish(seed, "original_saturn_anuradha_exception")
if len(group) == 1:
return finish(seed, "alone")
owner = SIGN_LORDS[SIGNS[signs[seed]]]
if owner in group:
return finish(owner, "sign_owner")
unknown_nodes = [p for p in group if p in {"Rahu", "Ketu"} and p not in nodes]
if unknown_nodes:
row["missing"] = unknown_nodes
return finish(None, "nodal_exaltation_profile_required")
exalted = [p for p in group if exaltation.get(p) == signs[p]]
if len(exalted) > 1:
return finish(None, "multiple_exalted_comparison_unresolved")
if exalted:
return finish(exalted[0], "exalted")
missing = [p for p in group if p not in strengths]
if missing:
row["missing"] = missing
return finish(None, "stellar_digbala_required")
directional = [p for p in group if strengths[p]]
if not directional:
return finish(seed, "no_stronger_companion")
own_star = [p for p in directional if stars[p]["nakshatra_lord"] == p]
return finish(min(own_star or directional, key=order.get),
"stellar_digbala_own_star" if own_star else "stellar_digbala_order")
jeeva = choose(stars[bhava_lord]["nakshatra_lord"], "jeeva")
sareera = None
if jeeva is not None:
seed = stars[jeeva]["nakshatra_lord"]
if seed == jeeva:
seed = SIGN_LORDS[SIGNS[signs[jeeva]]]
sareera = choose(seed, "sareera")
return {
"schema": "jyotish.stellar_jeeva_sareera.v1",
"method": "meena_stellar_detailed_procedure_printed_p89",
"status": "executed" if sareera is not None else "blocked",
"evidence_grade": "parameter_sensitive",
"prediction_scoring_allowed": False,
"external_verified": False,
"profile": {"ayanamsa": ayanamsa, "node_mode": node_mode,
"stellar_digbala": dict(strengths), "nodal_exaltation_signs": dict(nodes)},
"bhava_lord": bhava_lord, "jeeva": jeeva, "sareera": sareera, "trace": trace,
"source": {"corpus_file": "Nadi_Stellar Effects -- Jyotish -- 2022 -- f8eef571147711ee4eb4479a35bd2428 -- Anna’s Archive.md",
"text_sha256": "f30db43d842a9484316d6608f6385dfabe7232265469e67938f42459e25c4b42",
"pdf_pages": [87, 89, 90, 92, 96], "printed_pages": [84, 86, 87, 89, 93]},
"boundaries": ["Structural roles only; no event, date or outcome inference",
"Printed p86 example names two different Sareera planets",
"Printed pp90-91 comparative list is not silently merged into p89",
"Supplied stellar Digbala and nodal exaltation require separate source review"],
}
def calc_tara_bala(moon_nak_idx: int, target_nak_idx: int) -> Dict:
"""
Tara Bala计算:月亮星宿到目标星宿的9星宿循环关系
+8 -3
View File
@@ -2439,15 +2439,20 @@ def render_pl9_parity_markdown(packet: dict) -> str:
lines.append('')
if candidates:
lines.extend([
'| Candidate source | Planet | House | Sign | Panchavargiya score |',
'|------------------|--------|-------|------|---------------------|',
# Upstream 0266b6ad: the selecting score is the native five-fold
# profile; its five components are shown beside it.
'| Candidate source | Planet | House | Sign | Graha | Uchcha | Hudda | Drekkana | Navamsha | Raw (of 80) | Panchavargiya score |',
'|------------------|--------|-------|------|-------|--------|-------|----------|----------|-------------|---------------------|',
])
for row in candidates:
if not isinstance(row, dict):
continue
parts = _dict(row.get('panchavargiya_components'))
lines.append(
f"| {_cell(row.get('source'))} | {_cell(_token(row.get('planet')))} | {_cell(row.get('planet_house'))} | "
f"{_cell(_token(row.get('planet_sign')))} | {_cell(row.get('panchavargiya_score'))} |"
f"{_cell(_token(row.get('planet_sign')))} | "
+ ''.join(f"{_cell(parts.get(key))} | " for key in ('graha_bala', 'uchcha_bala', 'hudda_bala', 'drekkana_bala', 'navamsha_bala'))
+ f"{_cell(row.get('panchavargiya_raw_total'))} | {_cell(row.get('panchavargiya_score'))} |"
)
lines.append('')
planet_longitudes = _dict(annual_chart_data.get('planet_longitudes'))
+11 -2
View File
@@ -313,6 +313,7 @@ def calc_panchadhikari_year_lord(
'panchavargiya_raw_total': pvb.get('raw_total'),
'panchavargiya_engine': pvb['engine'],
'panchavargiya_components': pvb['components'],
'external_oracle_comparison': pvb.get('external_oracle_comparison', {'status': 'unavailable'}),
}
)
@@ -351,6 +352,7 @@ def calc_panchadhikari_year_lord(
'muntha_sign': SIGNS[muntha_sign_idx],
'is_daytime': is_daytime,
'panchavargiya_engine': winner['panchavargiya_engine'],
'panchavargiya_selection_policy': 'native_components_external_observation_only',
'candidates': candidates,
}
@@ -1627,8 +1629,13 @@ def _resolve_year_lord_panchavargiya_score(
tz=tz,
ayanamsa_name=ayanamsa_name,
)
if external is not None:
return external
# Upstream 0266b6ad: the annual strength table uses the same native profile;
# external availability must not silently change the year-lord selection
# method. PyJHora (research switch only) is an observation beside it.
comparison = (
{**external, 'comparison_role': 'observation_only_not_selection_input'}
if external is not None else {'status': 'unavailable'}
)
if native_scores is None:
# Upstream c7117f92 behaviour (dignity proxy); no product caller.
proxy = _calc_panchavargiya_bala_for_planet(planet, lon_value)
@@ -1636,6 +1643,7 @@ def _resolve_year_lord_panchavargiya_score(
'score': proxy['score'],
'engine': 'native_proxy',
'components': proxy['components'],
'external_oracle_comparison': comparison,
}
# BUG-1217: Tajika five-fold Panchavargiya; None when it cannot be computed.
native = native_scores.get(planet) or {}
@@ -1646,6 +1654,7 @@ def _resolve_year_lord_panchavargiya_score(
'engine': 'tajika_native_five_fold',
'raw_total': native['raw_total'],
'components': native['components'],
'external_oracle_comparison': comparison,
}
+8 -1
View File
@@ -25,7 +25,10 @@ Q5) the candidates are compared by the Tajika five-fold Panchavargiya
Drekkana, Navamsa; raw out of 80, score = raw / 4 out of 20;
`engine: tajika_native_five_fold`); the dignity proxy no longer selects a year
lord. Set JYOTISH_YEAR_LORD_PYJHORA_REFERENCE=1 to pass the annual JD and let
the upstream PyJHora lookup run (research comparison only).
the upstream PyJHora lookup run (research comparison only). Since upstream
0266b6ad (2026-11-07 sync) that lookup never selects: it is attached to each
candidate as `external_oracle_comparison` (`comparison_role:
observation_only_not_selection_input`) and the native five-fold score decides.
No stand-in year lord (BUG-1223, product owner 2026-10-04, aligned with
upstream 03bea6ed `docs/research/tajika_shared_entrypoint_repair_2026_10_03.md`
@@ -140,6 +143,10 @@ def _candidate_list(candidates: List[Dict[str, Any]], winner: Optional[str]) ->
"panchavargiya_score": row.get("panchavargiya_score"),
"panchavargiya_raw_total": row.get("panchavargiya_raw_total"),
"panchavargiya_engine": row.get("panchavargiya_engine"),
# Upstream 0266b6ad: the native components that produced the score;
# an external engine is an observation beside it, never the selector.
"panchavargiya_components": row.get("panchavargiya_components"),
"external_oracle_comparison": row.get("external_oracle_comparison"),
"selected": row.get("planet") == winner,
} for row in candidates if isinstance(row, dict)]
+1
View File
@@ -28,6 +28,7 @@ CALCULATION_READINESS = {
}
FAMILY_VALUE = {"dasha": 18, "kp": 17, "pravesha": 16, "sphuta": 12, "chakra": 11, "panchanga": 14}
NON_IMPLEMENTATION_SCRIPTS = {
"scripts/audit_local_knowledge_copies.py",
"scripts/book_rule_lineage.py",
"scripts/technique_gap_scanner.py",
"scripts/technique_source_adapters.py",
+9 -2
View File
@@ -788,9 +788,15 @@ class UnifiedConsultationOrchestrator:
@staticmethod
def _section(value: Any, source_path: str) -> dict[str, Any]:
present = bool(value)
# Upstream ba8e36ec: a wrapped producer status is not a delivered section.
unavailable = isinstance(value, dict) and value.get("status") in {
"blocked", "missing", "error", "failed", "not_executed", "not_applicable",
"not_available_yet",
}
return {
"status": "used" if present else "missing",
"status": "blocked" if unavailable else "used" if present else "missing",
"source_path": source_path,
"claim_boundary": "payload_presence_not_independent_execution_verification",
}
@staticmethod
@@ -981,6 +987,7 @@ class UnifiedConsultationOrchestrator:
# policy can require the second without the first standing in for it.
"dasha_sub_periods": self._section(modules.get("dasha_sub_periods"), "modules.dasha_sub_periods"),
"narayana_dasha": self._section(modules.get("narayana_dasha"), "modules.narayana_dasha"),
"transit": self._section(modules.get("transit_multi_reference"), "modules.transit_multi_reference"),
"shadbala": self._section(modules.get("shadbala") or chart_data.get("shadbala"), "modules.shadbala"),
"ashtakavarga": self._section(modules.get("ashtakavarga") or chart_data.get("ashtakavarga"), "modules.ashtakavarga"),
"yogas": self._section(modules.get("yogas") or chart_data.get("yogas"), "modules.yogas"),
@@ -1045,7 +1052,7 @@ class UnifiedConsultationOrchestrator:
spectrum if spectrum.get("status") == "used" else None,
"modules.varga_spectrum",
)
missing = [name for name, section in sections.items() if section.get("status") == "missing"]
missing = [name for name, section in sections.items() if section.get("status") in {"missing", "blocked"}]
signals = chart_data.get("cross_system_signals")
if not isinstance(signals, list):
signals = modules.get("cross_system_signals") if isinstance(modules.get("cross_system_signals"), list) else []
@@ -17,9 +17,9 @@ Generated from ontology, family templates and public interface documentation. It
- Use for: doctrine pack required before interpretation
- Never use as: hand-written period dates, standalone event certainty
- Required rule fields: seed, applicability, sequence, duration_model, birth_balance, child_period_model, year_definition
- Local evidence hits: references/book_rule_technique_lineage.json, references/technique_registry.json, scripts/bhrigu_pada_dasha.py, scripts/chara_narayana_book_golden_cases.py, scripts/chara_narayana_book_runtime_replay.py, scripts/chara_narayana_pd_source_closure_ledger.py, scripts/chara_narayana_public_dated_examples_round2.py, scripts/chara_narayana_public_dated_examples_round3.py, scripts/chara_narayana_public_dated_examples_round4.py, scripts/chara_narayana_public_dated_examples_round5.py, scripts/chara_narayana_public_dated_examples_round7.py, scripts/chara_narayana_source_field_extraction.py, scripts/chara_narayana_source_rule_ledger.py, scripts/event_prediction_model.py, scripts/full_technique_invocation_matrix.py, scripts/jaimini.py, scripts/jyotish_api_server.py, scripts/jyotish_engine.py, scripts/jyotish_next_gap_closure_ledger.py, scripts/jyotish_public_gap_intake_round2.py, scripts/karan_singh_narayana_event_pd_replay.py, scripts/kn_rao_bhrigu_aggregation_ledger.py, scripts/marriage_counting.py, scripts/narayana_continuation_source_sweep.py, scripts/pl9_core_table_extractor.py, scripts/pl9_report_corpus_audit.py, scripts/professional_parity_closure.py, scripts/reading_orchestrator.py, scripts/relocation_signal.py, scripts/specialized_indian_closure_review.py, scripts/sthira_brahma_selector_contract.py, scripts/technique_promotion_audit_varga_karaka.py, scripts/technique_registry.py, scripts/timing_evidence_packets.py, scripts/unified_calculation_archive.py, scripts/unified_consultation_orchestrator.py, scripts/vedastro_official_capability_runner.py, scripts/vedastro_parity_matrix.py, scripts/vedastro_python_bridge.py, scripts/vedastro_service_adapter.py
- Local implementation-candidate hits: scripts/bhrigu_pada_dasha.py, scripts/chara_narayana_book_golden_cases.py, scripts/chara_narayana_book_runtime_replay.py, scripts/chara_narayana_pd_source_closure_ledger.py, scripts/chara_narayana_public_dated_examples_round2.py, scripts/chara_narayana_public_dated_examples_round3.py, scripts/chara_narayana_public_dated_examples_round4.py, scripts/chara_narayana_public_dated_examples_round5.py, scripts/chara_narayana_public_dated_examples_round7.py, scripts/chara_narayana_source_field_extraction.py, scripts/chara_narayana_source_rule_ledger.py, scripts/event_prediction_model.py, scripts/full_technique_invocation_matrix.py, scripts/jaimini.py, scripts/jyotish_api_server.py, scripts/jyotish_engine.py, scripts/jyotish_next_gap_closure_ledger.py, scripts/jyotish_public_gap_intake_round2.py, scripts/karan_singh_narayana_event_pd_replay.py, scripts/kn_rao_bhrigu_aggregation_ledger.py, scripts/marriage_counting.py, scripts/narayana_continuation_source_sweep.py, scripts/pl9_core_table_extractor.py, scripts/pl9_report_corpus_audit.py, scripts/professional_parity_closure.py, scripts/reading_orchestrator.py, scripts/relocation_signal.py, scripts/specialized_indian_closure_review.py, scripts/sthira_brahma_selector_contract.py, scripts/technique_promotion_audit_varga_karaka.py, scripts/technique_registry.py, scripts/timing_evidence_packets.py, scripts/unified_calculation_archive.py, scripts/unified_consultation_orchestrator.py, scripts/vedastro_official_capability_runner.py, scripts/vedastro_parity_matrix.py, scripts/vedastro_python_bridge.py, scripts/vedastro_service_adapter.py
- Book rule links: boney_major_life_events_composite_parashari_jaimini_timing
- Local evidence hits: references/book_rule_technique_lineage.json, references/technique_registry.json, scripts/bhrigu_pada_dasha.py, scripts/chara_narayana_book_golden_cases.py, scripts/chara_narayana_book_runtime_replay.py, scripts/chara_narayana_pd_source_closure_ledger.py, scripts/chara_narayana_public_dated_examples_round2.py, scripts/chara_narayana_public_dated_examples_round3.py, scripts/chara_narayana_public_dated_examples_round4.py, scripts/chara_narayana_public_dated_examples_round5.py, scripts/chara_narayana_public_dated_examples_round7.py, scripts/chara_narayana_source_field_extraction.py, scripts/chara_narayana_source_rule_ledger.py, scripts/event_prediction_model.py, scripts/full_technique_invocation_matrix.py, scripts/jaimini.py, scripts/jyotish_api_server.py, scripts/jyotish_engine.py, scripts/jyotish_next_gap_closure_ledger.py, scripts/jyotish_public_gap_intake_round2.py, scripts/karan_singh_narayana_event_pd_replay.py, scripts/kn_rao_bhrigu_aggregation_ledger.py, scripts/narayana_continuation_source_sweep.py, scripts/pl9_core_table_extractor.py, scripts/pl9_report_corpus_audit.py, scripts/professional_parity_closure.py, scripts/reading_orchestrator.py, scripts/relocation_signal.py, scripts/specialized_indian_closure_review.py, scripts/sthira_brahma_selector_contract.py, scripts/technique_promotion_audit_varga_karaka.py, scripts/technique_registry.py, scripts/timing_evidence_packets.py, scripts/unified_calculation_archive.py, scripts/unified_consultation_orchestrator.py, scripts/vedastro_official_capability_runner.py, scripts/vedastro_parity_matrix.py, scripts/vedastro_python_bridge.py, scripts/vedastro_service_adapter.py
- Local implementation-candidate hits: scripts/bhrigu_pada_dasha.py, scripts/chara_narayana_book_golden_cases.py, scripts/chara_narayana_book_runtime_replay.py, scripts/chara_narayana_pd_source_closure_ledger.py, scripts/chara_narayana_public_dated_examples_round2.py, scripts/chara_narayana_public_dated_examples_round3.py, scripts/chara_narayana_public_dated_examples_round4.py, scripts/chara_narayana_public_dated_examples_round5.py, scripts/chara_narayana_public_dated_examples_round7.py, scripts/chara_narayana_source_field_extraction.py, scripts/chara_narayana_source_rule_ledger.py, scripts/event_prediction_model.py, scripts/full_technique_invocation_matrix.py, scripts/jaimini.py, scripts/jyotish_api_server.py, scripts/jyotish_engine.py, scripts/jyotish_next_gap_closure_ledger.py, scripts/jyotish_public_gap_intake_round2.py, scripts/karan_singh_narayana_event_pd_replay.py, scripts/kn_rao_bhrigu_aggregation_ledger.py, scripts/narayana_continuation_source_sweep.py, scripts/pl9_core_table_extractor.py, scripts/pl9_report_corpus_audit.py, scripts/professional_parity_closure.py, scripts/reading_orchestrator.py, scripts/relocation_signal.py, scripts/specialized_indian_closure_review.py, scripts/sthira_brahma_selector_contract.py, scripts/technique_promotion_audit_varga_karaka.py, scripts/technique_registry.py, scripts/timing_evidence_packets.py, scripts/unified_calculation_archive.py, scripts/unified_consultation_orchestrator.py, scripts/vedastro_official_capability_runner.py, scripts/vedastro_parity_matrix.py, scripts/vedastro_python_bridge.py, scripts/vedastro_service_adapter.py
- Book rule links: boney_major_life_events_composite_parashari_jaimini_timing, jaimini_seventh_from_karakamsha_marriage, jaimini_seventh_from_swamsha_marriage, spouse_meeting_place_via_seventh_from_venus
- Book topic links: jaimini_chara_narayana
- Documented external module: `jhora.horoscope.dhasa.raasi.chara`
- Documented external methods: `get_dhasa_antardhasa(dob,tob,place,divisional_chart_factor=1,years=1,months=1,sixty_hours=1,include_antardasa=True)`
@@ -89,8 +89,8 @@ Generated from ontology, family templates and public interface documentation. It
- Use for: doctrine pack required before interpretation
- Never use as: hand-written period dates, standalone event certainty
- Required rule fields: seed, applicability, sequence, duration_model, birth_balance, child_period_model, year_definition
- Local evidence hits: references/book_rule_technique_lineage.json, references/technique_registry.json, scripts/active_rectification_candidates.py, scripts/active_rectification_event_engine.py, scripts/active_rectification_questions.py, scripts/buddhi_gati.py, scripts/candidate_multi_engine_observation_replay_2026_07_23.py, scripts/canonical_profile_regression.py, scripts/chara_narayana_book_golden_cases.py, scripts/chara_narayana_book_runtime_replay.py, scripts/chara_narayana_karan_singh_public_replay.py, scripts/chara_narayana_krymsk_d1_row_replay.py, scripts/chara_narayana_maneka_gandhi_md_table_replay.py, scripts/chara_narayana_public_dated_examples_replay.py, scripts/chara_narayana_rath_d16_table40_replay.py, scripts/chara_narayana_rath_vimsamsa_md_ad_replay.py, scripts/chara_narayana_rath_vimsamsa_runtime_probe.py, scripts/chara_narayana_rath_wadiyar_table25_replay.py, scripts/chara_narayana_round6_pd_order_reverse_probe.py, scripts/chara_narayana_round6_runtime_probe.py, scripts/chara_narayana_source_rule_ledger.py, scripts/chara_narayana_us_election_public_candidate_compatibility.py, scripts/chara_narayana_vajpayee_md_table_replay.py, scripts/cmd_narayana_dasha.py, scripts/commercial_astrology_e2e_acceptance_runner.py, scripts/current_blocked_optimization_audit.py, scripts/event_adjudication_receipt.py, scripts/evidence_labeled_reporting.py, scripts/extended_dashas.py, scripts/extract_research_export_sample.py, scripts/full_report_quality_gate.py, scripts/full_spectrum_invocation_plan.py, scripts/full_technique_invocation_matrix.py, scripts/global_top3_jyotish_audit.py, scripts/guided_topic_discovery.py, scripts/jyotish_api_server.py, scripts/jyotish_engine.py, scripts/jyotish_skill_architecture_registry.py, scripts/karan_singh_narayana_dated_event_stack_replay.py, scripts/karan_singh_narayana_duration_profile_arbitration.py, scripts/karan_singh_narayana_event_pd_replay.py, scripts/karan_singh_narayana_real_chart_duration_replay.py, scripts/kn_rao_bhrigu_aggregation_ledger.py, scripts/lagnamsaka_dasha.py, scripts/life_stage_hook.py, scripts/local_accepted_evidence_fixtures.py, scripts/narayana_dasha.py, scripts/narayana_direction_closure_packet.py, scripts/narayana_pl9_profile_probe.py, scripts/narayana_variant_regression.py, scripts/padanadhamsa.py, scripts/pl9_full_parity_dashboard.py, scripts/pl9_p92_p103_field_audit.py, scripts/pl9_report_corpus_audit.py, scripts/professional_parity_closure.py, scripts/profile_aware_benchmark_boundary_dashboard.py, scripts/public_oracle_replay.py, scripts/public_real_case_benchmark.py, scripts/real_case_website_e2e_eval.py, scripts/rectification_report_replay.py, scripts/sanjay_rath_narayana_book_ledger.py, scripts/specialized_dasha_external_replay.py, scripts/specialized_dasha_router.py, scripts/sudarsana_dasha.py, scripts/sudasa.py, scripts/technique_registry.py, scripts/timing_evidence_packets.py, scripts/trikona_dasha.py, scripts/trikona_raman.py, scripts/unified_calculation_archive.py, scripts/unified_consultation_orchestrator.py, scripts/varnada_dasha.py, scripts/varsha_narayana.py, scripts/vedastro_evidence_orchestrator.py, scripts/vedastro_parity_matrix.py
- Local implementation-candidate hits: scripts/active_rectification_candidates.py, scripts/active_rectification_event_engine.py, scripts/active_rectification_questions.py, scripts/buddhi_gati.py, scripts/candidate_multi_engine_observation_replay_2026_07_23.py, scripts/canonical_profile_regression.py, scripts/chara_narayana_book_golden_cases.py, scripts/chara_narayana_book_runtime_replay.py, scripts/chara_narayana_karan_singh_public_replay.py, scripts/chara_narayana_krymsk_d1_row_replay.py, scripts/chara_narayana_maneka_gandhi_md_table_replay.py, scripts/chara_narayana_public_dated_examples_replay.py, scripts/chara_narayana_rath_d16_table40_replay.py, scripts/chara_narayana_rath_vimsamsa_md_ad_replay.py, scripts/chara_narayana_rath_vimsamsa_runtime_probe.py, scripts/chara_narayana_rath_wadiyar_table25_replay.py, scripts/chara_narayana_round6_pd_order_reverse_probe.py, scripts/chara_narayana_round6_runtime_probe.py, scripts/chara_narayana_source_rule_ledger.py, scripts/chara_narayana_us_election_public_candidate_compatibility.py, scripts/chara_narayana_vajpayee_md_table_replay.py, scripts/cmd_narayana_dasha.py, scripts/commercial_astrology_e2e_acceptance_runner.py, scripts/current_blocked_optimization_audit.py, scripts/event_adjudication_receipt.py, scripts/evidence_labeled_reporting.py, scripts/extended_dashas.py, scripts/extract_research_export_sample.py, scripts/full_report_quality_gate.py, scripts/full_spectrum_invocation_plan.py, scripts/full_technique_invocation_matrix.py, scripts/global_top3_jyotish_audit.py, scripts/guided_topic_discovery.py, scripts/jyotish_api_server.py, scripts/jyotish_engine.py, scripts/jyotish_skill_architecture_registry.py, scripts/karan_singh_narayana_dated_event_stack_replay.py, scripts/karan_singh_narayana_duration_profile_arbitration.py, scripts/karan_singh_narayana_event_pd_replay.py, scripts/karan_singh_narayana_real_chart_duration_replay.py, scripts/kn_rao_bhrigu_aggregation_ledger.py, scripts/lagnamsaka_dasha.py, scripts/life_stage_hook.py, scripts/local_accepted_evidence_fixtures.py, scripts/narayana_dasha.py, scripts/narayana_direction_closure_packet.py, scripts/narayana_pl9_profile_probe.py, scripts/narayana_variant_regression.py, scripts/padanadhamsa.py, scripts/pl9_full_parity_dashboard.py, scripts/pl9_p92_p103_field_audit.py, scripts/pl9_report_corpus_audit.py, scripts/professional_parity_closure.py, scripts/profile_aware_benchmark_boundary_dashboard.py, scripts/public_oracle_replay.py, scripts/public_real_case_benchmark.py, scripts/real_case_website_e2e_eval.py, scripts/rectification_report_replay.py, scripts/sanjay_rath_narayana_book_ledger.py, scripts/specialized_dasha_external_replay.py, scripts/specialized_dasha_router.py, scripts/sudarsana_dasha.py, scripts/sudasa.py, scripts/technique_registry.py, scripts/timing_evidence_packets.py, scripts/trikona_dasha.py, scripts/trikona_raman.py, scripts/unified_calculation_archive.py, scripts/unified_consultation_orchestrator.py, scripts/varnada_dasha.py, scripts/varsha_narayana.py, scripts/vedastro_evidence_orchestrator.py, scripts/vedastro_parity_matrix.py
- Local evidence hits: references/book_rule_technique_lineage.json, references/technique_registry.json, scripts/active_rectification_candidates.py, scripts/active_rectification_event_engine.py, scripts/active_rectification_questions.py, scripts/buddhi_gati.py, scripts/candidate_multi_engine_observation_replay_2026_07_23.py, scripts/canonical_profile_regression.py, scripts/chara_narayana_book_golden_cases.py, scripts/chara_narayana_book_runtime_replay.py, scripts/chara_narayana_karan_singh_public_replay.py, scripts/chara_narayana_krymsk_d1_row_replay.py, scripts/chara_narayana_maneka_gandhi_md_table_replay.py, scripts/chara_narayana_public_dated_examples_replay.py, scripts/chara_narayana_rath_d16_table40_replay.py, scripts/chara_narayana_rath_vimsamsa_md_ad_replay.py, scripts/chara_narayana_rath_vimsamsa_runtime_probe.py, scripts/chara_narayana_rath_wadiyar_table25_replay.py, scripts/chara_narayana_round6_pd_order_reverse_probe.py, scripts/chara_narayana_round6_runtime_probe.py, scripts/chara_narayana_source_rule_ledger.py, scripts/chara_narayana_us_election_public_candidate_compatibility.py, scripts/chara_narayana_vajpayee_md_table_replay.py, scripts/cmd_narayana_dasha.py, scripts/commercial_astrology_e2e_acceptance_runner.py, scripts/current_blocked_optimization_audit.py, scripts/event_adjudication_receipt.py, scripts/evidence_labeled_reporting.py, scripts/extended_dashas.py, scripts/extract_research_export_sample.py, scripts/finance_astrology_support_review.py, scripts/full_report_quality_gate.py, scripts/full_spectrum_invocation_plan.py, scripts/full_technique_invocation_matrix.py, scripts/global_top3_jyotish_audit.py, scripts/goel_jaimini_strength.py, scripts/guided_topic_discovery.py, scripts/jaimini.py, scripts/jyotish_api_server.py, scripts/jyotish_engine.py, scripts/jyotish_skill_architecture_registry.py, scripts/karan_singh_narayana_dated_event_stack_replay.py, scripts/karan_singh_narayana_duration_profile_arbitration.py, scripts/karan_singh_narayana_event_pd_replay.py, scripts/karan_singh_narayana_real_chart_duration_replay.py, scripts/kn_rao_bhrigu_aggregation_ledger.py, scripts/lagnamsaka_dasha.py, scripts/life_stage_hook.py, scripts/local_accepted_evidence_fixtures.py, scripts/narayana_dasha.py, scripts/narayana_direction_closure_packet.py, scripts/narayana_pl9_profile_probe.py, scripts/narayana_variant_regression.py, scripts/padanadhamsa.py, scripts/pl9_full_parity_dashboard.py, scripts/pl9_p92_p103_field_audit.py, scripts/pl9_report_corpus_audit.py, scripts/professional_parity_closure.py, scripts/profile_aware_benchmark_boundary_dashboard.py, scripts/public_oracle_replay.py, scripts/public_real_case_benchmark.py, scripts/real_case_website_e2e_eval.py, scripts/rectification_report_replay.py, scripts/sanjay_rath_narayana_book_ledger.py, scripts/specialized_dasha_external_replay.py, scripts/specialized_dasha_router.py, scripts/sudarsana_dasha.py, scripts/sudasa.py, scripts/technique_registry.py, scripts/timing_evidence_packets.py, scripts/trikona_dasha.py, scripts/trikona_raman.py, scripts/unified_calculation_archive.py, scripts/unified_consultation_orchestrator.py, scripts/varnada_dasha.py, scripts/varsha_narayana.py, scripts/vedastro_evidence_orchestrator.py, scripts/vedastro_parity_matrix.py
- Local implementation-candidate hits: scripts/active_rectification_candidates.py, scripts/active_rectification_event_engine.py, scripts/active_rectification_questions.py, scripts/buddhi_gati.py, scripts/candidate_multi_engine_observation_replay_2026_07_23.py, scripts/canonical_profile_regression.py, scripts/chara_narayana_book_golden_cases.py, scripts/chara_narayana_book_runtime_replay.py, scripts/chara_narayana_karan_singh_public_replay.py, scripts/chara_narayana_krymsk_d1_row_replay.py, scripts/chara_narayana_maneka_gandhi_md_table_replay.py, scripts/chara_narayana_public_dated_examples_replay.py, scripts/chara_narayana_rath_d16_table40_replay.py, scripts/chara_narayana_rath_vimsamsa_md_ad_replay.py, scripts/chara_narayana_rath_vimsamsa_runtime_probe.py, scripts/chara_narayana_rath_wadiyar_table25_replay.py, scripts/chara_narayana_round6_pd_order_reverse_probe.py, scripts/chara_narayana_round6_runtime_probe.py, scripts/chara_narayana_source_rule_ledger.py, scripts/chara_narayana_us_election_public_candidate_compatibility.py, scripts/chara_narayana_vajpayee_md_table_replay.py, scripts/cmd_narayana_dasha.py, scripts/commercial_astrology_e2e_acceptance_runner.py, scripts/current_blocked_optimization_audit.py, scripts/event_adjudication_receipt.py, scripts/evidence_labeled_reporting.py, scripts/extended_dashas.py, scripts/extract_research_export_sample.py, scripts/finance_astrology_support_review.py, scripts/full_report_quality_gate.py, scripts/full_spectrum_invocation_plan.py, scripts/full_technique_invocation_matrix.py, scripts/global_top3_jyotish_audit.py, scripts/goel_jaimini_strength.py, scripts/guided_topic_discovery.py, scripts/jaimini.py, scripts/jyotish_api_server.py, scripts/jyotish_engine.py, scripts/jyotish_skill_architecture_registry.py, scripts/karan_singh_narayana_dated_event_stack_replay.py, scripts/karan_singh_narayana_duration_profile_arbitration.py, scripts/karan_singh_narayana_event_pd_replay.py, scripts/karan_singh_narayana_real_chart_duration_replay.py, scripts/kn_rao_bhrigu_aggregation_ledger.py, scripts/lagnamsaka_dasha.py, scripts/life_stage_hook.py, scripts/local_accepted_evidence_fixtures.py, scripts/narayana_dasha.py, scripts/narayana_direction_closure_packet.py, scripts/narayana_pl9_profile_probe.py, scripts/narayana_variant_regression.py, scripts/padanadhamsa.py, scripts/pl9_full_parity_dashboard.py, scripts/pl9_p92_p103_field_audit.py, scripts/pl9_report_corpus_audit.py, scripts/professional_parity_closure.py, scripts/profile_aware_benchmark_boundary_dashboard.py, scripts/public_oracle_replay.py, scripts/public_real_case_benchmark.py, scripts/real_case_website_e2e_eval.py, scripts/rectification_report_replay.py, scripts/sanjay_rath_narayana_book_ledger.py, scripts/specialized_dasha_external_replay.py, scripts/specialized_dasha_router.py, scripts/sudarsana_dasha.py, scripts/sudasa.py, scripts/technique_registry.py, scripts/timing_evidence_packets.py, scripts/trikona_dasha.py, scripts/trikona_raman.py, scripts/unified_calculation_archive.py, scripts/unified_consultation_orchestrator.py, scripts/varnada_dasha.py, scripts/varsha_narayana.py, scripts/vedastro_evidence_orchestrator.py, scripts/vedastro_parity_matrix.py
- Book rule links: web_pvr_varsha_narayana_ch30_4
- Book topic links: jaimini_chara_narayana
- Documented external module: `jhora.horoscope.dhasa.raasi.narayana`
@@ -387,12 +387,12 @@ Generated from ontology, family templates and public interface documentation. It
- Canonical ID: `niryaana_shoola_dasha`
- Legacy family priority (not scoped development count): P0 / 88
- Next work: complete_variant_and_report_contract
- Remaining requirements: See missing dimensions and source-specific usage notes
- Remaining requirements: source-backed AD/PD rule and row-level replay; independent same-profile MD case; no predictive or global admission
- Native producer: `scripts.niryaana_shoola_dasha.calculate_niryaana_shoola_dasha`
- Producer maturity: `source_bounded_wired`
- Producer verification scope: stronger Lagna/seventh selection, explicit tie-break, named Goel/Raman/PyJHora duration profiles and MD/AD/PD boundaries
- Producer verification scope: PVR second/eighth ayur-strength selection with unresolved ties blocked; movable/fixed/dual 7/8/9 calendar-year MD; external observation separate
- Report paths: `jyotish_engine.worksheets.timing_and_predictive_systems.dasha_master_pack.families.niryaana_shoola`, `references/oracle/pl9_p92_p103_field_audit_2026_09_11.json`
- Variant profiles: `niryaana_shoola_goel_profile` (adopted_source_bounded), `niryaana_shoola_raman_profile` (optional_source_profile), `niryaana_shoola_pyjhora_observation_profile` (external_observation_only)
- Variant profiles: `pvr_niryaana_shoola_v1` (adopted_source_bounded), `niryaana_shoola_pyjhora_observation_profile` (external_observation_only)
- Required inputs: birth_datetime, birth_location, ayanamsa, rule_profile
- Use for: doctrine pack required before interpretation
- Never use as: hand-written period dates, standalone event certainty
@@ -445,8 +445,8 @@ Generated from ontology, family templates and public interface documentation. It
- Use for: doctrine pack required before interpretation
- Never use as: hand-written period dates, standalone event certainty
- Required rule fields: seed, applicability, sequence, duration_model, birth_balance, child_period_model, year_definition
- Local evidence hits: scripts/extended_dashas.py, scripts/jyotish_engine.py, scripts/niryaana_shoola_dasha.py, scripts/niryaana_shoola_family_contract.py, scripts/pl9_named_dasha_row_replay.py, scripts/pl9_p92_p103_field_audit.py, scripts/pl9_report_corpus_audit.py, scripts/professional_parity_closure.py, scripts/shoola_dasha.py, scripts/shoola_family_contract.py
- Local implementation-candidate hits: scripts/extended_dashas.py, scripts/jyotish_engine.py, scripts/niryaana_shoola_dasha.py, scripts/niryaana_shoola_family_contract.py, scripts/pl9_named_dasha_row_replay.py, scripts/pl9_p92_p103_field_audit.py, scripts/pl9_report_corpus_audit.py, scripts/professional_parity_closure.py, scripts/shoola_dasha.py, scripts/shoola_family_contract.py
- Local evidence hits: scripts/dasha_profile_arbitration.py, scripts/extended_dashas.py, scripts/jyotish_engine.py, scripts/niryaana_shoola_dasha.py, scripts/niryaana_shoola_family_contract.py, scripts/pl9_named_dasha_row_replay.py, scripts/pl9_p92_p103_field_audit.py, scripts/pl9_report_corpus_audit.py, scripts/professional_parity_closure.py, scripts/shoola_dasha.py, scripts/shoola_family_contract.py
- Local implementation-candidate hits: scripts/dasha_profile_arbitration.py, scripts/extended_dashas.py, scripts/jyotish_engine.py, scripts/niryaana_shoola_dasha.py, scripts/niryaana_shoola_family_contract.py, scripts/pl9_named_dasha_row_replay.py, scripts/pl9_p92_p103_field_audit.py, scripts/pl9_report_corpus_audit.py, scripts/professional_parity_closure.py, scripts/shoola_dasha.py, scripts/shoola_family_contract.py
- Book rule links: not mapped
- Book topic links: not mapped
- Documented external module: `jhora.horoscope.dhasa.raasi.shoola`
+2
View File
@@ -107,6 +107,8 @@ Sudarsana Dasha 已有两个独立的原生太阳弧 MD/AD/PD profile,见
[原生与外部用法包](references/sudarsana-dasha-usage.md)。教材从父星座起子期,2004研究从父宫主本命落座起;必须显式选择,不混合。外部固定年观察和旧占位拒绝保持独立。静态 Sudarshana 三参考盘已可调用
[严格结构用法包](references/sudarshana-structure-usage.md),不要混同二者。
Chandra Kala Nadi 已有[非均匀具名分段及误差候选入口](references/chandra-kala-nadi-usage.md):Patel十六分盘边界并集,保留均分对照profile、三类星座编号和几何子段;不是D150落座或命运判断。
Meena Jeeva/Sareera 结构入口:`jyotish_engine.py nadi-stellar-roles --request <local.json>`,复用 `nakshatra_advanced.calculate_stellar_jeeva_sareera`。采用具名的原书 printed-p89 步骤;缺星宿方向力或交点尊贵口径时保留 blocked,不得代入 Shadbala。详见 `docs/research/stellar_jeeva_sareera_2026_11_07.md`;尚不启用预测评分或客户导出。
这些条目可以用于方法比较或缺口研究,但必须保留原始状态,不得写入确定性
应期或客户报告。
@@ -557,8 +557,8 @@ Static entrypoint presence is not a runtime claim: not_run_by_scanner. Original
- Use for: named nonuniform Nadi amsa lookup, geometric subparts and uncertainty candidates
- Never use as: generic equal D150 substitution, standalone predictive or rectification verdict
- Required rule fields: boundary union, sign modality numbering, named source list, half-open endpoints, uncertainty and geometric subparts
- Local evidence hits: references/book_rule_packets.json, references/book_rule_technique_lineage.json, references/internet_technique_gap_registry.json, scripts/chandra_kala_nadi.py, scripts/nadi_book_extraction_workflow.py, scripts/nadi_knowledge_extraction_plan.py
- Local implementation-candidate hits: scripts/chandra_kala_nadi.py, scripts/nadi_book_extraction_workflow.py, scripts/nadi_knowledge_extraction_plan.py
- Local evidence hits: references/book_rule_packets.json, references/book_rule_technique_lineage.json, references/internet_technique_gap_registry.json, scripts/audit_local_knowledge_copies.py, scripts/chandra_kala_nadi.py, scripts/nadi_book_extraction_workflow.py, scripts/nadi_knowledge_extraction_plan.py, scripts/nadi_leaf_assistant.py
- Local implementation-candidate hits: scripts/chandra_kala_nadi.py, scripts/nadi_book_extraction_workflow.py, scripts/nadi_knowledge_extraction_plan.py, scripts/nadi_leaf_assistant.py
- Book rule links: patel_nadi_ch1_ch2_named_nonuniform
- Book topic links: nadi
- Documented external module: `not documented`
@@ -1073,7 +1073,7 @@ Static entrypoint presence is not a runtime claim: not_run_by_scanner. Original
- Use for: source-labelled sensitive-point sums and classical_local D9 parity
- Never use as: fertility diagnosis, medical probability, event timing or outcome verdict
- Required rule fields: formula, point_names, normalization, gulika_or_mandi_convention, interpretation_scope
- Local evidence hits: scripts/beeja_kshetra_sphuta.py
- Local evidence hits: scripts/audit_local_knowledge_copies.py, scripts/beeja_kshetra_sphuta.py
- Local implementation-candidate hits: scripts/beeja_kshetra_sphuta.py
- Book rule links: not mapped
- Book topic links: not mapped
+45
View File
@@ -0,0 +1,45 @@
"""Ported from upstream 8813c851 (astrologer branch d09feab7, 2026-11-07 sync):
BCP is observed at an explicit target date; the birth epoch is only a labelled default."""
import ast
from pathlib import Path
def test_bcp_report_uses_explicit_target_epoch_and_labels_birth_default():
from scripts.bhrigu_pada_dasha import bhrigu_pada_dasha_full_report
natal = bhrigu_pada_dasha_full_report(10., 2451545.)
later = bhrigu_pada_dasha_full_report(10., 2451545., target_date_jd=2451910.25)
assert natal['bcp_cycle']['years_elapsed'] == 0
assert natal['bcp_reference']['scope'] == 'birth_epoch_default'
assert later['bcp_cycle']['years_elapsed'] == 1
assert later['bcp_cycle']['bcp_longitude'] == 40.
assert later['bcp_reference'] == {'target_jd': 2451910.25, 'scope': 'explicit_target'}
assert later['dasha_sequence'] == natal['dasha_sequence']
def test_full_reading_passes_the_reading_date_to_bcp(monkeypatch):
import sys
from types import SimpleNamespace
from datetime import datetime
import swisseph as swe
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
import bhrigu_pada_dasha
calls = []
def calculate(moon, jd, **kwargs):
calls.append((moon, jd, kwargs.get('target_date_jd')))
return {'dasha_sequence': []}
monkeypatch.setattr(bhrigu_pada_dasha, 'bhrigu_pada_dasha_full_report', calculate)
tree = ast.parse((Path(__file__).resolve().parents[1] / 'scripts/jyotish_engine.py').read_text())
blocks = [n for n in ast.walk(tree) if isinstance(n, ast.Try) and any(
isinstance(item, ast.ImportFrom) and item.module == 'bhrigu_pada_dasha' for item in n.body)]
assert len(blocks) == 1
env = {'report': {'modules': {}, 'errors': []}, 'planet_lons': {'Moon': 10.}, 'jd': 2451545.,
'_varga_planet_lons': lambda value: {}, 'SIGNS': [], 'SIGN_LORDS': {},
'args': SimpleNamespace(transit_date='2001-01-01', today='2000-01-01', tz=0),
'swe': swe, 'datetime': datetime}
exec(compile(ast.Module(body=blocks, type_ignores=[]), '<bhrigu-full-reading>', 'exec'), env)
assert calls == [(10., 2451545., 2451911.)]
assert not env['report']['errors']
module = env['report']['modules']['bhrigu_pada_dasha']
assert module['status'] == 'parameter_sensitive'
assert module['bcp_reference_date'] == '2001-01-01'
+24
View File
@@ -0,0 +1,24 @@
import pytest
from scripts.jyotish_engine import calc_dispositor_chain
def test_dispositor_location_is_not_the_owned_sign():
chain = calc_dispositor_chain("Mercury", {
"Mercury": {"sign": "Leo"}, "Sun": {"sign": "Cancer"},
"Moon": {"sign": "Cancer"},
})
assert [(row["planet"], row["dispositor"], row["dispositor_sign"]) for row in chain] == [
("Mercury", "Sun", "Cancer"), ("Sun", "Moon", "Cancer"), ("Moon", "Moon", "Cancer")]
@pytest.mark.parametrize("lord", [None, {}, "unavailable"])
def test_missing_dispositor_location_is_not_invented(lord):
chain = calc_dispositor_chain("Mercury", {"Mercury": {"sign": "Leo"}, "Sun": lord})
assert len(chain) == 1
assert chain[0]["dispositor_sign"] is None
def test_exchange_keeps_both_actual_locations_without_infinite_loop():
chain = calc_dispositor_chain("Mars", {"Mars": {"sign": "Taurus"}, "Venus": {"sign": "Aries"}})
assert [r["dispositor_sign"] for r in chain] == ["Aries", "Taurus"]
+78
View File
@@ -0,0 +1,78 @@
from copy import deepcopy
import pytest
from scripts.finance_astrology_support_review import (
build_finance_astrology_support_review, build_finance_delivery_inventory,
)
from scripts.full_report_quality_gate import evaluate_evidence_claims, evaluate_full_report
from scripts.unified_consultation_orchestrator import UnifiedConsultationOrchestrator
@pytest.mark.parametrize("status", ["blocked", "missing", "error", "failed",
"not_executed", "not_applicable", "not_available_yet"])
def test_unavailable_narayana_cannot_become_available_by_wrapping(status):
packet = UnifiedConsultationOrchestrator().machine_evidence_packet(
chart={"modules": {"narayana_dasha": {"status": status, "reason": "fixture"}}},
route_packet={"question_type": "finance"})
review = build_finance_astrology_support_review(
route_packet={"question_type": "finance"}, machine_evidence_packet=packet,
runtime_evidence_log={})
assert review["coverage"]["narayana"] == "blocked"
assert "narayana_dasha" in packet["missing_sections"]
def test_actual_native_sections_reach_finance_review():
chart = {"modules": {
"varga_full": {"D11_Rudramsa": {"fixture": True}, "D4_Turyamsa": {"fixture": True}},
"narayana_dasha": {"periods": [{"sign": "Leo"}]},
"transit_multi_reference": {"status": "blocked"},
}}
packet = UnifiedConsultationOrchestrator().machine_evidence_packet(
chart=chart, route_packet={"question_type": "finance"})
assert packet["sections"]["D11"]["status"] == "used"
assert packet["sections"]["D4"]["status"] == "used"
review = build_finance_astrology_support_review(
route_packet={"question_type": "finance"}, machine_evidence_packet=packet,
runtime_evidence_log={})
assert review["coverage"]["narayana"] == "present"
assert review["coverage"]["transit"] == "blocked"
assert review["readiness"] == "incomplete"
def test_arbitration_is_not_a_narayana_execution_receipt():
review = build_finance_astrology_support_review(
route_packet={"question_type": "finance"}, machine_evidence_packet={},
runtime_evidence_log={"cross_system_arbitration": {"status": "used"}})
assert review["coverage"]["narayana"] == "missing"
assert review["audit_rows"][0]["effect_on_confidence"] == "incomplete"
def test_delivery_preserves_raw_without_promoting_or_mutating():
modules = {"argala": {"status": "blocked", "candidate": [1]},
"dispositor_chains": {"chain": ["Mercury", "Jupiter"]}}
original = deepcopy(modules)
inventory = build_finance_delivery_inventory(modules)
rows = {r["technique"]: r for r in inventory["rows"]}
assert rows["argala"]["delivery_status"] == "producer_status_blocked"
assert rows["sudasa"]["delivery_status"] == "not_delivered"
assert rows["dispositor_chains"]["execution_status"] == "not_verified_by_inventory"
rows["argala"]["raw"]["candidate"].append(2)
assert modules == original
def test_conflicting_claim_binding_blocks_gate():
packet = {"points": {"A11": "Leo"}, "evidence_claims": [
{"path": ["points", "A11"], "value": "Taurus"}]}
assert evaluate_evidence_claims(packet)["errors"] == ["claim_0:value_mismatch"]
assert "evidence_claim_bindings_invalid" in evaluate_full_report(packet)["blocking_reasons"]
packet["evidence_claims"][0]["value"] = "Leo"
assert evaluate_evidence_claims(packet)["status"] == "bindings_match"
def test_missing_and_empty_bindings_never_claim_verification():
assert evaluate_evidence_claims({"evidence_claims": []})["status"] == "not_supplied"
assert evaluate_evidence_claims({})["status"] == "not_supplied"
for path, value in [(["absent"], 0), (["evidence_claims"], []), (["zero"], False)]:
packet = {"zero": 0, "evidence_claims": [{"path": path, "value": value}]}
assert evaluate_evidence_claims(packet)["status"] == "blocked"
+92 -5
View File
@@ -3,6 +3,8 @@
from __future__ import annotations
import pytest
import jyotish_engine
from mcp_server import (
@@ -518,7 +520,10 @@ def test_finance_sodhita_and_kakshya_friction_reduce_score_without_forcing_label
assert base["dominant_label"] == "public_wealth_status"
assert weighted["dominant_label"] == "public_wealth_status"
assert weighted["score"] == base["score"] - 4
# 原值: base - 4(Sodhita -2 + Kakshya -2); 新值: base - 2,且无 sodhita_wealth_friction;
# 原因: 产品 2026-11-07 同步占星师工作分支 d09feab7(a5b75886 Sodhita 无已裁定财富阈值,不再投票)。
assert weighted["score"] == base["score"] - 2
assert "sodhita_wealth_friction" not in weighted["secondary_context"]
def test_collect_strict_evidence_finance_derives_wealth_promise_from_dhana_yogas() -> None:
@@ -763,7 +768,7 @@ def test_collect_strict_evidence_finance_adds_pav_and_kakshya_support_as_seconda
assert "kakshya_finance_support" in strict["event_judgement"]["secondary_context"]
def test_collect_strict_evidence_finance_flags_sodhita_wealth_friction() -> None:
def test_collect_strict_evidence_retains_unproven_sodhita_without_friction() -> None:
result = {
"modules": {
"chart": {"ascendant": {"sign": "Aries"}},
@@ -814,14 +819,20 @@ def test_collect_strict_evidence_finance_flags_sodhita_wealth_friction() -> None
strict = _collect_strict_evidence("finance", result)
# 原值: level obstructive + signals [sodhita_wealth_friction],且进入 secondary_context;
# 新值: level none、signals []、status method_unverified、prediction_scoring_allowed False;
# 原因: 产品 2026-11-07 同步占星师工作分支 d09feab7(a5b75886)。
assert strict["present_evidence"]["sodhita_finance_support"] == {
"level": "obstructive",
"level": "none",
"source": "ashtakavarga_sodhita_bridge_v1",
"signals": ["sodhita_wealth_friction"],
"signals": [],
"target_houses": [2, 11],
"raw_scores": {"2": 18, "11": 17},
"status": "method_unverified",
"prediction_scoring_allowed": False,
"reason": "unadjudicated_contribution_subtraction_not_wealth_evidence",
}
assert "sodhita_wealth_friction" in strict["event_judgement"]["secondary_context"]
assert "sodhita_wealth_friction" not in strict["event_judgement"]["secondary_context"]
def test_collect_strict_evidence_finance_flags_kakshya_friction_as_secondary_only() -> None:
@@ -1494,3 +1505,79 @@ def test_finance_narrative_payload_forces_monthly_adjudication_layers_into_final
assert any("月度主状态" in item for item in payload["strengths"])
assert any("阻力来源" in item for item in payload["risks"])
assert "时间置信度" in payload["markdown"]
# Ported from upstream 0266b6ad / 8c4bff8a / a5b75886 (d09feab7 branch).
@pytest.mark.parametrize('metadata', [
{},
{'prediction_scoring_allowed': True},
{'method_id': 'unspecified_historical_method'},
{'method_id': 'unregistered_method', 'prediction_scoring_allowed': True},
{'prediction_scoring_allowed': False},
{'method': 'Sodhita Ashtakavarga (BPHS标准)'},
{'status': 'executed', 'method': 'Trikona Shodhana -> Ekadhipatya Shodhana'},
])
def test_legacy_sodhita_retains_raw_values_without_wealth_penalty(metadata):
from mcp_server import _derive_sodhita_finance_support
result = _derive_sodhita_finance_support({'sodhita': {
**metadata, 'sodhita_sav': {'assessment': [
{'sign': 'Taurus', 'score': 5}, {'sign': 'Aquarius', 'score': 6}]}}}, 'Aries')
assert result['raw_scores'] == {'2': 5, '11': 6}
assert result['level'] == 'none'
assert result['signals'] == []
assert result['prediction_scoring_allowed'] is False
@pytest.mark.parametrize('metadata', [
{},
{'prediction_scoring_allowed': True},
{'method_id': 'unspecified_historical_method'},
{'method_id': 'unregistered_method', 'prediction_scoring_allowed': True},
{'prediction_scoring_allowed': False},
{'status': 'legacy_reference_only'},
{'status': 'reference_only'},
{'status': 'blocked'},
{'status': 'incomplete'},
{'status': 'not_available_yet'},
{'status': 'unavailable'},
{'method_id': 'legacy_malefic_contribution_subtraction_v1'},
{'method': 'Sodhita Ashtakavarga (BPHS标准)'},
])
def test_cached_sodhita_restrictions_survive_finance_adjudication(metadata):
from copy import deepcopy
present = {'vimshottari_current': {'mahadasha': 'Venus'}}
baseline = _derive_event_judgement('finance', present, [])
present['sodhita_finance_support'] = {
**metadata, 'level': 'obstructive',
'signals': ['sodhita_wealth_friction'], 'raw_scores': {'2': 5, '11': 6},
}
original = deepcopy(present)
assert _derive_event_judgement('finance', present, []) == baseline
assert present == original
@pytest.mark.parametrize('status', ['blocked', 'reference_only', 'unavailable'])
def test_sodhita_producer_restriction_survives_bridge(status):
from mcp_server import _derive_sodhita_finance_support
result = _derive_sodhita_finance_support({'sodhita': {
'status': status, 'sodhita_sav': {'assessment': [
{'sign': 'Taurus', 'score': 5}, {'sign': 'Aquarius', 'score': 6}]}}}, 'Aries')
assert result['raw_scores'] == {'2': 5, '11': 6}
assert result['status'] == status
assert result['prediction_scoring_allowed'] is False
assert result['level'] == 'none'
assert result['signals'] == []
# Ported from upstream ba8e36ec.
def test_optional_delivery_inventory_does_not_vote_or_fill_required_evidence():
baseline = _collect_strict_evidence("finance", {"modules": {}})
result = _collect_strict_evidence("finance", {"modules": {
"argala": {"status": "blocked", "candidate": "retained"},
"sudasa": {"periods": [{"sign": "Leo"}]},
}})
rows = {r["technique"]: r for r in result["finance_delivery_inventory"]["rows"]}
assert rows["argala"]["raw"]["candidate"] == "retained"
assert rows["sudasa"]["delivery_status"] == "payload_delivered"
assert result["event_judgement"] == baseline["event_judgement"]
assert result["missing_evidence"] == baseline["missing_evidence"]
+117
View File
@@ -0,0 +1,117 @@
"""Synthetic structural cases; no personal data or predictive validation."""
import json
from pathlib import Path
import subprocess
import sys
import pytest
from scripts.nakshatra_advanced import calculate_stellar_jeeva_sareera
BASE = dict(Sun=125., Moon=45., Mars=215., Mercury=75.,
Jupiter=255., Venus=185., Saturn=305., Rahu=10., Ketu=190.)
def run(lord, positions=None, **kwargs):
return calculate_stellar_jeeva_sareera(
positions or BASE, bhava_lord=lord, ayanamsa="synthetic",
node_mode="mean", **kwargs)
def test_own_star_uses_sign_lord_instead_of_self_loop():
result = run("Moon")
assert (result["jeeva"], result["sareera"]) == ("Moon", "Venus")
assert result["prediction_scoring_allowed"] is False
def test_own_star_and_own_sign_retains_both():
result = run("Sun", dict(BASE, Sun=148.))
assert (result["jeeva"], result["sareera"]) == ("Sun", "Sun")
def test_owner_beats_exalted_companion():
result = run("Mercury", dict(BASE, Mercury=20., Saturn=200.))
assert result["jeeva"] == "Venus"
def test_original_saturn_anuradha_retains_against_owner():
result = run("Saturn", dict(BASE, Saturn=220.))
assert result["jeeva"] == "Saturn"
def test_missing_stellar_digbala_is_not_false():
result = run("Moon", dict(BASE, Moon=275., Sun=65., Mercury=100., Jupiter=70.))
assert result["status"] == "blocked"
assert result["jeeva"] is None
assert "stellar_digbala" in result["trace"][0]["reason"]
def test_explicit_stellar_digbala_transfers_then_recomputes_sareera():
result = run("Moon", dict(BASE, Moon=275., Sun=65., Mercury=100., Jupiter=70.),
stellar_digbala={"Sun": False, "Jupiter": True})
assert result["jeeva"] == "Jupiter"
assert result["sareera"] == "Rahu"
@pytest.mark.parametrize("value", [float("nan"), float("inf"), True, "45"])
def test_invalid_longitude_rejected(value):
with pytest.raises(ValueError):
run("Moon", dict(BASE, Moon=value))
def test_missing_planet_cannot_hide_a_companion():
with pytest.raises(ValueError):
run("Moon", {k: v for k, v in BASE.items() if k != "Ketu"})
def test_numeric_shadbala_cannot_masquerade_as_stellar_digbala():
with pytest.raises(ValueError):
run("Moon", stellar_digbala={"Sun": 60})
def test_exalted_companion_replaces_seed_without_strength_scores():
result = run("Moon", dict(BASE, Moon=275., Sun=332., Venus=355.))
assert result["jeeva"] == "Venus"
assert result["trace"][0]["reason"] == "exalted"
def test_unselected_saturn_does_not_override_owner_under_p89_profile():
result = run("Venus", dict(BASE, Saturn=220.))
assert result["jeeva"] == "Mars"
def test_nodal_exaltation_not_silently_imported_from_another_school():
result = run("Moon", dict(BASE, Moon=275., Sun=65., Mercury=100., Rahu=70.))
assert result["status"] == "blocked"
assert result["trace"][0]["reason"] == "nodal_exaltation_profile_required"
def test_source_table_roles_replay_with_explicit_stellar_assumption():
# Anonymous printed pp92-96 structural example. Not independent verification.
# Mars uses 29 degrees: OCR minute is uncertain, same star/sign throughout.
chart = dict(Sun=157+52/60, Moon=147+28/60, Mars=239., Mercury=144+51/60,
Jupiter=250+29/60, Venus=184+48/60, Saturn=200+2/60,
Rahu=36+43/60, Ketu=216+43/60)
expected = dict(Sun=("Sun", "Mercury"), Moon=("Sun", "Mercury"),
Mars=("Mercury", "Venus"), Mercury=("Venus", "Mars"),
Jupiter=("Mars", "Mercury"), Venus=("Mars", "Mercury"),
Saturn=("Jupiter", "Mars"))
for lord, pair in expected.items():
result = run(lord, chart, stellar_digbala={"Moon": False, "Mercury": True})
assert (result["jeeva"], result["sareera"]) == pair
assert result["external_verified"] is False
def test_native_cli_executes_without_export_or_prediction(tmp_path):
request = tmp_path / "synthetic.json"
request.write_text(json.dumps(dict(positions=BASE, bhava_lord="Moon",
ayanamsa="synthetic", node_mode="mean")))
root = Path(__file__).resolve().parents[1]
completed = subprocess.run(
[sys.executable, "scripts/jyotish_engine.py", "nadi-stellar-roles", "--request", str(request)],
cwd=root, text=True, capture_output=True, check=True)
result = json.loads(completed.stdout)
assert result["status"] == "executed"
assert result["sareera"] == "Venus"
assert result["prediction_scoring_allowed"] is False
+66
View File
@@ -0,0 +1,66 @@
"""Ported from upstream 0266b6ad (astrologer branch d09feab7, 2026-11-07 sync).
The year lord is selected by the native Tajika five-fold Panchavargiya; an
external (PyJHora) score is an observation beside it and cannot change the
selection, the components, or fill missing native inputs. The annual strength
table shows the same native score and components.
Local divergence: upstream's third test relabels the legacy Sun/Mars/Saturn
subtraction (`calc_sodhita_av`). That table was removed here (BUG-1209), so the
test asserts it stays absent and that the product chain is the classical one.
"""
from scripts import ashtakavarga, tajika
from scripts import tajika_year_lord
POSITIONS = {'Sun': 10., 'Moon': 42., 'Mars': 75., 'Mercury': 108.,
'Jupiter': 141., 'Venus': 174., 'Saturn': 207.}
def test_external_score_cannot_change_year_lord_or_native_components(monkeypatch):
monkeypatch.setattr(tajika, '_external_pyjhora_panchavargiya_score', lambda *a, **kw: None)
baseline = tajika.calc_panchadhikari_year_lord(POSITIONS, 0, 10, 25)
monkeypatch.setattr(tajika, '_external_pyjhora_panchavargiya_score',
lambda *a, **kw: {'score': 999, 'engine': 'external_fixture', 'components': {}})
observed = tajika.calc_panchadhikari_year_lord(POSITIONS, 0, 10, 25)
assert observed['year_lord'] == baseline['year_lord']
assert observed['panchavargiya_selection_policy'] == 'native_components_external_observation_only'
native = tajika._calc_native_panchavargiya_bala(POSITIONS)
for row in observed['candidates']:
assert row['panchavargiya_score'] == native[row['planet']]['score']
assert row['panchavargiya_components'] == native[row['planet']]['components']
assert row['panchavargiya_engine'] == 'tajika_native_five_fold'
assert row['external_oracle_comparison']['score'] == 999
assert row['external_oracle_comparison']['comparison_role'] == 'observation_only_not_selection_input'
def test_external_score_cannot_fill_missing_native_inputs(monkeypatch):
monkeypatch.setattr(tajika, '_external_pyjhora_panchavargiya_score',
lambda *a, **kw: {'score': 10, 'engine': 'fixture', 'components': {}})
assert tajika.calc_panchadhikari_year_lord({'Sun': 10., 'Moon': 42.}, 0, 10, 25)['status'] == 'blocked'
def test_year_lord_selection_and_strength_table_show_the_same_native_score():
selected = tajika.calc_panchadhikari_year_lord(POSITIONS, 0, 10, 25)
table = tajika.calc_tajika_strength_layers(POSITIONS, 0.0, selected['year_lord'])['panchavargiya_bala']
listed = tajika_year_lord._candidate_list(selected['candidates'], selected['year_lord'])
assert listed and any(row['selected'] for row in listed)
for row in listed:
shown = table[row['planet']]
assert row['panchavargiya_score'] == shown['score']
assert row['panchavargiya_raw_total'] == shown['raw_total']
assert row['panchavargiya_components'] == shown['components']
assert set(row['panchavargiya_components']) == {
'graha_bala', 'uchcha_bala', 'hudda_bala', 'drekkana_bala', 'navamsha_bala'}
def test_legacy_subtraction_table_stays_absent_from_the_product():
assert not hasattr(ashtakavarga, 'calc_sodhita_av')
planets = {name: {'sign': tajika.SIGNS[int(lon // 30)]} for name, lon in POSITIONS.items()}
planets.update({'Rahu': {'sign': 'Aries'}, 'Ketu': {'sign': 'Libra'}})
av = ashtakavarga.calc_ashtakavarga(planets, 0)
from scripts.ashtakavarga_shodhana import sodhita_chain
chain = sodhita_chain(av['bav'], planets)
assert chain['status'] == 'executed'
assert 'BPHS标准' not in chain['method']
assert len(chain['sodhita_sav']['assessment']) == 12