feat: import oracle collection and calibration parity
This commit is contained in:
+3766
File diff suppressed because it is too large
Load Diff
@@ -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."
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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())
|
||||
@@ -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.",
|
||||
}
|
||||
|
||||
|
||||
|
||||
+28
-3
@@ -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.",
|
||||
}
|
||||
|
||||
@@ -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", ""),
|
||||
|
||||
@@ -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())
|
||||
@@ -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.",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
@@ -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"}
|
||||
|
||||
+19
-2
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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"])
|
||||
|
||||
Reference in New Issue
Block a user