From 8c9b4263440c4402e41aa907c674e34b1f8f88f4 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Fri, 17 Jul 2026 22:35:19 +0800 Subject: [PATCH] feat: import oracle collection and calibration parity --- ...ols_holdout_v2_month_nearest_20260717.json | 3766 +++++++++++++++++ .../western_jobs_iphone_2007_calibration.json | 883 ++++ ...rashna_sphuta_pyjhora_parity_2026_07_17.md | 26 + ...ercial_external_validation_release.v1.json | 18 + .../prashna_sphuta_pyjhora_public_smoke.json | 42 + .../collect_vedastro_official_parity_raw.py | 206 + scripts/diagnose_external_engine_adapters.py | 57 +- scripts/gulika.py | 31 +- .../three_engine_parity_replay_validator.py | 22 + scripts/western_real_case_calibration.py | 73 + scripts/western_timing_engine.py | 74 +- ...st_collect_vedastro_official_parity_raw.py | 59 + ...est_external_engine_adapter_diagnostics.py | 23 +- tests/test_gulika.py | 21 +- tests/test_prashna_sphuta_pyjhora_oracle.py | 24 + ...st_three_engine_parity_replay_validator.py | 13 +- tests/test_western_real_case_calibration.py | 17 + tests/test_western_timing_engine.py | 13 +- 18 files changed, 5344 insertions(+), 24 deletions(-) create mode 100644 docs/benchmark/public_real_case_negative_controls_holdout_v2_month_nearest_20260717.json create mode 100644 docs/benchmark/western_jobs_iphone_2007_calibration.json create mode 100644 docs/research/prashna_sphuta_pyjhora_parity_2026_07_17.md create mode 100644 references/oracle/prashna_sphuta_pyjhora_public_smoke.json create mode 100644 scripts/collect_vedastro_official_parity_raw.py create mode 100644 scripts/western_real_case_calibration.py create mode 100644 tests/test_collect_vedastro_official_parity_raw.py create mode 100644 tests/test_prashna_sphuta_pyjhora_oracle.py create mode 100644 tests/test_western_real_case_calibration.py diff --git a/docs/benchmark/public_real_case_negative_controls_holdout_v2_month_nearest_20260717.json b/docs/benchmark/public_real_case_negative_controls_holdout_v2_month_nearest_20260717.json new file mode 100644 index 00000000..bce629ed --- /dev/null +++ b/docs/benchmark/public_real_case_negative_controls_holdout_v2_month_nearest_20260717.json @@ -0,0 +1,3766 @@ +{ + "benchmark_id": "public_real_case_negative_control_pilot_2026_07_11", + "boundary": "Controls are dates without the exact recorded target outcome. They may contain other life events. This pilot measures date ranking and false domain activation, not scientific causal validity.", + "cases": [ + { + "case_id": "albert_ii_accession_2005", + "controls": [ + { + "blocked": false, + "blocked_reason": null, + "date": "2005-03-07", + "result_class": "weak_hit", + "score": 6 + }, + { + "blocked": false, + "blocked_reason": null, + "date": "2005-05-06", + "result_class": "weak_hit", + "score": 6 + } + ], + "domain": "career", + "name": "Albert II, Prince of Monaco", + "positive": { + "actual_label": "domain_activation", + "birth_time_rating": "AA", + "blocked": false, + "case_id": "albert_ii_accession_2005", + "domain": "career", + "event_date": "2005-04-06", + "evidence": { + "arudha_lord": "Venus", + "ascendant": { + "degree": 28.0929, + "degree_in_sign": 28.0929, + "degree_in_sign_raw": 58.0929006900906, + "degree_raw": 58.0929, + "lon": 58.0929, + "lord": "Venus", + "sign": "Taurus", + "sign_cn": "金牛座" + }, + "ashtakavarga_audit": { + "all_bav_valid": true, + "boundary": "Audit evidence only. SAV/BAV does not change V2.1 event scores until a fresh holdout validates it.", + "event_house_sav": { + "10": { + "level": "极吉", + "sav_score": 31, + "sign": "Aquarius" + }, + "11": { + "level": "吉利", + "sav_score": 29, + "sign": "Pisces" + }, + "6": { + "level": "极吉", + "sav_score": 37, + "sign": "Libra" + }, + "9": { + "level": "吉利", + "sav_score": 29, + "sign": "Capricorn" + } + }, + "method": "Ashtakavarga八分法(BPHS/PVR书例校准v2.1)", + "sav_total": 337, + "sav_valid": true, + "scoring_effect": 0, + "settings": { + "ayanamsa": 23.9306, + "node_mode": "mean" + }, + "status": "used_non_scoring", + "transit_support": { + "Jupiter": { + "bav": 3, + "sav": 24, + "sign": "Virgo" + }, + "Saturn": { + "bav": 3, + "sav": 22, + "sign": "Gemini" + } + }, + "version": "2.1" + }, + "birth_source": { + "evidence_basis": "quoted BC/BR", + "source_grade": "primary", + "time_accuracy_rating": "AA", + "url": "https://www.astro.com/adbvip/adbvip_03_14.htm" + }, + "domain_varga": { + "birth_info": "1958-03-14 10:50", + "divisional_charts": { + "D10_Dasamsa": { + "Jupiter": { + "sign": "Sagittarius", + "sign_cn": "射手座" + }, + "Ketu": { + "sign": "Cancer", + "sign_cn": "巨蟹座" + }, + "Mars": { + "sign": "Libra", + "sign_cn": "天秤座" + }, + "Mercury": { + "sign": "Aquarius", + "sign_cn": "水瓶座" + }, + "Moon": { + "sign": "Cancer", + "sign_cn": "巨蟹座" + }, + "Rahu": { + "sign": "Capricorn", + "sign_cn": "摩羯座" + }, + "Saturn": { + "sign": "Sagittarius", + "sign_cn": "射手座" + }, + "Sun": { + "sign": "Scorpio", + "sign_cn": "天蝎座" + }, + "Venus": { + "sign": "Aquarius", + "sign_cn": "水瓶座" + }, + "ascendant": "Libra" + } + } + }, + "double_transit_pac": { + "cl": { + "jupiter": { + "CL_10宫(Virgo)": [ + { + "desc": "同宫(10宫)", + "type": "Position" + }, + { + "desc": "合相(4.68°)", + "type": "Conjunction" + } + ] + }, + "saturn": { + "CL_10宫(Virgo)": [ + { + "desc": "3宫相位", + "offset": 3, + "type": "Aspect" + } + ] + } + }, + "d1": { + "jupiter": { + "10宫(Aquarius)": [ + { + "desc": "5宫相位", + "offset": 5, + "type": "Aspect" + } + ] + }, + "saturn": { + "Mars(对宫主)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ], + "Venus(LL)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ] + } + }, + "d9": { + "jupiter": { + "D9_10宫(Gemini)": [ + { + "desc": "同宫(1宫)", + "type": "Position" + }, + { + "desc": "合相(4.68°)", + "type": "Conjunction" + } + ], + "Saturn_D9(Aries)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ], + "Venus_D9(Gemini)": [ + { + "desc": "9宫相位", + "offset": 9, + "type": "Aspect" + } + ] + }, + "saturn": { + "D9_10宫(Gemini)": [ + { + "desc": "3宫相位", + "offset": 3, + "type": "Aspect" + } + ], + "Saturn_D9(Aries)": [ + { + "desc": "10宫相位", + "offset": 10, + "type": "Aspect" + } + ], + "Venus_D9(Gemini)": [ + { + "desc": "同宫(10宫)", + "type": "Position" + } + ] + } + }, + "double_transit": [ + { + "jupiter_pac": [ + { + "desc": "9宫相位", + "offset": 9, + "type": "Aspect" + } + ], + "layer": "D9", + "saturn_pac": [ + { + "desc": "同宫(10宫)", + "type": "Position" + } + ], + "strength": "strong", + "target": "Venus_D9(Gemini)" + }, + { + "jupiter_pac": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ], + "layer": "D9", + "saturn_pac": [ + { + "desc": "10宫相位", + "offset": 10, + "type": "Aspect" + } + ], + "strength": "strong", + "target": "Saturn_D9(Aries)" + }, + { + "jupiter_pac": [ + { + "desc": "同宫(1宫)", + "type": "Position" + }, + { + "desc": "合相(4.68°)", + "type": "Conjunction" + } + ], + "layer": "D9", + "saturn_pac": [ + { + "desc": "3宫相位", + "offset": 3, + "type": "Aspect" + } + ], + "strength": "strong", + "target": "D9_10宫(Gemini)" + }, + { + "jupiter_pac": [ + { + "desc": "同宫(10宫)", + "type": "Position" + }, + { + "desc": "合相(4.68°)", + "type": "Conjunction" + } + ], + "layer": "CL", + "saturn_pac": [ + { + "desc": "3宫相位", + "offset": 3, + "type": "Aspect" + } + ], + "strength": "strong", + "target": "CL_10宫(Virgo)" + } + ], + "event_house": 10, + "stats": { + "chandra_lagna": "Sagittarius", + "cl_jupiter_targets": [ + "CL_10宫(Virgo)" + ], + "cl_overlap": [ + "CL_10宫(Virgo)" + ], + "cl_saturn_targets": [ + "CL_10宫(Virgo)" + ], + "d1_jupiter_targets": [ + "10宫(Aquarius)" + ], + "d1_overlap": [], + "d1_saturn_targets": [ + "Mars(对宫主)", + "Venus(LL)" + ], + "d9_ascendant": "Virgo", + "d9_jupiter_targets": [ + "D9_10宫(Gemini)", + "Saturn_D9(Aries)", + "Venus_D9(Gemini)" + ], + "d9_overlap": [ + "D9_10宫(Gemini)", + "Saturn_D9(Aries)", + "Venus_D9(Gemini)" + ], + "d9_saturn_targets": [ + "D9_10宫(Gemini)", + "Saturn_D9(Aries)", + "Venus_D9(Gemini)" + ], + "event_lord_d9_sign": "Aries" + }, + "summary": "✅ Double Transit PAC 确认: D9+CL 多层激活10宫主题", + "transit_date": "2005-04-06" + }, + "event_source": { + "source_grade": "primary", + "url": "https://www.palais.mc/en/princely-family/h-s-h-prince-albert-ii/biography-1-9.html" + }, + "functional_benefic_malefic": { + "ascendant": "Taurus", + "effect_on_confidence": "高严谨模式下必须叠加功能性宫主吉凶与自然吉凶;若功能属性与自然属性冲突,应降低置信度或显式标记冲突。", + "functional_benefics": [ + "Mercury", + "Saturn", + "Venus" + ], + "functional_malefics": [ + "Jupiter", + "Mars", + "Moon" + ], + "functional_neutrals": [ + "Sun" + ], + "owned_houses": { + "Jupiter": [ + 8, + 11 + ], + "Mars": [ + 7, + 12 + ], + "Mercury": [ + 2, + 5 + ], + "Moon": [ + 3 + ], + "Saturn": [ + 9, + 10 + ], + "Sun": [ + 4 + ], + "Venus": [ + 1, + 6 + ] + }, + "source": "strict_functional_benefic_malefic_v1", + "status": "used", + "yogakarakas": [ + "Saturn", + "Venus" + ] + }, + "narayana": { + "ad": { + "end_age": 47.3563, + "lord": "Mercury", + "sign": "Gemini", + "sign_idx": 2, + "start_age": 46.3218, + "years": 1.0345 + }, + "md": { + "end_age": 50.0, + "lord": "Jupiter", + "sign": "Sagittarius", + "sign_idx": 8, + "start_age": 40.0, + "years": 10 + }, + "pd": { + "end_age": 47.0709, + "lord": "Saturn", + "sign": "Aquarius", + "sign_idx": 10, + "start_age": 46.952, + "years": 0.1189 + }, + "remaining_years": 2.94 + }, + "vimshottari": { + "antardasha": "Jupiter", + "mahadasha": "Jupiter" + } + }, + "expected_label": "career_status", + "matched_expected_label": false, + "name": "Albert II, Prince of Monaco", + "outcome": "Began reign as Prince of Monaco", + "result_class": "weak_hit", + "rule_version": "v2_1", + "score": 6, + "signals": [ + "Jupiter_owns_event_houses:[11]", + "Jupiter_occupies_event_house:6", + "narayana_lord_owns_event_house:Jupiter", + "double_transit_pac_strong" + ] + }, + "positive_date": "2005-04-06", + "ranking": { + "candidate_count": 3, + "max_control_score": 6, + "positive_rank": 3, + "positive_score": 6, + "reciprocal_rank": 0.3333333333333333, + "score_margin": 0, + "top_1": false, + "top_3": true + } + }, + { + "case_id": "boy_george_grammy_1984", + "controls": [ + { + "blocked": false, + "blocked_reason": null, + "date": "1984-01-29", + "result_class": "weak_hit", + "score": 6 + }, + { + "blocked": false, + "blocked_reason": null, + "date": "1984-03-29", + "result_class": "weak_hit", + "score": 6 + } + ], + "domain": "career", + "name": "Boy George", + "positive": { + "actual_label": "domain_activation", + "birth_time_rating": "AA", + "blocked": false, + "case_id": "boy_george_grammy_1984", + "domain": "career", + "event_date": "1984-02-28", + "evidence": { + "arudha_lord": "Venus", + "ascendant": { + "degree": 19.5865, + "degree_in_sign": 19.5865, + "degree_in_sign_raw": 19.58652184346338, + "degree_raw": 19.5865, + "lon": 19.5865, + "lord": "Mars", + "sign": "Aries", + "sign_cn": "白羊座" + }, + "ashtakavarga_audit": { + "all_bav_valid": true, + "boundary": "Audit evidence only. SAV/BAV does not change V2.1 event scores until a fresh holdout validates it.", + "event_house_sav": { + "10": { + "level": "中等", + "sav_score": 25, + "sign": "Capricorn" + }, + "11": { + "level": "极吉", + "sav_score": 34, + "sign": "Aquarius" + }, + "6": { + "level": "中等", + "sav_score": 25, + "sign": "Virgo" + }, + "9": { + "level": "挑战", + "sav_score": 21, + "sign": "Sagittarius" + } + }, + "method": "Ashtakavarga八分法(BPHS/PVR书例校准v2.1)", + "sav_total": 337, + "sav_valid": true, + "scoring_effect": 0, + "settings": { + "ayanamsa": 23.6358, + "node_mode": "mean" + }, + "status": "used_non_scoring", + "transit_support": { + "Jupiter": { + "bav": 5, + "sav": 21, + "sign": "Sagittarius" + }, + "Saturn": { + "bav": 0, + "sav": 27, + "sign": "Libra" + } + }, + "version": "2.1" + }, + "birth_source": { + "evidence_basis": "quoted BC/BR", + "source_grade": "primary", + "time_accuracy_rating": "AA", + "url": "https://www.astro.com/adbvip/adbvip_06_14.htm" + }, + "domain_varga": { + "birth_info": "1961-06-14 02:50", + "divisional_charts": { + "D10_Dasamsa": { + "Jupiter": { + "sign": "Capricorn", + "sign_cn": "摩羯座" + }, + "Ketu": { + "sign": "Aries", + "sign_cn": "白羊座" + }, + "Mars": { + "sign": "Sagittarius", + "sign_cn": "射手座" + }, + "Mercury": { + "sign": "Scorpio", + "sign_cn": "天蝎座" + }, + "Moon": { + "sign": "Virgo", + "sign_cn": "处女座" + }, + "Rahu": { + "sign": "Libra", + "sign_cn": "天秤座" + }, + "Saturn": { + "sign": "Libra", + "sign_cn": "天秤座" + }, + "Sun": { + "sign": "Libra", + "sign_cn": "天秤座" + }, + "Venus": { + "sign": "Leo", + "sign_cn": "狮子座" + }, + "ascendant": "Libra" + } + } + }, + "double_transit_pac": { + "cl": { + "jupiter": {}, + "saturn": { + "CL_Jupiter(宫主)": [ + { + "desc": "3宫相位", + "offset": 3, + "type": "Aspect" + } + ] + } + }, + "d1": { + "jupiter": { + "Mars(LL)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ] + }, + "saturn": { + "10宫(Capricorn)": [ + { + "desc": "3宫相位", + "offset": 3, + "type": "Aspect" + } + ], + "Saturn(宫主)": [ + { + "desc": "3宫相位", + "offset": 3, + "type": "Aspect" + } + ] + } + }, + "d9": { + "jupiter": { + "D9_10宫(Gemini)": [ + { + "desc": "9宫相位", + "offset": 9, + "type": "Aspect" + } + ] + }, + "saturn": {} + }, + "double_transit": [], + "event_house": 10, + "stats": { + "chandra_lagna": "Gemini", + "cl_jupiter_targets": [], + "cl_overlap": [], + "cl_saturn_targets": [ + "CL_Jupiter(宫主)" + ], + "d1_jupiter_targets": [ + "Mars(LL)" + ], + "d1_overlap": [], + "d1_saturn_targets": [ + "10宫(Capricorn)", + "Saturn(宫主)" + ], + "d9_ascendant": "Virgo", + "d9_jupiter_targets": [ + "D9_10宫(Gemini)" + ], + "d9_overlap": [], + "d9_saturn_targets": [], + "event_lord_d9_sign": "Aquarius" + }, + "summary": "❌ 无 Double Transit PAC 激活", + "transit_date": "1984-02-28" + }, + "event_source": { + "source_grade": "verified_secondary", + "url": "https://en.wikipedia.org/wiki/26th_Annual_Grammy_Awards" + }, + "functional_benefic_malefic": { + "ascendant": "Aries", + "effect_on_confidence": "高严谨模式下必须叠加功能性宫主吉凶与自然吉凶;若功能属性与自然属性冲突,应降低置信度或显式标记冲突。", + "functional_benefics": [ + "Jupiter", + "Mars", + "Sun" + ], + "functional_malefics": [ + "Mercury", + "Saturn" + ], + "functional_neutrals": [ + "Moon", + "Venus" + ], + "owned_houses": { + "Jupiter": [ + 9, + 12 + ], + "Mars": [ + 1, + 8 + ], + "Mercury": [ + 3, + 6 + ], + "Moon": [ + 4 + ], + "Saturn": [ + 10, + 11 + ], + "Sun": [ + 5 + ], + "Venus": [ + 2, + 7 + ] + }, + "source": "strict_functional_benefic_malefic_v1", + "status": "used", + "yogakarakas": [ + "Mars" + ] + }, + "narayana": { + "ad": { + "end_age": 23.2039, + "lord": "Saturn", + "sign": "Aquarius", + "sign_idx": 10, + "start_age": 21.9223, + "years": 1.2816 + }, + "md": { + "end_age": 26.0, + "lord": "Mercury", + "sign": "Gemini", + "sign_idx": 2, + "start_age": 14.0, + "years": 12 + }, + "pd": { + "end_age": 22.756, + "lord": "Sun", + "sign": "Leo", + "sign_idx": 4, + "start_age": 22.644, + "years": 0.112 + }, + "remaining_years": 3.29 + }, + "vimshottari": { + "antardasha": "Venus", + "mahadasha": "Jupiter" + } + }, + "expected_label": "career_status", + "matched_expected_label": false, + "name": "Boy George", + "outcome": "Culture Club won the Grammy for Best New Artist", + "result_class": "weak_hit", + "rule_version": "v2_1", + "score": 6, + "signals": [ + "Jupiter_owns_event_houses:[9]", + "Jupiter_occupies_event_house:10", + "active_dasha_matches_D10_Lagna_lord:Venus", + "active_dasha_matches_A10_lord:Venus", + "narayana_lord_owns_event_house:Mercury" + ] + }, + "positive_date": "1984-02-28", + "ranking": { + "candidate_count": 3, + "max_control_score": 6, + "positive_rank": 3, + "positive_score": 6, + "reciprocal_rank": 0.3333333333333333, + "score_margin": 0, + "top_1": false, + "top_3": true + } + }, + { + "case_id": "bergman_oscar_1945", + "controls": [ + { + "blocked": false, + "blocked_reason": null, + "date": "1945-02-13", + "result_class": "strong_hit", + "score": 8 + }, + { + "blocked": false, + "blocked_reason": null, + "date": "1945-04-14", + "result_class": "strong_hit", + "score": 8 + } + ], + "domain": "career", + "name": "Ingrid Bergman", + "positive": { + "actual_label": "career_status", + "birth_time_rating": "AA", + "blocked": false, + "case_id": "bergman_oscar_1945", + "domain": "career", + "event_date": "1945-03-15", + "evidence": { + "arudha_lord": "Saturn", + "ascendant": { + "degree": 0.9464, + "degree_in_sign": 0.9464, + "degree_in_sign_raw": 120.94642922543406, + "degree_raw": 120.9464, + "lon": 120.9464, + "lord": "Sun", + "sign": "Leo", + "sign_cn": "狮子座" + }, + "ashtakavarga_audit": { + "all_bav_valid": true, + "boundary": "Audit evidence only. SAV/BAV does not change V2.1 event scores until a fresh holdout validates it.", + "event_house_sav": { + "10": { + "level": "中等", + "sav_score": 26, + "sign": "Taurus" + }, + "11": { + "level": "极吉", + "sav_score": 41, + "sign": "Gemini" + }, + "6": { + "level": "极吉", + "sav_score": 37, + "sign": "Capricorn" + }, + "9": { + "level": "极吉", + "sav_score": 31, + "sign": "Aries" + } + }, + "method": "Ashtakavarga八分法(BPHS/PVR书例校准v2.1)", + "sav_total": 337, + "sav_valid": true, + "scoring_effect": 0, + "settings": { + "ayanamsa": 23.0917, + "node_mode": "mean" + }, + "status": "used_non_scoring", + "transit_support": { + "Jupiter": { + "bav": 5, + "sav": 26, + "sign": "Leo" + }, + "Saturn": { + "bav": 5, + "sav": 41, + "sign": "Gemini" + } + }, + "version": "2.1" + }, + "birth_source": { + "evidence_basis": "BC/BR in hand", + "source_grade": "primary", + "time_accuracy_rating": "AA", + "url": "https://www.astro.com/adbvip/adbvip_08_29.htm" + }, + "domain_varga": { + "birth_info": "1915-08-29 03:30", + "divisional_charts": { + "D10_Dasamsa": { + "Jupiter": { + "sign": "Sagittarius", + "sign_cn": "射手座" + }, + "Ketu": { + "sign": "Libra", + "sign_cn": "天秤座" + }, + "Mars": { + "sign": "Libra", + "sign_cn": "天秤座" + }, + "Mercury": { + "sign": "Aries", + "sign_cn": "白羊座" + }, + "Moon": { + "sign": "Aries", + "sign_cn": "白羊座" + }, + "Rahu": { + "sign": "Aries", + "sign_cn": "白羊座" + }, + "Saturn": { + "sign": "Sagittarius", + "sign_cn": "射手座" + }, + "Sun": { + "sign": "Scorpio", + "sign_cn": "天蝎座" + }, + "Venus": { + "sign": "Libra", + "sign_cn": "天秤座" + }, + "ascendant": "Leo" + } + } + }, + "double_transit_pac": { + "cl": { + "jupiter": { + "CL_10宫(Capricorn)": [ + { + "desc": "5宫相位", + "offset": 5, + "type": "Aspect" + } + ] + }, + "saturn": { + "CL_10宫(Capricorn)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ], + "CL_Saturn(宫主)": [ + { + "desc": "同宫(3宫)", + "type": "Position" + }, + { + "desc": "合相(9.73°)", + "type": "Conjunction" + } + ] + } + }, + "d1": { + "jupiter": { + "10宫(Taurus)": [ + { + "desc": "9宫相位", + "offset": 9, + "type": "Aspect" + } + ], + "Sun(LL)": [ + { + "desc": "同宫(1宫)", + "type": "Position" + } + ], + "Venus(宫主)": [ + { + "desc": "同宫(1宫)", + "type": "Position" + } + ] + }, + "saturn": { + "Saturn(对宫主)": [ + { + "desc": "同宫(11宫)", + "type": "Position" + }, + { + "desc": "合相(9.73°)", + "type": "Conjunction" + } + ] + } + }, + "d9": { + "jupiter": {}, + "saturn": { + "D9_10宫(Capricorn)": [ + { + "desc": "10宫相位", + "offset": 10, + "type": "Aspect" + } + ], + "D9_Saturn(宫主)": [ + { + "desc": "同宫(3宫)", + "type": "Position" + }, + { + "desc": "合相(9.73°)", + "type": "Conjunction" + } + ], + "Venus_D9(Gemini)": [ + { + "desc": "同宫(3宫)", + "type": "Position" + }, + { + "desc": "合相(4.18°)", + "type": "Conjunction" + } + ] + } + }, + "double_transit": [ + { + "jupiter_pac": [ + { + "desc": "5宫相位", + "offset": 5, + "type": "Aspect" + } + ], + "layer": "CL", + "saturn_pac": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ], + "strength": "strong", + "target": "CL_10宫(Capricorn)" + }, + { + "jupiter_pac": [ + { + "desc": "同宫(1宫)", + "type": "Position" + } + ], + "layer": "D1+D9", + "saturn_pac": [ + { + "desc": "同宫(3宫)", + "type": "Position" + }, + { + "desc": "合相(4.18°)", + "type": "Conjunction" + } + ], + "strength": "moderate", + "target": "Jupiter(D1)Venus(宫主) + Saturn(D9)Venus_D9(Gemini)" + } + ], + "event_house": 10, + "stats": { + "chandra_lagna": "Aries", + "cl_jupiter_targets": [ + "CL_10宫(Capricorn)" + ], + "cl_overlap": [ + "CL_10宫(Capricorn)" + ], + "cl_saturn_targets": [ + "CL_10宫(Capricorn)", + "CL_Saturn(宫主)" + ], + "d1_jupiter_targets": [ + "10宫(Taurus)", + "Sun(LL)", + "Venus(宫主)" + ], + "d1_overlap": [], + "d1_saturn_targets": [ + "Saturn(对宫主)" + ], + "d9_ascendant": "Aries", + "d9_jupiter_targets": [], + "d9_overlap": [], + "d9_saturn_targets": [ + "D9_10宫(Capricorn)", + "D9_Saturn(宫主)", + "Venus_D9(Gemini)" + ], + "event_lord_d9_sign": "Gemini" + }, + "summary": "⚠️ Chandra Lagna 层 Double Transit 激活,D1/D9 未确认", + "transit_date": "1945-03-15" + }, + "event_source": { + "source_grade": "primary", + "url": "https://www.oscars.org/oscars/ceremonies/1945" + }, + "functional_benefic_malefic": { + "ascendant": "Leo", + "effect_on_confidence": "高严谨模式下必须叠加功能性宫主吉凶与自然吉凶;若功能属性与自然属性冲突,应降低置信度或显式标记冲突。", + "functional_benefics": [ + "Jupiter", + "Mars", + "Sun" + ], + "functional_malefics": [ + "Mercury", + "Moon", + "Saturn", + "Venus" + ], + "functional_neutrals": [], + "owned_houses": { + "Jupiter": [ + 5, + 8 + ], + "Mars": [ + 4, + 9 + ], + "Mercury": [ + 2, + 11 + ], + "Moon": [ + 12 + ], + "Saturn": [ + 6, + 7 + ], + "Sun": [ + 1 + ], + "Venus": [ + 3, + 10 + ] + }, + "source": "strict_functional_benefic_malefic_v1", + "status": "used", + "yogakarakas": [ + "Mars" + ] + }, + "narayana": { + "ad": { + "end_age": 30.125, + "lord": "Moon", + "sign": "Cancer", + "sign_idx": 3, + "start_age": 29.0, + "years": 1.125 + }, + "md": { + "end_age": 33.0, + "lord": "Venus", + "sign": "Libra", + "sign_idx": 6, + "start_age": 23.0, + "years": 10 + }, + "pd": { + "end_age": 29.5906, + "lord": "Venus", + "sign": "Libra", + "sign_idx": 6, + "start_age": 29.45, + "years": 0.1406 + }, + "remaining_years": 3.46 + }, + "vimshottari": { + "antardasha": "Mercury", + "mahadasha": "Sun" + } + }, + "expected_label": "career_status", + "matched_expected_label": true, + "name": "Ingrid Bergman", + "outcome": "Won Best Actress for Gaslight", + "result_class": "strong_hit", + "rule_version": "v2_1", + "score": 8, + "signals": [ + "Sun_domain_karaka", + "Mercury_owns_event_houses:[11]", + "Mercury_domain_karaka", + "active_dasha_matches_D10_Lagna_lord:Sun", + "narayana_lord_owns_event_house:Venus", + "double_transit_pac_strong" + ] + }, + "positive_date": "1945-03-15", + "ranking": { + "candidate_count": 3, + "max_control_score": 8, + "positive_rank": 3, + "positive_score": 8, + "reciprocal_rank": 0.3333333333333333, + "score_margin": 0, + "top_1": false, + "top_3": true + } + }, + { + "case_id": "morissette_grammy_1996", + "controls": [ + { + "blocked": false, + "blocked_reason": null, + "date": "1996-01-29", + "result_class": "weak_hit", + "score": 6 + }, + { + "blocked": false, + "blocked_reason": null, + "date": "1996-03-29", + "result_class": "weak_hit", + "score": 4 + } + ], + "domain": "career", + "name": "Alanis Morissette", + "positive": { + "actual_label": "domain_activation", + "birth_time_rating": "A", + "blocked": false, + "case_id": "morissette_grammy_1996", + "domain": "career", + "event_date": "1996-02-28", + "evidence": { + "arudha_lord": "Saturn", + "ascendant": { + "degree": 15.1562, + "degree_in_sign": 15.1562, + "degree_in_sign_raw": 105.15618971934725, + "degree_raw": 105.1562, + "lon": 105.1562, + "lord": "Moon", + "sign": "Cancer", + "sign_cn": "巨蟹座" + }, + "ashtakavarga_audit": { + "all_bav_valid": true, + "boundary": "Audit evidence only. SAV/BAV does not change V2.1 event scores until a fresh holdout validates it.", + "event_house_sav": { + "10": { + "level": "极吉", + "sav_score": 32, + "sign": "Aries" + }, + "11": { + "level": "吉利", + "sav_score": 28, + "sign": "Taurus" + }, + "6": { + "level": "极吉", + "sav_score": 34, + "sign": "Sagittarius" + }, + "9": { + "level": "极吉", + "sav_score": 31, + "sign": "Pisces" + } + }, + "method": "Ashtakavarga八分法(BPHS/PVR书例校准v2.1)", + "sav_total": 337, + "sav_valid": true, + "scoring_effect": 0, + "settings": { + "ayanamsa": 23.8034, + "node_mode": "mean" + }, + "status": "used_non_scoring", + "transit_support": { + "Jupiter": { + "bav": 4, + "sav": 34, + "sign": "Sagittarius" + }, + "Saturn": { + "bav": 4, + "sav": 31, + "sign": "Pisces" + } + }, + "version": "2.1" + }, + "birth_source": { + "evidence_basis": "from memory", + "source_grade": "verified_secondary", + "time_accuracy_rating": "A", + "url": "https://www.astro.com/adbvip/adbvip_06_01.htm" + }, + "domain_varga": { + "birth_info": "1974-06-01 09:51", + "divisional_charts": { + "D10_Dasamsa": { + "Jupiter": { + "sign": "Virgo", + "sign_cn": "处女座" + }, + "Ketu": { + "sign": "Virgo", + "sign_cn": "处女座" + }, + "Mars": { + "sign": "Pisces", + "sign_cn": "双鱼座" + }, + "Mercury": { + "sign": "Virgo", + "sign_cn": "处女座" + }, + "Moon": { + "sign": "Sagittarius", + "sign_cn": "射手座" + }, + "Rahu": { + "sign": "Pisces", + "sign_cn": "双鱼座" + }, + "Saturn": { + "sign": "Virgo", + "sign_cn": "处女座" + }, + "Sun": { + "sign": "Gemini", + "sign_cn": "双子座" + }, + "Venus": { + "sign": "Gemini", + "sign_cn": "双子座" + }, + "ascendant": "Leo" + } + } + }, + "double_transit_pac": { + "cl": { + "jupiter": { + "CL_10宫(Cancer)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ] + }, + "saturn": { + "CL_Moon(宫主)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ] + } + }, + "d1": { + "jupiter": { + "Mars(宫主)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ] + }, + "saturn": { + "Moon(LL)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ], + "Saturn(对宫主)": [ + { + "desc": "3宫相位", + "offset": 3, + "type": "Aspect" + } + ] + } + }, + "d9": { + "jupiter": { + "D9_Sun(宫主)": [ + { + "desc": "5宫相位", + "offset": 5, + "type": "Aspect" + } + ], + "Mars_D9(Cancer)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ], + "Moon_D9(Sagittarius)": [ + { + "desc": "同宫(2宫)", + "type": "Position" + }, + { + "desc": "合相(2.71°)", + "type": "Conjunction" + } + ] + }, + "saturn": {} + }, + "double_transit": [], + "event_house": 10, + "stats": { + "chandra_lagna": "Libra", + "cl_jupiter_targets": [ + "CL_10宫(Cancer)" + ], + "cl_overlap": [], + "cl_saturn_targets": [ + "CL_Moon(宫主)" + ], + "d1_jupiter_targets": [ + "Mars(宫主)" + ], + "d1_overlap": [], + "d1_saturn_targets": [ + "Moon(LL)", + "Saturn(对宫主)" + ], + "d9_ascendant": "Scorpio", + "d9_jupiter_targets": [ + "D9_Sun(宫主)", + "Mars_D9(Cancer)", + "Moon_D9(Sagittarius)" + ], + "d9_overlap": [], + "d9_saturn_targets": [], + "event_lord_d9_sign": "Cancer" + }, + "summary": "❌ 无 Double Transit PAC 激活", + "transit_date": "1996-02-28" + }, + "event_source": { + "source_grade": "verified_secondary", + "url": "https://en.wikipedia.org/wiki/38th_Annual_Grammy_Awards" + }, + "functional_benefic_malefic": { + "ascendant": "Cancer", + "effect_on_confidence": "高严谨模式下必须叠加功能性宫主吉凶与自然吉凶;若功能属性与自然属性冲突,应降低置信度或显式标记冲突。", + "functional_benefics": [ + "Jupiter", + "Mars", + "Moon" + ], + "functional_malefics": [ + "Mercury", + "Saturn", + "Venus" + ], + "functional_neutrals": [ + "Sun" + ], + "owned_houses": { + "Jupiter": [ + 6, + 9 + ], + "Mars": [ + 5, + 10 + ], + "Mercury": [ + 3, + 12 + ], + "Moon": [ + 1 + ], + "Saturn": [ + 7, + 8 + ], + "Sun": [ + 2 + ], + "Venus": [ + 4, + 11 + ] + }, + "source": "strict_functional_benefic_malefic_v1", + "status": "used", + "yogakarakas": [ + "Mars" + ] + }, + "narayana": { + "ad": { + "end_age": 22.012, + "lord": "Mars", + "sign": "Scorpio", + "sign_idx": 7, + "start_age": 21.4337, + "years": 0.5783 + }, + "md": { + "end_age": 27.0, + "lord": "Venus", + "sign": "Libra", + "sign_idx": 6, + "start_age": 21.0, + "years": 6 + }, + "pd": { + "end_age": 21.8239, + "lord": "Mercury", + "sign": "Gemini", + "sign_idx": 2, + "start_age": 21.7403, + "years": 0.0836 + }, + "remaining_years": 5.26 + }, + "vimshottari": { + "antardasha": "Mercury", + "mahadasha": "Jupiter" + } + }, + "expected_label": "career_status", + "matched_expected_label": false, + "name": "Alanis Morissette", + "outcome": "Won four Grammy awards", + "result_class": "weak_hit", + "rule_version": "v2_1", + "score": 4, + "signals": [ + "Jupiter_owns_event_houses:[6, 9]", + "Mercury_domain_karaka", + "narayana_lord_owns_event_house:Venus" + ] + }, + "positive_date": "1996-02-28", + "ranking": { + "candidate_count": 3, + "max_control_score": 6, + "positive_rank": 3, + "positive_score": 4, + "reciprocal_rank": 0.3333333333333333, + "score_margin": -2, + "top_1": false, + "top_3": true + } + }, + { + "case_id": "dion_eurovision_1988", + "controls": [ + { + "blocked": false, + "blocked_reason": null, + "date": "1988-03-31", + "result_class": "weak_hit", + "score": 5 + }, + { + "blocked": false, + "blocked_reason": null, + "date": "1988-05-30", + "result_class": "weak_hit", + "score": 5 + } + ], + "domain": "career", + "name": "Celine Dion", + "positive": { + "actual_label": "domain_activation", + "birth_time_rating": "A", + "blocked": false, + "case_id": "dion_eurovision_1988", + "domain": "career", + "event_date": "1988-04-30", + "evidence": { + "arudha_lord": "Saturn", + "ascendant": { + "degree": 9.1356, + "degree_in_sign": 9.1356, + "degree_in_sign_raw": 99.13557485171225, + "degree_raw": 99.1356, + "lon": 99.1356, + "lord": "Moon", + "sign": "Cancer", + "sign_cn": "巨蟹座" + }, + "ashtakavarga_audit": { + "all_bav_valid": true, + "boundary": "Audit evidence only. SAV/BAV does not change V2.1 event scores until a fresh holdout validates it.", + "event_house_sav": { + "10": { + "level": "中等", + "sav_score": 27, + "sign": "Aries" + }, + "11": { + "level": "极吉", + "sav_score": 31, + "sign": "Taurus" + }, + "6": { + "level": "极吉", + "sav_score": 37, + "sign": "Sagittarius" + }, + "9": { + "level": "挑战", + "sav_score": 19, + "sign": "Pisces" + } + }, + "method": "Ashtakavarga八分法(BPHS/PVR书例校准v2.1)", + "sav_total": 337, + "sav_valid": true, + "scoring_effect": 0, + "settings": { + "ayanamsa": 23.6941, + "node_mode": "mean" + }, + "status": "used_non_scoring", + "transit_support": { + "Jupiter": { + "bav": 3, + "sav": 27, + "sign": "Aries" + }, + "Saturn": { + "bav": 5, + "sav": 37, + "sign": "Sagittarius" + } + }, + "version": "2.1" + }, + "birth_source": { + "evidence_basis": "from memory", + "source_grade": "verified_secondary", + "time_accuracy_rating": "A", + "url": "https://www.astro.com/adbvip/adbvip_03_30.htm" + }, + "domain_varga": { + "birth_info": "1968-03-30 12:15", + "divisional_charts": { + "D10_Dasamsa": { + "Jupiter": { + "sign": "Virgo", + "sign_cn": "处女座" + }, + "Ketu": { + "sign": "Capricorn", + "sign_cn": "摩羯座" + }, + "Mars": { + "sign": "Gemini", + "sign_cn": "双子座" + }, + "Mercury": { + "sign": "Libra", + "sign_cn": "天秤座" + }, + "Moon": { + "sign": "Gemini", + "sign_cn": "双子座" + }, + "Rahu": { + "sign": "Cancer", + "sign_cn": "巨蟹座" + }, + "Saturn": { + "sign": "Gemini", + "sign_cn": "双子座" + }, + "Sun": { + "sign": "Aries", + "sign_cn": "白羊座" + }, + "Venus": { + "sign": "Libra", + "sign_cn": "天秤座" + }, + "ascendant": "Gemini" + } + } + }, + "double_transit_pac": { + "cl": { + "jupiter": { + "CL_10宫(Capricorn)": [ + { + "desc": "9宫相位", + "offset": 9, + "type": "Aspect" + } + ] + }, + "saturn": { + "CL_Saturn(宫主)": [ + { + "desc": "3宫相位", + "offset": 3, + "type": "Aspect" + } + ] + } + }, + "d1": { + "jupiter": { + "10宫(Aries)": [ + { + "desc": "同宫(10宫)", + "type": "Position" + }, + { + "desc": "合相(3.45°)", + "type": "Conjunction" + } + ], + "Mars(宫主)": [ + { + "desc": "同宫(10宫)", + "type": "Position" + }, + { + "desc": "合相(9.85°)", + "type": "Conjunction" + } + ], + "Moon(LL)": [ + { + "desc": "同宫(10宫)", + "type": "Position" + } + ] + }, + "saturn": { + "Saturn(对宫主)": [ + { + "desc": "3宫相位", + "offset": 3, + "type": "Aspect" + } + ] + } + }, + "d9": { + "jupiter": { + "D9_10宫(Gemini)": [ + { + "desc": "5宫相位", + "offset": 5, + "type": "Aspect" + } + ] + }, + "saturn": {} + }, + "double_transit": [], + "event_house": 10, + "stats": { + "chandra_lagna": "Aries", + "cl_jupiter_targets": [ + "CL_10宫(Capricorn)" + ], + "cl_overlap": [], + "cl_saturn_targets": [ + "CL_Saturn(宫主)" + ], + "d1_jupiter_targets": [ + "10宫(Aries)", + "Mars(宫主)", + "Moon(LL)" + ], + "d1_overlap": [], + "d1_saturn_targets": [ + "Saturn(对宫主)" + ], + "d9_ascendant": "Virgo", + "d9_jupiter_targets": [ + "D9_10宫(Gemini)" + ], + "d9_overlap": [], + "d9_saturn_targets": [], + "event_lord_d9_sign": "Gemini" + }, + "summary": "❌ 无 Double Transit PAC 激活", + "transit_date": "1988-04-30" + }, + "event_source": { + "source_grade": "primary", + "url": "https://eurovision.tv/event/dublin-1988" + }, + "functional_benefic_malefic": { + "ascendant": "Cancer", + "effect_on_confidence": "高严谨模式下必须叠加功能性宫主吉凶与自然吉凶;若功能属性与自然属性冲突,应降低置信度或显式标记冲突。", + "functional_benefics": [ + "Jupiter", + "Mars", + "Moon" + ], + "functional_malefics": [ + "Mercury", + "Saturn", + "Venus" + ], + "functional_neutrals": [ + "Sun" + ], + "owned_houses": { + "Jupiter": [ + 6, + 9 + ], + "Mars": [ + 5, + 10 + ], + "Mercury": [ + 3, + 12 + ], + "Moon": [ + 1 + ], + "Saturn": [ + 7, + 8 + ], + "Sun": [ + 2 + ], + "Venus": [ + 4, + 11 + ] + }, + "source": "strict_functional_benefic_malefic_v1", + "status": "used", + "yogakarakas": [ + "Mars" + ] + }, + "narayana": { + "ad": { + "end_age": 20.5333, + "lord": "Moon", + "sign": "Cancer", + "sign_idx": 3, + "start_age": 19.9333, + "years": 0.6 + }, + "md": { + "end_age": 21.0, + "lord": "Mercury", + "sign": "Virgo", + "sign_idx": 5, + "start_age": 16.0, + "years": 5 + }, + "pd": { + "end_age": 20.1013, + "lord": "Mercury", + "sign": "Virgo", + "sign_idx": 5, + "start_age": 20.0613, + "years": 0.04 + }, + "remaining_years": 0.91 + }, + "vimshottari": { + "antardasha": "Mercury", + "mahadasha": "Venus" + } + }, + "expected_label": "career_status", + "matched_expected_label": false, + "name": "Celine Dion", + "outcome": "Won the Eurovision Song Contest for Switzerland", + "result_class": "weak_hit", + "rule_version": "v2_1", + "score": 5, + "signals": [ + "Venus_owns_event_houses:[11]", + "Mercury_domain_karaka", + "active_dasha_matches_D10_Lagna_lord:Mercury", + "active_dasha_matches_Amatyakaraka:Mercury" + ] + }, + "positive_date": "1988-04-30", + "ranking": { + "candidate_count": 3, + "max_control_score": 5, + "positive_rank": 3, + "positive_score": 5, + "reciprocal_rank": 0.3333333333333333, + "score_margin": 0, + "top_1": false, + "top_3": true + } + }, + { + "case_id": "mccartney_marriage_1969", + "controls": [ + { + "blocked": false, + "blocked_reason": null, + "date": "1969-02-10", + "result_class": "strong_hit", + "score": 9 + }, + { + "blocked": false, + "blocked_reason": null, + "date": "1969-04-11", + "result_class": "strong_hit", + "score": 7 + } + ], + "domain": "marriage", + "name": "Paul McCartney", + "positive": { + "actual_label": "legal_marriage", + "birth_time_rating": "A", + "blocked": false, + "case_id": "mccartney_marriage_1969", + "domain": "marriage", + "event_date": "1969-03-12", + "evidence": { + "arudha_lord": "Mars", + "ascendant": { + "degree": 2.2532, + "degree_in_sign": 2.2532, + "degree_in_sign_raw": 152.25316730126025, + "degree_raw": 152.2532, + "lon": 152.2532, + "lord": "Mercury", + "sign": "Virgo", + "sign_cn": "处女座" + }, + "ashtakavarga_audit": { + "all_bav_valid": true, + "boundary": "Audit evidence only. SAV/BAV does not change V2.1 event scores until a fresh holdout validates it.", + "event_house_sav": { + "11": { + "level": "极吉", + "sav_score": 30, + "sign": "Cancer" + }, + "2": { + "level": "中等", + "sav_score": 26, + "sign": "Libra" + }, + "5": { + "level": "吉利", + "sav_score": 29, + "sign": "Capricorn" + }, + "7": { + "level": "极吉", + "sav_score": 32, + "sign": "Pisces" + } + }, + "method": "Ashtakavarga八分法(BPHS/PVR书例校准v2.1)", + "sav_total": 337, + "sav_valid": true, + "scoring_effect": 0, + "settings": { + "ayanamsa": 23.4268, + "node_mode": "mean" + }, + "status": "used_non_scoring", + "transit_support": { + "Jupiter": { + "bav": 6, + "sav": 32, + "sign": "Virgo" + }, + "Saturn": { + "bav": 4, + "sav": 31, + "sign": "Aries" + } + }, + "version": "2.1" + }, + "birth_source": { + "evidence_basis": "from memory", + "source_grade": "verified_secondary", + "time_accuracy_rating": "A", + "url": "https://www.astro.com/adbvip/adbvip_06_18.htm" + }, + "domain_varga": { + "birth_info": "1942-06-18 14:00", + "divisional_charts": { + "D9_Navamsa": { + "Jupiter": { + "sign": "Sagittarius", + "sign_cn": "射手座" + }, + "Ketu": { + "sign": "Aquarius", + "sign_cn": "水瓶座" + }, + "Mars": { + "sign": "Virgo", + "sign_cn": "处女座" + }, + "Mercury": { + "sign": "Leo", + "sign_cn": "狮子座" + }, + "Moon": { + "sign": "Aquarius", + "sign_cn": "水瓶座" + }, + "Rahu": { + "sign": "Leo", + "sign_cn": "狮子座" + }, + "Saturn": { + "sign": "Aries", + "sign_cn": "白羊座" + }, + "Sun": { + "sign": "Scorpio", + "sign_cn": "天蝎座" + }, + "Venus": { + "sign": "Scorpio", + "sign_cn": "天蝎座" + }, + "ascendant": "Capricorn" + } + } + }, + "double_transit_pac": { + "cl": { + "jupiter": {}, + "saturn": {} + }, + "d1": { + "jupiter": { + "Jupiter(宫主)": [ + { + "desc": "9宫相位", + "offset": 9, + "type": "Aspect" + } + ], + "Jupiter(对宫主)": [ + { + "desc": "9宫相位", + "offset": 9, + "type": "Aspect" + } + ] + }, + "saturn": {} + }, + "d9": { + "jupiter": {}, + "saturn": { + "D9_Moon(宫主)": [ + { + "desc": "3宫相位", + "offset": 3, + "type": "Aspect" + } + ] + } + }, + "double_transit": [], + "event_house": 7, + "stats": { + "chandra_lagna": "Cancer", + "cl_jupiter_targets": [], + "cl_overlap": [], + "cl_saturn_targets": [], + "d1_jupiter_targets": [ + "Jupiter(宫主)", + "Jupiter(对宫主)" + ], + "d1_overlap": [], + "d1_saturn_targets": [], + "d9_ascendant": "Capricorn", + "d9_jupiter_targets": [], + "d9_overlap": [], + "d9_saturn_targets": [ + "D9_Moon(宫主)" + ], + "event_lord_d9_sign": "Sagittarius" + }, + "summary": "❌ 无 Double Transit PAC 激活", + "transit_date": "1969-03-12" + }, + "event_source": { + "source_grade": "verified_secondary", + "url": "https://en.wikipedia.org/wiki/Personal_relationships_of_Paul_McCartney" + }, + "functional_benefic_malefic": { + "ascendant": "Virgo", + "effect_on_confidence": "高严谨模式下必须叠加功能性宫主吉凶与自然吉凶;若功能属性与自然属性冲突,应降低置信度或显式标记冲突。", + "functional_benefics": [ + "Mercury", + "Saturn", + "Venus" + ], + "functional_malefics": [ + "Mars", + "Moon", + "Sun" + ], + "functional_neutrals": [ + "Jupiter" + ], + "owned_houses": { + "Jupiter": [ + 4, + 7 + ], + "Mars": [ + 3, + 8 + ], + "Mercury": [ + 1, + 10 + ], + "Moon": [ + 11 + ], + "Saturn": [ + 5, + 6 + ], + "Sun": [ + 12 + ], + "Venus": [ + 2, + 9 + ] + }, + "source": "strict_functional_benefic_malefic_v1", + "status": "used", + "yogakarakas": [ + "Mercury" + ] + }, + "narayana": { + "ad": { + "end_age": 27.0118, + "lord": "Mercury", + "sign": "Virgo", + "sign_idx": 5, + "start_age": 26.4471, + "years": 0.5647 + }, + "md": { + "end_age": 28.0, + "lord": "Jupiter", + "sign": "Sagittarius", + "sign_idx": 8, + "start_age": 22.0, + "years": 6 + }, + "pd": { + "end_age": 26.7926, + "lord": "Venus", + "sign": "Taurus", + "sign_idx": 1, + "start_age": 26.7195, + "years": 0.0731 + }, + "remaining_years": 1.27 + }, + "vimshottari": { + "antardasha": "Jupiter", + "mahadasha": "Venus" + } + }, + "expected_label": "legal_marriage", + "matched_expected_label": true, + "name": "Paul McCartney", + "outcome": "Married Linda Eastman", + "result_class": "strong_hit", + "rule_version": "v2_1", + "score": 7, + "signals": [ + "Venus_owns_event_houses:[2]", + "Venus_domain_karaka", + "Jupiter_owns_event_houses:[7]", + "Jupiter_domain_karaka", + "narayana_lord_owns_event_house:Jupiter" + ] + }, + "positive_date": "1969-03-12", + "ranking": { + "candidate_count": 3, + "max_control_score": 9, + "positive_rank": 3, + "positive_score": 7, + "reciprocal_rank": 0.3333333333333333, + "score_margin": -2, + "top_1": false, + "top_3": true + } + }, + { + "case_id": "depp_marriage_2015", + "controls": [ + { + "blocked": false, + "blocked_reason": null, + "date": "2015-01-04", + "result_class": "weak_hit", + "score": 5 + }, + { + "blocked": false, + "blocked_reason": null, + "date": "2015-03-05", + "result_class": "weak_hit", + "score": 5 + } + ], + "domain": "marriage", + "name": "Johnny Depp", + "positive": { + "actual_label": "domain_activation", + "birth_time_rating": "AA", + "blocked": false, + "case_id": "depp_marriage_2015", + "domain": "marriage", + "event_date": "2015-02-03", + "evidence": { + "arudha_lord": "Mars", + "ascendant": { + "degree": 6.9997, + "degree_in_sign": 6.9997, + "degree_in_sign_raw": 96.99973733317259, + "degree_raw": 96.9997, + "lon": 96.9997, + "lord": "Moon", + "sign": "Cancer", + "sign_cn": "巨蟹座" + }, + "ashtakavarga_audit": { + "all_bav_valid": true, + "boundary": "Audit evidence only. SAV/BAV does not change V2.1 event scores until a fresh holdout validates it.", + "event_house_sav": { + "11": { + "level": "极吉", + "sav_score": 31, + "sign": "Taurus" + }, + "2": { + "level": "中等", + "sav_score": 26, + "sign": "Leo" + }, + "5": { + "level": "挑战", + "sav_score": 23, + "sign": "Scorpio" + }, + "7": { + "level": "吉利", + "sav_score": 29, + "sign": "Capricorn" + } + }, + "method": "Ashtakavarga八分法(BPHS/PVR书例校准v2.1)", + "sav_total": 337, + "sav_valid": true, + "scoring_effect": 0, + "settings": { + "ayanamsa": 24.0679, + "node_mode": "mean" + }, + "status": "used_non_scoring", + "transit_support": { + "Jupiter": { + "bav": 2, + "sav": 26, + "sign": "Cancer" + }, + "Saturn": { + "bav": 2, + "sav": 23, + "sign": "Scorpio" + } + }, + "version": "2.1" + }, + "birth_source": { + "evidence_basis": "BC/BR in hand", + "source_grade": "primary", + "time_accuracy_rating": "AA", + "url": "https://www.astro.com/adbvip/adbvip_06_09.htm" + }, + "domain_varga": { + "birth_info": "1963-06-09 08:44", + "divisional_charts": { + "D9_Navamsa": { + "Jupiter": { + "sign": "Capricorn", + "sign_cn": "摩羯座" + }, + "Ketu": { + "sign": "Sagittarius", + "sign_cn": "射手座" + }, + "Mars": { + "sign": "Gemini", + "sign_cn": "双子座" + }, + "Mercury": { + "sign": "Capricorn", + "sign_cn": "摩羯座" + }, + "Moon": { + "sign": "Virgo", + "sign_cn": "处女座" + }, + "Rahu": { + "sign": "Gemini", + "sign_cn": "双子座" + }, + "Saturn": { + "sign": "Virgo", + "sign_cn": "处女座" + }, + "Sun": { + "sign": "Leo", + "sign_cn": "狮子座" + }, + "Venus": { + "sign": "Capricorn", + "sign_cn": "摩羯座" + }, + "ascendant": "Virgo" + } + } + }, + "double_transit_pac": { + "cl": { + "jupiter": {}, + "saturn": { + "CL_7宫(Gemini)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ] + } + }, + "d1": { + "jupiter": { + "Moon(LL)": [ + { + "desc": "5宫相位", + "offset": 5, + "type": "Aspect" + } + ] + }, + "saturn": {} + }, + "d9": { + "jupiter": {}, + "saturn": { + "D9_7宫(Pisces)": [ + { + "desc": "10宫相位", + "offset": 10, + "type": "Aspect" + } + ], + "Moon_D9(Virgo)": [ + { + "desc": "10宫相位", + "offset": 10, + "type": "Aspect" + } + ], + "Saturn_D9(Virgo)": [ + { + "desc": "10宫相位", + "offset": 10, + "type": "Aspect" + } + ] + } + }, + "double_transit": [], + "event_house": 7, + "stats": { + "chandra_lagna": "Sagittarius", + "cl_jupiter_targets": [], + "cl_overlap": [], + "cl_saturn_targets": [ + "CL_7宫(Gemini)" + ], + "d1_jupiter_targets": [ + "Moon(LL)" + ], + "d1_overlap": [], + "d1_saturn_targets": [], + "d9_ascendant": "Virgo", + "d9_jupiter_targets": [], + "d9_overlap": [], + "d9_saturn_targets": [ + "D9_7宫(Pisces)", + "Moon_D9(Virgo)", + "Saturn_D9(Virgo)" + ], + "event_lord_d9_sign": "Virgo" + }, + "summary": "❌ 无 Double Transit PAC 激活", + "transit_date": "2015-02-03" + }, + "event_source": { + "source_grade": "verified_secondary", + "url": "https://variety.com/2016/biz/news/johnny-depp-amber-heard-divorce-settlement-1201837685/" + }, + "functional_benefic_malefic": { + "ascendant": "Cancer", + "effect_on_confidence": "高严谨模式下必须叠加功能性宫主吉凶与自然吉凶;若功能属性与自然属性冲突,应降低置信度或显式标记冲突。", + "functional_benefics": [ + "Jupiter", + "Mars", + "Moon" + ], + "functional_malefics": [ + "Mercury", + "Saturn", + "Venus" + ], + "functional_neutrals": [ + "Sun" + ], + "owned_houses": { + "Jupiter": [ + 6, + 9 + ], + "Mars": [ + 5, + 10 + ], + "Mercury": [ + 3, + 12 + ], + "Moon": [ + 1 + ], + "Saturn": [ + 7, + 8 + ], + "Sun": [ + 2 + ], + "Venus": [ + 4, + 11 + ] + }, + "source": "strict_functional_benefic_malefic_v1", + "status": "used", + "yogakarakas": [ + "Mars" + ] + }, + "narayana": { + "ad": { + "end_age": 52.6505, + "lord": "Mars", + "sign": "Scorpio", + "sign_idx": 7, + "start_age": 51.6019, + "years": 1.0485 + }, + "md": { + "end_age": 53.0, + "lord": "Saturn", + "sign": "Capricorn", + "sign_idx": 9, + "start_age": 41.0, + "years": 12 + }, + "pd": { + "end_age": 51.6935, + "lord": "Mars", + "sign": "Scorpio", + "sign_idx": 7, + "start_age": 51.6019, + "years": 0.0916 + }, + "remaining_years": 1.34 + }, + "vimshottari": { + "antardasha": "Jupiter", + "mahadasha": "Jupiter" + } + }, + "expected_label": "legal_marriage", + "matched_expected_label": false, + "name": "Johnny Depp", + "outcome": "Married Amber Heard in a civil ceremony", + "result_class": "weak_hit", + "rule_version": "v2_1", + "score": 5, + "signals": [ + "Jupiter_domain_karaka", + "active_dasha_matches_D9_7L:Jupiter", + "narayana_activates_primary_event_sign:Capricorn", + "narayana_lord_owns_event_house:Saturn" + ] + }, + "positive_date": "2015-02-03", + "ranking": { + "candidate_count": 3, + "max_control_score": 5, + "positive_rank": 3, + "positive_score": 5, + "reciprocal_rank": 0.3333333333333333, + "score_margin": 0, + "top_1": false, + "top_3": true + } + }, + { + "case_id": "kidman_marriage_2006", + "controls": [ + { + "blocked": false, + "blocked_reason": null, + "date": "2006-05-26", + "result_class": "miss", + "score": 3 + }, + { + "blocked": false, + "blocked_reason": null, + "date": "2006-07-25", + "result_class": "miss", + "score": 3 + } + ], + "domain": "marriage", + "name": "Nicole Kidman", + "positive": { + "actual_label": null, + "birth_time_rating": "A", + "blocked": false, + "case_id": "kidman_marriage_2006", + "domain": "marriage", + "event_date": "2006-06-25", + "evidence": { + "arudha_lord": "Mercury", + "ascendant": { + "degree": 12.9323, + "degree_in_sign": 12.9323, + "degree_in_sign_raw": 192.9322623333046, + "degree_raw": 192.9323, + "lon": 192.9323, + "lord": "Venus", + "sign": "Libra", + "sign_cn": "天秤座" + }, + "ashtakavarga_audit": { + "all_bav_valid": true, + "boundary": "Audit evidence only. SAV/BAV does not change V2.1 event scores until a fresh holdout validates it.", + "event_house_sav": { + "11": { + "level": "吉利", + "sav_score": 28, + "sign": "Leo" + }, + "2": { + "level": "吉利", + "sav_score": 28, + "sign": "Scorpio" + }, + "5": { + "level": "挑战", + "sav_score": 23, + "sign": "Aquarius" + }, + "7": { + "level": "极吉", + "sav_score": 33, + "sign": "Aries" + } + }, + "method": "Ashtakavarga八分法(BPHS/PVR书例校准v2.1)", + "sav_total": 337, + "sav_valid": true, + "scoring_effect": 0, + "settings": { + "ayanamsa": 23.9476, + "node_mode": "mean" + }, + "status": "used_non_scoring", + "transit_support": { + "Jupiter": { + "bav": 4, + "sav": 28, + "sign": "Libra" + }, + "Saturn": { + "bav": 4, + "sav": 28, + "sign": "Cancer" + } + }, + "version": "2.1" + }, + "birth_source": { + "evidence_basis": "from memory", + "source_grade": "verified_secondary", + "time_accuracy_rating": "A", + "url": "https://www.astro.com/adbvip/adbvip_06_20.htm" + }, + "domain_varga": { + "birth_info": "1967-06-20 15:15", + "divisional_charts": { + "D9_Navamsa": { + "Jupiter": { + "sign": "Libra", + "sign_cn": "天秤座" + }, + "Ketu": { + "sign": "Capricorn", + "sign_cn": "摩羯座" + }, + "Mars": { + "sign": "Leo", + "sign_cn": "狮子座" + }, + "Mercury": { + "sign": "Gemini", + "sign_cn": "双子座" + }, + "Moon": { + "sign": "Capricorn", + "sign_cn": "摩羯座" + }, + "Rahu": { + "sign": "Cancer", + "sign_cn": "巨蟹座" + }, + "Saturn": { + "sign": "Sagittarius", + "sign_cn": "射手座" + }, + "Sun": { + "sign": "Scorpio", + "sign_cn": "天蝎座" + }, + "Venus": { + "sign": "Capricorn", + "sign_cn": "摩羯座" + }, + "ascendant": "Capricorn" + } + } + }, + "double_transit_pac": { + "cl": { + "jupiter": { + "CL_7宫(Taurus)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ], + "CL_Venus(宫主)": [ + { + "desc": "9宫相位", + "offset": 9, + "type": "Aspect" + } + ] + }, + "saturn": { + "CL_7宫(Taurus)": [ + { + "desc": "10宫相位", + "offset": 10, + "type": "Aspect" + } + ], + "CL_Venus(宫主)": [ + { + "desc": "同宫(9宫)", + "type": "Position" + }, + { + "desc": "合相(5.32°)", + "type": "Conjunction" + } + ] + } + }, + "d1": { + "jupiter": { + "Venus(LL)": [ + { + "desc": "9宫相位", + "offset": 9, + "type": "Aspect" + } + ] + }, + "saturn": { + "Venus(LL)": [ + { + "desc": "同宫(10宫)", + "type": "Position" + }, + { + "desc": "合相(5.32°)", + "type": "Conjunction" + } + ] + } + }, + "d9": { + "jupiter": {}, + "saturn": {} + }, + "double_transit": [ + { + "jupiter_pac": [ + { + "desc": "9宫相位", + "offset": 9, + "type": "Aspect" + } + ], + "layer": "D1", + "saturn_pac": [ + { + "desc": "同宫(10宫)", + "type": "Position" + }, + { + "desc": "合相(5.32°)", + "type": "Conjunction" + } + ], + "strength": "strong", + "target": "Venus(LL)" + }, + { + "jupiter_pac": [ + { + "desc": "9宫相位", + "offset": 9, + "type": "Aspect" + } + ], + "layer": "CL", + "saturn_pac": [ + { + "desc": "同宫(9宫)", + "type": "Position" + }, + { + "desc": "合相(5.32°)", + "type": "Conjunction" + } + ], + "strength": "strong", + "target": "CL_Venus(宫主)" + }, + { + "jupiter_pac": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ], + "layer": "CL", + "saturn_pac": [ + { + "desc": "10宫相位", + "offset": 10, + "type": "Aspect" + } + ], + "strength": "strong", + "target": "CL_7宫(Taurus)" + } + ], + "event_house": 7, + "stats": { + "chandra_lagna": "Scorpio", + "cl_jupiter_targets": [ + "CL_7宫(Taurus)", + "CL_Venus(宫主)" + ], + "cl_overlap": [ + "CL_7宫(Taurus)", + "CL_Venus(宫主)" + ], + "cl_saturn_targets": [ + "CL_7宫(Taurus)", + "CL_Venus(宫主)" + ], + "d1_jupiter_targets": [ + "Venus(LL)" + ], + "d1_overlap": [ + "Venus(LL)" + ], + "d1_saturn_targets": [ + "Venus(LL)" + ], + "d9_ascendant": "Capricorn", + "d9_jupiter_targets": [], + "d9_overlap": [], + "d9_saturn_targets": [], + "event_lord_d9_sign": "Leo" + }, + "summary": "✅ Double Transit PAC 确认: D1+CL 多层激活7宫主题", + "transit_date": "2006-06-25" + }, + "event_source": { + "source_grade": "verified_secondary", + "url": "https://en.wikipedia.org/wiki/Nicole_Kidman" + }, + "functional_benefic_malefic": { + "ascendant": "Libra", + "effect_on_confidence": "高严谨模式下必须叠加功能性宫主吉凶与自然吉凶;若功能属性与自然属性冲突,应降低置信度或显式标记冲突。", + "functional_benefics": [ + "Mercury", + "Saturn", + "Venus" + ], + "functional_malefics": [ + "Jupiter", + "Sun" + ], + "functional_neutrals": [ + "Mars", + "Moon" + ], + "owned_houses": { + "Jupiter": [ + 3, + 6 + ], + "Mars": [ + 2, + 7 + ], + "Mercury": [ + 9, + 12 + ], + "Moon": [ + 10 + ], + "Saturn": [ + 4, + 5 + ], + "Sun": [ + 11 + ], + "Venus": [ + 1, + 8 + ] + }, + "source": "strict_functional_benefic_malefic_v1", + "status": "used", + "yogakarakas": [ + "Saturn", + "Venus" + ] + }, + "narayana": { + "ad": { + "end_age": 39.2267, + "lord": "Venus", + "sign": "Libra", + "sign_idx": 6, + "start_age": 38.9867, + "years": 0.24 + }, + "md": { + "end_age": 40.0, + "lord": "Venus", + "sign": "Taurus", + "sign_idx": 1, + "start_age": 38.0, + "years": 2 + }, + "pd": { + "end_age": 39.0155, + "lord": "Venus", + "sign": "Libra", + "sign_idx": 6, + "start_age": 38.9867, + "years": 0.0288 + }, + "remaining_years": 0.98 + }, + "vimshottari": { + "antardasha": "Ketu", + "mahadasha": "Venus" + } + }, + "expected_label": "legal_marriage", + "matched_expected_label": false, + "name": "Nicole Kidman", + "outcome": "Married Keith Urban", + "result_class": "miss", + "rule_version": "v2_1", + "score": 3, + "signals": [ + "Venus_domain_karaka", + "double_transit_pac_strong" + ] + }, + "positive_date": "2006-06-25", + "ranking": { + "candidate_count": 3, + "max_control_score": 3, + "positive_rank": 3, + "positive_score": 3, + "reciprocal_rank": 0.3333333333333333, + "score_margin": 0, + "top_1": false, + "top_3": true + } + }, + { + "case_id": "demi_moore_marriage_1987", + "controls": [ + { + "blocked": false, + "blocked_reason": null, + "date": "1987-10-22", + "result_class": "miss", + "score": 0 + }, + { + "blocked": false, + "blocked_reason": null, + "date": "1987-12-21", + "result_class": "miss", + "score": 2 + } + ], + "domain": "marriage", + "name": "Demi Moore", + "positive": { + "actual_label": null, + "birth_time_rating": "A", + "blocked": false, + "case_id": "demi_moore_marriage_1987", + "domain": "marriage", + "event_date": "1987-11-21", + "evidence": { + "arudha_lord": "Venus", + "ascendant": { + "degree": 29.0648, + "degree_in_sign": 29.0648, + "degree_in_sign_raw": 329.06483110736514, + "degree_raw": 329.0648, + "lon": 329.0648, + "lord": "Saturn", + "sign": "Aquarius", + "sign_cn": "水瓶座" + }, + "ashtakavarga_audit": { + "all_bav_valid": true, + "boundary": "Audit evidence only. SAV/BAV does not change V2.1 event scores until a fresh holdout validates it.", + "event_house_sav": { + "11": { + "level": "极吉", + "sav_score": 32, + "sign": "Sagittarius" + }, + "2": { + "level": "中等", + "sav_score": 26, + "sign": "Pisces" + }, + "5": { + "level": "中等", + "sav_score": 27, + "sign": "Gemini" + }, + "7": { + "level": "极吉", + "sav_score": 34, + "sign": "Leo" + } + }, + "method": "Ashtakavarga八分法(BPHS/PVR书例校准v2.1)", + "sav_total": 337, + "sav_valid": true, + "scoring_effect": 0, + "settings": { + "ayanamsa": 23.6879, + "node_mode": "mean" + }, + "status": "used_non_scoring", + "transit_support": { + "Jupiter": { + "bav": 5, + "sav": 26, + "sign": "Pisces" + }, + "Saturn": { + "bav": 4, + "sav": 27, + "sign": "Scorpio" + } + }, + "version": "2.1" + }, + "birth_source": { + "evidence_basis": "from memory", + "source_grade": "verified_secondary", + "time_accuracy_rating": "A", + "url": "https://www.astro.com/adbvip/adbvip_11_11.htm" + }, + "domain_varga": { + "birth_info": "1962-11-11 14:16", + "divisional_charts": { + "D9_Navamsa": { + "Jupiter": { + "sign": "Sagittarius", + "sign_cn": "射手座" + }, + "Ketu": { + "sign": "Pisces", + "sign_cn": "双鱼座" + }, + "Mars": { + "sign": "Capricorn", + "sign_cn": "摩羯座" + }, + "Mercury": { + "sign": "Pisces", + "sign_cn": "双鱼座" + }, + "Moon": { + "sign": "Scorpio", + "sign_cn": "天蝎座" + }, + "Rahu": { + "sign": "Virgo", + "sign_cn": "处女座" + }, + "Saturn": { + "sign": "Aries", + "sign_cn": "白羊座" + }, + "Sun": { + "sign": "Taurus", + "sign_cn": "金牛座" + }, + "Venus": { + "sign": "Gemini", + "sign_cn": "双子座" + }, + "ascendant": "Gemini" + } + } + }, + "double_transit_pac": { + "cl": { + "jupiter": { + "CL_7宫(Libra)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ], + "CL_Venus(宫主)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ] + }, + "saturn": {} + }, + "d1": { + "jupiter": { + "7宫(Leo)": [ + { + "desc": "5宫相位", + "offset": 5, + "type": "Aspect" + } + ], + "Sun(宫主)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ], + "Sun(对宫主)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ] + }, + "saturn": {} + }, + "d9": { + "jupiter": {}, + "saturn": { + "D9_7宫(Sagittarius)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ], + "D9_Jupiter(宫主)": [ + { + "desc": "3宫相位", + "offset": 3, + "type": "Aspect" + } + ] + } + }, + "double_transit": [], + "event_house": 7, + "stats": { + "chandra_lagna": "Aries", + "cl_jupiter_targets": [ + "CL_7宫(Libra)", + "CL_Venus(宫主)" + ], + "cl_overlap": [], + "cl_saturn_targets": [], + "d1_jupiter_targets": [ + "7宫(Leo)", + "Sun(宫主)", + "Sun(对宫主)" + ], + "d1_overlap": [], + "d1_saturn_targets": [], + "d9_ascendant": "Gemini", + "d9_jupiter_targets": [], + "d9_overlap": [], + "d9_saturn_targets": [ + "D9_7宫(Sagittarius)", + "D9_Jupiter(宫主)" + ], + "event_lord_d9_sign": "Taurus" + }, + "summary": "❌ 无 Double Transit PAC 激活", + "transit_date": "1987-11-21" + }, + "event_source": { + "source_grade": "verified_secondary", + "url": "https://en.wikipedia.org/wiki/Demi_Moore" + }, + "functional_benefic_malefic": { + "ascendant": "Aquarius", + "effect_on_confidence": "高严谨模式下必须叠加功能性宫主吉凶与自然吉凶;若功能属性与自然属性冲突,应降低置信度或显式标记冲突。", + "functional_benefics": [ + "Mercury", + "Saturn", + "Venus" + ], + "functional_malefics": [ + "Jupiter", + "Mars", + "Moon" + ], + "functional_neutrals": [ + "Sun" + ], + "owned_houses": { + "Jupiter": [ + 2, + 11 + ], + "Mars": [ + 3, + 10 + ], + "Mercury": [ + 5, + 8 + ], + "Moon": [ + 6 + ], + "Saturn": [ + 1, + 12 + ], + "Sun": [ + 7 + ], + "Venus": [ + 4, + 9 + ] + }, + "source": "strict_functional_benefic_malefic_v1", + "status": "used", + "yogakarakas": [ + "Saturn", + "Venus" + ] + }, + "narayana": { + "ad": { + "end_age": 25.3125, + "lord": "Venus", + "sign": "Taurus", + "sign_idx": 1, + "start_age": 25.0, + "years": 0.3125 + }, + "md": { + "end_age": 30.0, + "lord": "Venus", + "sign": "Taurus", + "sign_idx": 1, + "start_age": 25.0, + "years": 5 + }, + "pd": { + "end_age": 25.0352, + "lord": "Mercury", + "sign": "Gemini", + "sign_idx": 2, + "start_age": 25.0195, + "years": 0.0156 + }, + "remaining_years": 4.97 + }, + "vimshottari": { + "antardasha": "Moon", + "mahadasha": "Mars" + } + }, + "expected_label": "legal_marriage", + "matched_expected_label": false, + "name": "Demi Moore", + "outcome": "Married Bruce Willis", + "result_class": "miss", + "rule_version": "v2_1", + "score": 0, + "signals": [] + }, + "positive_date": "1987-11-21", + "ranking": { + "candidate_count": 3, + "max_control_score": 2, + "positive_rank": 3, + "positive_score": 0, + "reciprocal_rank": 0.3333333333333333, + "score_margin": -2, + "top_1": false, + "top_3": true + } + }, + { + "case_id": "chelsea_clinton_marriage_2010", + "controls": [ + { + "blocked": false, + "blocked_reason": null, + "date": "2010-07-01", + "result_class": "weak_hit", + "score": 5 + }, + { + "blocked": false, + "blocked_reason": null, + "date": "2010-08-30", + "result_class": "weak_hit", + "score": 5 + } + ], + "domain": "marriage", + "name": "Chelsea Clinton", + "positive": { + "actual_label": "domain_activation", + "birth_time_rating": "AA", + "blocked": false, + "case_id": "chelsea_clinton_marriage_2010", + "domain": "marriage", + "event_date": "2010-07-31", + "evidence": { + "arudha_lord": "Moon", + "ascendant": { + "degree": 22.7703, + "degree_in_sign": 22.7703, + "degree_in_sign_raw": 202.7702714237087, + "degree_raw": 202.7703, + "lon": 202.7703, + "lord": "Venus", + "sign": "Libra", + "sign_cn": "天秤座" + }, + "ashtakavarga_audit": { + "all_bav_valid": true, + "boundary": "Audit evidence only. SAV/BAV does not change V2.1 event scores until a fresh holdout validates it.", + "event_house_sav": { + "11": { + "level": "中等", + "sav_score": 26, + "sign": "Leo" + }, + "2": { + "level": "极吉", + "sav_score": 30, + "sign": "Scorpio" + }, + "5": { + "level": "挑战", + "sav_score": 22, + "sign": "Aquarius" + }, + "7": { + "level": "挑战", + "sav_score": 24, + "sign": "Aries" + } + }, + "method": "Ashtakavarga八分法(BPHS/PVR书例校准v2.1)", + "sav_total": 337, + "sav_valid": true, + "scoring_effect": 0, + "settings": { + "ayanamsa": 24.0049, + "node_mode": "mean" + }, + "status": "used_non_scoring", + "transit_support": { + "Jupiter": { + "bav": 6, + "sav": 26, + "sign": "Pisces" + }, + "Saturn": { + "bav": 3, + "sav": 25, + "sign": "Virgo" + } + }, + "version": "2.1" + }, + "birth_source": { + "evidence_basis": "BC/BR in hand", + "source_grade": "primary", + "time_accuracy_rating": "AA", + "url": "https://www.astro.com/adbvip/adbvip_02_27.htm" + }, + "domain_varga": { + "birth_info": "1980-02-27 23:24", + "divisional_charts": { + "D9_Navamsa": { + "Jupiter": { + "sign": "Cancer", + "sign_cn": "巨蟹座" + }, + "Ketu": { + "sign": "Scorpio", + "sign_cn": "天蝎座" + }, + "Mars": { + "sign": "Cancer", + "sign_cn": "巨蟹座" + }, + "Mercury": { + "sign": "Gemini", + "sign_cn": "双子座" + }, + "Moon": { + "sign": "Scorpio", + "sign_cn": "天蝎座" + }, + "Rahu": { + "sign": "Taurus", + "sign_cn": "金牛座" + }, + "Saturn": { + "sign": "Capricorn", + "sign_cn": "摩羯座" + }, + "Sun": { + "sign": "Aquarius", + "sign_cn": "水瓶座" + }, + "Venus": { + "sign": "Pisces", + "sign_cn": "双鱼座" + }, + "ascendant": "Aries" + } + } + }, + "double_transit_pac": { + "cl": { + "jupiter": {}, + "saturn": { + "CL_Saturn(宫主)": [ + { + "desc": "同宫(3宫)", + "type": "Position" + }, + { + "desc": "合相(5.63°)", + "type": "Conjunction" + } + ] + } + }, + "d1": { + "jupiter": { + "Mars(宫主)": [ + { + "desc": "5宫相位", + "offset": 5, + "type": "Aspect" + } + ], + "Mars(对宫主)": [ + { + "desc": "5宫相位", + "offset": 5, + "type": "Aspect" + } + ], + "Venus(LL)": [ + { + "desc": "同宫(6宫)", + "type": "Position" + } + ] + }, + "saturn": { + "7宫(Aries)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ] + } + }, + "d9": { + "jupiter": { + "D9_Venus(宫主)": [ + { + "desc": "同宫(12宫)", + "type": "Position" + } + ], + "Venus_D9(Pisces)": [ + { + "desc": "同宫(12宫)", + "type": "Position" + }, + { + "desc": "合相(5.71°)", + "type": "Conjunction" + } + ] + }, + "saturn": { + "D9_7宫(Libra)": [ + { + "desc": "7宫相位", + "offset": 7, + "type": "Aspect" + } + ], + "Mars_D9(Cancer)": [ + { + "desc": "10宫相位", + "offset": 10, + "type": "Aspect" + } + ] + } + }, + "double_transit": [ + { + "jupiter_pac": [ + { + "desc": "5宫相位", + "offset": 5, + "type": "Aspect" + } + ], + "layer": "D1+D9", + "saturn_pac": [ + { + "desc": "10宫相位", + "offset": 10, + "type": "Aspect" + } + ], + "strength": "moderate", + "target": "Jupiter(D1)Mars(对宫主) + Saturn(D9)Mars_D9(Cancer)" + }, + { + "jupiter_pac": [ + { + "desc": "5宫相位", + "offset": 5, + "type": "Aspect" + } + ], + "layer": "D1+D9", + "saturn_pac": [ + { + "desc": "10宫相位", + "offset": 10, + "type": "Aspect" + } + ], + "strength": "moderate", + "target": "Jupiter(D1)Mars(宫主) + Saturn(D9)Mars_D9(Cancer)" + } + ], + "event_house": 7, + "stats": { + "chandra_lagna": "Cancer", + "cl_jupiter_targets": [], + "cl_overlap": [], + "cl_saturn_targets": [ + "CL_Saturn(宫主)" + ], + "d1_jupiter_targets": [ + "Mars(宫主)", + "Mars(对宫主)", + "Venus(LL)" + ], + "d1_overlap": [], + "d1_saturn_targets": [ + "7宫(Aries)" + ], + "d9_ascendant": "Aries", + "d9_jupiter_targets": [ + "D9_Venus(宫主)", + "Venus_D9(Pisces)" + ], + "d9_overlap": [], + "d9_saturn_targets": [ + "D9_7宫(Libra)", + "Mars_D9(Cancer)" + ], + "event_lord_d9_sign": "Cancer" + }, + "summary": "⚠️ 跨层间接 Double Transit (D1+D9),需结合 Dasha 确认", + "transit_date": "2010-07-31" + }, + "event_source": { + "source_grade": "verified_secondary", + "url": "https://en.wikipedia.org/wiki/Chelsea_Clinton" + }, + "functional_benefic_malefic": { + "ascendant": "Libra", + "effect_on_confidence": "高严谨模式下必须叠加功能性宫主吉凶与自然吉凶;若功能属性与自然属性冲突,应降低置信度或显式标记冲突。", + "functional_benefics": [ + "Mercury", + "Saturn", + "Venus" + ], + "functional_malefics": [ + "Jupiter", + "Sun" + ], + "functional_neutrals": [ + "Mars", + "Moon" + ], + "owned_houses": { + "Jupiter": [ + 3, + 6 + ], + "Mars": [ + 2, + 7 + ], + "Mercury": [ + 9, + 12 + ], + "Moon": [ + 10 + ], + "Saturn": [ + 4, + 5 + ], + "Sun": [ + 11 + ], + "Venus": [ + 1, + 8 + ] + }, + "source": "strict_functional_benefic_malefic_v1", + "status": "used", + "yogakarakas": [ + "Saturn", + "Venus" + ] + }, + "narayana": { + "ad": { + "end_age": 30.5632, + "lord": "Saturn", + "sign": "Aquarius", + "sign_idx": 10, + "start_age": 30.0, + "years": 0.5632 + }, + "md": { + "end_age": 37.0, + "lord": "Saturn", + "sign": "Aquarius", + "sign_idx": 10, + "start_age": 30.0, + "years": 7 + }, + "pd": { + "end_age": 30.4596, + "lord": "Mars", + "sign": "Scorpio", + "sign_idx": 7, + "start_age": 30.4014, + "years": 0.0583 + }, + "remaining_years": 6.58 + }, + "vimshottari": { + "antardasha": "Moon", + "mahadasha": "Venus" + } + }, + "expected_label": "legal_marriage", + "matched_expected_label": false, + "name": "Chelsea Clinton", + "outcome": "Married Marc Mezvinsky", + "result_class": "weak_hit", + "rule_version": "v2_1", + "score": 5, + "signals": [ + "Venus_domain_karaka", + "active_dasha_matches_D9_7L:Venus", + "active_dasha_matches_UL_lord:Moon", + "narayana_lord_owns_event_house:Saturn", + "double_transit_pac_present" + ] + }, + "positive_date": "2010-07-31", + "ranking": { + "candidate_count": 3, + "max_control_score": 5, + "positive_rank": 3, + "positive_score": 5, + "reciprocal_rank": 0.3333333333333333, + "score_margin": 0, + "top_1": false, + "top_3": true + } + } + ], + "control_offsets_days": [ + -30, + 30 + ], + "rule_version": "v2_1", + "summary": { + "balanced_accuracy": null, + "balanced_accuracy_blocked_reason": "controls_are_non_target_dates_not_independently_adjudicated_all-domain_non_events", + "case_count": 10, + "control_activation_rate": 0.8, + "control_date_count": 20, + "control_strong_activation_rate": 0.2, + "mean_reciprocal_rank": 0.3333333333333333, + "mean_score_margin": -0.6, + "positive_top_1_rate": 0.0, + "positive_top_3_rate": 1.0, + "ranked_case_count": 10, + "specificity_proxy": 0.19999999999999996, + "strong_specificity_proxy": 0.8, + "timing_precision_gate": { + "positive_top_3_minimum": 0.6, + "reason": "positive_dates_do_not_rank_above_controls_with_required_specificity", + "specificity_proxy_minimum": 0.6, + "status": "blocked" + } + } +} diff --git a/docs/benchmark/western_jobs_iphone_2007_calibration.json b/docs/benchmark/western_jobs_iphone_2007_calibration.json new file mode 100644 index 00000000..0e1f619f --- /dev/null +++ b/docs/benchmark/western_jobs_iphone_2007_calibration.json @@ -0,0 +1,883 @@ +{ + "scope": "western_real_case_calibration", + "status": "calculated_not_predictive_validation", + "case_id": "jobs_iphone_2007", + "subject": "Steve Jobs", + "event": { + "date": "2007-01-09", + "type": "career_breakthrough", + "source": { + "url": "https://www.apple.com/newsroom/2007/01/09Apple-Reinvents-the-Phone-with-iPhone/", + "source_grade": "primary" + } + }, + "birth_source": { + "url": "https://www.astro.com/adbvip/adbvip_02_24.htm", + "source_grade": "primary", + "time_accuracy_rating": "AA", + "evidence_basis": "BC/BR in hand" + }, + "layers": { + "secondary_progressions": { + "technique": "secondary_progressions", + "status": "partial", + "method": "one_ephemeris_day_per_tropical_year", + "target_date": "2007-01-09", + "target_local_time": "2007-01-09T00:00:00-08:00", + "elapsed_tropical_years": 51.8729722, + "progressed_julian_day_ut": 2435215.50838887, + "natal_sun_longitude": 335.74814, + "progressed_planets": { + "sun": { + "longitude": 27.200726, + "sign": "Aries", + "degree_in_sign": 27.200726, + "speed_longitude": 0.97771866, + "retrograde": false + }, + "moon": { + "longitude": 327.085209, + "sign": "Aquarius", + "degree_in_sign": 27.085209, + "speed_longitude": 13.22736915, + "retrograde": false + }, + "mercury": { + "longitude": 21.467043, + "sign": "Aries", + "degree_in_sign": 21.467043, + "speed_longitude": 2.03176132, + "retrograde": false + }, + "venus": { + "longitude": 352.062856, + "sign": "Pisces", + "degree_in_sign": 22.062856, + "speed_longitude": 1.19645701, + "retrograde": false + }, + "mars": { + "longitude": 64.755496, + "sign": "Gemini", + "degree_in_sign": 4.755496, + "speed_longitude": 0.67328564, + "retrograde": false + }, + "jupiter": { + "longitude": 111.475558, + "sign": "Cancer", + "degree_in_sign": 21.475558, + "speed_longitude": 0.09538143, + "retrograde": false + }, + "saturn": { + "longitude": 229.414858, + "sign": "Scorpio", + "degree_in_sign": 19.414858, + "speed_longitude": -0.06655717, + "retrograde": true + }, + "uranus": { + "longitude": 113.715663, + "sign": "Cancer", + "degree_in_sign": 23.715663, + "speed_longitude": 0.01464552, + "retrograde": false + }, + "neptune": { + "longitude": 206.868278, + "sign": "Libra", + "degree_in_sign": 26.868278, + "speed_longitude": -0.02739973, + "retrograde": true + }, + "pluto": { + "longitude": 144.383855, + "sign": "Leo", + "degree_in_sign": 24.383855, + "speed_longitude": -0.00882106, + "retrograde": true + }, + "true_node": { + "longitude": 268.467552, + "sign": "Sagittarius", + "degree_in_sign": 28.467552, + "speed_longitude": -0.07938677, + "retrograde": true + } + }, + "progressed_angles": { + "status": "used", + "method": "secondary_quotidian_progressed_date_same_location", + "progressed_local_time": "1955-04-17T16:12:04.798161-08:00", + "angles": { + "ascendant": { + "longitude": 176.691966, + "sign": "Virgo", + "degree_in_sign": 26.691966 + }, + "mc": { + "longitude": 86.278308, + "sign": "Gemini", + "degree_in_sign": 26.278308 + }, + "descendant": { + "longitude": 356.691966, + "sign": "Pisces", + "degree_in_sign": 26.691966 + }, + "ic": { + "longitude": 266.278308, + "sign": "Sagittarius", + "degree_in_sign": 26.278308 + } + }, + "boundary": "Quotidian progressed-date angles; Naibod and solar-arc angle variants are separate methods." + }, + "aspects": [ + { + "transit_planet": "mercury", + "natal_point": "mc", + "aspect": "sextile", + "exact_degrees": 60.0, + "separation": 59.85056, + "orb": 0.14944, + "allowed_orb": 5.0 + }, + { + "transit_planet": "venus", + "natal_point": "ascendant", + "aspect": "opposition", + "exact_degrees": 180.0, + "separation": 179.768817, + "orb": 0.231183, + "allowed_orb": 5.0 + }, + { + "transit_planet": "mercury", + "natal_point": "venus", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 90.295161, + "orb": 0.295161, + "allowed_orb": 6.0 + }, + { + "transit_planet": "jupiter", + "natal_point": "venus", + "aspect": "opposition", + "exact_degrees": 180.0, + "separation": 179.696324, + "orb": 0.303676, + "allowed_orb": 6.0 + }, + { + "transit_planet": "jupiter", + "natal_point": "saturn", + "aspect": "trine", + "exact_degrees": 120.0, + "separation": 119.687136, + "orb": 0.312864, + "allowed_orb": 6.0 + }, + { + "transit_planet": "true_node", + "natal_point": "neptune", + "aspect": "sextile", + "exact_degrees": 60.0, + "separation": 60.416254, + "orb": 0.416254, + "allowed_orb": 6.0 + }, + { + "transit_planet": "uranus", + "natal_point": "uranus", + "aspect": "conjunction", + "exact_degrees": 0.0, + "separation": 0.419243, + "orb": 0.419243, + "allowed_orb": 6.0 + }, + { + "transit_planet": "true_node", + "natal_point": "mars", + "aspect": "trine", + "exact_degrees": 120.0, + "separation": 120.622924, + "orb": 0.622924, + "allowed_orb": 6.0 + }, + { + "transit_planet": "venus", + "natal_point": "mc", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 89.254747, + "orb": 0.745253, + "allowed_orb": 5.0 + }, + { + "transit_planet": "jupiter", + "natal_point": "ascendant", + "aspect": "sextile", + "exact_degrees": 60.0, + "separation": 60.818481, + "orb": 0.818481, + "allowed_orb": 5.0 + }, + { + "transit_planet": "sun", + "natal_point": "neptune", + "aspect": "opposition", + "exact_degrees": 180.0, + "separation": 179.149428, + "orb": 0.850572, + "allowed_orb": 6.0 + }, + { + "transit_planet": "venus", + "natal_point": "venus", + "aspect": "sextile", + "exact_degrees": 60.0, + "separation": 60.890974, + "orb": 0.890974, + "allowed_orb": 6.0 + }, + { + "transit_planet": "venus", + "natal_point": "saturn", + "aspect": "trine", + "exact_degrees": 120.0, + "separation": 120.900162, + "orb": 0.900162, + "allowed_orb": 6.0 + }, + { + "transit_planet": "pluto", + "natal_point": "pluto", + "aspect": "conjunction", + "exact_degrees": 0.0, + "separation": 0.938933, + "orb": 0.938933, + "allowed_orb": 6.0 + }, + { + "transit_planet": "mercury", + "natal_point": "jupiter", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 89.040846, + "orb": 0.959154, + "allowed_orb": 6.0 + }, + { + "transit_planet": "moon", + "natal_point": "neptune", + "aspect": "trine", + "exact_degrees": 120.0, + "separation": 119.033911, + "orb": 0.966089, + "allowed_orb": 6.0 + }, + { + "transit_planet": "jupiter", + "natal_point": "jupiter", + "aspect": "conjunction", + "exact_degrees": 0.0, + "separation": 0.967669, + "orb": 0.967669, + "allowed_orb": 6.0 + }, + { + "transit_planet": "mars", + "natal_point": "sun", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 89.007356, + "orb": 0.992644, + "allowed_orb": 6.0 + }, + { + "transit_planet": "saturn", + "natal_point": "jupiter", + "aspect": "trine", + "exact_degrees": 120.0, + "separation": 118.906969, + "orb": 1.093031, + "allowed_orb": 6.0 + }, + { + "transit_planet": "neptune", + "natal_point": "neptune", + "aspect": "conjunction", + "exact_degrees": 0.0, + "separation": 1.18302, + "orb": 1.18302, + "allowed_orb": 6.0 + }, + { + "transit_planet": "uranus", + "natal_point": "ascendant", + "aspect": "sextile", + "exact_degrees": 60.0, + "separation": 58.578376, + "orb": 1.421624, + "allowed_orb": 5.0 + }, + { + "transit_planet": "neptune", + "natal_point": "pluto", + "aspect": "sextile", + "exact_degrees": 60.0, + "separation": 61.54549, + "orb": 1.54549, + "allowed_orb": 6.0 + }, + { + "transit_planet": "venus", + "natal_point": "jupiter", + "aspect": "trine", + "exact_degrees": 120.0, + "separation": 118.445033, + "orb": 1.554967, + "allowed_orb": 6.0 + }, + { + "transit_planet": "saturn", + "natal_point": "saturn", + "aspect": "conjunction", + "exact_degrees": 0.0, + "separation": 1.747836, + "orb": 1.747836, + "allowed_orb": 6.0 + }, + { + "transit_planet": "saturn", + "natal_point": "venus", + "aspect": "sextile", + "exact_degrees": 60.0, + "separation": 61.757024, + "orb": 1.757024, + "allowed_orb": 6.0 + }, + { + "transit_planet": "moon", + "natal_point": "pluto", + "aspect": "opposition", + "exact_degrees": 180.0, + "separation": 178.237579, + "orb": 1.762421, + "allowed_orb": 6.0 + }, + { + "transit_planet": "sun", + "natal_point": "pluto", + "aspect": "trine", + "exact_degrees": 120.0, + "separation": 118.122062, + "orb": 1.877938, + "allowed_orb": 6.0 + }, + { + "transit_planet": "sun", + "natal_point": "mars", + "aspect": "conjunction", + "exact_degrees": 0.0, + "separation": 1.88975, + "orb": 1.88975, + "allowed_orb": 6.0 + }, + { + "transit_planet": "moon", + "natal_point": "mars", + "aspect": "sextile", + "exact_degrees": 60.0, + "separation": 62.005267, + "orb": 2.005267, + "allowed_orb": 6.0 + }, + { + "transit_planet": "venus", + "natal_point": "uranus", + "aspect": "trine", + "exact_degrees": 120.0, + "separation": 122.07205, + "orb": 2.07205, + "allowed_orb": 6.0 + }, + { + "transit_planet": "neptune", + "natal_point": "mars", + "aspect": "opposition", + "exact_degrees": 180.0, + "separation": 177.777802, + "orb": 2.222198, + "allowed_orb": 6.0 + }, + { + "transit_planet": "uranus", + "natal_point": "venus", + "aspect": "opposition", + "exact_degrees": 180.0, + "separation": 177.456219, + "orb": 2.543781, + "allowed_orb": 6.0 + }, + { + "transit_planet": "uranus", + "natal_point": "saturn", + "aspect": "trine", + "exact_degrees": 120.0, + "separation": 117.447031, + "orb": 2.552969, + "allowed_orb": 6.0 + }, + { + "transit_planet": "jupiter", + "natal_point": "uranus", + "aspect": "conjunction", + "exact_degrees": 0.0, + "separation": 2.659348, + "orb": 2.659348, + "allowed_orb": 6.0 + }, + { + "transit_planet": "mercury", + "natal_point": "uranus", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 92.667863, + "orb": 2.667863, + "allowed_orb": 6.0 + }, + { + "transit_planet": "neptune", + "natal_point": "uranus", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 92.733372, + "orb": 2.733372, + "allowed_orb": 6.0 + }, + { + "transit_planet": "saturn", + "natal_point": "ascendant", + "aspect": "sextile", + "exact_degrees": 60.0, + "separation": 57.120819, + "orb": 2.879181, + "allowed_orb": 5.0 + }, + { + "transit_planet": "mars", + "natal_point": "moon", + "aspect": "sextile", + "exact_degrees": 60.0, + "separation": 57.008321, + "orb": 2.991679, + "allowed_orb": 6.0 + }, + { + "transit_planet": "sun", + "natal_point": "uranus", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 86.93418, + "orb": 3.06582, + "allowed_orb": 6.0 + }, + { + "transit_planet": "pluto", + "natal_point": "mc", + "aspect": "sextile", + "exact_degrees": 60.0, + "separation": 63.066252, + "orb": 3.066252, + "allowed_orb": 5.0 + }, + { + "transit_planet": "true_node", + "natal_point": "pluto", + "aspect": "trine", + "exact_degrees": 120.0, + "separation": 123.144764, + "orb": 3.144764, + "allowed_orb": 6.0 + }, + { + "transit_planet": "uranus", + "natal_point": "jupiter", + "aspect": "conjunction", + "exact_degrees": 0.0, + "separation": 3.207774, + "orb": 3.207774, + "allowed_orb": 6.0 + }, + { + "transit_planet": "pluto", + "natal_point": "saturn", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 86.778839, + "orb": 3.221161, + "allowed_orb": 6.0 + }, + { + "transit_planet": "pluto", + "natal_point": "neptune", + "aspect": "sextile", + "exact_degrees": 60.0, + "separation": 63.667443, + "orb": 3.667443, + "allowed_orb": 6.0 + }, + { + "transit_planet": "mercury", + "natal_point": "pluto", + "aspect": "trine", + "exact_degrees": 120.0, + "separation": 123.855745, + "orb": 3.855745, + "allowed_orb": 6.0 + }, + { + "transit_planet": "uranus", + "natal_point": "neptune", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 94.335635, + "orb": 4.335635, + "allowed_orb": 6.0 + }, + { + "transit_planet": "pluto", + "natal_point": "mars", + "aspect": "trine", + "exact_degrees": 120.0, + "separation": 115.293379, + "orb": 4.706621, + "allowed_orb": 6.0 + }, + { + "transit_planet": "saturn", + "natal_point": "uranus", + "aspect": "trine", + "exact_degrees": 120.0, + "separation": 115.279952, + "orb": 4.720048, + "allowed_orb": 6.0 + }, + { + "transit_planet": "true_node", + "natal_point": "true_node", + "aspect": "conjunction", + "exact_degrees": 0.0, + "separation": 4.943849, + "orb": 4.943849, + "allowed_orb": 6.0 + }, + { + "transit_planet": "saturn", + "natal_point": "mercury", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 84.946868, + "orb": 5.053132, + "allowed_orb": 6.0 + }, + { + "transit_planet": "uranus", + "natal_point": "mars", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 84.625187, + "orb": 5.374813, + "allowed_orb": 6.0 + }, + { + "transit_planet": "neptune", + "natal_point": "venus", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 84.303604, + "orb": 5.696396, + "allowed_orb": 6.0 + }, + { + "transit_planet": "saturn", + "natal_point": "pluto", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 84.09207, + "orb": 5.90793, + "allowed_orb": 6.0 + }, + { + "transit_planet": "moon", + "natal_point": "saturn", + "aspect": "square", + "exact_degrees": 90.0, + "separation": 95.922515, + "orb": 5.922515, + "allowed_orb": 6.0 + } + ], + "boundary": "Progressed planets plus explicitly selected quotidian angles. Lunar phases, stations, duration, and interpretation remain separate audited layers." + }, + "parans": { + "technique": "parans", + "status": "used", + "target_date": "2007-01-09", + "method": "Swiss Ephemeris rise_trans latitude-aware angular events", + "event_count": 33, + "events": [ + { + "planet": "saturn", + "angle": "upper_culmination", + "julian_day_ut": 2454109.94604537, + "local_time": "2007-01-09T02:42:18.320357-08:00" + }, + { + "planet": "jupiter", + "angle": "rise", + "julian_day_ut": 2454110.02462397, + "local_time": "2007-01-09T04:35:27.510729-08:00" + }, + { + "planet": "moon", + "angle": "upper_culmination", + "julian_day_ut": 2454110.03029454, + "local_time": "2007-01-09T04:43:37.448205-08:00" + }, + { + "planet": "pluto", + "angle": "rise", + "julian_day_ut": 2454110.06527459, + "local_time": "2007-01-09T05:33:59.724429-08:00" + }, + { + "planet": "mars", + "angle": "rise", + "julian_day_ut": 2454110.07489361, + "local_time": "2007-01-09T05:47:50.807509-08:00" + }, + { + "planet": "sun", + "angle": "rise", + "julian_day_ut": 2454110.14259961, + "local_time": "2007-01-09T07:25:20.606518-08:00" + }, + { + "planet": "mercury", + "angle": "rise", + "julian_day_ut": 2454110.15358739, + "local_time": "2007-01-09T07:41:09.950433-08:00" + }, + { + "planet": "venus", + "angle": "rise", + "julian_day_ut": 2454110.19141445, + "local_time": "2007-01-09T08:35:38.208827-08:00" + }, + { + "planet": "neptune", + "angle": "rise", + "julian_day_ut": 2454110.21153608, + "local_time": "2007-01-09T09:04:36.717483-08:00" + }, + { + "planet": "jupiter", + "angle": "upper_culmination", + "julian_day_ut": 2454110.22767351, + "local_time": "2007-01-09T09:27:50.991352-08:00" + }, + { + "planet": "saturn", + "angle": "set", + "julian_day_ut": 2454110.22986714, + "local_time": "2007-01-09T09:31:00.520680-08:00" + }, + { + "planet": "uranus", + "angle": "rise", + "julian_day_ut": 2454110.25682944, + "local_time": "2007-01-09T10:09:50.063788-08:00" + }, + { + "planet": "true_node", + "angle": "rise", + "julian_day_ut": 2454110.26490969, + "local_time": "2007-01-09T10:21:28.196996-08:00" + }, + { + "planet": "mars", + "angle": "upper_culmination", + "julian_day_ut": 2454110.27172895, + "local_time": "2007-01-09T10:31:17.381368-08:00" + }, + { + "planet": "pluto", + "angle": "upper_culmination", + "julian_day_ut": 2454110.27983419, + "local_time": "2007-01-09T10:42:57.673969-08:00" + }, + { + "planet": "moon", + "angle": "set", + "julian_day_ut": 2454110.28420722, + "local_time": "2007-01-09T10:49:15.504146-08:00" + }, + { + "planet": "sun", + "angle": "upper_culmination", + "julian_day_ut": 2454110.34500033, + "local_time": "2007-01-09T12:16:48.028382-08:00" + }, + { + "planet": "mercury", + "angle": "upper_culmination", + "julian_day_ut": 2454110.35064807, + "local_time": "2007-01-09T12:24:55.993106-08:00" + }, + { + "planet": "venus", + "angle": "upper_culmination", + "julian_day_ut": 2454110.39828854, + "local_time": "2007-01-09T13:33:32.129903-08:00" + }, + { + "planet": "neptune", + "angle": "upper_culmination", + "julian_day_ut": 2454110.42850297, + "local_time": "2007-01-09T14:17:02.656278-08:00" + }, + { + "planet": "jupiter", + "angle": "set", + "julian_day_ut": 2454110.43069242, + "local_time": "2007-01-09T14:20:11.825158-08:00" + }, + { + "planet": "mars", + "angle": "set", + "julian_day_ut": 2454110.46852186, + "local_time": "2007-01-09T15:14:40.288339-08:00" + }, + { + "planet": "uranus", + "angle": "upper_culmination", + "julian_day_ut": 2454110.49125881, + "local_time": "2007-01-09T15:47:24.760886-08:00" + }, + { + "planet": "pluto", + "angle": "set", + "julian_day_ut": 2454110.49439241, + "local_time": "2007-01-09T15:51:55.504177-08:00" + }, + { + "planet": "true_node", + "angle": "upper_culmination", + "julian_day_ut": 2454110.50587744, + "local_time": "2007-01-09T16:08:27.811086-08:00" + }, + { + "planet": "sun", + "angle": "set", + "julian_day_ut": 2454110.5475517, + "local_time": "2007-01-09T17:08:28.467114-08:00" + }, + { + "planet": "mercury", + "angle": "set", + "julian_day_ut": 2454110.54793949, + "local_time": "2007-01-09T17:09:01.972344-08:00" + }, + { + "planet": "venus", + "angle": "set", + "julian_day_ut": 2454110.6054813, + "local_time": "2007-01-09T18:31:53.584685-08:00" + }, + { + "planet": "neptune", + "angle": "set", + "julian_day_ut": 2454110.6454801, + "local_time": "2007-01-09T19:29:29.481048-08:00" + }, + { + "planet": "saturn", + "angle": "rise", + "julian_day_ut": 2454110.65931501, + "local_time": "2007-01-09T19:49:24.817192-08:00" + }, + { + "planet": "uranus", + "angle": "set", + "julian_day_ut": 2454110.72570392, + "local_time": "2007-01-09T21:25:00.818990-08:00" + }, + { + "planet": "true_node", + "angle": "set", + "julian_day_ut": 2454110.74685172, + "local_time": "2007-01-09T21:55:27.988239-08:00" + }, + { + "planet": "moon", + "angle": "rise", + "julian_day_ut": 2454110.80992635, + "local_time": "2007-01-09T23:26:17.637054-08:00" + } + ], + "paran_pairs_within_4_minutes": [ + { + "first": { + "planet": "jupiter", + "angle": "upper_culmination", + "local_time": "2007-01-09T09:27:50.991352-08:00" + }, + "second": { + "planet": "saturn", + "angle": "set", + "local_time": "2007-01-09T09:31:00.520680-08:00" + }, + "separation_minutes": 3.1588 + }, + { + "first": { + "planet": "neptune", + "angle": "upper_culmination", + "local_time": "2007-01-09T14:17:02.656278-08:00" + }, + "second": { + "planet": "jupiter", + "angle": "set", + "local_time": "2007-01-09T14:20:11.825158-08:00" + }, + "separation_minutes": 3.1528 + }, + { + "first": { + "planet": "sun", + "angle": "set", + "local_time": "2007-01-09T17:08:28.467114-08:00" + }, + "second": { + "planet": "mercury", + "angle": "set", + "local_time": "2007-01-09T17:09:01.972344-08:00" + }, + "separation_minutes": 0.5584 + } + ], + "boundary": "Geometric rise/culmination/set simultaneity only; no interpretation or predictive claim is inferred." + } + }, + "summary": { + "progressed_aspect_count": 54, + "paran_event_count": 33, + "paran_pair_count": 3 + }, + "boundary": "Known-event geometry replay only. One positive case cannot establish specificity, false-positive rate, or predictive accuracy." +} diff --git a/docs/research/prashna_sphuta_pyjhora_parity_2026_07_17.md b/docs/research/prashna_sphuta_pyjhora_parity_2026_07_17.md new file mode 100644 index 00000000..46e5634c --- /dev/null +++ b/docs/research/prashna_sphuta_pyjhora_parity_2026_07_17.md @@ -0,0 +1,26 @@ +# Prashna Gulika/Sphuta PyJHora Parity + +## Scope + +One public synthetic daytime chart: Beijing, 1990-01-01 12:00, UTC+8, +Lahiri, Mean Node. PyJHora 4.8.7 is used only as an AGPL external black-box +benchmark. + +## Result + +| Point | Local | PyJHora | Absolute delta | +|---|---:|---:|---:| +| Gulika | 22.154273° | 21.867466° | 0.286807° | +| Tri Sphuta | 315.345416° | 315.052227° | 0.293189° | +| Chatur Sphuta | 212.102430° | 211.811742° | 0.290688° | +| Pancha Sphuta | 146.837653° | 146.543676° | 0.293977° | + +All rows pass the declared 0.5° tolerance. The local Gulika method now uses +the start of Saturn's share in eight equal day/night parts. The historical +Ghatika-end variant remains available as `legacy_ghatika_end`. + +## Boundary + +This closes one daytime synthetic comparison only. Night charts, all weekdays, +high-latitude sunrise behavior, and additional locations remain untested. No +Prashna verdict, health claim, or production tuning is unlocked by this packet. diff --git a/references/evidence_manifests/commercial_external_validation_release.v1.json b/references/evidence_manifests/commercial_external_validation_release.v1.json index d4714899..83614d12 100644 --- a/references/evidence_manifests/commercial_external_validation_release.v1.json +++ b/references/evidence_manifests/commercial_external_validation_release.v1.json @@ -189,6 +189,24 @@ "path": "references/real_case_calibration/day_level_holdout_v3_preregistration.json", "sha256": "d7f794630d3def6f9f7b5d94c4365bb508c60a8b44228f35a2d2d4548ea9f360", "scope": "independent-label day-level holdout preregistration" + }, + { + "id": "prashna_sphuta_pyjhora_public_smoke", + "path": "references/oracle/prashna_sphuta_pyjhora_public_smoke.json", + "sha256": "d939a6abf5862bb5ebe600e09149e99042ddc46937fd585cf839f7596d3abaa2", + "scope": "public synthetic Prashna Sphuta PyJHora numeric smoke parity" + }, + { + "id": "western_jobs_iphone_calibration", + "path": "docs/benchmark/western_jobs_iphone_2007_calibration.json", + "sha256": "16dda7b6fe4f4df18254edf97b400358a78e44370bc2ca13501084df87fa1a91", + "scope": "public Western progressed-angle and paran geometry replay" + }, + { + "id": "month_nearest_negative_controls", + "path": "docs/benchmark/public_real_case_negative_controls_holdout_v2_month_nearest_20260717.json", + "sha256": "cef9e9c9dccb202ce8909d63d8fccf6c2699cf7a2c28e30b58c42e04b5e2ba2c", + "scope": "public month-nearest negative-control evidence, not tuning data" } ] } diff --git a/references/oracle/prashna_sphuta_pyjhora_public_smoke.json b/references/oracle/prashna_sphuta_pyjhora_public_smoke.json new file mode 100644 index 00000000..a9d277cc --- /dev/null +++ b/references/oracle/prashna_sphuta_pyjhora_public_smoke.json @@ -0,0 +1,42 @@ +{ + "schema_version": 1, + "case_id": "synthetic_beijing_1990_noon_lahiri_mean", + "status": "local_parity_verified_single_case", + "birth_data_policy": "public_synthetic_smoke_only", + "engine": { + "name": "PyJHora", + "version": "4.8.7", + "license_boundary": "AGPL external benchmark only; no source code vendored" + }, + "settings": { + "ayanamsa": "lahiri", + "node_mode": "mean", + "timezone": 8, + "latitude": 39.9042, + "longitude": 116.4074, + "date": "1990-01-01", + "time": "12:00:00" + }, + "outputs": { + "gulika": {"sign_index": 0, "sign": "Aries", "degree_in_sign": 21.86746645438376}, + "kunda_lagna": {"sign_index": 3, "sign": "Cancer", "degree_in_sign": 27.444477539207583}, + "tri_sphuta": {"sign_index": 10, "sign": "Aquarius", "degree_in_sign": 15.052227332937264}, + "chatur_sphuta": {"sign_index": 7, "sign": "Scorpio", "degree_in_sign": 1.8117419385885114}, + "pancha_sphuta": {"sign_index": 4, "sign": "Leo", "degree_in_sign": 26.543675779058276} + }, + "capture": { + "adapter": "PyJHora black-box calls via drik.gulika_longitude, drik.kunda_lagna and horoscope.chart.sphuta", + "captured_at": "2026-07-17", + "raw_response_available": true + }, + "local_comparison": { + "tolerance_degrees": 0.5, + "status": "pass", + "gulika_local_longitude": 22.154273227231883, + "gulika_absolute_delta_degrees": 0.286806772848123, + "tri_sphuta_absolute_delta_degrees": 0.2931885210780365, + "chatur_sphuta_absolute_delta_degrees": 0.290688097340535, + "pancha_sphuta_absolute_delta_degrees": 0.29397742371492086 + }, + "boundary": "One public synthetic daytime case passes local numeric comparison within 0.5 degrees. Nighttime, weekday, latitude, and additional-location cases remain required before verdict or production tuning can be unlocked." +} diff --git a/scripts/collect_vedastro_official_parity_raw.py b/scripts/collect_vedastro_official_parity_raw.py new file mode 100644 index 00000000..fc67ee27 --- /dev/null +++ b/scripts/collect_vedastro_official_parity_raw.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +"""Collect bounded, secret-free VedAstro HTTP raw for divisional parity.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +from typing import Any + +try: + from scripts import vedastro_service_adapter as adapter +except ModuleNotFoundError: # pragma: no cover - direct script execution + import vedastro_service_adapter as adapter + + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_PLANETS = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"] +VARGA_FIELDS = { + "D2": "PlanetHoraD2Signs", + "D4": "PlanetChaturthamshaD4Sign", + "D9": "PlanetNavamshaD9Sign", + "D10": "PlanetDashamamshaD10Sign", +} +SHADBALA_COMPONENT_FIELDS = [ + "PlanetSthanaBala", + "PlanetDigBala", + "PlanetKalaBala", + "PlanetChestaBala", + "PlanetNaisargikaBala", + "PlanetDrikBala", +] + + +def _hash(payload: Any) -> str: + raw = json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") + return hashlib.sha256(raw).hexdigest() + + +def collect_chart_core( + case: dict[str, Any], + *, + case_id: str, + planets: list[str], + output_path: Path, +) -> dict[str, Any]: + endpoint = os.environ.get("VEDASTRO_API_ENDPOINT", "").strip() + if not endpoint: + raise RuntimeError("VEDASTRO_API_ENDPOINT is not configured") + + manifest = adapter._official_full_snapshot_manifest(case, case_id) + chart_request = next(item for item in manifest["requests"] if item["section"] == "chart_core") + raw_responses: dict[str, Any] = {} + statuses: dict[str, str] = {} + scalar_responses: dict[str, Any] = {} + scalar_statuses: dict[str, str] = {} + component_responses: dict[str, Any] = {} + component_statuses: dict[str, str] = {} + retry_error_codes: list[int] = [] + attempt_count = 0 + + for planet in planets: + request_item = {**chart_request, "fanout_value": planet} + try: + payload, attempts, retries = adapter._post_official_snapshot_section(endpoint, request_item) + raw_responses[planet] = payload + statuses[planet] = adapter._payload_status(payload) + attempt_count += attempts + retry_error_codes.extend(retries) + except Exception as exc: # Preserve partial raw instead of discarding the batch. + statuses[planet] = f"{type(exc).__name__}:{str(exc)[:200]}" + + common_body = adapter._official_common_body(case) + scalar_requests = { + "shadbala": { + "section": "shadbala", + "endpoint_path": "/Calculate/AllPlanetStrength", + "method": "POST", + "headers": {"Content-Type": "application/json"}, + "body": common_body, + "calculator_name": "AllPlanetStrength", + }, + "ashtakavarga_sav": { + "section": "ashtakavarga_sav", + "endpoint_path": "/Calculate/AshtakvargaLifeMap", + "method": "POST", + "headers": {"Content-Type": "application/json"}, + "body": {"birthTime": common_body["time"], "Ayanamsa": common_body["Ayanamsa"]}, + "calculator_name": "AshtakvargaLifeMap", + }, + "ashtakavarga_bav": { + "section": "ashtakavarga_bav", + "endpoint_path": "/Calculate/BhinnashtakavargaChart", + "method": "POST", + "headers": {"Content-Type": "application/json"}, + "body": {"birthTime": common_body["time"], "Ayanamsa": common_body["Ayanamsa"]}, + "calculator_name": "BhinnashtakavargaChart", + }, + "ashtakavarga_sav_chart": { + "section": "ashtakavarga_sav_chart", + "endpoint_path": "/Calculate/SarvashtakavargaChart", + "method": "POST", + "headers": {"Content-Type": "application/json"}, + "body": {"birthTime": common_body["time"], "Ayanamsa": common_body["Ayanamsa"]}, + "calculator_name": "SarvashtakavargaChart", + }, + } + for section, request_item in scalar_requests.items(): + try: + payload, attempts, retries = adapter._post_official_snapshot_section(endpoint, request_item) + scalar_responses[section] = payload + scalar_statuses[section] = adapter._payload_status(payload) + attempt_count += attempts + retry_error_codes.extend(retries) + except Exception as exc: + scalar_statuses[section] = f"{type(exc).__name__}:{str(exc)[:200]}" + + for planet in planets: + key = f"{planet}.chesta" + request_item = { + "section": "shadbala_chesta", + "endpoint_path": "/Calculate/PlanetChestaBala", + "method": "POST", + "headers": {"Content-Type": "application/json"}, + "body": { + "planetName": {"Name": planet}, + "time": common_body["time"], + "useSpecialSunMoon": False, + "Ayanamsa": common_body["Ayanamsa"], + }, + "calculator_name": "PlanetChestaBala", + } + try: + payload, attempts, retries = adapter._post_official_snapshot_section(endpoint, request_item) + component_responses[key] = payload + component_statuses[key] = adapter._payload_status(payload) + attempt_count += attempts + retry_error_codes.extend(retries) + except Exception as exc: + component_statuses[key] = f"{type(exc).__name__}:{str(exc)[:200]}" + + settings = { + "case_id": case_id, + "birth": {key: case.get(key) for key in ("year", "month", "day", "hour", "minute", "second", "lat", "lon", "tz")}, + "ayanamsa_policy": case.get("ayanamsa_policy") or case.get("ayanamsa") or "lahiri", + "node_policy": case.get("node_policy") or case.get("node_mode") or "mean", + "endpoint_host": adapter._endpoint_host(endpoint), + "calculator": "AllPlanetData", + "planets": planets, + } + packet = { + "scope": "vedastro_official_http_divisional_parity_raw", + "status": "ok" + if statuses and scalar_statuses and component_statuses and all(value == "ok" for value in [*statuses.values(), *scalar_statuses.values(), *component_statuses.values()]) + else "partial", + "source": "vedastro_official_http", + "settings": settings, + "coverage": { + "sections": [ + *VARGA_FIELDS, + "shadbala_total", + "shadbala_components", + "ashtakavarga_bav", + "ashtakavarga_sav", + ], + "field_map": VARGA_FIELDS, + "shadbala_component_fields": SHADBALA_COMPONENT_FIELDS, + "blocked_sections": [], + }, + "fanout_statuses": dict(sorted(statuses.items())), + "scalar_statuses": dict(sorted(scalar_statuses.items())), + "component_statuses": dict(sorted(component_statuses.items())), + "attempt_count": attempt_count, + "retry_error_codes": retry_error_codes, + "raw_responses": raw_responses, + "scalar_responses": scalar_responses, + "component_responses": component_responses, + } + packet["response_hash"] = _hash({"chart_core": raw_responses, "scalar": scalar_responses, "components": component_responses}) + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(json.dumps(packet, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + return packet + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--case", choices=sorted(adapter.PARITY_CASES), default="beijing_first_use_demo") + parser.add_argument("--planets", default=",".join(DEFAULT_PLANETS)) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + planets = [item.strip() for item in args.planets.split(",") if item.strip()] + output = args.output or ROOT / "references" / "oracle" / "artifacts" / f"vedastro_{args.case}_divisional_raw.json" + report = collect_chart_core( + dict(adapter.PARITY_CASES[args.case]), + case_id=args.case, + planets=planets, + output_path=output, + ) + print(json.dumps({"status": report["status"], "output": str(output), "response_hash": report["response_hash"], "fanout_statuses": report["fanout_statuses"]}, ensure_ascii=False, indent=2, sort_keys=True)) + return 0 if report["status"] == "ok" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/diagnose_external_engine_adapters.py b/scripts/diagnose_external_engine_adapters.py index bd24e6bf..35707036 100644 --- a/scripts/diagnose_external_engine_adapters.py +++ b/scripts/diagnose_external_engine_adapters.py @@ -25,25 +25,72 @@ REQUIRED_PARITY_OUTPUTS = ["D1", "D9", "D10", "D2", "D4", "Vimshottari", "Shadba def _public_pyjhora_parity_manifest() -> dict: path = Path("references/oracle/pyjhora_same_chart_parity_public_smoke_manifest.json") try: - return json.loads(path.read_text(encoding="utf-8")) + baseline = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError): return {"status": "not_available", "tested": False} + supplemental_path = Path("references/oracle/pyjhora_extended_parity_public_smoke_manifest.json") + try: + supplemental = json.loads(supplemental_path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + supplemental = None + + result = dict(baseline) + supplements = [supplemental] if supplemental and supplemental.get("tested") else [] + covered = set(baseline.get("covered_outputs", [])) + sample_counts = {name: baseline.get("sample_count", 0) for name in covered} + partial_sample_counts = {} + source_reports = [baseline["source_report"]] if baseline.get("source_report") else [] + for item in supplements: + item_count = item.get("sample_count", 0) + for name in item.get("covered_outputs", []): + covered.add(name) + sample_counts[name] = max(sample_counts.get(name, 0), item_count) + for name in item.get("partial_outputs", []): + partial_sample_counts[name] = max(partial_sample_counts.get(name, 0), item_count) + if item.get("source_report"): + source_reports.append(item["source_report"]) + + result["covered_outputs"] = [name for name in REQUIRED_PARITY_OUTPUTS if name in covered] + result["missing_required_outputs"] = [name for name in REQUIRED_PARITY_OUTPUTS if name not in covered] + result["output_sample_counts"] = sample_counts + result["partial_output_sample_counts"] = partial_sample_counts + result["source_reports"] = source_reports + result["supplemental_verifications"] = supplements + result["boundary"] = ( + "Partial PyJHora verification with per-output sample counts. D2/D4/Ashtakavarga use a one-chart " + "supplemental replay; Shadbala absolute values remain mismatched. This is not JHora desktop, " + "VedAstro, jyotishganit, or predictive validation." + ) + return result + def _same_chart_parity_contract(engines: dict) -> dict: pyjhora_public = _public_pyjhora_parity_manifest() + replay_manifest = validate_parity_replay_manifest("references/oracle/three_engine_parity_replay_manifest.json") + replay_covers_all_engines = bool( + replay_manifest.get("tested") and not replay_manifest.get("missing_engines") + ) engine_states = {} for name, engine in engines.items(): available = engine["status"] == "available" engine_states[name] = { "available": available, - "tested": bool(name == "PyJHora/JHora" and pyjhora_public.get("tested")), + "tested": bool( + replay_covers_all_engines + or (name == "PyJHora/JHora" and pyjhora_public.get("tested")) + ), "blocked": not available, "blocking_reason": "" if available else engine["status"], } - replay_manifest = validate_parity_replay_manifest("references/oracle/three_engine_parity_replay_manifest.json") + if not all(state["available"] for state in engine_states.values()): + contract_status = "blocked" + elif not all(state["tested"] for state in engine_states.values()): + contract_status = "partial" + else: + contract_status = replay_manifest.get("status", "partial") return { - "status": "ready" if all(state["available"] for state in engine_states.values()) else "blocked", + "status": contract_status, "required_outputs": REQUIRED_PARITY_OUTPUTS, "expected_oracle_fields": { "VedAstro": [ @@ -71,7 +118,7 @@ def _same_chart_parity_contract(engines: dict) -> dict: "engine_states": engine_states, "replay_manifest": replay_manifest, "partial_verifications": {"PyJHora/JHora": pyjhora_public}, - "boundary": "This is a parity contract. Public PyJHora partial verification does not close missing outputs or other engine raw-oracle requirements.", + "boundary": "Availability, executed raw coverage, and numerical parity are separate states; mismatch is not ready.", } diff --git a/scripts/gulika.py b/scripts/gulika.py index f92f439b..0ca827ea 100644 --- a/scripts/gulika.py +++ b/scripts/gulika.py @@ -25,6 +25,18 @@ GHATIKA_END = { 6: {"day": 26, "night": 10}, } +# Saturn's zero-based share in eight equal day/night parts. Python weekday: +# Monday=0. This is the variant used by PyJHora's public black-box oracle. +SATURN_PART_START = { + 0: {"day": 5, "night": 1}, + 1: {"day": 4, "night": 0}, + 2: {"day": 3, "night": 6}, + 3: {"day": 2, "night": 5}, + 4: {"day": 1, "night": 4}, + 5: {"day": 0, "night": 3}, + 6: {"day": 6, "night": 2}, +} + def _sidereal_ascendant(jd_ut: float, lat: float, lon: float) -> float: swe.set_sid_mode(swe.SIDM_LAHIRI) @@ -38,17 +50,27 @@ def calculate_gulika( lat: float, lon: float, tz: float, + method: str = "saturn_part_start", ) -> dict[str, Any]: """Return Gulika from local moment/location using Swiss sunrise and sunset.""" daynight = determine_daytime(moment, lat=lat, lon=lon, tz=tz) is_day = bool(daynight["is_daytime"]) period = "day" if is_day else "night" - ghatika_end = GHATIKA_END[moment.weekday()][period] start_jd = daynight["sunrise_jd_ut"] if is_day else daynight["sunset_jd_ut"] end_jd = daynight["sunset_jd_ut"] if is_day else daynight["sunrise_jd_ut"] + 1.0 if end_jd <= start_jd: end_jd += 1.0 - segment_jd = start_jd + (end_jd - start_jd) * (ghatika_end / 30.0) + if method == "saturn_part_start": + part_index = SATURN_PART_START[moment.weekday()][period] + segment_fraction = part_index / 8.0 + ghatika_end = None + elif method == "legacy_ghatika_end": + part_index = None + ghatika_end = GHATIKA_END[moment.weekday()][period] + segment_fraction = ghatika_end / 30.0 + else: + raise ValueError("method must be saturn_part_start or legacy_ghatika_end") + segment_jd = start_jd + (end_jd - start_jd) * segment_fraction longitude = _sidereal_ascendant(segment_jd, float(lat), float(lon)) return { "scope": "gulika_prasna_marga", @@ -58,10 +80,13 @@ def calculate_gulika( "degree_in_sign": round(longitude % 30, 6), "period": period, "weekday": moment.weekday(), + "method": method, + "part_index": part_index, + "segment_fraction": segment_fraction, "ghatika_end": ghatika_end, "segment_jd_ut": segment_jd, "daynight_evidence": daynight, "ayanamsa": "lahiri", "rule_source": "references/prashna-complete-guide.md#3.5", - "boundary": "Formula is implemented from the local classical guide; external JHora/PyJHora numeric parity remains required before enabling Sphuta or verdict layers.", + "boundary": "PyJHora-aligned Saturn-part-start variant. Numeric parity is evidence only and does not enable Prashna verdict layers.", } diff --git a/scripts/three_engine_parity_replay_validator.py b/scripts/three_engine_parity_replay_validator.py index 39081435..d26af4f7 100644 --- a/scripts/three_engine_parity_replay_validator.py +++ b/scripts/three_engine_parity_replay_validator.py @@ -12,6 +12,17 @@ from typing import Any REQUIRED_ENGINES = {"VedAstro", "PyJHora_JHora", "jyotishganit"} REQUIRED_ROW_FIELDS = {"section", "field", "local_value", "oracle_values", "status"} +REQUIRED_HIGH_RIGOR_SECTIONS = { + "D1", + "D2", + "D4", + "D9", + "D10", + "ashtakavarga_bav", + "ashtakavarga_sav", + "shadbala_total", + "shadbala_components", +} VALID_ROW_STATUSES = {"match", "mismatch", "blocked", "not_comparable"} RAW_VERIFIED_STATUSES = {"verified", "official_verified", "imported"} @@ -79,6 +90,12 @@ def validate_manifest(path: str | Path) -> dict[str, Any]: for row in rows: if isinstance(row, dict) and row.get("status") in counts: counts[row["status"]] += 1 + covered_sections = { + str(row.get("section")) + for row in rows + if isinstance(row, dict) and row.get("status") in {"match", "mismatch"} + } + missing_high_rigor_sections = sorted(REQUIRED_HIGH_RIGOR_SECTIONS - covered_sections) if errors: status = "invalid" @@ -92,6 +109,9 @@ def validate_manifest(path: str | Path) -> dict[str, Any]: elif counts["blocked"]: status = "partial" blocked_reason = "some_comparison_rows_blocked" + elif missing_high_rigor_sections: + status = "partial" + blocked_reason = "missing_high_rigor_sections" else: status = "pass" blocked_reason = None @@ -110,6 +130,8 @@ def validate_manifest(path: str | Path) -> dict[str, Any]: "mismatch_count": counts["mismatch"], "blocked_row_count": counts["blocked"], "not_comparable_count": counts["not_comparable"], + "covered_sections": sorted(covered_sections), + "missing_high_rigor_sections": missing_high_rigor_sections, "blocked_reason": blocked_reason, "errors": errors, "runtime_boundary": manifest.get("runtime_boundary", ""), diff --git a/scripts/western_real_case_calibration.py b/scripts/western_real_case_calibration.py new file mode 100644 index 00000000..f59bf8de --- /dev/null +++ b/scripts/western_real_case_calibration.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Replay Western timing geometry for an existing public real-case manifest.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +try: + from western_timing_engine import calculate_parans_status, calculate_secondary_progressions +except ImportError: + from scripts.western_timing_engine import calculate_parans_status, calculate_secondary_progressions + + +DEFAULT_MANIFEST = Path("references/real_case_calibration/replay_manifest.json") + + +def build_case(case_id: str, manifest_path: Path = DEFAULT_MANIFEST) -> dict: + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + case = next((row for row in manifest["cases"] if row["case_id"] == case_id), None) + if case is None: + return {"status": "blocked", "reason": "case_id_not_found", "case_id": case_id} + event = case["event_outcomes"][0] + subject = case["subject"] + birth = { + "year": subject["year"], "month": subject["month"], "day": subject["day"], + "hour": subject["hour"], "minute": subject["minute"], "second": 0, + "latitude": subject["lat"], "longitude": subject["lon"], "timezone": subject["tz"], + } + progressions = calculate_secondary_progressions(target_date=event["event_date"], **birth) + parans = calculate_parans_status(target_date=event["event_date"], **birth) + return { + "scope": "western_real_case_calibration", + "status": "calculated_not_predictive_validation", + "case_id": case_id, + "subject": subject["name"], + "event": { + "date": event["event_date"], + "type": event["event_type"], + "source": event["source"], + }, + "birth_source": subject["birth_source"], + "layers": { + "secondary_progressions": progressions, + "parans": parans, + }, + "summary": { + "progressed_aspect_count": len(progressions["aspects"]), + "paran_event_count": parans["event_count"], + "paran_pair_count": len(parans["paran_pairs_within_4_minutes"]), + }, + "boundary": "Known-event geometry replay only. One positive case cannot establish specificity, false-positive rate, or predictive accuracy.", + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--case-id", default="jobs_iphone_2007") + parser.add_argument("--manifest", type=Path, default=DEFAULT_MANIFEST) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + report = build_case(args.case_id, args.manifest) + rendered = json.dumps(report, ensure_ascii=False, indent=2) + "\n" + if args.output: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(rendered, encoding="utf-8") + print(rendered, end="") + return 0 if report["status"] != "blocked" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/western_timing_engine.py b/scripts/western_timing_engine.py index 82dc0e48..0bdcdac5 100644 --- a/scripts/western_timing_engine.py +++ b/scripts/western_timing_engine.py @@ -132,6 +132,27 @@ def _progressed_planets(progressed_jd: float) -> dict[str, dict[str, Any]]: return planets +def _quotidian_progressed_angles(progressed_jd: float, birth: dict[str, Any]) -> dict[str, Any]: + local = _jd_to_local(progressed_jd, birth["timezone"]) + progressed_birth = { + **birth, + "year": local.year, + "month": local.month, + "day": local.day, + "hour": local.hour, + "minute": local.minute, + "second": local.second, + } + chart = build_tropical_natal_chart(**progressed_birth) + return { + "status": "used", + "method": "secondary_quotidian_progressed_date_same_location", + "progressed_local_time": local.isoformat(), + "angles": chart["natal"]["angles"], + "boundary": "Quotidian progressed-date angles; Naibod and solar-arc angle variants are separate methods.", + } + + def calculate_secondary_progressions(*, target_date: str, **birth: Any) -> dict[str, Any]: """Calculate progressed planets using one ephemeris day per tropical year.""" natal_chart = build_tropical_natal_chart(**birth) @@ -140,6 +161,7 @@ def calculate_secondary_progressions(*, target_date: str, **birth: Any) -> dict[ elapsed_years = (target_jd - birth_jd) / 365.242189 progressed_jd = birth_jd + elapsed_years planets = _progressed_planets(progressed_jd) + progressed_angles = _quotidian_progressed_angles(progressed_jd, birth) natal_points = { **natal_chart["natal"]["planets"], "ascendant": natal_chart["natal"]["angles"]["ascendant"], @@ -155,8 +177,9 @@ def calculate_secondary_progressions(*, target_date: str, **birth: Any) -> dict[ "progressed_julian_day_ut": round(progressed_jd, 8), "natal_sun_longitude": natal_chart["natal"]["planets"]["sun"]["longitude"], "progressed_planets": planets, + "progressed_angles": progressed_angles, "aspects": _cross_aspects(planets, natal_points), - "boundary": "Progressed planets only. Progressed angles, lunar phases, stations, duration, and interpretation remain separate audited layers.", + "boundary": "Progressed planets plus explicitly selected quotidian angles. Lunar phases, stations, duration, and interpretation remain separate audited layers.", } @@ -391,11 +414,56 @@ def calculate_transit_duration_scan(*, start_date: str, end_date: str, max_days: def calculate_parans_status(*, target_date: str | None = None, **birth: Any) -> dict[str, Any]: + if not target_date: + return {"technique": "parans", "status": "blocked", "reason": "target_date_required"} + start_jd, target_local = _target_jd(target_date, birth["timezone"]) + geopos = (float(birth["longitude"]), float(birth["latitude"]), 0.0) + modes = { + "rise": swe.CALC_RISE, + "upper_culmination": swe.CALC_MTRANSIT, + "set": swe.CALC_SET, + } + events: list[dict[str, Any]] = [] + for planet, planet_id in _PLANETS.items(): + for angle, mode in modes.items(): + try: + status, values = swe.rise_trans(start_jd, planet_id, mode, geopos, 0.0, 15.0, swe.FLG_SWIEPH) + except (swe.Error, TypeError, ValueError): + continue + if status != 0: + continue + event_local = _jd_to_local(float(values[0]), birth["timezone"]) + if event_local.date() != target_local.date(): + continue + events.append({ + "planet": planet, + "angle": angle, + "julian_day_ut": round(float(values[0]), 8), + "local_time": event_local.isoformat(), + }) + events.sort(key=lambda row: (row["julian_day_ut"], row["planet"], row["angle"])) + pairs: list[dict[str, Any]] = [] + for index, first in enumerate(events): + for second in events[index + 1:]: + delta_minutes = (second["julian_day_ut"] - first["julian_day_ut"]) * 1440.0 + if delta_minutes > 4.0: + break + if first["planet"] == second["planet"]: + continue + pairs.append({ + "first": {key: first[key] for key in ("planet", "angle", "local_time")}, + "second": {key: second[key] for key in ("planet", "angle", "local_time")}, + "separation_minutes": round(delta_minutes, 4), + }) return { "technique": "parans", - "status": "blocked", + "status": "used", "target_date": target_date, - "reason": "Parans need a dedicated rising/setting/culminating engine and latitude-aware event solver; not yet implemented in this repository.", + "method": "Swiss Ephemeris rise_trans latitude-aware angular events", + "event_count": len(events), + "events": events, + "paran_pairs_within_4_minutes": pairs, + "boundary": "Geometric rise/culmination/set simultaneity only; no interpretation or predictive claim is inferred.", } diff --git a/tests/test_collect_vedastro_official_parity_raw.py b/tests/test_collect_vedastro_official_parity_raw.py new file mode 100644 index 00000000..1cc2c261 --- /dev/null +++ b/tests/test_collect_vedastro_official_parity_raw.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +import json + +from scripts import collect_vedastro_official_parity_raw as collector + + +def test_collect_chart_core_writes_secret_free_hashed_packet(tmp_path, monkeypatch) -> None: + monkeypatch.setenv("VEDASTRO_API_ENDPOINT", "https://api.vedastro.org/api") + monkeypatch.setenv("VEDASTRO_API_KEY", "must-not-leak") + + def fake_post(endpoint, request_item): + if request_item["section"] == "shadbala_chesta": + return {"Status": "Pass", "Payload": {"PlanetChestaBala": "12.5"}}, 1, [] + if request_item["section"] == "shadbala": + return {"Status": "Pass", "Payload": {"AllPlanetStrength": "(400, Sun)"}}, 1, [] + if request_item["section"] == "ashtakavarga_sav": + return {"Status": "Pass", "Payload": {"AshtakvargaLifeMap": {"TotalBindus": 337}}}, 1, [] + if request_item["section"] == "ashtakavarga_bav": + return {"Status": "Pass", "Payload": {"BhinnashtakavargaChart": {"Sun": {"Rows": [1] * 12}}}}, 1, [] + if request_item["section"] == "ashtakavarga_sav_chart": + return {"Status": "Pass", "Payload": {"SarvashtakavargaChart": {"Sarvashtakavarga": {"Rows": [28] * 12}}}}, 1, [] + planet = request_item["fanout_value"] + return { + "Status": "Pass", + "Payload": { + "AllPlanetData": { + "PlanetHoraD2Signs": {"Name": f"{planet}-D2"}, + "PlanetChaturthamshaD4Sign": {"Name": f"{planet}-D4"}, + "PlanetNavamshaD9Sign": {"Name": f"{planet}-D9"}, + "PlanetDashamamshaD10Sign": {"Name": f"{planet}-D10"}, + } + }, + }, 1, [] + + monkeypatch.setattr(collector.adapter, "_post_official_snapshot_section", fake_post) + output = tmp_path / "packet.json" + report = collector.collect_chart_core( + collector.adapter.PARITY_CASES["beijing_first_use_demo"], + case_id="beijing_first_use_demo", + planets=["Sun", "Moon"], + output_path=output, + ) + + stored = json.loads(output.read_text(encoding="utf-8")) + assert report["status"] == "ok" + assert stored["coverage"]["sections"] == [ + "D2", "D4", "D9", "D10", "shadbala_total", "shadbala_components", "ashtakavarga_bav", "ashtakavarga_sav" + ] + assert stored["fanout_statuses"] == {"Moon": "ok", "Sun": "ok"} + assert stored["scalar_statuses"] == { + "ashtakavarga_bav": "ok", + "ashtakavarga_sav": "ok", + "ashtakavarga_sav_chart": "ok", + "shadbala": "ok", + } + assert stored["component_statuses"] == {"Moon.chesta": "ok", "Sun.chesta": "ok"} + assert len(stored["response_hash"]) == 64 + assert "must-not-leak" not in output.read_text(encoding="utf-8") diff --git a/tests/test_external_engine_adapter_diagnostics.py b/tests/test_external_engine_adapter_diagnostics.py index a35079bb..34fe158f 100644 --- a/tests/test_external_engine_adapter_diagnostics.py +++ b/tests/test_external_engine_adapter_diagnostics.py @@ -31,15 +31,30 @@ def test_external_engine_adapter_diagnostics_aggregates_three_engines() -> None: assert report["engines"]["PyJHora/JHora"]["license_boundary"].startswith("AGPL external benchmark") assert report["engines"]["jyotishganit"]["license"] == "MIT" contract = report["same_chart_parity_contract"] - assert contract["status"] in {"ready", "blocked"} + expected_contract_status = ( + "mismatch" + if all(state["available"] for state in contract["engine_states"].values()) + else "blocked" + ) + assert contract["status"] == expected_contract_status assert contract["required_outputs"] == ["D1", "D9", "D10", "D2", "D4", "Vimshottari", "Shadbala", "Ashtakavarga"] assert "official_raw_response" in contract["expected_oracle_fields"]["VedAstro"] assert "raw_output_path" in contract["expected_oracle_fields"]["PyJHora/JHora"] assert contract["engine_states"]["jyotishganit"]["available"] is True + assert contract["engine_states"]["jyotishganit"]["tested"] is True + assert contract["engine_states"]["VedAstro"]["tested"] is True assert contract["engine_states"]["PyJHora/JHora"]["tested"] is True pyjhora = contract["partial_verifications"]["PyJHora/JHora"] assert pyjhora["status"] == "partial_verified" - assert pyjhora["missing_required_outputs"] == ["D2", "D4", "Shadbala", "Ashtakavarga"] - assert contract["replay_manifest"]["tested"] is False - assert contract["replay_manifest"]["blocked_reason"] == "no_same_chart_oracle_rows_imported" + assert pyjhora["missing_required_outputs"] == ["Shadbala"] + assert {"D2", "D4", "Ashtakavarga"} <= set(pyjhora["covered_outputs"]) + assert pyjhora["output_sample_counts"]["D2"] == 1 + assert pyjhora["output_sample_counts"]["D4"] == 1 + assert pyjhora["output_sample_counts"]["Ashtakavarga"] == 1 + assert pyjhora["output_sample_counts"]["D1"] == 10 + assert pyjhora["supplemental_verifications"][0]["partial_outputs"] == ["Shadbala"] + assert contract["replay_manifest"]["tested"] is True + assert contract["replay_manifest"]["status"] == "mismatch" + assert contract["replay_manifest"]["blocked_reason"] is None + assert contract["replay_manifest"]["missing_high_rigor_sections"] == [] assert report["status"] in {"complete", "partial"} diff --git a/tests/test_gulika.py b/tests/test_gulika.py index 0fa09021..a03a56d2 100644 --- a/tests/test_gulika.py +++ b/tests/test_gulika.py @@ -1,11 +1,15 @@ from datetime import datetime -from scripts.gulika import GHATIKA_END, calculate_gulika +import json +from pathlib import Path + +from scripts.gulika import GHATIKA_END, SATURN_PART_START, calculate_gulika def test_gulika_uses_prasna_marga_weekday_table() -> None: assert GHATIKA_END[6] == {"day": 26, "night": 10} assert GHATIKA_END[0] == {"day": 22, "night": 6} + assert SATURN_PART_START[0] == {"day": 5, "night": 1} def test_gulika_returns_sidereal_segment_ascendant_with_audit_trace() -> None: @@ -13,5 +17,18 @@ def test_gulika_returns_sidereal_segment_ascendant_with_audit_trace() -> None: assert result["status"] == "partial" assert 0 <= result["longitude"] < 360 - assert result["ghatika_end"] in range(0, 31) + assert result["part_index"] in range(0, 8) + assert result["ghatika_end"] is None assert result["rule_source"].endswith("#3.5") + + +def test_gulika_matches_public_pyjhora_smoke_oracle() -> None: + packet = json.loads( + Path("references/oracle/prashna_sphuta_pyjhora_public_smoke.json").read_text(encoding="utf-8") + ) + expected = packet["outputs"]["gulika"] + result = calculate_gulika(datetime(1990, 1, 1, 12, 0), lat=39.9042, lon=116.4074, tz=8) + + assert result["method"] == "saturn_part_start" + assert result["sign_idx"] == expected["sign_index"] + assert abs(result["degree_in_sign"] - expected["degree_in_sign"]) <= 0.5 diff --git a/tests/test_prashna_sphuta_pyjhora_oracle.py b/tests/test_prashna_sphuta_pyjhora_oracle.py new file mode 100644 index 00000000..90d2c5d1 --- /dev/null +++ b/tests/test_prashna_sphuta_pyjhora_oracle.py @@ -0,0 +1,24 @@ +import json +from pathlib import Path + + +def test_public_pyjhora_prashna_sphuta_packet_is_numeric_and_guarded() -> None: + packet = json.loads( + Path("references/oracle/prashna_sphuta_pyjhora_public_smoke.json").read_text(encoding="utf-8") + ) + + assert packet["status"] == "local_parity_verified_single_case" + assert packet["birth_data_policy"] == "public_synthetic_smoke_only" + assert packet["capture"]["raw_response_available"] is True + assert packet["engine"]["license_boundary"].startswith("AGPL external benchmark") + assert set(packet["outputs"]) == { + "gulika", "kunda_lagna", "tri_sphuta", "chatur_sphuta", "pancha_sphuta" + } + for row in packet["outputs"].values(): + assert 0 <= row["sign_index"] < 12 + assert 0 <= row["degree_in_sign"] < 30 + assert packet["local_comparison"]["status"] == "pass" + assert max( + value for key, value in packet["local_comparison"].items() if key.endswith("delta_degrees") + ) <= packet["local_comparison"]["tolerance_degrees"] + assert "remain required" in packet["boundary"] diff --git a/tests/test_three_engine_parity_replay_validator.py b/tests/test_three_engine_parity_replay_validator.py index 1f27d9fc..70605e80 100644 --- a/tests/test_three_engine_parity_replay_validator.py +++ b/tests/test_three_engine_parity_replay_validator.py @@ -12,14 +12,16 @@ from scripts.three_engine_parity_replay_validator import validate_manifest ROOT = Path(__file__).resolve().parents[1] -def test_three_engine_parity_manifest_passes_d1_with_vedastro_and_pyjhora_raw() -> None: +def test_three_engine_parity_manifest_exposes_raw_coverage_and_formula_mismatches() -> None: result = validate_manifest(ROOT / "references/oracle/three_engine_parity_replay_manifest.json") - assert result["status"] == "pass" - assert result["comparison_row_count"] == 15 - assert result["match_count"] == 14 + assert result["status"] == "mismatch" + assert result["comparison_row_count"] == 92 + assert result["match_count"] == 32 + assert result["mismatch_count"] == 60 assert result["tested"] is True assert result["blocked_reason"] is None + assert result["missing_high_rigor_sections"] == [] def test_three_engine_parity_validator_accepts_one_same_chart_row(tmp_path: Path) -> None: @@ -54,7 +56,8 @@ def test_three_engine_parity_validator_accepts_one_same_chart_row(tmp_path: Path result = validate_manifest(path) - assert result["status"] == "pass" + assert result["status"] == "partial" + assert result["blocked_reason"] == "missing_high_rigor_sections" assert result["tested"] is True assert result["comparison_row_count"] == 1 assert result["match_count"] == 1 diff --git a/tests/test_western_real_case_calibration.py b/tests/test_western_real_case_calibration.py new file mode 100644 index 00000000..3257dceb --- /dev/null +++ b/tests/test_western_real_case_calibration.py @@ -0,0 +1,17 @@ +from scripts.western_real_case_calibration import build_case + + +def test_jobs_public_case_replays_progressed_angles_and_parans() -> None: + report = build_case("jobs_iphone_2007") + + assert report["status"] == "calculated_not_predictive_validation" + assert report["event"]["source"]["source_grade"] == "primary" + assert report["birth_source"]["time_accuracy_rating"] == "AA" + assert report["layers"]["secondary_progressions"]["progressed_angles"]["status"] == "used" + assert report["layers"]["parans"]["status"] == "used" + assert report["summary"]["paran_event_count"] > 0 + assert "cannot establish" in report["boundary"] + + +def test_unknown_case_is_blocked() -> None: + assert build_case("missing")["reason"] == "case_id_not_found" diff --git a/tests/test_western_timing_engine.py b/tests/test_western_timing_engine.py index 7510c6eb..0196dab9 100644 --- a/tests/test_western_timing_engine.py +++ b/tests/test_western_timing_engine.py @@ -97,10 +97,12 @@ def test_transit_duration_scan_groups_daily_windows() -> None: assert isinstance(scan["windows"], list) -def test_parans_are_explicitly_blocked_until_solver_exists() -> None: +def test_parans_emit_latitude_aware_angular_events() -> None: parans = calculate_parans_status(**_BIRTH, target_date="2026-07-09") assert parans["technique"] == "parans" - assert parans["status"] == "blocked" + assert parans["status"] == "used" + assert parans["method"].startswith("Swiss Ephemeris rise_trans") + assert parans["event_count"] > 0 def test_timing_builder_can_emit_advanced_layers() -> None: @@ -122,3 +124,10 @@ def test_timing_builder_can_emit_advanced_layers() -> None: "transit_duration_scan", "parans", } <= set(timing) + progressed = build_timing_techniques(**_BIRTH, secondary_progression_date="2026-07-09")["secondary_progressions"] + assert progressed["progressed_angles"]["status"] == "used" + assert set(progressed["progressed_angles"]["angles"]) == {"ascendant", "mc", "descendant", "ic"} + parans = timing["parans"] + assert parans["status"] == "used" + assert parans["event_count"] > 0 + assert all(row["separation_minutes"] <= 4 for row in parans["paran_pairs_within_4_minutes"])