@cosmosos/mcp-ephemeris 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -3
- package/dist/index.js +1262 -115
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -242,8 +242,8 @@ function helioToGeoLonLat(p, e) {
|
|
|
242
242
|
lat: Math.atan2(zg, Math.hypot(xg, yg)) * D
|
|
243
243
|
};
|
|
244
244
|
}
|
|
245
|
-
function
|
|
246
|
-
if (!(key in PE)) return
|
|
245
|
+
function angularSpeed(key, JD) {
|
|
246
|
+
if (!(key in PE)) return void 0;
|
|
247
247
|
const T1 = (JD - 0.5 - 2451545) / 36525;
|
|
248
248
|
const T2 = (JD + 0.5 - 2451545) / 36525;
|
|
249
249
|
const e1 = helioPos3D("earth", T1), e2 = helioPos3D("earth", T2);
|
|
@@ -253,7 +253,11 @@ function isRetrograde(key, JD) {
|
|
|
253
253
|
let diff = g2 - g1;
|
|
254
254
|
if (diff > 180) diff -= 360;
|
|
255
255
|
if (diff < -180) diff += 360;
|
|
256
|
-
return diff
|
|
256
|
+
return diff;
|
|
257
|
+
}
|
|
258
|
+
function isRetrograde(key, JD) {
|
|
259
|
+
const v = angularSpeed(key, JD);
|
|
260
|
+
return v !== void 0 && v < 0;
|
|
257
261
|
}
|
|
258
262
|
|
|
259
263
|
// ../ephemeris/src/solar-lunar.ts
|
|
@@ -406,8 +410,18 @@ function placidusHouses(RAMC, lat, ob) {
|
|
|
406
410
|
c[5] = n360(c[11] + 180);
|
|
407
411
|
c[7] = n360(c[1] + 180);
|
|
408
412
|
c[8] = n360(c[2] + 180);
|
|
413
|
+
if (!cuspsInZodiacalOrder(c)) return equalHouses(asc);
|
|
409
414
|
return c;
|
|
410
415
|
}
|
|
416
|
+
function cuspsInZodiacalOrder(c) {
|
|
417
|
+
let sum = 0;
|
|
418
|
+
for (let i = 0; i < 12; i++) {
|
|
419
|
+
const arc = n360(c[(i + 1) % 12] - c[i]);
|
|
420
|
+
if (arc <= 0 || arc >= 180) return false;
|
|
421
|
+
sum += arc;
|
|
422
|
+
}
|
|
423
|
+
return Math.abs(sum - 360) < 1e-6;
|
|
424
|
+
}
|
|
411
425
|
function equalHouses(asc) {
|
|
412
426
|
return Array.from({ length: 12 }, (_, i) => n360(asc + i * 30));
|
|
413
427
|
}
|
|
@@ -415,8 +429,32 @@ function wholeSignHouses(asc) {
|
|
|
415
429
|
const base = Math.floor(asc / 30) * 30;
|
|
416
430
|
return Array.from({ length: 12 }, (_, i) => n360(base + i * 30));
|
|
417
431
|
}
|
|
418
|
-
|
|
419
|
-
|
|
432
|
+
var _houseSubstitutionWarned = /* @__PURE__ */ new Set();
|
|
433
|
+
function warnHouseSubstitutionOnce(system) {
|
|
434
|
+
if (_houseSubstitutionWarned.has(system)) return;
|
|
435
|
+
_houseSubstitutionWarned.add(system);
|
|
436
|
+
console.warn(
|
|
437
|
+
`[ephemeris/houses] Syst\xE8me "${system}" non disponible sur le moteur de secours AstraCore \u2014 repli honn\xEAte sur Placidus (system renvoy\xE9 = "placidus"). Le vrai calcul n\xE9cessite Swiss Ephemeris.`
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
function porphyryHouses(asc, mc) {
|
|
441
|
+
const ic = n360(mc + 180);
|
|
442
|
+
const dsc = n360(asc + 180);
|
|
443
|
+
const c = new Array(12);
|
|
444
|
+
c[0] = asc;
|
|
445
|
+
c[3] = ic;
|
|
446
|
+
c[6] = dsc;
|
|
447
|
+
c[9] = mc;
|
|
448
|
+
const tris = (from, to, i1, i2) => {
|
|
449
|
+
const arc = n360(to - from);
|
|
450
|
+
c[i1] = n360(from + arc / 3);
|
|
451
|
+
c[i2] = n360(from + 2 * arc / 3);
|
|
452
|
+
};
|
|
453
|
+
tris(asc, ic, 1, 2);
|
|
454
|
+
tris(ic, dsc, 4, 5);
|
|
455
|
+
tris(dsc, mc, 7, 8);
|
|
456
|
+
tris(mc, asc, 10, 11);
|
|
457
|
+
return c;
|
|
420
458
|
}
|
|
421
459
|
function calculateHousesByCoords(system, JD, lat, lng) {
|
|
422
460
|
const T = (JD - 2451545) / 36525;
|
|
@@ -425,6 +463,7 @@ function calculateHousesByCoords(system, JD, lat, lng) {
|
|
|
425
463
|
const asc = calcASC(RA, lat, ob);
|
|
426
464
|
const mc = calcMC(RA, ob);
|
|
427
465
|
let cusps;
|
|
466
|
+
let effectiveSystem = system;
|
|
428
467
|
switch (system) {
|
|
429
468
|
case "equal":
|
|
430
469
|
cusps = equalHouses(asc);
|
|
@@ -432,14 +471,24 @@ function calculateHousesByCoords(system, JD, lat, lng) {
|
|
|
432
471
|
case "whole_sign":
|
|
433
472
|
cusps = wholeSignHouses(asc);
|
|
434
473
|
break;
|
|
474
|
+
// HOUSE-SYSTEMS-EXTEND-V1 : Porphyry = trisection pure des quadrants
|
|
475
|
+
// ASC→IC→DSC→MC. Exact sans trigonométrie sphérique, donc calculable
|
|
476
|
+
// aussi en AstraCore (pas de substitution).
|
|
477
|
+
case "porphyry":
|
|
478
|
+
cusps = porphyryHouses(asc, mc);
|
|
479
|
+
break;
|
|
435
480
|
case "koch":
|
|
436
|
-
|
|
481
|
+
case "regiomontanus":
|
|
482
|
+
case "campanus":
|
|
483
|
+
warnHouseSubstitutionOnce(system);
|
|
484
|
+
cusps = placidusHouses(RA, lat, ob);
|
|
485
|
+
effectiveSystem = "placidus";
|
|
437
486
|
break;
|
|
438
487
|
case "placidus":
|
|
439
488
|
default:
|
|
440
489
|
cusps = placidusHouses(RA, lat, ob);
|
|
441
490
|
}
|
|
442
|
-
return { cusps, asc, mc, vertex: null, system };
|
|
491
|
+
return { cusps, asc, mc, vertex: null, system: effectiveSystem };
|
|
443
492
|
}
|
|
444
493
|
function houseOfLongitude(lon, cusps) {
|
|
445
494
|
for (let i = 0; i < 12; i++) {
|
|
@@ -531,41 +580,112 @@ function computeHermeticLots(asc, planets, isNight, fortune) {
|
|
|
531
580
|
}
|
|
532
581
|
|
|
533
582
|
// ../ephemeris/src/astro-engine.ts
|
|
583
|
+
function deltaTSeconds(JD) {
|
|
584
|
+
const year = 2e3 + (JD - 2451545) / 365.25;
|
|
585
|
+
const longTerm = (y) => {
|
|
586
|
+
const u = (y - 1820) / 100;
|
|
587
|
+
return -20 + 32 * u * u;
|
|
588
|
+
};
|
|
589
|
+
if (year < 1800 || year >= 2150) return longTerm(year);
|
|
590
|
+
let t;
|
|
591
|
+
if (year < 1860) {
|
|
592
|
+
t = year - 1800;
|
|
593
|
+
return 13.72 - 0.332447 * t + 68612e-7 * t ** 2 + 41116e-7 * t ** 3 - 37436e-8 * t ** 4 + 121272e-10 * t ** 5 - 1699e-10 * t ** 6 + 875e-12 * t ** 7;
|
|
594
|
+
}
|
|
595
|
+
if (year < 1900) {
|
|
596
|
+
t = year - 1860;
|
|
597
|
+
return 7.62 + 0.5737 * t - 0.251754 * t ** 2 + 0.01680668 * t ** 3 - 4473624e-10 * t ** 4 + t ** 5 / 233174;
|
|
598
|
+
}
|
|
599
|
+
if (year < 1920) {
|
|
600
|
+
t = year - 1900;
|
|
601
|
+
return -2.79 + 1.494119 * t - 0.0598939 * t ** 2 + 61966e-7 * t ** 3 - 197e-6 * t ** 4;
|
|
602
|
+
}
|
|
603
|
+
if (year < 1941) {
|
|
604
|
+
t = year - 1920;
|
|
605
|
+
return 21.2 + 0.84493 * t - 0.0761 * t ** 2 + 20936e-7 * t ** 3;
|
|
606
|
+
}
|
|
607
|
+
if (year < 1961) {
|
|
608
|
+
t = year - 1950;
|
|
609
|
+
return 29.07 + 0.407 * t - t ** 2 / 233 + t ** 3 / 2547;
|
|
610
|
+
}
|
|
611
|
+
if (year < 1986) {
|
|
612
|
+
t = year - 1975;
|
|
613
|
+
return 45.45 + 1.067 * t - t ** 2 / 260 - t ** 3 / 718;
|
|
614
|
+
}
|
|
615
|
+
if (year < 2005) {
|
|
616
|
+
t = year - 2e3;
|
|
617
|
+
return 63.86 + 0.3345 * t - 0.060374 * t ** 2 + 17275e-7 * t ** 3 + 651814e-9 * t ** 4 + 2373599e-11 * t ** 5;
|
|
618
|
+
}
|
|
619
|
+
if (year < 2050) {
|
|
620
|
+
t = year - 2e3;
|
|
621
|
+
return 62.92 + 0.32217 * t + 5589e-6 * t ** 2;
|
|
622
|
+
}
|
|
623
|
+
return -20 + 32 * ((year - 1820) / 100) ** 2 - 0.5628 * (2150 - year);
|
|
624
|
+
}
|
|
625
|
+
function ttCenturiesFromUT(jdUT) {
|
|
626
|
+
return (jdUT + deltaTSeconds(jdUT) / 86400 - 2451545) / 36525;
|
|
627
|
+
}
|
|
534
628
|
function allPositions(JD) {
|
|
535
|
-
const T = (JD
|
|
629
|
+
const T = ttCenturiesFromUT(JD);
|
|
536
630
|
const pos = {};
|
|
631
|
+
const HALF_DAY_T = 0.5 / 36525;
|
|
632
|
+
const speedOf = (lonAt) => {
|
|
633
|
+
let diff = lonAt(T + HALF_DAY_T) - lonAt(T - HALF_DAY_T);
|
|
634
|
+
if (diff > 180) diff -= 360;
|
|
635
|
+
if (diff < -180) diff += 360;
|
|
636
|
+
return diff;
|
|
637
|
+
};
|
|
537
638
|
const sun = sunGeo(T);
|
|
639
|
+
const sunSpeed = speedOf((t) => sunGeo(t).lon);
|
|
538
640
|
pos["sun"] = {
|
|
539
641
|
key: "sun",
|
|
540
642
|
longitude: sun.lon,
|
|
541
643
|
signIdx: Math.floor(sun.lon / 30),
|
|
542
|
-
degree: sun.lon % 30
|
|
644
|
+
degree: sun.lon % 30,
|
|
645
|
+
speed: sunSpeed,
|
|
646
|
+
retrograde: false
|
|
647
|
+
// le Soleil apparent ne rétrograde jamais
|
|
543
648
|
};
|
|
544
649
|
const ml = moonGeo(T);
|
|
650
|
+
const moonSpeed = speedOf((t) => moonGeo(t));
|
|
545
651
|
pos["moon"] = {
|
|
546
652
|
key: "moon",
|
|
547
653
|
longitude: ml,
|
|
548
654
|
signIdx: Math.floor(ml / 30),
|
|
549
|
-
degree: ml % 30
|
|
655
|
+
degree: ml % 30,
|
|
656
|
+
speed: moonSpeed,
|
|
657
|
+
retrograde: false
|
|
658
|
+
// la Lune non plus
|
|
550
659
|
};
|
|
551
660
|
const earthH = helioPos3D("earth", T);
|
|
661
|
+
const earthH1 = helioPos3D("earth", T - HALF_DAY_T);
|
|
662
|
+
const earthH2 = helioPos3D("earth", T + HALF_DAY_T);
|
|
552
663
|
OUTER_PLANETS.forEach((k) => {
|
|
553
664
|
const pH = helioPos3D(k, T);
|
|
554
665
|
const lon = helioToGeoLon(pH, earthH);
|
|
666
|
+
const g1 = helioToGeoLon(helioPos3D(k, T - HALF_DAY_T), earthH1);
|
|
667
|
+
const g2 = helioToGeoLon(helioPos3D(k, T + HALF_DAY_T), earthH2);
|
|
668
|
+
let speed = g2 - g1;
|
|
669
|
+
if (speed > 180) speed -= 360;
|
|
670
|
+
if (speed < -180) speed += 360;
|
|
555
671
|
pos[k] = {
|
|
556
672
|
key: k,
|
|
557
673
|
longitude: lon,
|
|
558
674
|
signIdx: Math.floor(lon / 30),
|
|
559
|
-
degree: lon % 30
|
|
675
|
+
degree: lon % 30,
|
|
676
|
+
speed,
|
|
677
|
+
retrograde: speed < 0
|
|
560
678
|
};
|
|
561
679
|
});
|
|
562
680
|
const nn = lunarNode(T);
|
|
681
|
+
const nodeSpeed = speedOf((t) => lunarNode(t));
|
|
563
682
|
pos["northNode"] = {
|
|
564
683
|
key: "northNode",
|
|
565
684
|
longitude: nn,
|
|
566
685
|
signIdx: Math.floor(nn / 30),
|
|
567
686
|
degree: nn % 30,
|
|
568
|
-
retrograde: true
|
|
687
|
+
retrograde: true,
|
|
688
|
+
speed: nodeSpeed
|
|
569
689
|
};
|
|
570
690
|
const sn = n360(nn + 180);
|
|
571
691
|
pos["southNode"] = {
|
|
@@ -573,7 +693,8 @@ function allPositions(JD) {
|
|
|
573
693
|
longitude: sn,
|
|
574
694
|
signIdx: Math.floor(sn / 30),
|
|
575
695
|
degree: sn % 30,
|
|
576
|
-
retrograde: true
|
|
696
|
+
retrograde: true,
|
|
697
|
+
speed: nodeSpeed
|
|
577
698
|
};
|
|
578
699
|
return pos;
|
|
579
700
|
}
|
|
@@ -590,7 +711,7 @@ var ACG_BODY_KEYS = [
|
|
|
590
711
|
"pluto"
|
|
591
712
|
];
|
|
592
713
|
function equatorialPositions(JD) {
|
|
593
|
-
const T = (JD
|
|
714
|
+
const T = ttCenturiesFromUT(JD);
|
|
594
715
|
const eps = obl(T);
|
|
595
716
|
const out = {};
|
|
596
717
|
out["sun"] = eclipticToEquatorial(sunGeo(T).lon, 0, eps);
|
|
@@ -640,8 +761,12 @@ function computeChartFromJD(JD, latitude, longitude, opts = {}) {
|
|
|
640
761
|
}
|
|
641
762
|
const retrogrades = [];
|
|
642
763
|
["mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto"].forEach((k) => {
|
|
643
|
-
const
|
|
644
|
-
|
|
764
|
+
const v = angularSpeed(k, JD);
|
|
765
|
+
const r = v !== void 0 && v < 0;
|
|
766
|
+
if (planets[k]) {
|
|
767
|
+
planets[k].retrograde = r;
|
|
768
|
+
if (v !== void 0) planets[k].speed = v;
|
|
769
|
+
}
|
|
645
770
|
if (r) retrogrades.push(k);
|
|
646
771
|
});
|
|
647
772
|
const aspects = calculateAspects(planets);
|
|
@@ -704,6 +829,9 @@ function loadSwisseph() {
|
|
|
704
829
|
return false;
|
|
705
830
|
}
|
|
706
831
|
}
|
|
832
|
+
function isSwissephLoaded() {
|
|
833
|
+
return _swe !== null && _swe !== false;
|
|
834
|
+
}
|
|
707
835
|
function getSwissephLoadError() {
|
|
708
836
|
return _swissephLoadError;
|
|
709
837
|
}
|
|
@@ -783,7 +911,11 @@ var HOUSE_SYSTEM_CHAR = {
|
|
|
783
911
|
placidus: "P",
|
|
784
912
|
koch: "K",
|
|
785
913
|
equal: "E",
|
|
786
|
-
whole_sign: "W"
|
|
914
|
+
whole_sign: "W",
|
|
915
|
+
// HOUSE-SYSTEMS-EXTEND-V1 : codes swisseph natifs.
|
|
916
|
+
porphyry: "O",
|
|
917
|
+
regiomontanus: "R",
|
|
918
|
+
campanus: "C"
|
|
787
919
|
};
|
|
788
920
|
function allPositionsSwiss(JD) {
|
|
789
921
|
if (!loadSwisseph()) throw new Error("swisseph not loaded");
|
|
@@ -806,12 +938,14 @@ function allPositionsSwiss(JD) {
|
|
|
806
938
|
}
|
|
807
939
|
throw new Error(`swe_calc_ut failed for ${key} (ipl=${code}): ${errored ? r.error : "longitude non finie"}`);
|
|
808
940
|
}
|
|
941
|
+
const speed = typeof r.longitudeSpeed === "number" && Number.isFinite(r.longitudeSpeed) ? r.longitudeSpeed : void 0;
|
|
809
942
|
out[key] = {
|
|
810
943
|
key,
|
|
811
944
|
longitude: lon,
|
|
812
945
|
signIdx: Math.floor(lon / 30),
|
|
813
946
|
degree: lon % 30,
|
|
814
|
-
retrograde:
|
|
947
|
+
retrograde: speed !== void 0 ? speed < 0 : false,
|
|
948
|
+
...speed !== void 0 ? { speed } : {}
|
|
815
949
|
};
|
|
816
950
|
}
|
|
817
951
|
if (out["northNode"]) {
|
|
@@ -824,7 +958,9 @@ function allPositionsSwiss(JD) {
|
|
|
824
958
|
// SOUTH-NODE-RETRO-FIX : le Nœud Sud partage l'axe nodal — il a
|
|
825
959
|
// donc le même mouvement que le Nœud Nord. Sans cette ligne il
|
|
826
960
|
// s'affichait « Direct » alors que le Nœud Nord est rétrograde.
|
|
827
|
-
retrograde: out["northNode"].retrograde ?? false
|
|
961
|
+
retrograde: out["northNode"].retrograde ?? false,
|
|
962
|
+
// TRANSIT-SPEED-V1 : longitude = nord + 180 ⇒ dérivée identique.
|
|
963
|
+
...out["northNode"].speed !== void 0 ? { speed: out["northNode"].speed } : {}
|
|
828
964
|
};
|
|
829
965
|
}
|
|
830
966
|
return out;
|
|
@@ -991,6 +1127,23 @@ function computeCurrentSkySwiss(lat, lng, opts = {}) {
|
|
|
991
1127
|
const JD = Date.now() / 864e5 + 24405875e-1;
|
|
992
1128
|
return computeChartFromJDSwiss(JD, lat, lng, opts);
|
|
993
1129
|
}
|
|
1130
|
+
function isRetrogradeSwiss(key, JD) {
|
|
1131
|
+
if (!loadSwisseph()) throw new Error("swisseph not loaded");
|
|
1132
|
+
const code = getPlanetIpl()[key];
|
|
1133
|
+
if (typeof code !== "number") return false;
|
|
1134
|
+
const flag = (FILE_BASED_BODIES.has(key) ? _swe.SEFLG_SWIEPH : _swe.SEFLG_MOSEPH) | _swe.SEFLG_SPEED;
|
|
1135
|
+
let r;
|
|
1136
|
+
try {
|
|
1137
|
+
r = _swe.swe_calc_ut(JD, code, flag);
|
|
1138
|
+
} catch (err) {
|
|
1139
|
+
r = { error: err instanceof Error ? err.message : String(err) };
|
|
1140
|
+
}
|
|
1141
|
+
if (r && typeof r === "object" && "error" in r && r.error) {
|
|
1142
|
+
if (OPTIONAL_BODIES.has(key)) return false;
|
|
1143
|
+
throw new Error(`swe_calc_ut failed for ${key}: ${r.error}`);
|
|
1144
|
+
}
|
|
1145
|
+
return typeof r.longitudeSpeed === "number" ? r.longitudeSpeed < 0 : false;
|
|
1146
|
+
}
|
|
994
1147
|
function moonPhaseSwiss(JD) {
|
|
995
1148
|
if (!loadSwisseph()) throw new Error("swisseph not loaded");
|
|
996
1149
|
const ipl = getPlanetIpl();
|
|
@@ -998,6 +1151,83 @@ function moonPhaseSwiss(JD) {
|
|
|
998
1151
|
const moon = _swe.swe_calc_ut(JD, ipl.moon, _swe.SEFLG_MOSEPH);
|
|
999
1152
|
return moonPhaseFromLongitudes(n360(sun.longitude), n360(moon.longitude));
|
|
1000
1153
|
}
|
|
1154
|
+
function decodeSolarRflag(rflag, swe) {
|
|
1155
|
+
if (rflag & (swe.SE_ECL_ANNULAR_TOTAL ?? 8)) return "hybrid";
|
|
1156
|
+
if (rflag & (swe.SE_ECL_TOTAL ?? 1)) return "total";
|
|
1157
|
+
if (rflag & (swe.SE_ECL_ANNULAR ?? 2)) return "annular";
|
|
1158
|
+
return "partial";
|
|
1159
|
+
}
|
|
1160
|
+
function decodeLunarRflag(rflag, swe) {
|
|
1161
|
+
if (rflag & (swe.SE_ECL_TOTAL ?? 1)) return "total";
|
|
1162
|
+
if (rflag & (swe.SE_ECL_PARTIAL ?? 4)) return "partial";
|
|
1163
|
+
if (rflag & (swe.SE_ECL_PENUMBRAL ?? 16)) return "penumbral";
|
|
1164
|
+
return "penumbral";
|
|
1165
|
+
}
|
|
1166
|
+
function computeSolarEclipseDetailsSwiss(JD) {
|
|
1167
|
+
if (!loadSwisseph()) return null;
|
|
1168
|
+
try {
|
|
1169
|
+
const r = _swe.swe_sol_eclipse_where(JD, _swe.SEFLG_MOSEPH);
|
|
1170
|
+
if (!r || "error" in r) return null;
|
|
1171
|
+
return {
|
|
1172
|
+
magnitude: r.eclipseMagnitude,
|
|
1173
|
+
obscuration: r.solarDiscFraction,
|
|
1174
|
+
kind: decodeSolarRflag(r.rflag, _swe),
|
|
1175
|
+
saros: r.sarosNumber,
|
|
1176
|
+
sarosMember: r.sarosMember
|
|
1177
|
+
};
|
|
1178
|
+
} catch {
|
|
1179
|
+
return null;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
function computeLunarEclipseDetailsSwiss(JD) {
|
|
1183
|
+
if (!loadSwisseph()) return null;
|
|
1184
|
+
try {
|
|
1185
|
+
const r = _swe.swe_lun_eclipse_how(JD, _swe.SEFLG_MOSEPH, 0, 0, 0);
|
|
1186
|
+
if (!r || "error" in r) return null;
|
|
1187
|
+
return {
|
|
1188
|
+
magnitude: r.umbralMagnitude,
|
|
1189
|
+
penumbralMagnitude: r.penumbralMagnitude,
|
|
1190
|
+
kind: decodeLunarRflag(r.rflag, _swe),
|
|
1191
|
+
saros: r.sarosNumber,
|
|
1192
|
+
sarosMember: r.sarosMember
|
|
1193
|
+
};
|
|
1194
|
+
} catch {
|
|
1195
|
+
return null;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
var numOrNull = (v) => typeof v === "number" ? v : null;
|
|
1199
|
+
function findNextSolarEclipseSwiss(jdStart, backward = false) {
|
|
1200
|
+
if (!loadSwisseph()) return null;
|
|
1201
|
+
try {
|
|
1202
|
+
const r = _swe.swe_sol_eclipse_when_glob(jdStart, _swe.SEFLG_MOSEPH, 0, backward ? 1 : 0);
|
|
1203
|
+
if (!r || "error" in r || typeof r.maximum !== "number") return null;
|
|
1204
|
+
return {
|
|
1205
|
+
jdMaximum: r.maximum,
|
|
1206
|
+
kind: decodeSolarRflag(r.rflag, _swe),
|
|
1207
|
+
begin: numOrNull(r.begin),
|
|
1208
|
+
end: numOrNull(r.end)
|
|
1209
|
+
};
|
|
1210
|
+
} catch {
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
function findNextLunarEclipseSwiss(jdStart, backward = false) {
|
|
1215
|
+
if (!loadSwisseph()) return null;
|
|
1216
|
+
try {
|
|
1217
|
+
const r = _swe.swe_lun_eclipse_when(jdStart, _swe.SEFLG_MOSEPH, 0, backward ? 1 : 0);
|
|
1218
|
+
if (!r || "error" in r || typeof r.maximum !== "number") return null;
|
|
1219
|
+
return {
|
|
1220
|
+
jdMaximum: r.maximum,
|
|
1221
|
+
kind: decodeLunarRflag(r.rflag, _swe),
|
|
1222
|
+
partialBegin: numOrNull(r.partialBegin),
|
|
1223
|
+
partialEnd: numOrNull(r.partialEnd),
|
|
1224
|
+
penumbralBegin: numOrNull(r.penumbralBegin),
|
|
1225
|
+
penumbralEnd: numOrNull(r.penumbralEnd)
|
|
1226
|
+
};
|
|
1227
|
+
} catch {
|
|
1228
|
+
return null;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1001
1231
|
|
|
1002
1232
|
// ../ephemeris/src/engine-router.ts
|
|
1003
1233
|
var _activeEngine = null;
|
|
@@ -1031,6 +1261,16 @@ function resolveEngine() {
|
|
|
1031
1261
|
function getActiveEngine() {
|
|
1032
1262
|
return resolveEngine();
|
|
1033
1263
|
}
|
|
1264
|
+
function getEngineDiagnostic() {
|
|
1265
|
+
const active = resolveEngine();
|
|
1266
|
+
return {
|
|
1267
|
+
active,
|
|
1268
|
+
requested: (process.env["ASTRO_ENGINE"] ?? "swisseph").toLowerCase().trim(),
|
|
1269
|
+
swissephLoaded: isSwissephLoaded(),
|
|
1270
|
+
swissephError: getSwissephLoadError(),
|
|
1271
|
+
resolutionLog: _resolutionLog ?? "(non r\xE9solu)"
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1034
1274
|
function computeChartFromJD2(JD, latitude, longitude, opts = {}) {
|
|
1035
1275
|
if (resolveEngine() === "swisseph") {
|
|
1036
1276
|
return computeChartFromJDSwiss(JD, latitude, longitude, opts);
|
|
@@ -1046,6 +1286,9 @@ function computeCurrentSky2(lat, lng, opts = {}) {
|
|
|
1046
1286
|
function allPositions2(JD) {
|
|
1047
1287
|
return resolveEngine() === "swisseph" ? allPositionsSwiss(JD) : allPositions(JD);
|
|
1048
1288
|
}
|
|
1289
|
+
function isRetrograde3(key, JD) {
|
|
1290
|
+
return resolveEngine() === "swisseph" ? isRetrogradeSwiss(key, JD) : isRetrograde(key, JD);
|
|
1291
|
+
}
|
|
1049
1292
|
function moonPhase2(JD) {
|
|
1050
1293
|
return resolveEngine() === "swisseph" ? moonPhaseSwiss(JD) : moonPhase(JD);
|
|
1051
1294
|
}
|
|
@@ -1077,6 +1320,419 @@ var CityNotFoundError = class extends Error {
|
|
|
1077
1320
|
suggestions;
|
|
1078
1321
|
};
|
|
1079
1322
|
|
|
1323
|
+
// ../ephemeris/src/planting-calendar.ts
|
|
1324
|
+
function R2(a, b) {
|
|
1325
|
+
const out = [];
|
|
1326
|
+
let m = a;
|
|
1327
|
+
for (let i = 0; i < 12; i++) {
|
|
1328
|
+
out.push(m);
|
|
1329
|
+
if (m === b) break;
|
|
1330
|
+
m = m === 12 ? 1 : m + 1;
|
|
1331
|
+
}
|
|
1332
|
+
return out;
|
|
1333
|
+
}
|
|
1334
|
+
var VEGETABLES = [
|
|
1335
|
+
// Racines / bulbes
|
|
1336
|
+
{ key: "carrot", fr: "carottes", en: "carrots", category: "root", sow: R2(3, 7), harvest: R2(6, 11) },
|
|
1337
|
+
{ key: "radish", fr: "radis", en: "radishes", category: "root", sow: R2(3, 9), harvest: R2(4, 10) },
|
|
1338
|
+
{ key: "beetroot", fr: "betteraves", en: "beetroot", category: "root", sow: R2(4, 6), harvest: R2(7, 11) },
|
|
1339
|
+
{ key: "turnip", fr: "navets", en: "turnips", category: "root", sow: R2(3, 9), harvest: R2(5, 11) },
|
|
1340
|
+
{ key: "parsnip", fr: "panais", en: "parsnips", category: "root", sow: R2(3, 5), harvest: R2(10, 2) },
|
|
1341
|
+
{ key: "potato", fr: "pommes de terre", en: "potatoes", category: "root", plant: R2(3, 5), harvest: R2(6, 9) },
|
|
1342
|
+
{ key: "onion", fr: "oignons", en: "onions", category: "root", sow: R2(2, 4), plant: R2(3, 4), harvest: R2(7, 9) },
|
|
1343
|
+
{ key: "garlic", fr: "ail", en: "garlic", category: "root", plant: [10, 11, 12, 2, 3], harvest: R2(6, 7) },
|
|
1344
|
+
{ key: "shallot", fr: "\xE9chalotes", en: "shallots", category: "root", plant: R2(10, 3), harvest: R2(6, 7) },
|
|
1345
|
+
{ key: "celeriac", fr: "c\xE9leri-rave", en: "celeriac", category: "root", sowUnder: R2(3, 4), plant: R2(5, 6), harvest: R2(9, 11) },
|
|
1346
|
+
{ key: "jerusalem_artichoke", fr: "topinambours", en: "Jerusalem artichokes", category: "root", plant: R2(2, 4), harvest: R2(10, 3) },
|
|
1347
|
+
{ key: "salsify", fr: "salsifis", en: "salsify", category: "root", sow: R2(4, 5), harvest: R2(10, 2) },
|
|
1348
|
+
// Feuilles
|
|
1349
|
+
{ key: "lettuce", fr: "salades", en: "lettuces", category: "leaf", sowUnder: R2(2, 3), sow: R2(3, 9), harvest: R2(4, 11) },
|
|
1350
|
+
{ key: "spinach", fr: "\xE9pinards", en: "spinach", category: "leaf", sow: [3, 4, 8, 9], harvest: R2(10, 6) },
|
|
1351
|
+
{ key: "leek", fr: "poireaux", en: "leeks", category: "leaf", sow: R2(2, 4), plant: R2(6, 8), harvest: R2(9, 3) },
|
|
1352
|
+
{ key: "chard", fr: "blettes", en: "chard", category: "leaf", sow: R2(4, 6), harvest: R2(7, 11) },
|
|
1353
|
+
{ key: "lambs_lettuce", fr: "m\xE2che", en: "lamb's lettuce", category: "leaf", sow: R2(8, 9), harvest: R2(10, 3) },
|
|
1354
|
+
{ key: "cabbage", fr: "choux pomm\xE9s", en: "cabbages", category: "leaf", sowUnder: R2(3, 4), plant: R2(5, 7), harvest: R2(9, 2) },
|
|
1355
|
+
{ key: "kale", fr: "chou fris\xE9", en: "kale", category: "leaf", sow: R2(4, 6), plant: R2(6, 7), harvest: R2(10, 2) },
|
|
1356
|
+
{ key: "brussels", fr: "choux de Bruxelles", en: "Brussels sprouts", category: "leaf", sowUnder: R2(3, 4), plant: R2(5, 6), harvest: R2(10, 2) },
|
|
1357
|
+
{ key: "rocket", fr: "roquette", en: "rocket", category: "leaf", sow: R2(3, 9), harvest: R2(4, 10) },
|
|
1358
|
+
{ key: "parsley", fr: "persil", en: "parsley", category: "leaf", sow: R2(3, 7), harvest: R2(5, 11) },
|
|
1359
|
+
{ key: "chicory", fr: "endives", en: "chicory", category: "leaf", sow: R2(5, 6), harvest: R2(10, 2) },
|
|
1360
|
+
{ key: "fennel", fr: "fenouil", en: "fennel", category: "leaf", sow: R2(5, 7), harvest: R2(8, 11) },
|
|
1361
|
+
{ key: "basil", fr: "basilic", en: "basil", category: "leaf", sowUnder: R2(3, 4), plant: [5], harvest: R2(6, 9) },
|
|
1362
|
+
{ key: "chives", fr: "ciboulette", en: "chives", category: "leaf", plant: R2(3, 5), harvest: R2(4, 10) },
|
|
1363
|
+
{ key: "sorrel", fr: "oseille", en: "sorrel", category: "leaf", sow: R2(3, 5), harvest: R2(5, 10) },
|
|
1364
|
+
// Fleurs (comestibles / inflorescences)
|
|
1365
|
+
{ key: "cauliflower", fr: "choux-fleurs", en: "cauliflowers", category: "flower", sowUnder: R2(3, 4), plant: R2(5, 6), harvest: R2(9, 11) },
|
|
1366
|
+
{ key: "broccoli", fr: "brocolis", en: "broccoli", category: "flower", sow: R2(3, 5), plant: R2(5, 6), harvest: R2(8, 11) },
|
|
1367
|
+
{ key: "artichoke", fr: "artichauts", en: "artichokes", category: "flower", plant: R2(3, 4), harvest: R2(6, 9) },
|
|
1368
|
+
// Fruits (légumes-fruits)
|
|
1369
|
+
{ key: "tomato", fr: "tomates", en: "tomatoes", category: "fruit", sowUnder: R2(2, 4), plant: [5], harvest: R2(7, 10) },
|
|
1370
|
+
{ key: "courgette", fr: "courgettes", en: "courgettes", category: "fruit", sowUnder: R2(3, 4), sow: [5], plant: R2(5, 6), harvest: R2(6, 10) },
|
|
1371
|
+
{ key: "cucumber", fr: "concombres", en: "cucumbers", category: "fruit", sowUnder: R2(3, 4), plant: R2(5, 6), harvest: R2(7, 9) },
|
|
1372
|
+
{ key: "gherkin", fr: "cornichons", en: "gherkins", category: "fruit", sow: [5], harvest: R2(7, 9) },
|
|
1373
|
+
{ key: "squash", fr: "courges", en: "squash", category: "fruit", sowUnder: [4], sow: [5], harvest: R2(9, 11) },
|
|
1374
|
+
{ key: "green_bean", fr: "haricots verts", en: "green beans", category: "fruit", sow: R2(5, 7), harvest: R2(7, 10) },
|
|
1375
|
+
{ key: "shell_bean", fr: "haricots \xE0 \xE9cosser", en: "shelling beans", category: "fruit", sow: R2(5, 6), harvest: R2(8, 10) },
|
|
1376
|
+
{ key: "pea", fr: "petits pois", en: "peas", category: "fruit", sow: [2, 3, 4, 10, 11], harvest: R2(5, 7) },
|
|
1377
|
+
{ key: "broad_bean", fr: "f\xE8ves", en: "broad beans", category: "fruit", sow: [2, 3, 4, 10, 11], harvest: R2(5, 7) },
|
|
1378
|
+
{ key: "pepper", fr: "poivrons", en: "peppers", category: "fruit", sowUnder: R2(2, 3), plant: [5], harvest: R2(7, 10) },
|
|
1379
|
+
{ key: "eggplant", fr: "aubergines", en: "eggplants", category: "fruit", sowUnder: R2(2, 3), plant: [5], harvest: R2(7, 10) },
|
|
1380
|
+
{ key: "chilli", fr: "piments", en: "chillies", category: "fruit", sowUnder: R2(2, 3), plant: [5], harvest: R2(8, 10) },
|
|
1381
|
+
{ key: "melon", fr: "melons", en: "melons", category: "fruit", sowUnder: R2(3, 4), plant: [5], harvest: R2(7, 9) },
|
|
1382
|
+
{ key: "sweetcorn", fr: "ma\xEFs doux", en: "sweetcorn", category: "fruit", sow: R2(4, 6), harvest: R2(8, 10) },
|
|
1383
|
+
{ key: "strawberry", fr: "fraises", en: "strawberries", category: "fruit", plant: [3, 4, 8, 9], harvest: R2(5, 7) }
|
|
1384
|
+
];
|
|
1385
|
+
var FLOWERS = [
|
|
1386
|
+
// Annuelles (semis)
|
|
1387
|
+
{ key: "cosmos", fr: "cosmos", en: "cosmos", category: "flower", sow: R2(4, 6), harvest: R2(7, 10) },
|
|
1388
|
+
{ key: "zinnia", fr: "zinnias", en: "zinnias", category: "flower", sowUnder: R2(3, 4), sow: [5], harvest: R2(7, 10) },
|
|
1389
|
+
{ key: "calendula", fr: "soucis", en: "marigolds (calendula)", category: "flower", sow: R2(3, 6), harvest: R2(5, 10) },
|
|
1390
|
+
{ key: "nasturtium", fr: "capucines", en: "nasturtiums", category: "flower", sow: R2(4, 6), harvest: R2(6, 10) },
|
|
1391
|
+
{ key: "sunflower", fr: "tournesols", en: "sunflowers", category: "flower", sow: R2(4, 6), harvest: R2(7, 9) },
|
|
1392
|
+
{ key: "tagetes", fr: "\u0153illets d'Inde", en: "French marigolds", category: "flower", sowUnder: R2(3, 4), plant: [5], harvest: R2(6, 10) },
|
|
1393
|
+
{ key: "sweet_pea", fr: "pois de senteur", en: "sweet peas", category: "flower", sow: R2(2, 4), harvest: R2(6, 9) },
|
|
1394
|
+
{ key: "cornflower", fr: "bleuets", en: "cornflowers", category: "flower", sow: [3, 4, 5, 9], harvest: R2(6, 8) },
|
|
1395
|
+
{ key: "poppy", fr: "coquelicots", en: "poppies", category: "flower", sow: [3, 4, 5, 9], harvest: R2(5, 7) },
|
|
1396
|
+
{ key: "nigella", fr: "nigelles", en: "love-in-a-mist", category: "flower", sow: [3, 4, 5, 9], harvest: R2(6, 8) },
|
|
1397
|
+
{ key: "aster", fr: "reines-marguerites", en: "asters", category: "flower", sow: R2(4, 5), harvest: R2(8, 10) },
|
|
1398
|
+
// Bisannuelles
|
|
1399
|
+
{ key: "hollyhock", fr: "roses tr\xE9mi\xE8res", en: "hollyhocks", category: "flower", sow: R2(5, 7), harvest: R2(6, 8) },
|
|
1400
|
+
{ key: "foxglove", fr: "digitales", en: "foxgloves", category: "flower", sow: R2(5, 7), harvest: R2(6, 7) },
|
|
1401
|
+
{ key: "forget_me_not", fr: "myosotis", en: "forget-me-nots", category: "flower", sow: R2(6, 7), harvest: R2(3, 5) },
|
|
1402
|
+
{ key: "pansy", fr: "pens\xE9es", en: "pansies", category: "flower", sow: R2(6, 8), plant: [9, 10, 3], harvest: [3, 4, 5, 10, 11] },
|
|
1403
|
+
// Vivaces / bulbes (plantation)
|
|
1404
|
+
{ key: "dahlia", fr: "dahlias", en: "dahlias", category: "flower", plant: R2(4, 5), harvest: R2(7, 10) },
|
|
1405
|
+
{ key: "gladiolus", fr: "gla\xEFeuls", en: "gladioli", category: "flower", plant: R2(3, 6), harvest: R2(7, 9) },
|
|
1406
|
+
{ key: "tulip", fr: "tulipes", en: "tulips", category: "flower", plant: R2(10, 12), harvest: R2(3, 5) },
|
|
1407
|
+
{ key: "daffodil", fr: "narcisses", en: "daffodils", category: "flower", plant: R2(9, 11), harvest: R2(3, 4) },
|
|
1408
|
+
{ key: "crocus", fr: "crocus", en: "crocuses", category: "flower", plant: R2(9, 11), harvest: R2(2, 3) },
|
|
1409
|
+
{ key: "hyacinth", fr: "jacinthes", en: "hyacinths", category: "flower", plant: R2(9, 11), harvest: R2(3, 4) },
|
|
1410
|
+
{ key: "lily", fr: "lys", en: "lilies", category: "flower", plant: R2(10, 4), harvest: R2(6, 8) },
|
|
1411
|
+
{ key: "peony", fr: "pivoines", en: "peonies", category: "flower", plant: R2(9, 11), harvest: R2(5, 6) },
|
|
1412
|
+
{ key: "iris", fr: "iris", en: "irises", category: "flower", plant: R2(7, 9), harvest: R2(5, 6) },
|
|
1413
|
+
{ key: "lily_valley", fr: "muguet", en: "lily of the valley", category: "flower", plant: R2(10, 11), harvest: [5] },
|
|
1414
|
+
{ key: "rose", fr: "rosiers", en: "roses", category: "flower", plant: [11, 12, 1, 2, 3], harvest: R2(5, 10) },
|
|
1415
|
+
{ key: "lavender", fr: "lavande", en: "lavender", category: "flower", plant: [3, 4, 5, 9, 10], harvest: R2(6, 8) },
|
|
1416
|
+
{ key: "chrysanthemum", fr: "chrysanth\xE8mes", en: "chrysanthemums", category: "flower", plant: R2(5, 6), harvest: R2(9, 11) }
|
|
1417
|
+
];
|
|
1418
|
+
var PLANTING_CALENDAR = [...VEGETABLES, ...FLOWERS];
|
|
1419
|
+
|
|
1420
|
+
// ../ephemeris/src/dignities.ts
|
|
1421
|
+
var TABLE = {
|
|
1422
|
+
sun: { domicile: [4], exaltation: [0], detriment: [10], fall: [6] },
|
|
1423
|
+
moon: { domicile: [3], exaltation: [1], detriment: [9], fall: [7] },
|
|
1424
|
+
mercury: { domicile: [2, 5], exaltation: [5], detriment: [8, 11], fall: [11] },
|
|
1425
|
+
venus: { domicile: [1, 6], exaltation: [11], detriment: [0, 7], fall: [5] },
|
|
1426
|
+
mars: { domicile: [0, 7], exaltation: [9], detriment: [1, 6], fall: [3] },
|
|
1427
|
+
jupiter: { domicile: [8, 11], exaltation: [3], detriment: [2, 5], fall: [9] },
|
|
1428
|
+
saturn: { domicile: [9, 10], exaltation: [6], detriment: [3, 4], fall: [0] }
|
|
1429
|
+
};
|
|
1430
|
+
var LABELS = {
|
|
1431
|
+
domicile: { fr: "en domicile", en: "in domicile" },
|
|
1432
|
+
exaltation: { fr: "en exaltation", en: "exalted" },
|
|
1433
|
+
detriment: { fr: "en exil", en: "in detriment" },
|
|
1434
|
+
fall: { fr: "en chute", en: "in fall" }
|
|
1435
|
+
};
|
|
1436
|
+
var PRIORITY = ["domicile", "exaltation", "detriment", "fall"];
|
|
1437
|
+
function essentialDignity(planetKey, signIdx) {
|
|
1438
|
+
const key = typeof planetKey === "string" ? planetKey.toLowerCase() : "";
|
|
1439
|
+
const row = TABLE[key];
|
|
1440
|
+
if (!row || signIdx == null || !Number.isInteger(signIdx) || signIdx < 0 || signIdx > 11) {
|
|
1441
|
+
return null;
|
|
1442
|
+
}
|
|
1443
|
+
for (const kind of PRIORITY) {
|
|
1444
|
+
if (row[kind].includes(signIdx)) {
|
|
1445
|
+
return { kind, labelFr: LABELS[kind].fr, labelEn: LABELS[kind].en };
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
return null;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// ../ephemeris/src/natal-weight.ts
|
|
1452
|
+
var TRADITIONAL_RULER = [
|
|
1453
|
+
"mars",
|
|
1454
|
+
// 0 Bélier
|
|
1455
|
+
"venus",
|
|
1456
|
+
// 1 Taureau
|
|
1457
|
+
"mercury",
|
|
1458
|
+
// 2 Gémeaux
|
|
1459
|
+
"moon",
|
|
1460
|
+
// 3 Cancer
|
|
1461
|
+
"sun",
|
|
1462
|
+
// 4 Lion
|
|
1463
|
+
"mercury",
|
|
1464
|
+
// 5 Vierge
|
|
1465
|
+
"venus",
|
|
1466
|
+
// 6 Balance
|
|
1467
|
+
"mars",
|
|
1468
|
+
// 7 Scorpion
|
|
1469
|
+
"jupiter",
|
|
1470
|
+
// 8 Sagittaire
|
|
1471
|
+
"saturn",
|
|
1472
|
+
// 9 Capricorne
|
|
1473
|
+
"saturn",
|
|
1474
|
+
// 10 Verseau
|
|
1475
|
+
"jupiter"
|
|
1476
|
+
// 11 Poissons
|
|
1477
|
+
];
|
|
1478
|
+
var W = {
|
|
1479
|
+
ascRuler: 0.45,
|
|
1480
|
+
// maître d'ascendant
|
|
1481
|
+
sunRuler: 0.22,
|
|
1482
|
+
// maître du signe solaire
|
|
1483
|
+
angular: 0.2,
|
|
1484
|
+
// en maison angulaire (1/4/7/10)
|
|
1485
|
+
conjAngle: 0.28,
|
|
1486
|
+
// à ≤ 5° de l'Ascendant ou du MC
|
|
1487
|
+
luminary: 0.16,
|
|
1488
|
+
// Soleil / Lune (base)
|
|
1489
|
+
tightAspectCore: 0.18,
|
|
1490
|
+
// aspect natal serré (<2°) impliquant un luminaire ou le maître
|
|
1491
|
+
tightAspect: 0.1,
|
|
1492
|
+
// aspect natal serré (<2°) sinon
|
|
1493
|
+
dignity: 0.1
|
|
1494
|
+
// domicile ou exaltation
|
|
1495
|
+
};
|
|
1496
|
+
var CONJ_ANGLE_ORB = 5;
|
|
1497
|
+
var TIGHT_ORB = 2;
|
|
1498
|
+
var TOP_THRESHOLD = 0.25;
|
|
1499
|
+
var TOP_MAX = 3;
|
|
1500
|
+
var LUMINARIES = /* @__PURE__ */ new Set(["sun", "moon"]);
|
|
1501
|
+
var ANGULAR_HOUSES = /* @__PURE__ */ new Set([1, 4, 7, 10]);
|
|
1502
|
+
function angularSep(a, b) {
|
|
1503
|
+
let d = Math.abs(a - b) % 360;
|
|
1504
|
+
if (d > 180) d = 360 - d;
|
|
1505
|
+
return d;
|
|
1506
|
+
}
|
|
1507
|
+
function signOf(p) {
|
|
1508
|
+
return p.signIdx ?? Math.floor((p.longitude % 360 + 360) % 360 / 30);
|
|
1509
|
+
}
|
|
1510
|
+
function computeNatalWeight(chart) {
|
|
1511
|
+
const timeKnown = chart.birthTimeKnown ?? true;
|
|
1512
|
+
const planets = chart.planets ?? {};
|
|
1513
|
+
const acc = {};
|
|
1514
|
+
const add = (planet, delta, role) => {
|
|
1515
|
+
if (!planet || !planets[planet]) return;
|
|
1516
|
+
const e = acc[planet] ?? (acc[planet] = { weight: 0, roles: [] });
|
|
1517
|
+
e.weight += delta;
|
|
1518
|
+
if (!e.roles.some((r) => r.key === role.key)) e.roles.push(role);
|
|
1519
|
+
};
|
|
1520
|
+
let chartRuler = null;
|
|
1521
|
+
if (timeKnown && typeof chart.asc === "number") {
|
|
1522
|
+
const ascSign = Math.floor((chart.asc % 360 + 360) % 360 / 30);
|
|
1523
|
+
chartRuler = TRADITIONAL_RULER[ascSign] ?? null;
|
|
1524
|
+
if (chartRuler) {
|
|
1525
|
+
add(chartRuler, W.ascRuler, { key: "ascRuler", labelFr: "ma\xEEtre d'ascendant", labelEn: "chart ruler" });
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
const sun = planets["sun"];
|
|
1529
|
+
if (sun) {
|
|
1530
|
+
const sunRuler = TRADITIONAL_RULER[signOf(sun)];
|
|
1531
|
+
if (sunRuler) {
|
|
1532
|
+
add(sunRuler, W.sunRuler, { key: "sunRuler", labelFr: "ma\xEEtre du Soleil", labelEn: "ruler of the Sun" });
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
for (const lum of LUMINARIES) {
|
|
1536
|
+
if (planets[lum]) add(lum, W.luminary, { key: "luminary", labelFr: "luminaire", labelEn: "luminary" });
|
|
1537
|
+
}
|
|
1538
|
+
if (timeKnown) {
|
|
1539
|
+
for (const [key, p] of Object.entries(planets)) {
|
|
1540
|
+
if (!p || typeof p.longitude !== "number") continue;
|
|
1541
|
+
if (p.house != null && ANGULAR_HOUSES.has(p.house)) {
|
|
1542
|
+
add(key, W.angular, { key: "angular", labelFr: "angulaire", labelEn: "angular" });
|
|
1543
|
+
}
|
|
1544
|
+
if (typeof chart.asc === "number" && angularSep(p.longitude, chart.asc) <= CONJ_ANGLE_ORB) {
|
|
1545
|
+
add(key, W.conjAngle, { key: "conjAsc", labelFr: "conjoint \xE0 l'Ascendant", labelEn: "conjunct the Ascendant" });
|
|
1546
|
+
}
|
|
1547
|
+
if (typeof chart.mc === "number" && angularSep(p.longitude, chart.mc) <= CONJ_ANGLE_ORB) {
|
|
1548
|
+
add(key, W.conjAngle, { key: "conjMc", labelFr: "conjoint au Milieu du Ciel", labelEn: "conjunct the Midheaven" });
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
for (const asp of chart.aspects ?? []) {
|
|
1553
|
+
if (typeof asp.orb !== "number" || asp.orb >= TIGHT_ORB) continue;
|
|
1554
|
+
const a = asp.planet1 ?? asp.p1;
|
|
1555
|
+
const b = asp.planet2 ?? asp.p2;
|
|
1556
|
+
if (!a || !b) continue;
|
|
1557
|
+
const core = LUMINARIES.has(a) || LUMINARIES.has(b) || a === chartRuler || b === chartRuler;
|
|
1558
|
+
const delta = core ? W.tightAspectCore : W.tightAspect;
|
|
1559
|
+
const role = { key: "tightAspect", labelFr: "aspect natal serr\xE9", labelEn: "tight natal aspect" };
|
|
1560
|
+
add(a, delta, role);
|
|
1561
|
+
add(b, delta, role);
|
|
1562
|
+
}
|
|
1563
|
+
for (const [key, p] of Object.entries(planets)) {
|
|
1564
|
+
if (!p || typeof p.longitude !== "number") continue;
|
|
1565
|
+
const dig = essentialDignity(key, signOf(p));
|
|
1566
|
+
if (dig && (dig.kind === "domicile" || dig.kind === "exaltation")) {
|
|
1567
|
+
add(key, W.dignity, { key: "dignity", labelFr: dig.labelFr, labelEn: dig.labelEn });
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
const byPlanet = {};
|
|
1571
|
+
for (const [planet, e] of Object.entries(acc)) {
|
|
1572
|
+
byPlanet[planet] = { planet, weight: Math.min(1, Math.round(e.weight * 100) / 100), roles: e.roles };
|
|
1573
|
+
}
|
|
1574
|
+
const top = Object.values(byPlanet).filter((e) => e.weight >= TOP_THRESHOLD).sort((x, y) => y.weight - x.weight).slice(0, TOP_MAX);
|
|
1575
|
+
return { byPlanet, top, chartRuler };
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// ../ephemeris/src/natal-patterns.ts
|
|
1579
|
+
var MAJORS = ["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto"];
|
|
1580
|
+
var ORB = {
|
|
1581
|
+
grandTrine: 8,
|
|
1582
|
+
square: 7,
|
|
1583
|
+
// T-carré / grande croix
|
|
1584
|
+
opposition: 7,
|
|
1585
|
+
yodSextile: 4,
|
|
1586
|
+
yodQuincunx: 3
|
|
1587
|
+
};
|
|
1588
|
+
var STELLIUM_MIN = 3;
|
|
1589
|
+
function sep(a, b) {
|
|
1590
|
+
let d = Math.abs(a - b) % 360;
|
|
1591
|
+
if (d > 180) d = 360 - d;
|
|
1592
|
+
return d;
|
|
1593
|
+
}
|
|
1594
|
+
function isAspect(a, b, angle, orb) {
|
|
1595
|
+
return Math.abs(sep(a, b) - angle) <= orb;
|
|
1596
|
+
}
|
|
1597
|
+
function signOf2(p) {
|
|
1598
|
+
return p.signIdx ?? Math.floor((p.longitude % 360 + 360) % 360 / 30);
|
|
1599
|
+
}
|
|
1600
|
+
var ELEMENT_FR = ["Feu", "Terre", "Air", "Eau"];
|
|
1601
|
+
var ELEMENT_EN = ["Fire", "Earth", "Air", "Water"];
|
|
1602
|
+
function detectNatalPatterns(input) {
|
|
1603
|
+
const timeKnown = input.birthTimeKnown ?? true;
|
|
1604
|
+
const bodies = MAJORS.filter((k) => {
|
|
1605
|
+
const p = input.planets?.[k];
|
|
1606
|
+
return p && typeof p.longitude === "number";
|
|
1607
|
+
});
|
|
1608
|
+
const lon = (k) => input.planets[k].longitude;
|
|
1609
|
+
const out = [];
|
|
1610
|
+
const bySign = /* @__PURE__ */ new Map();
|
|
1611
|
+
for (const k of bodies) {
|
|
1612
|
+
const s = signOf2(input.planets[k]);
|
|
1613
|
+
(bySign.get(s) ?? bySign.set(s, []).get(s)).push(k);
|
|
1614
|
+
}
|
|
1615
|
+
for (const [s, members] of bySign) {
|
|
1616
|
+
if (members.length >= STELLIUM_MIN) {
|
|
1617
|
+
out.push({
|
|
1618
|
+
kind: "stellium",
|
|
1619
|
+
by: "sign",
|
|
1620
|
+
signIdx: s,
|
|
1621
|
+
planets: members,
|
|
1622
|
+
typeFr: "stellium",
|
|
1623
|
+
typeEn: "stellium"
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
if (timeKnown) {
|
|
1628
|
+
const byHouse = /* @__PURE__ */ new Map();
|
|
1629
|
+
for (const k of bodies) {
|
|
1630
|
+
const h = input.planets[k].house;
|
|
1631
|
+
if (h != null) (byHouse.get(h) ?? byHouse.set(h, []).get(h)).push(k);
|
|
1632
|
+
}
|
|
1633
|
+
for (const [h, members] of byHouse) {
|
|
1634
|
+
if (members.length >= STELLIUM_MIN) {
|
|
1635
|
+
const sameAsSign = out.some((p) => p.kind === "stellium" && p.by === "sign" && p.planets.length === members.length && members.every((m) => p.planets.includes(m)));
|
|
1636
|
+
if (!sameAsSign) {
|
|
1637
|
+
out.push({
|
|
1638
|
+
kind: "stellium",
|
|
1639
|
+
by: "house",
|
|
1640
|
+
house: h,
|
|
1641
|
+
planets: members,
|
|
1642
|
+
typeFr: "stellium",
|
|
1643
|
+
typeEn: "stellium"
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
const grandTrines = [];
|
|
1650
|
+
for (let i = 0; i < bodies.length; i++)
|
|
1651
|
+
for (let j = i + 1; j < bodies.length; j++)
|
|
1652
|
+
for (let k = j + 1; k < bodies.length; k++) {
|
|
1653
|
+
const [a, b, c] = [bodies[i], bodies[j], bodies[k]];
|
|
1654
|
+
if (isAspect(lon(a), lon(b), 120, ORB.grandTrine) && isAspect(lon(b), lon(c), 120, ORB.grandTrine) && isAspect(lon(a), lon(c), 120, ORB.grandTrine)) {
|
|
1655
|
+
const els = [a, b, c].map((x) => signOf2(input.planets[x]) % 4);
|
|
1656
|
+
const element = els.every((e) => e === els[0]) ? els[0] : void 0;
|
|
1657
|
+
grandTrines.push([a, b, c]);
|
|
1658
|
+
out.push({
|
|
1659
|
+
kind: "grandTrine",
|
|
1660
|
+
planets: [a, b, c],
|
|
1661
|
+
element,
|
|
1662
|
+
typeFr: element != null ? `grand trigone de ${ELEMENT_FR[element]}` : "grand trigone",
|
|
1663
|
+
typeEn: element != null ? `grand trine in ${ELEMENT_EN[element]}` : "grand trine"
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
const opps = [];
|
|
1668
|
+
for (let i = 0; i < bodies.length; i++)
|
|
1669
|
+
for (let j = i + 1; j < bodies.length; j++)
|
|
1670
|
+
if (isAspect(lon(bodies[i]), lon(bodies[j]), 180, ORB.opposition))
|
|
1671
|
+
opps.push([bodies[i], bodies[j]]);
|
|
1672
|
+
const squares = (x, y) => isAspect(lon(x), lon(y), 90, ORB.square);
|
|
1673
|
+
const crossSets = [];
|
|
1674
|
+
for (let i = 0; i < opps.length; i++)
|
|
1675
|
+
for (let j = i + 1; j < opps.length; j++) {
|
|
1676
|
+
const [a, b] = opps[i], [c, d] = opps[j];
|
|
1677
|
+
const four = [a, b, c, d];
|
|
1678
|
+
if (new Set(four).size !== 4) continue;
|
|
1679
|
+
if (squares(a, c) && squares(a, d) && squares(b, c) && squares(b, d)) {
|
|
1680
|
+
crossSets.push(new Set(four));
|
|
1681
|
+
out.push({ kind: "grandCross", planets: four, typeFr: "grande croix", typeEn: "grand cross" });
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
for (const [a, b] of opps) {
|
|
1685
|
+
for (const c of bodies) {
|
|
1686
|
+
if (c === a || c === b) continue;
|
|
1687
|
+
if (squares(a, c) && squares(b, c)) {
|
|
1688
|
+
if (crossSets.some((s) => s.has(a) && s.has(b) && s.has(c))) continue;
|
|
1689
|
+
out.push({ kind: "tSquare", planets: [a, b, c], apex: c, typeFr: "T-carr\xE9", typeEn: "T-square" });
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
for (let i = 0; i < bodies.length; i++)
|
|
1694
|
+
for (let j = i + 1; j < bodies.length; j++) {
|
|
1695
|
+
const a = bodies[i], b = bodies[j];
|
|
1696
|
+
if (!isAspect(lon(a), lon(b), 60, ORB.yodSextile)) continue;
|
|
1697
|
+
for (const c of bodies) {
|
|
1698
|
+
if (c === a || c === b) continue;
|
|
1699
|
+
if (isAspect(lon(a), lon(c), 150, ORB.yodQuincunx) && isAspect(lon(b), lon(c), 150, ORB.yodQuincunx)) {
|
|
1700
|
+
out.push({ kind: "yod", planets: [a, b, c], apex: c, typeFr: "yod", typeEn: "yod" });
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
return out;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// ../ephemeris/src/planetary-ages.ts
|
|
1708
|
+
var AGE_BANDS = [
|
|
1709
|
+
{ maxAge: 7, planetKey: "moon", fr: "l'enfance, le lien affectif et le besoin de s\xE9curit\xE9", en: "childhood, emotional bonding and the need for security" },
|
|
1710
|
+
{ maxAge: 14, planetKey: "mercury", fr: "l'apprentissage, la curiosit\xE9 et les premiers \xE9changes", en: "learning, curiosity and first exchanges" },
|
|
1711
|
+
{ maxAge: 24, planetKey: "venus", fr: "l'amour, la sensualit\xE9 et le go\xFBt de plaire", en: "love, sensuality and the taste for pleasing" },
|
|
1712
|
+
{ maxAge: 36, planetKey: "mars", fr: "l'affirmation de soi, la conqu\xEAte et l'action", en: "self-assertion, conquest and action" },
|
|
1713
|
+
{ maxAge: 48, planetKey: "sun", fr: "la r\xE9ussite, le rayonnement et l'autorit\xE9", en: "achievement, radiance and authority" },
|
|
1714
|
+
{ maxAge: 63, planetKey: "jupiter", fr: "le d\xE9ploiement, la transmission et l'expansion", en: "growth, transmission and expansion" },
|
|
1715
|
+
{ maxAge: Number.POSITIVE_INFINITY, planetKey: "saturn", fr: "le bilan, la sagesse et l'essentiel", en: "reckoning, wisdom and the essential" }
|
|
1716
|
+
];
|
|
1717
|
+
|
|
1718
|
+
// ../ephemeris/src/birth-lunation.ts
|
|
1719
|
+
var LUNATION_TYPES = [
|
|
1720
|
+
{ key: "new", nameFr: "Nouvelle Lune", nameEn: "New Moon", glossFr: "instinct, \xE9lan spontan\xE9, commencement subjectif", glossEn: "instinct, spontaneous drive, subjective beginnings" },
|
|
1721
|
+
{ key: "crescent", nameFr: "Croissant", nameEn: "Crescent", glossFr: "l'effort d'\xE9merger, s'arracher au pass\xE9, volont\xE9 d'avancer", glossEn: "the effort to emerge, breaking from the past, will to advance" },
|
|
1722
|
+
{ key: "first_quarter", nameFr: "Premier Quartier", nameEn: "First Quarter", glossFr: "crise d'action, construire, bousculer les r\xE9sistances", glossEn: "crisis of action, building, pushing through resistance" },
|
|
1723
|
+
{ key: "gibbous", nameFr: "Gibbeuse", nameEn: "Gibbous", glossFr: "perfectionnement, qu\xEAte de sens, analyse, d\xE9vouement", glossEn: "refinement, search for meaning, analysis, devotion" },
|
|
1724
|
+
{ key: "full", nameFr: "Pleine Lune", nameEn: "Full Moon", glossFr: "pleine conscience, relation, objectivit\xE9, r\xE9v\xE9lation", glossEn: "full awareness, relationship, objectivity, revelation" },
|
|
1725
|
+
{ key: "disseminating", nameFr: "Diffusante", nameEn: "Disseminating", glossFr: "transmettre, partager, enseigner ce qu'on a compris", glossEn: "transmitting, sharing, teaching what one has grasped" },
|
|
1726
|
+
{ key: "last_quarter", nameFr: "Dernier Quartier", nameEn: "Last Quarter", glossFr: "crise de conscience, r\xE9orientation, remise en question", glossEn: "crisis of consciousness, reorientation, questioning" },
|
|
1727
|
+
{ key: "balsamic", nameFr: "Balsamique", nameEn: "Balsamic", glossFr: "l\xE2cher-prise, transition, graine du futur, retrait", glossEn: "release, transition, seed of the future, withdrawal" }
|
|
1728
|
+
];
|
|
1729
|
+
function lunationType(sunLon, moonLon) {
|
|
1730
|
+
if (!Number.isFinite(sunLon) || !Number.isFinite(moonLon)) return null;
|
|
1731
|
+
const elong = ((moonLon - sunLon) % 360 + 360) % 360;
|
|
1732
|
+
const idx = Math.min(7, Math.floor(elong / 45));
|
|
1733
|
+
return LUNATION_TYPES[idx];
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1080
1736
|
// ../ephemeris/src/time-utc.service.ts
|
|
1081
1737
|
import { DateTime } from "luxon";
|
|
1082
1738
|
var TimezoneError = class extends Error {
|
|
@@ -1274,8 +1930,27 @@ function enrich(result, meta) {
|
|
|
1274
1930
|
symbol: a.symbol,
|
|
1275
1931
|
tone: a.tone
|
|
1276
1932
|
}));
|
|
1277
|
-
|
|
1933
|
+
const planets = {};
|
|
1934
|
+
for (const [key, p] of Object.entries(result.planets)) {
|
|
1935
|
+
const signIdx = p.signIdx ?? Math.floor((p.longitude % 360 + 360) % 360 / 30);
|
|
1936
|
+
planets[key] = { ...p, dignity: essentialDignity(key, signIdx) };
|
|
1937
|
+
}
|
|
1938
|
+
const sunLon = result.planets?.sun?.longitude;
|
|
1939
|
+
const moonLon = result.planets?.moon?.longitude;
|
|
1940
|
+
const lunation = typeof sunLon === "number" && typeof moonLon === "number" ? lunationType(sunLon, moonLon) : null;
|
|
1941
|
+
const weight = computeNatalWeight({
|
|
1942
|
+
planets: result.planets,
|
|
1943
|
+
asc: result.houses.asc ?? null,
|
|
1944
|
+
mc: result.houses.mc ?? null,
|
|
1945
|
+
aspects: result.aspects,
|
|
1946
|
+
birthTimeKnown: meta.birthTimeKnown
|
|
1947
|
+
});
|
|
1948
|
+
const patterns = detectNatalPatterns({
|
|
1278
1949
|
planets: result.planets,
|
|
1950
|
+
birthTimeKnown: meta.birthTimeKnown
|
|
1951
|
+
});
|
|
1952
|
+
return {
|
|
1953
|
+
planets,
|
|
1279
1954
|
houses,
|
|
1280
1955
|
asc: result.houses.asc,
|
|
1281
1956
|
mc: result.houses.mc,
|
|
@@ -1288,6 +1963,12 @@ function enrich(result, meta) {
|
|
|
1288
1963
|
moonPhase: result.moonPhase,
|
|
1289
1964
|
numerology: meta.localBirthDate ? computeLifePath(meta.localBirthDate) : result.numerology,
|
|
1290
1965
|
// STAB-PRE-5-V1
|
|
1966
|
+
lunation,
|
|
1967
|
+
// NATAL-EXPOSE-V1
|
|
1968
|
+
weight,
|
|
1969
|
+
// NATAL-EXPOSE-V1
|
|
1970
|
+
patterns,
|
|
1971
|
+
// NATAL-EXPOSE-V1
|
|
1291
1972
|
source: result.source,
|
|
1292
1973
|
zodiac: result.zodiac,
|
|
1293
1974
|
houseSystem: result.houseSystem,
|
|
@@ -1312,7 +1993,7 @@ function chartCacheKey(args) {
|
|
|
1312
1993
|
e: getActiveEngine()
|
|
1313
1994
|
});
|
|
1314
1995
|
const hash = createHash("sha256").update(payload).digest("hex").slice(0, 16);
|
|
1315
|
-
return `chart:
|
|
1996
|
+
return `chart:v8:${args.natalId}:${hash}`;
|
|
1316
1997
|
}
|
|
1317
1998
|
var EphemerisService = class {
|
|
1318
1999
|
/**
|
|
@@ -1413,6 +2094,33 @@ var EphemerisService = class {
|
|
|
1413
2094
|
longitude: city.lng
|
|
1414
2095
|
});
|
|
1415
2096
|
}
|
|
2097
|
+
// ──────────────────────────────────────────────────────
|
|
2098
|
+
// SYNASTRY-DECAN-V1
|
|
2099
|
+
// Thème « décan » : positions planétaires calculées à midi UTC
|
|
2100
|
+
// (ou à l'heure fournie) SANS lieu de naissance. Les longitudes
|
|
2101
|
+
// écliptiques géocentriques ne dépendent que du temps (JD), pas
|
|
2102
|
+
// de lat/lon — seuls les maisons/ASC/MC en dépendent, et on les
|
|
2103
|
+
// ignore en aval (mode décan : Soleil + planètes lentes only).
|
|
2104
|
+
// Utilisé en synastrie quand un partenaire n'a ni heure ni ville.
|
|
2105
|
+
// ──────────────────────────────────────────────────────
|
|
2106
|
+
/**
|
|
2107
|
+
* @param birthDate Date de naissance locale (YYYY-MM-DD).
|
|
2108
|
+
* @param birthTime Heure locale (HH:MM). Défaut 12:00 si absente.
|
|
2109
|
+
* @param birthTimeKnown Propagé dans meta pour le hedging IA.
|
|
2110
|
+
*/
|
|
2111
|
+
async calculateDecanChart(args) {
|
|
2112
|
+
const localBirthTime = args.birthTime ?? "12:00";
|
|
2113
|
+
const key = createHash("sha256").update(`decan:${args.birthDate}:${localBirthTime}:${args.birthTimeKnown}`).digest("hex").slice(0, 12);
|
|
2114
|
+
return this.calculateNatalChart({
|
|
2115
|
+
natalId: `decan-${key}`,
|
|
2116
|
+
localBirthDate: args.birthDate,
|
|
2117
|
+
localBirthTime,
|
|
2118
|
+
ianaTz: "UTC",
|
|
2119
|
+
latitude: 0,
|
|
2120
|
+
longitude: 0,
|
|
2121
|
+
birthTimeKnown: args.birthTimeKnown
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
1416
2124
|
/** Ciel du moment présent (transits) pour une position. */
|
|
1417
2125
|
async getCurrentSky(latitude, longitude, opts = {}) {
|
|
1418
2126
|
const result = computeCurrentSky2(latitude, longitude, opts);
|
|
@@ -1427,6 +2135,30 @@ var EphemerisService = class {
|
|
|
1427
2135
|
utcISO: nowIso
|
|
1428
2136
|
});
|
|
1429
2137
|
}
|
|
2138
|
+
/**
|
|
2139
|
+
* Ciel à un instant précis (JD UT) pour une position — variante déterministe
|
|
2140
|
+
* de `getCurrentSky`.
|
|
2141
|
+
*
|
|
2142
|
+
* HOROSCOPE-CHIP-COHERENCE-V1 : sert à ancrer les aspects transit→natal qui
|
|
2143
|
+
* pilotent les « pastilles d'énergies » et la prose IA sur un MÊME instant
|
|
2144
|
+
* stable du jour (midi UTC), au lieu du « maintenant » horaire. Sans cet
|
|
2145
|
+
* ancrage, la prose (cachée au premier chargement) et les puces (recalculées
|
|
2146
|
+
* en direct à chaque requête) divergeaient dès qu'un corps rapide (Lune,
|
|
2147
|
+
* astéroïde) franchissait un seuil d'orbe dans la journée.
|
|
2148
|
+
*/
|
|
2149
|
+
async getSkyAt(JD, latitude, longitude, opts = {}) {
|
|
2150
|
+
const result = computeChartFromJD2(JD, latitude, longitude, opts);
|
|
2151
|
+
const iso = new Date((JD - 24405875e-1) * 864e5).toISOString();
|
|
2152
|
+
return enrich(result, {
|
|
2153
|
+
localBirthDate: iso.slice(0, 10),
|
|
2154
|
+
localBirthTime: iso.slice(11, 16),
|
|
2155
|
+
ianaTz: "UTC",
|
|
2156
|
+
offsetMinutes: 0,
|
|
2157
|
+
resolution: "valid",
|
|
2158
|
+
birthTimeKnown: true,
|
|
2159
|
+
utcISO: iso
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
1430
2162
|
// ──────────────────────────────────────────────────────
|
|
1431
2163
|
// LEGACY — conservé pour compat pendant la migration
|
|
1432
2164
|
// ──────────────────────────────────────────────────────
|
|
@@ -1478,9 +2210,7 @@ var EphemerisService = class {
|
|
|
1478
2210
|
};
|
|
1479
2211
|
var ephemerisService = new EphemerisService();
|
|
1480
2212
|
|
|
1481
|
-
// src/
|
|
1482
|
-
process.env["EPHEMERIS_DISABLE_REDIS"] ??= "1";
|
|
1483
|
-
process.env["ASTRO_ALLOW_FALLBACK"] ??= "true";
|
|
2213
|
+
// src/astro-tools.ts
|
|
1484
2214
|
var SIGNS_FR = [
|
|
1485
2215
|
"B\xE9lier",
|
|
1486
2216
|
"Taureau",
|
|
@@ -1495,9 +2225,160 @@ var SIGNS_FR = [
|
|
|
1495
2225
|
"Verseau",
|
|
1496
2226
|
"Poissons"
|
|
1497
2227
|
];
|
|
1498
|
-
|
|
1499
|
-
|
|
2228
|
+
var JD_UNIX_EPOCH = 24405875e-1;
|
|
2229
|
+
var MS_PER_DAY = 864e5;
|
|
2230
|
+
var JULIAN_YEAR = 365.25;
|
|
2231
|
+
function jdToDate(jd2) {
|
|
2232
|
+
return new Date((jd2 - JD_UNIX_EPOCH) * MS_PER_DAY);
|
|
2233
|
+
}
|
|
2234
|
+
function dateToJd(d) {
|
|
2235
|
+
return d.getTime() / MS_PER_DAY + JD_UNIX_EPOCH;
|
|
2236
|
+
}
|
|
2237
|
+
function n3602(x) {
|
|
2238
|
+
return (x % 360 + 360) % 360;
|
|
2239
|
+
}
|
|
2240
|
+
function wrap1802(x) {
|
|
2241
|
+
return ((x + 180) % 360 + 360) % 360 - 180;
|
|
2242
|
+
}
|
|
2243
|
+
function angularSep2(a, b) {
|
|
2244
|
+
return Math.abs(wrap1802(a - b));
|
|
2245
|
+
}
|
|
2246
|
+
function signIdxOf(lon) {
|
|
2247
|
+
return Math.floor(n3602(lon) / 30) % 12;
|
|
2248
|
+
}
|
|
2249
|
+
function circularMidpoint(a, b) {
|
|
2250
|
+
return n3602(a + wrap1802(b - a) / 2);
|
|
2251
|
+
}
|
|
2252
|
+
function bodyLongitude(key, jd2) {
|
|
2253
|
+
return allPositions2(jd2)[key]?.longitude;
|
|
2254
|
+
}
|
|
2255
|
+
var NOISE_KEYS = /* @__PURE__ */ new Set([
|
|
2256
|
+
"fortune",
|
|
2257
|
+
"southNode",
|
|
2258
|
+
"ceres",
|
|
2259
|
+
"pallas",
|
|
2260
|
+
"juno",
|
|
2261
|
+
"vesta",
|
|
2262
|
+
"lilithTrue"
|
|
2263
|
+
]);
|
|
2264
|
+
function crossAspects(left, right) {
|
|
2265
|
+
const out = [];
|
|
2266
|
+
for (const [lk, lp] of Object.entries(left)) {
|
|
2267
|
+
if (NOISE_KEYS.has(lk)) continue;
|
|
2268
|
+
for (const [rk, rp] of Object.entries(right)) {
|
|
2269
|
+
if (NOISE_KEYS.has(rk)) continue;
|
|
2270
|
+
const sep2 = angularSep2(lp.longitude, rp.longitude);
|
|
2271
|
+
for (const a of ASPECT_TYPES) {
|
|
2272
|
+
const isLum = lk === "sun" || lk === "moon" || rk === "sun" || rk === "moon";
|
|
2273
|
+
const maxOrb = a.orb + (isLum ? 2 : 0);
|
|
2274
|
+
const o = Math.abs(sep2 - a.angle);
|
|
2275
|
+
if (o <= maxOrb) {
|
|
2276
|
+
out.push({
|
|
2277
|
+
a: lk,
|
|
2278
|
+
b: rk,
|
|
2279
|
+
type: a.type,
|
|
2280
|
+
typeFr: a.nameFr,
|
|
2281
|
+
symbol: a.symbol,
|
|
2282
|
+
tone: a.tone,
|
|
2283
|
+
orb: Number(o.toFixed(2)),
|
|
2284
|
+
exact: o < 1
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
return out.sort((x, y) => x.orb - y.orb);
|
|
2291
|
+
}
|
|
2292
|
+
function toneSummary(aspects) {
|
|
2293
|
+
let harmoniques = 0, tensions = 0, neutres = 0;
|
|
2294
|
+
for (const a of aspects) {
|
|
2295
|
+
if (a.tone === "h") harmoniques++;
|
|
2296
|
+
else if (a.tone === "t") tensions++;
|
|
2297
|
+
else neutres++;
|
|
2298
|
+
}
|
|
2299
|
+
return { total: aspects.length, harmoniques, tensions, neutres };
|
|
1500
2300
|
}
|
|
2301
|
+
function compositePositions(a, b) {
|
|
2302
|
+
const out = [];
|
|
2303
|
+
for (const key of Object.keys(a)) {
|
|
2304
|
+
if (NOISE_KEYS.has(key)) continue;
|
|
2305
|
+
const bp = b[key];
|
|
2306
|
+
if (!bp) continue;
|
|
2307
|
+
const lon = circularMidpoint(a[key].longitude, bp.longitude);
|
|
2308
|
+
out.push({ corps: key, longitude: Number(lon.toFixed(4)), signIdx: signIdxOf(lon) });
|
|
2309
|
+
}
|
|
2310
|
+
return out;
|
|
2311
|
+
}
|
|
2312
|
+
var MAX_ITERS = 42;
|
|
2313
|
+
function bisectZero(f, j0, j1) {
|
|
2314
|
+
let lo = j0, hi = j1;
|
|
2315
|
+
for (let i = 0; i < MAX_ITERS; i++) {
|
|
2316
|
+
const mid = (lo + hi) / 2;
|
|
2317
|
+
if (f(mid) <= 0) lo = mid;
|
|
2318
|
+
else hi = mid;
|
|
2319
|
+
}
|
|
2320
|
+
return (lo + hi) / 2;
|
|
2321
|
+
}
|
|
2322
|
+
function bisectFlip(value, j0, j1) {
|
|
2323
|
+
const v0 = value(j0);
|
|
2324
|
+
let lo = j0, hi = j1;
|
|
2325
|
+
for (let i = 0; i < MAX_ITERS; i++) {
|
|
2326
|
+
const mid = (lo + hi) / 2;
|
|
2327
|
+
if (value(mid) === v0) lo = mid;
|
|
2328
|
+
else hi = mid;
|
|
2329
|
+
}
|
|
2330
|
+
return (lo + hi) / 2;
|
|
2331
|
+
}
|
|
2332
|
+
function findLongitudeReturn(key, target, jdStart, jdEnd, step) {
|
|
2333
|
+
const f = (jd2) => wrap1802((bodyLongitude(key, jd2) ?? NaN) - target);
|
|
2334
|
+
let prev = jdStart;
|
|
2335
|
+
let fprev = f(prev);
|
|
2336
|
+
for (let jd2 = jdStart + step; jd2 <= jdEnd + 1e-9; jd2 += step) {
|
|
2337
|
+
const fcur = f(jd2);
|
|
2338
|
+
if (fprev <= 0 && fcur > 0 && fcur - fprev < 180) {
|
|
2339
|
+
return bisectZero(f, prev, jd2);
|
|
2340
|
+
}
|
|
2341
|
+
prev = jd2;
|
|
2342
|
+
fprev = fcur;
|
|
2343
|
+
}
|
|
2344
|
+
return null;
|
|
2345
|
+
}
|
|
2346
|
+
function findIngresses(key, jdStart, jdEnd, step) {
|
|
2347
|
+
const sIdx = (jd2) => signIdxOf(bodyLongitude(key, jd2) ?? NaN);
|
|
2348
|
+
const out = [];
|
|
2349
|
+
let prev = jdStart;
|
|
2350
|
+
let sPrev = sIdx(prev);
|
|
2351
|
+
for (let jd2 = jdStart + step; jd2 <= jdEnd + 1e-9; jd2 += step) {
|
|
2352
|
+
const sCur = sIdx(jd2);
|
|
2353
|
+
if (sCur !== sPrev) {
|
|
2354
|
+
const tJd = bisectFlip(sIdx, prev, jd2);
|
|
2355
|
+
out.push({ jd: tJd, deSigneIdx: sPrev, versSigneIdx: sIdx(tJd + 1e-6) });
|
|
2356
|
+
}
|
|
2357
|
+
prev = jd2;
|
|
2358
|
+
sPrev = sCur;
|
|
2359
|
+
}
|
|
2360
|
+
return out;
|
|
2361
|
+
}
|
|
2362
|
+
function findRetrogradeStations(key, jdStart, jdEnd, step) {
|
|
2363
|
+
const retro = (jd2) => isRetrograde3(key, jd2);
|
|
2364
|
+
const out = [];
|
|
2365
|
+
let prev = jdStart;
|
|
2366
|
+
let rPrev = retro(prev);
|
|
2367
|
+
for (let jd2 = jdStart + step; jd2 <= jdEnd + 1e-9; jd2 += step) {
|
|
2368
|
+
const rCur = retro(jd2);
|
|
2369
|
+
if (rCur !== rPrev) {
|
|
2370
|
+
const tJd = bisectFlip(retro, prev, jd2);
|
|
2371
|
+
out.push({ jd: tJd, station: retro(tJd + 1e-6) ? "retrograde" : "direct" });
|
|
2372
|
+
}
|
|
2373
|
+
prev = jd2;
|
|
2374
|
+
rPrev = rCur;
|
|
2375
|
+
}
|
|
2376
|
+
return out;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
// src/index.ts
|
|
2380
|
+
process.env["EPHEMERIS_DISABLE_REDIS"] ??= "1";
|
|
2381
|
+
process.env["ASTRO_ALLOW_FALLBACK"] ??= "true";
|
|
1501
2382
|
function parseUtc(datetime) {
|
|
1502
2383
|
const d = new Date(datetime);
|
|
1503
2384
|
if (Number.isNaN(d.getTime())) {
|
|
@@ -1505,6 +2386,9 @@ function parseUtc(datetime) {
|
|
|
1505
2386
|
}
|
|
1506
2387
|
return d;
|
|
1507
2388
|
}
|
|
2389
|
+
function signOf3(lon) {
|
|
2390
|
+
return SIGNS_FR[signIdxOf(lon)] ?? null;
|
|
2391
|
+
}
|
|
1508
2392
|
function formatPlanets(positions) {
|
|
1509
2393
|
return Object.values(positions).map((p) => ({
|
|
1510
2394
|
corps: p.key,
|
|
@@ -1518,9 +2402,38 @@ function formatPlanets(positions) {
|
|
|
1518
2402
|
function jsonResult(value) {
|
|
1519
2403
|
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
1520
2404
|
}
|
|
2405
|
+
var BIRTH_SHAPE = {
|
|
2406
|
+
date: z.string().describe('Date de naissance locale, format YYYY-MM-DD, ex. "1990-05-21".'),
|
|
2407
|
+
time: z.string().describe('Heure de naissance locale, format HH:mm (24 h), ex. "14:30".'),
|
|
2408
|
+
timezone: z.string().describe('Fuseau IANA du lieu de naissance, ex. "Europe/Paris".'),
|
|
2409
|
+
latitude: z.number().min(-90).max(90).describe("Latitude en degr\xE9s d\xE9cimaux (positif = Nord)."),
|
|
2410
|
+
longitude: z.number().min(-180).max(180).describe("Longitude en degr\xE9s d\xE9cimaux (positif = Est).")
|
|
2411
|
+
};
|
|
2412
|
+
var BODY_ENUM = z.enum([
|
|
2413
|
+
"sun",
|
|
2414
|
+
"moon",
|
|
2415
|
+
"mercury",
|
|
2416
|
+
"venus",
|
|
2417
|
+
"mars",
|
|
2418
|
+
"jupiter",
|
|
2419
|
+
"saturn",
|
|
2420
|
+
"uranus",
|
|
2421
|
+
"neptune",
|
|
2422
|
+
"pluto"
|
|
2423
|
+
]);
|
|
2424
|
+
var RETRO_BODY_ENUM = z.enum([
|
|
2425
|
+
"mercury",
|
|
2426
|
+
"venus",
|
|
2427
|
+
"mars",
|
|
2428
|
+
"jupiter",
|
|
2429
|
+
"saturn",
|
|
2430
|
+
"uranus",
|
|
2431
|
+
"neptune",
|
|
2432
|
+
"pluto"
|
|
2433
|
+
]);
|
|
1521
2434
|
var server = new McpServer({
|
|
1522
2435
|
name: "llmastro-ephemeris",
|
|
1523
|
-
version: "0.0
|
|
2436
|
+
version: "0.2.0"
|
|
1524
2437
|
});
|
|
1525
2438
|
server.tool(
|
|
1526
2439
|
"get_planet_positions",
|
|
@@ -1529,13 +2442,12 @@ server.tool(
|
|
|
1529
2442
|
datetime: z.string().describe('Instant en ISO 8601 UTC, ex. "2026-07-18T14:00:00Z". Sans fuseau, interpr\xE9t\xE9 en UTC.')
|
|
1530
2443
|
},
|
|
1531
2444
|
async ({ datetime }) => {
|
|
1532
|
-
const jd2 =
|
|
1533
|
-
const positions = allPositions2(jd2);
|
|
2445
|
+
const jd2 = dateToJd(parseUtc(datetime));
|
|
1534
2446
|
return jsonResult({
|
|
1535
2447
|
datetimeUTC: parseUtc(datetime).toISOString(),
|
|
1536
2448
|
jd: jd2,
|
|
1537
2449
|
moteur: getActiveEngine(),
|
|
1538
|
-
corps: formatPlanets(
|
|
2450
|
+
corps: formatPlanets(allPositions2(jd2))
|
|
1539
2451
|
});
|
|
1540
2452
|
}
|
|
1541
2453
|
);
|
|
@@ -1543,10 +2455,10 @@ server.tool(
|
|
|
1543
2455
|
"get_moon_phase",
|
|
1544
2456
|
"Phase de la Lune (nom, illumination %, description) \xE0 un instant donn\xE9, d\xE9riv\xE9e de la position r\xE9elle Soleil/Lune.",
|
|
1545
2457
|
{
|
|
1546
|
-
datetime: z.string().describe(
|
|
2458
|
+
datetime: z.string().describe('Instant en ISO 8601 UTC, ex. "2026-07-18T14:00:00Z".')
|
|
1547
2459
|
},
|
|
1548
2460
|
async ({ datetime }) => {
|
|
1549
|
-
const jd2 =
|
|
2461
|
+
const jd2 = dateToJd(parseUtc(datetime));
|
|
1550
2462
|
const mp = moonPhase2(jd2);
|
|
1551
2463
|
return jsonResult({
|
|
1552
2464
|
datetimeUTC: parseUtc(datetime).toISOString(),
|
|
@@ -1560,76 +2472,347 @@ server.tool(
|
|
|
1560
2472
|
}
|
|
1561
2473
|
);
|
|
1562
2474
|
server.tool(
|
|
1563
|
-
"
|
|
1564
|
-
"
|
|
2475
|
+
"get_current_sky",
|
|
2476
|
+
"Le ciel \xE0 l'instant pr\xE9sent (transits du moment) pour un lieu donn\xE9 : positions en maisons + aspects.",
|
|
1565
2477
|
{
|
|
1566
|
-
date: z.string().describe('Date de naissance locale, format YYYY-MM-DD, ex. "1990-05-21".'),
|
|
1567
|
-
time: z.string().describe('Heure de naissance locale, format HH:mm (24 h), ex. "14:30".'),
|
|
1568
|
-
timezone: z.string().describe('Fuseau IANA du lieu de naissance, ex. "Europe/Paris", "America/New_York".'),
|
|
1569
2478
|
latitude: z.number().min(-90).max(90).describe("Latitude en degr\xE9s d\xE9cimaux (positif = Nord)."),
|
|
1570
2479
|
longitude: z.number().min(-180).max(180).describe("Longitude en degr\xE9s d\xE9cimaux (positif = Est).")
|
|
1571
2480
|
},
|
|
1572
|
-
async ({
|
|
2481
|
+
async ({ latitude, longitude }) => {
|
|
2482
|
+
const chart = computeCurrentSky2(latitude, longitude);
|
|
2483
|
+
return jsonResult({
|
|
2484
|
+
instant: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2485
|
+
jd: chart.JD,
|
|
2486
|
+
moteur: getActiveEngine(),
|
|
2487
|
+
retrogrades: chart.retrogrades,
|
|
2488
|
+
phaseLunaire: { phase: chart.moonPhase.phase, illumination: chart.moonPhase.illumination },
|
|
2489
|
+
corps: formatPlanets(chart.planets),
|
|
2490
|
+
aspects: chart.aspects
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
);
|
|
2494
|
+
server.tool(
|
|
2495
|
+
"get_aspects",
|
|
2496
|
+
"Aspects (majeurs et mineurs) entre les plan\xE8tes \xE0 un instant donn\xE9, avec orbe et tonalit\xE9. Grille d'aspects calcul\xE9e par le moteur, pas estim\xE9e.",
|
|
2497
|
+
{
|
|
2498
|
+
datetime: z.string().describe('Instant en ISO 8601 UTC, ex. "2026-07-18T14:00:00Z".')
|
|
2499
|
+
},
|
|
2500
|
+
async ({ datetime }) => {
|
|
2501
|
+
const jd2 = dateToJd(parseUtc(datetime));
|
|
2502
|
+
return jsonResult({
|
|
2503
|
+
datetimeUTC: parseUtc(datetime).toISOString(),
|
|
2504
|
+
jd: jd2,
|
|
2505
|
+
moteur: getActiveEngine(),
|
|
2506
|
+
aspects: calculateAspects(allPositions2(jd2))
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
);
|
|
2510
|
+
server.tool(
|
|
2511
|
+
"get_natal_chart",
|
|
2512
|
+
"Th\xE8me natal complet : positions en maisons, aspects, ASC/MC, phase lunaire, Lots herm\xE9tiques. Prend une date/heure LOCALE + fuseau IANA + coordonn\xE9es. Zodiaque tropical (d\xE9faut) ou sid\xE9ral (Lahiri), et syst\xE8me de maisons au choix (Placidus par d\xE9faut).",
|
|
2513
|
+
{
|
|
2514
|
+
...BIRTH_SHAPE,
|
|
2515
|
+
zodiac: z.enum(["tropical", "sidereal"]).optional().describe('Zodiaque : "tropical" (d\xE9faut, occidental) ou "sidereal" (ayanamsa Lahiri).'),
|
|
2516
|
+
houseSystem: z.enum(["placidus", "equal", "whole_sign", "koch", "porphyry", "regiomontanus", "campanus"]).optional().describe('Syst\xE8me de maisons (d\xE9faut "placidus"). Regiomontanus/Campanus/Koch exigent le moteur swisseph ; sinon repli Placidus signal\xE9 par le champ systemeMaisons retourn\xE9.')
|
|
2517
|
+
},
|
|
2518
|
+
async ({ date, time, timezone, latitude, longitude, zodiac, houseSystem }) => {
|
|
1573
2519
|
const { jdUT, utcISO, offsetMinutes } = localToUTC(date, time, timezone);
|
|
1574
|
-
const
|
|
2520
|
+
const opts = {};
|
|
2521
|
+
if (zodiac) opts.zodiac = zodiac;
|
|
2522
|
+
if (houseSystem) opts.houseSystem = houseSystem;
|
|
2523
|
+
const chart = computeChartFromJD2(jdUT, latitude, longitude, opts);
|
|
1575
2524
|
return jsonResult({
|
|
1576
2525
|
naissance: { date, time, timezone, latitude, longitude },
|
|
1577
2526
|
utcISO,
|
|
1578
2527
|
offsetMinutes,
|
|
1579
2528
|
jd: jdUT,
|
|
1580
2529
|
moteur: getActiveEngine(),
|
|
1581
|
-
ascendant: {
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
},
|
|
1589
|
-
systemeMaisons: chart.houseSystem,
|
|
2530
|
+
ascendant: { longitude: Number(chart.houses.asc?.toFixed(4)), signe: signOf3(chart.houses.asc ?? 0) },
|
|
2531
|
+
milieuDuCiel: { longitude: Number(chart.houses.mc?.toFixed(4)), signe: signOf3(chart.houses.mc ?? 0) },
|
|
2532
|
+
// systemeMaisons = ce qui a RÉELLEMENT été calculé (chart.houses.system).
|
|
2533
|
+
// Diffère du demandé quand un système swiss-only (koch/regiomontanus/
|
|
2534
|
+
// campanus) retombe sur Placidus en mode AstraCore — honnêteté explicite.
|
|
2535
|
+
systemeMaisons: chart.houses.system,
|
|
2536
|
+
systemeMaisonsDemande: chart.houseSystem,
|
|
1590
2537
|
zodiaque: chart.zodiac,
|
|
2538
|
+
ayanamsa: chart.ayanamsa,
|
|
1591
2539
|
phaseLunaire: { phase: chart.moonPhase.phase, illumination: chart.moonPhase.illumination },
|
|
1592
2540
|
retrogrades: chart.retrogrades,
|
|
1593
2541
|
corps: formatPlanets(chart.planets),
|
|
1594
|
-
aspects: chart.aspects
|
|
2542
|
+
aspects: chart.aspects,
|
|
2543
|
+
lotsHermetiques: chart.lots
|
|
1595
2544
|
});
|
|
1596
2545
|
}
|
|
1597
2546
|
);
|
|
1598
2547
|
server.tool(
|
|
1599
|
-
"
|
|
1600
|
-
"
|
|
2548
|
+
"get_transits",
|
|
2549
|
+
"Transits crois\xE9s au th\xE8me natal : aspects entre les plan\xE8tes qui transitent (aujourd'hui ou \xE0 une date donn\xE9e) et les plan\xE8tes de naissance, tri\xE9s par orbe. Le c\u0153ur des \xAB pr\xE9visions \xBB.",
|
|
1601
2550
|
{
|
|
1602
|
-
|
|
1603
|
-
|
|
2551
|
+
...BIRTH_SHAPE,
|
|
2552
|
+
datetime: z.string().optional().describe('Instant du transit en ISO UTC (d\xE9faut : maintenant), ex. "2026-07-19T09:00:00Z".')
|
|
1604
2553
|
},
|
|
1605
|
-
async ({ latitude, longitude }) => {
|
|
1606
|
-
const
|
|
2554
|
+
async ({ date, time, timezone, latitude, longitude, datetime }) => {
|
|
2555
|
+
const { jdUT: natalJd } = localToUTC(date, time, timezone);
|
|
2556
|
+
const natal = computeChartFromJD2(natalJd, latitude, longitude).planets;
|
|
2557
|
+
const transitDate = datetime ? parseUtc(datetime) : /* @__PURE__ */ new Date();
|
|
2558
|
+
const transit = allPositions2(dateToJd(transitDate));
|
|
2559
|
+
const aspects = crossAspects(transit, natal);
|
|
1607
2560
|
return jsonResult({
|
|
1608
|
-
|
|
1609
|
-
|
|
2561
|
+
naissance: { date, time, timezone },
|
|
2562
|
+
instantTransit: transitDate.toISOString(),
|
|
1610
2563
|
moteur: getActiveEngine(),
|
|
1611
|
-
|
|
1612
|
-
|
|
2564
|
+
nbAspects: aspects.length,
|
|
2565
|
+
aspects: aspects.map((a) => ({
|
|
2566
|
+
planeteTransit: a.a,
|
|
2567
|
+
aspect: a.typeFr,
|
|
2568
|
+
symbole: a.symbol,
|
|
2569
|
+
planeteNatale: a.b,
|
|
2570
|
+
orbe: a.orb,
|
|
2571
|
+
exact: a.exact
|
|
2572
|
+
}))
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
);
|
|
2576
|
+
server.tool(
|
|
2577
|
+
"get_synastry",
|
|
2578
|
+
"Synastrie : aspects inter-th\xE8mes entre deux personnes (plan\xE8tes A \xD7 plan\xE8tes B), tri\xE9s par orbe, avec un r\xE9sum\xE9 harmonie/tension. R\xE9utilise la table d'aspects canonique du moteur.",
|
|
2579
|
+
{
|
|
2580
|
+
personA: z.object(BIRTH_SHAPE).describe("Naissance de la personne A."),
|
|
2581
|
+
personB: z.object(BIRTH_SHAPE).describe("Naissance de la personne B.")
|
|
2582
|
+
},
|
|
2583
|
+
async ({ personA, personB }) => {
|
|
2584
|
+
const a = computeChartFromJD2(localToUTC(personA.date, personA.time, personA.timezone).jdUT, personA.latitude, personA.longitude).planets;
|
|
2585
|
+
const b = computeChartFromJD2(localToUTC(personB.date, personB.time, personB.timezone).jdUT, personB.latitude, personB.longitude).planets;
|
|
2586
|
+
const aspects = crossAspects(a, b);
|
|
2587
|
+
return jsonResult({
|
|
2588
|
+
moteur: getActiveEngine(),
|
|
2589
|
+
resume: toneSummary(aspects),
|
|
2590
|
+
aspects: aspects.map((x) => ({
|
|
2591
|
+
planeteA: x.a,
|
|
2592
|
+
aspect: x.typeFr,
|
|
2593
|
+
symbole: x.symbol,
|
|
2594
|
+
tonalite: x.tone,
|
|
2595
|
+
planeteB: x.b,
|
|
2596
|
+
orbe: x.orb,
|
|
2597
|
+
exact: x.exact
|
|
2598
|
+
}))
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2601
|
+
);
|
|
2602
|
+
server.tool(
|
|
2603
|
+
"get_composite_chart",
|
|
2604
|
+
"Th\xE8me composite par mi-points : pour chaque corps, le point milieu circulaire entre les deux th\xE8mes (convention Robert Hand). Repr\xE9sente la relation elle-m\xEAme.",
|
|
2605
|
+
{
|
|
2606
|
+
personA: z.object(BIRTH_SHAPE).describe("Naissance de la personne A."),
|
|
2607
|
+
personB: z.object(BIRTH_SHAPE).describe("Naissance de la personne B.")
|
|
2608
|
+
},
|
|
2609
|
+
async ({ personA, personB }) => {
|
|
2610
|
+
const a = computeChartFromJD2(localToUTC(personA.date, personA.time, personA.timezone).jdUT, personA.latitude, personA.longitude).planets;
|
|
2611
|
+
const b = computeChartFromJD2(localToUTC(personB.date, personB.time, personB.timezone).jdUT, personB.latitude, personB.longitude).planets;
|
|
2612
|
+
return jsonResult({
|
|
2613
|
+
moteur: getActiveEngine(),
|
|
2614
|
+
corpsComposite: compositePositions(a, b).map((c) => ({
|
|
2615
|
+
corps: c.corps,
|
|
2616
|
+
longitude: c.longitude,
|
|
2617
|
+
signe: SIGNS_FR[c.signIdx] ?? null
|
|
2618
|
+
}))
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
);
|
|
2622
|
+
server.tool(
|
|
2623
|
+
"get_secondary_progressions",
|
|
2624
|
+
"Progressions secondaires (m\xE9thode \xAB un jour apr\xE8s la naissance = une ann\xE9e de vie \xBB) : positions plan\xE9taires progress\xE9es \xE0 une date cible. La Lune progress\xE9e (~1 signe / 2,5 ans) est la plus lue.",
|
|
2625
|
+
{
|
|
2626
|
+
...BIRTH_SHAPE,
|
|
2627
|
+
targetDate: z.string().describe("Date pour laquelle progresser le th\xE8me, format YYYY-MM-DD.")
|
|
2628
|
+
},
|
|
2629
|
+
async ({ date, time, timezone, latitude, longitude, targetDate }) => {
|
|
2630
|
+
const { jdUT: natalJd } = localToUTC(date, time, timezone);
|
|
2631
|
+
const targetJd = dateToJd(parseUtc(targetDate + "T00:00:00Z"));
|
|
2632
|
+
const ageYears = (targetJd - natalJd) / JULIAN_YEAR;
|
|
2633
|
+
const progressedJd = natalJd + ageYears;
|
|
2634
|
+
const prog = allPositions2(progressedJd);
|
|
2635
|
+
return jsonResult({
|
|
2636
|
+
naissance: { date, time, timezone, latitude, longitude },
|
|
2637
|
+
dateCible: targetDate,
|
|
2638
|
+
ageAnnees: Number(ageYears.toFixed(2)),
|
|
2639
|
+
jdProgresse: progressedJd,
|
|
2640
|
+
moteur: getActiveEngine(),
|
|
2641
|
+
note: "Positions progress\xE9es (jour = an). Les maisons progress\xE9es ne sont pas incluses (m\xE9thode variable selon l'\xE9cole).",
|
|
2642
|
+
corpsProgresses: formatPlanets(prog)
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
);
|
|
2646
|
+
server.tool(
|
|
2647
|
+
"get_solar_return",
|
|
2648
|
+
"Retour solaire d'une ann\xE9e donn\xE9e : l'instant exact o\xF9 le Soleil retrouve sa longitude natale, et le th\xE8me dress\xE9 \xE0 cet instant (pour le lieu de r\xE9sidence si fourni, sinon lieu de naissance).",
|
|
2649
|
+
{
|
|
2650
|
+
...BIRTH_SHAPE,
|
|
2651
|
+
year: z.number().int().describe("Ann\xE9e du retour solaire, ex. 2026."),
|
|
2652
|
+
returnLatitude: z.number().min(-90).max(90).optional().describe("Latitude du lieu au moment du retour (d\xE9faut : lieu de naissance)."),
|
|
2653
|
+
returnLongitude: z.number().min(-180).max(180).optional().describe("Longitude du lieu au moment du retour (d\xE9faut : lieu de naissance).")
|
|
2654
|
+
},
|
|
2655
|
+
async ({ date, time, timezone, latitude, longitude, year, returnLatitude, returnLongitude }) => {
|
|
2656
|
+
const { jdUT: natalJd } = localToUTC(date, time, timezone);
|
|
2657
|
+
const natalSun = bodyLongitude("sun", natalJd);
|
|
2658
|
+
if (natalSun == null) throw new Error("Soleil natal introuvable (moteur).");
|
|
2659
|
+
const natalYear = Number(date.slice(0, 4));
|
|
2660
|
+
const approx = natalJd + (year - natalYear) * JULIAN_YEAR;
|
|
2661
|
+
const returnJd = findLongitudeReturn("sun", natalSun, approx - 5, approx + 5, 0.5);
|
|
2662
|
+
if (returnJd == null) throw new Error(`Retour solaire ${year} introuvable dans la fen\xEAtre calcul\xE9e.`);
|
|
2663
|
+
const lat = returnLatitude ?? latitude;
|
|
2664
|
+
const lng = returnLongitude ?? longitude;
|
|
2665
|
+
const chart = computeChartFromJD2(returnJd, lat, lng);
|
|
2666
|
+
return jsonResult({
|
|
2667
|
+
naissance: { date, time, timezone },
|
|
2668
|
+
annee: year,
|
|
2669
|
+
lieuRetour: { latitude: lat, longitude: lng },
|
|
2670
|
+
instantRetourUTC: jdToDate(returnJd).toISOString(),
|
|
2671
|
+
jd: returnJd,
|
|
2672
|
+
moteur: getActiveEngine(),
|
|
2673
|
+
ascendant: { longitude: Number(chart.houses.asc?.toFixed(4)), signe: signOf3(chart.houses.asc ?? 0) },
|
|
2674
|
+
milieuDuCiel: { longitude: Number(chart.houses.mc?.toFixed(4)), signe: signOf3(chart.houses.mc ?? 0) },
|
|
1613
2675
|
corps: formatPlanets(chart.planets),
|
|
1614
2676
|
aspects: chart.aspects
|
|
1615
2677
|
});
|
|
1616
2678
|
}
|
|
1617
2679
|
);
|
|
2680
|
+
server.tool(
|
|
2681
|
+
"get_lunar_return",
|
|
2682
|
+
"Retour lunaire : le prochain instant, apr\xE8s une date de r\xE9f\xE9rence, o\xF9 la Lune retrouve sa longitude natale (~tous les 27,3 jours), et le th\xE8me \xE0 cet instant.",
|
|
2683
|
+
{
|
|
2684
|
+
...BIRTH_SHAPE,
|
|
2685
|
+
after: z.string().optional().describe("Chercher le retour apr\xE8s cet instant ISO UTC (d\xE9faut : maintenant).")
|
|
2686
|
+
},
|
|
2687
|
+
async ({ date, time, timezone, latitude, longitude, after }) => {
|
|
2688
|
+
const { jdUT: natalJd } = localToUTC(date, time, timezone);
|
|
2689
|
+
const natalMoon = bodyLongitude("moon", natalJd);
|
|
2690
|
+
if (natalMoon == null) throw new Error("Lune natale introuvable (moteur).");
|
|
2691
|
+
const fromJd = dateToJd(after ? parseUtc(after) : /* @__PURE__ */ new Date());
|
|
2692
|
+
const returnJd = findLongitudeReturn("moon", natalMoon, fromJd, fromJd + 30, 0.25);
|
|
2693
|
+
if (returnJd == null) throw new Error("Retour lunaire introuvable dans les 30 jours.");
|
|
2694
|
+
const chart = computeChartFromJD2(returnJd, latitude, longitude);
|
|
2695
|
+
return jsonResult({
|
|
2696
|
+
naissance: { date, time, timezone },
|
|
2697
|
+
apres: jdToDate(fromJd).toISOString(),
|
|
2698
|
+
instantRetourUTC: jdToDate(returnJd).toISOString(),
|
|
2699
|
+
jd: returnJd,
|
|
2700
|
+
moteur: getActiveEngine(),
|
|
2701
|
+
ascendant: { longitude: Number(chart.houses.asc?.toFixed(4)), signe: signOf3(chart.houses.asc ?? 0) },
|
|
2702
|
+
corps: formatPlanets(chart.planets)
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
);
|
|
2706
|
+
server.tool(
|
|
2707
|
+
"get_ingresses",
|
|
2708
|
+
"Ingr\xE8s : dates auxquelles un corps change de signe sur une plage donn\xE9e (entr\xE9es/sorties de signe), robuste aux passages r\xE9trogrades.",
|
|
2709
|
+
{
|
|
2710
|
+
body: BODY_ENUM.describe("Corps \xE0 suivre."),
|
|
2711
|
+
start: z.string().describe('D\xE9but de la plage, ISO UTC, ex. "2026-01-01T00:00:00Z".'),
|
|
2712
|
+
end: z.string().describe("Fin de la plage, ISO UTC. Plage max conseill\xE9e : quelques ann\xE9es.")
|
|
2713
|
+
},
|
|
2714
|
+
async ({ body, start, end }) => {
|
|
2715
|
+
const j0 = dateToJd(parseUtc(start));
|
|
2716
|
+
const j1 = dateToJd(parseUtc(end));
|
|
2717
|
+
if (j1 <= j0) throw new Error("`end` doit \xEAtre post\xE9rieur \xE0 `start`.");
|
|
2718
|
+
const step = body === "moon" ? 0.25 : 1;
|
|
2719
|
+
const events = findIngresses(body, j0, j1, step);
|
|
2720
|
+
return jsonResult({
|
|
2721
|
+
corps: body,
|
|
2722
|
+
plage: { start: parseUtc(start).toISOString(), end: parseUtc(end).toISOString() },
|
|
2723
|
+
moteur: getActiveEngine(),
|
|
2724
|
+
nbIngres: events.length,
|
|
2725
|
+
ingres: events.map((e) => ({
|
|
2726
|
+
dateUTC: jdToDate(e.jd).toISOString(),
|
|
2727
|
+
deSigne: SIGNS_FR[e.deSigneIdx] ?? null,
|
|
2728
|
+
versSigne: SIGNS_FR[e.versSigneIdx] ?? null
|
|
2729
|
+
}))
|
|
2730
|
+
});
|
|
2731
|
+
}
|
|
2732
|
+
);
|
|
2733
|
+
server.tool(
|
|
2734
|
+
"get_retrograde_windows",
|
|
2735
|
+
"Stations de r\xE9trogradation : dates auxquelles un corps change de direction (station r\xE9trograde ou directe) sur une plage donn\xE9e. Le Soleil et la Lune ne r\xE9trogradent jamais.",
|
|
2736
|
+
{
|
|
2737
|
+
body: RETRO_BODY_ENUM.describe("Corps \xE0 suivre (Mercure\u2192Pluton)."),
|
|
2738
|
+
start: z.string().describe("D\xE9but de la plage, ISO UTC."),
|
|
2739
|
+
end: z.string().describe("Fin de la plage, ISO UTC.")
|
|
2740
|
+
},
|
|
2741
|
+
async ({ body, start, end }) => {
|
|
2742
|
+
const j0 = dateToJd(parseUtc(start));
|
|
2743
|
+
const j1 = dateToJd(parseUtc(end));
|
|
2744
|
+
if (j1 <= j0) throw new Error("`end` doit \xEAtre post\xE9rieur \xE0 `start`.");
|
|
2745
|
+
const stations = findRetrogradeStations(body, j0, j1, 0.5);
|
|
2746
|
+
return jsonResult({
|
|
2747
|
+
corps: body,
|
|
2748
|
+
plage: { start: parseUtc(start).toISOString(), end: parseUtc(end).toISOString() },
|
|
2749
|
+
moteur: getActiveEngine(),
|
|
2750
|
+
nbStations: stations.length,
|
|
2751
|
+
stations: stations.map((s) => ({
|
|
2752
|
+
dateUTC: jdToDate(s.jd).toISOString(),
|
|
2753
|
+
station: s.station === "retrograde" ? "r\xE9trograde" : "directe"
|
|
2754
|
+
}))
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
);
|
|
2758
|
+
server.tool(
|
|
2759
|
+
"get_eclipse_details",
|
|
2760
|
+
"D\xE9tail d'une \xE9clipse (magnitude, obscuration, type, s\xE9rie de Saros) pour une date d'\xE9clipse connue. N\xE9cessite le moteur swisseph (retourne null en mode AstraCore).",
|
|
2761
|
+
{
|
|
2762
|
+
datetime: z.string().describe(`Instant de l'\xE9clipse en ISO UTC, ex. "2026-08-12T17:46:00Z".`),
|
|
2763
|
+
kind: z.enum(["solar", "lunar"]).describe("Type d'\xE9clipse \xE0 d\xE9tailler.")
|
|
2764
|
+
},
|
|
2765
|
+
async ({ datetime, kind }) => {
|
|
2766
|
+
const jd2 = dateToJd(parseUtc(datetime));
|
|
2767
|
+
const details = kind === "solar" ? computeSolarEclipseDetailsSwiss(jd2) : computeLunarEclipseDetailsSwiss(jd2);
|
|
2768
|
+
return jsonResult({
|
|
2769
|
+
datetimeUTC: parseUtc(datetime).toISOString(),
|
|
2770
|
+
jd: jd2,
|
|
2771
|
+
moteur: getActiveEngine(),
|
|
2772
|
+
type: kind,
|
|
2773
|
+
details,
|
|
2774
|
+
note: details == null ? "D\xE9tail indisponible : moteur AstraCore actif (swisseph requis) ou date sans \xE9clipse. Passe en ASTRO_ENGINE=swisseph." : void 0
|
|
2775
|
+
});
|
|
2776
|
+
}
|
|
2777
|
+
);
|
|
2778
|
+
server.tool(
|
|
2779
|
+
"get_next_eclipse",
|
|
2780
|
+
"Trouve la prochaine (ou pr\xE9c\xE9dente) \xE9clipse solaire ou lunaire \xE0 partir d'une date : instant du maximum, type, et fen\xEAtres de contact. N\xE9cessite le moteur swisseph (retourne null en AstraCore).",
|
|
2781
|
+
{
|
|
2782
|
+
from: z.string().describe('Chercher \xE0 partir de cet instant ISO UTC, ex. "2026-01-01T00:00:00Z".'),
|
|
2783
|
+
kind: z.enum(["solar", "lunar"]).describe("Type d'\xE9clipse \xE0 chercher."),
|
|
2784
|
+
backward: z.boolean().optional().describe("true = chercher la pr\xE9c\xE9dente au lieu de la prochaine (d\xE9faut false).")
|
|
2785
|
+
},
|
|
2786
|
+
async ({ from, kind, backward }) => {
|
|
2787
|
+
const jd2 = dateToJd(parseUtc(from));
|
|
2788
|
+
const back = backward ?? false;
|
|
2789
|
+
const ecl = kind === "solar" ? findNextSolarEclipseSwiss(jd2, back) : findNextLunarEclipseSwiss(jd2, back);
|
|
2790
|
+
return jsonResult({
|
|
2791
|
+
depuis: parseUtc(from).toISOString(),
|
|
2792
|
+
sens: back ? "pr\xE9c\xE9dente" : "prochaine",
|
|
2793
|
+
type: kind,
|
|
2794
|
+
moteur: getActiveEngine(),
|
|
2795
|
+
eclipse: ecl == null ? null : {
|
|
2796
|
+
...ecl,
|
|
2797
|
+
maximumUTC: jdToDate(ecl.jdMaximum).toISOString()
|
|
2798
|
+
},
|
|
2799
|
+
note: ecl == null ? "Recherche indisponible : moteur AstraCore actif (swisseph requis). Passe en ASTRO_ENGINE=swisseph." : void 0
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
);
|
|
1618
2803
|
server.tool(
|
|
1619
2804
|
"get_astrocartography",
|
|
1620
|
-
"Astrocartographie \xE0 un instant donn\xE9 : pour chaque corps, les m\xE9ridiens MC/IC et
|
|
2805
|
+
"Astrocartographie \xE0 un instant donn\xE9 : pour chaque corps, les m\xE9ridiens MC/IC et le nombre de points des courbes lever/coucher, plus les parans (croisements de lignes).",
|
|
1621
2806
|
{
|
|
1622
|
-
datetime: z.string().describe('Instant en ISO 8601 UTC, ex. "2026-07-18T14:00:00Z".
|
|
2807
|
+
datetime: z.string().describe('Instant en ISO 8601 UTC, ex. "2026-07-18T14:00:00Z".')
|
|
1623
2808
|
},
|
|
1624
2809
|
async ({ datetime }) => {
|
|
1625
|
-
const jd2 =
|
|
2810
|
+
const jd2 = dateToJd(parseUtc(datetime));
|
|
1626
2811
|
const acg = computeAstrocartography(jd2);
|
|
1627
2812
|
return jsonResult({
|
|
1628
2813
|
datetimeUTC: parseUtc(datetime).toISOString(),
|
|
1629
2814
|
jd: jd2,
|
|
1630
2815
|
moteur: getActiveEngine(),
|
|
1631
|
-
// Résumé lisible : on omet les tableaux de points (courbes échantillonnées),
|
|
1632
|
-
// on garde les méridiens et le nombre de points par courbe.
|
|
1633
2816
|
lignes: acg.lines.map((l) => ({
|
|
1634
2817
|
corps: l.key,
|
|
1635
2818
|
meridienMC: Number(l.mcLng?.toFixed(4)),
|
|
@@ -1648,58 +2831,22 @@ server.tool(
|
|
|
1648
2831
|
});
|
|
1649
2832
|
}
|
|
1650
2833
|
);
|
|
1651
|
-
var NOISE_KEYS = /* @__PURE__ */ new Set(["fortune", "southNode", "ceres", "pallas", "juno", "vesta", "lilithTrue"]);
|
|
1652
|
-
function crossAspects(transit, natal) {
|
|
1653
|
-
const out = [];
|
|
1654
|
-
for (const [tk, tp] of Object.entries(transit)) {
|
|
1655
|
-
if (NOISE_KEYS.has(tk)) continue;
|
|
1656
|
-
for (const [nk, np] of Object.entries(natal)) {
|
|
1657
|
-
if (NOISE_KEYS.has(nk)) continue;
|
|
1658
|
-
let d = Math.abs(tp.longitude - np.longitude);
|
|
1659
|
-
if (d > 180) d = 360 - d;
|
|
1660
|
-
for (const a of ASPECT_TYPES) {
|
|
1661
|
-
const isLum = tk === "sun" || tk === "moon" || nk === "sun" || nk === "moon";
|
|
1662
|
-
const maxOrb = a.orb + (isLum ? 2 : 0);
|
|
1663
|
-
const o = Math.abs(d - a.angle);
|
|
1664
|
-
if (o <= maxOrb) {
|
|
1665
|
-
out.push({
|
|
1666
|
-
planeteTransit: tk,
|
|
1667
|
-
aspect: a.nameFr,
|
|
1668
|
-
symbole: a.symbol,
|
|
1669
|
-
planeteNatale: nk,
|
|
1670
|
-
orbe: Number(o.toFixed(2)),
|
|
1671
|
-
exact: o < 1
|
|
1672
|
-
});
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
return out.sort((x, y) => x.orbe - y.orbe);
|
|
1678
|
-
}
|
|
1679
2834
|
server.tool(
|
|
1680
|
-
"
|
|
1681
|
-
"
|
|
2835
|
+
"get_life_path",
|
|
2836
|
+
"Nombre du chemin de vie (num\xE9rologie pythagoricienne) calcul\xE9 depuis la date de naissance locale. Fait d\xE9terministe, jamais estim\xE9 par le mod\xE8le.",
|
|
1682
2837
|
{
|
|
1683
|
-
date: z.string().describe("Date de naissance locale, YYYY-MM-DD.")
|
|
1684
|
-
time: z.string().describe("Heure de naissance locale, HH:mm."),
|
|
1685
|
-
timezone: z.string().describe('Fuseau IANA du lieu de naissance, ex. "Europe/Paris".'),
|
|
1686
|
-
latitude: z.number().min(-90).max(90).describe("Latitude de naissance (deg d\xE9cimaux)."),
|
|
1687
|
-
longitude: z.number().min(-180).max(180).describe("Longitude de naissance (deg d\xE9cimaux)."),
|
|
1688
|
-
datetime: z.string().optional().describe('Instant du transit en ISO UTC (d\xE9faut : maintenant), ex. "2026-07-19T09:00:00Z".')
|
|
2838
|
+
date: z.string().describe("Date de naissance locale, format YYYY-MM-DD.")
|
|
1689
2839
|
},
|
|
1690
|
-
async ({ date
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
nbAspects: aspects.length,
|
|
1701
|
-
aspects
|
|
1702
|
-
});
|
|
2840
|
+
async ({ date }) => {
|
|
2841
|
+
return jsonResult({ date, cheminDeVie: computeLifePath(date) });
|
|
2842
|
+
}
|
|
2843
|
+
);
|
|
2844
|
+
server.tool(
|
|
2845
|
+
"get_engine_diagnostic",
|
|
2846
|
+
"Diagnostic du moteur de calcul : quel moteur est actif (swisseph sub-arcseconde vs AstraCore), si l'addon natif a charg\xE9, et pourquoi. Utile pour v\xE9rifier la pr\xE9cision dont vous b\xE9n\xE9ficiez.",
|
|
2847
|
+
{},
|
|
2848
|
+
async () => {
|
|
2849
|
+
return jsonResult({ moteur: getActiveEngine(), diagnostic: getEngineDiagnostic() });
|
|
1703
2850
|
}
|
|
1704
2851
|
);
|
|
1705
2852
|
var HORIZONS_API = "https://ssd.jpl.nasa.gov/api/horizons.api";
|
|
@@ -1758,10 +2905,10 @@ server.tool(
|
|
|
1758
2905
|
"Preuve d'exactitude : compare nos positions plan\xE9taires \xE0 la r\xE9f\xE9rence mondiale JPL Horizons (NASA) au m\xEAme instant, et donne l'\xE9cart en secondes d'arc. N\xE9cessite un acc\xE8s r\xE9seau.",
|
|
1759
2906
|
{
|
|
1760
2907
|
datetime: z.string().describe('Instant en ISO 8601 UTC, ex. "2026-07-19T00:00:00Z".'),
|
|
1761
|
-
bodies: z.array(
|
|
2908
|
+
bodies: z.array(BODY_ENUM).optional().describe("Sous-ensemble de corps \xE0 valider (d\xE9faut : les 10 classiques).")
|
|
1762
2909
|
},
|
|
1763
2910
|
async ({ datetime, bodies }) => {
|
|
1764
|
-
const jd2 =
|
|
2911
|
+
const jd2 = dateToJd(parseUtc(datetime));
|
|
1765
2912
|
const ours = allPositions2(jd2);
|
|
1766
2913
|
const keys = bodies ?? Object.keys(HORIZONS_CMD);
|
|
1767
2914
|
const rows = await Promise.all(
|