caelus 0.11.0 → 0.13.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.
Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/accuracy.json +1 -1
  3. package/dist/src/chart.d.ts +193 -13
  4. package/dist/src/chart.js +163 -13
  5. package/dist/src/compiler.d.ts +44 -3
  6. package/dist/src/compiler.js +44 -3
  7. package/dist/src/core.d.ts +33 -2
  8. package/dist/src/core.js +33 -2
  9. package/dist/src/derived.d.ts +91 -2
  10. package/dist/src/derived.js +91 -2
  11. package/dist/src/directions.d.ts +27 -0
  12. package/dist/src/directions.js +69 -0
  13. package/dist/src/eclipses.d.ts +17 -1
  14. package/dist/src/eclipses.js +17 -1
  15. package/dist/src/events.d.ts +53 -7
  16. package/dist/src/events.js +53 -7
  17. package/dist/src/features.d.ts +69 -7
  18. package/dist/src/features.js +69 -7
  19. package/dist/src/firdaria.d.ts +49 -0
  20. package/dist/src/firdaria.js +62 -0
  21. package/dist/src/houses.d.ts +13 -4
  22. package/dist/src/houses.js +13 -4
  23. package/dist/src/index.d.ts +9 -0
  24. package/dist/src/index.js +9 -0
  25. package/dist/src/lots.d.ts +18 -0
  26. package/dist/src/lots.js +52 -0
  27. package/dist/src/pheno.d.ts +16 -2
  28. package/dist/src/pheno.js +16 -2
  29. package/dist/src/profections.d.ts +27 -0
  30. package/dist/src/profections.js +49 -0
  31. package/dist/src/query.d.ts +47 -6
  32. package/dist/src/query.js +47 -6
  33. package/dist/src/releasing.d.ts +32 -0
  34. package/dist/src/releasing.js +109 -0
  35. package/dist/src/turbo.d.ts +35 -1
  36. package/dist/src/turbo.js +35 -1
  37. package/dist/src/vargas.d.ts +32 -0
  38. package/dist/src/vargas.js +52 -0
  39. package/dist/src/vedic.d.ts +66 -0
  40. package/dist/src/vedic.js +101 -0
  41. package/dist/src/yogas.d.ts +26 -0
  42. package/dist/src/yogas.js +39 -0
  43. package/dist/src/yogini.d.ts +54 -0
  44. package/dist/src/yogini.js +63 -0
  45. package/package.json +1 -1
@@ -1,19 +1,63 @@
1
1
  import { Engine, BodyId, Zodiac } from "./chart.js";
2
2
  import { RankedMoment } from "./scan.js";
3
3
  export declare const DEFAULT_BODIES: string[];
4
- /** Flat vector [w*cos(lon), w*sin(lon), ...] for the given (longitude, weight)
5
- * pairs, in order. */
4
+ /**
5
+ * Build a feature vector from explicit `(longitude, weight)` pairs: each pair
6
+ * contributes a weighted unit-circle point `[w·cos(lon), w·sin(lon)]`. The
7
+ * low-level primitive behind {@link chartFeatures}; most callers want that.
8
+ *
9
+ * @param weightedLons `[longitudeDeg, weight]` pairs, in the order they should
10
+ * appear in the vector.
11
+ * @returns A flat vector, two entries per pair.
12
+ */
6
13
  export declare function featureVector(weightedLons: [number, number][]): number[];
7
- /** Cosine similarity of two feature vectors, in [-1, 1]. */
14
+ /**
15
+ * Cosine similarity of two feature vectors, in `[-1, 1]`. For vectors from
16
+ * {@link chartFeatures} this is a weighted mean of `cos(Δlongitude)` per body:
17
+ * `1` when the configurations coincide, falling off as bodies diverge.
18
+ *
19
+ * @param a First feature vector.
20
+ * @param b Second feature vector (compared over the shorter length).
21
+ * @returns Similarity in `[-1, 1]`; `0` if either vector is all zeros.
22
+ */
8
23
  export declare function cosineSimilarity(a: number[], b: number[]): number;
9
24
  export interface FeatureOptions {
10
25
  bodies?: BodyId[];
11
26
  weights?: Record<string, number>;
12
27
  zodiac?: Zodiac;
13
28
  }
14
- /** Feature vector for the sky at jdUt over an ordered set of bodies. */
29
+ /**
30
+ * Encode the sky at an instant as a feature vector: each body's ecliptic
31
+ * longitude becomes a weighted unit-circle point. The deterministic substrate
32
+ * for matching and searching chart configurations — compare two with
33
+ * {@link cosineSimilarity}, or rank a time range against one with
34
+ * {@link searchConfigurations}.
35
+ *
36
+ * @param engine The engine used to evaluate positions.
37
+ * @param jdUt Julian Day in UT.
38
+ * @param opts `bodies` (ordered; defaults to the ten major bodies), per-body
39
+ * `weights`, and `zodiac` (tropical by default).
40
+ * @returns A flat vector `[w·cos(lon), w·sin(lon), ...]`, two entries per body
41
+ * in `bodies` order.
42
+ * @example
43
+ * ```ts
44
+ * const target = chartFeatures(engine, julianDay(2000, 1, 1));
45
+ * const now = chartFeatures(engine, julianDay(2025, 6, 1));
46
+ * cosineSimilarity(now, target); // 1 = identical configuration
47
+ * ```
48
+ */
15
49
  export declare function chartFeatures(engine: Engine, jdUt: number, opts?: FeatureOptions): number[];
16
- /** Similarity between the sky at jdUt and a target feature vector. */
50
+ /**
51
+ * Similarity between the sky at `jdUt` and a target feature vector — shorthand
52
+ * for `cosineSimilarity(chartFeatures(engine, jdUt, opts), target)`. The scoring
53
+ * function {@link searchConfigurations} maximizes.
54
+ *
55
+ * @param engine The engine used to evaluate positions.
56
+ * @param jdUt Julian Day in UT.
57
+ * @param target A target feature vector from {@link chartFeatures}.
58
+ * @param opts {@link FeatureOptions} — must match those used to build `target`.
59
+ * @returns Cosine similarity in `[-1, 1]`.
60
+ */
17
61
  export declare function configurationFit(engine: Engine, jdUt: number, target: number[], opts?: FeatureOptions): number;
18
62
  export interface SearchConfigOptions extends FeatureOptions {
19
63
  start: number;
@@ -21,6 +65,24 @@ export interface SearchConfigOptions extends FeatureOptions {
21
65
  step: number;
22
66
  limit?: number;
23
67
  }
24
- /** Rank the instants in [start, end] by how closely the sky resembles `target`
25
- * (a feature vector), best first. Realization search over the feature space. */
68
+ /**
69
+ * Rank the instants in `[start, end]` by how closely the sky resembles a
70
+ * `target` feature vector, best first — a realization search over the feature
71
+ * space. Build `target` with {@link chartFeatures} (e.g. from a natal chart).
72
+ *
73
+ * @param engine The engine used to evaluate positions.
74
+ * @param target A target feature vector from {@link chartFeatures}.
75
+ * @param opts `start`/`end` (Julian Days, UT) and `step` (days) define the
76
+ * scan, `limit` caps the results, plus the {@link FeatureOptions} (`bodies`,
77
+ * `weights`, `zodiac`) — which must match those used to build `target`.
78
+ * @returns Ranked `{ jd, score }` moments, highest similarity first.
79
+ * @example
80
+ * ```ts
81
+ * const natal = chartFeatures(engine, julianDay(1990, 6, 10, 14, 30));
82
+ * const matches = searchConfigurations(engine, natal, {
83
+ * start: julianDay(2025, 1, 1), end: julianDay(2026, 1, 1), step: 1, limit: 5,
84
+ * });
85
+ * matches[0].jd; // best-matching instant
86
+ * ```
87
+ */
26
88
  export declare function searchConfigurations(engine: Engine, target: number[], opts: SearchConfigOptions): RankedMoment[];
@@ -13,8 +13,15 @@ import { DEG } from "./core.js";
13
13
  import { rankMoments } from "./scan.js";
14
14
  export const DEFAULT_BODIES = ["sun", "moon", "mercury", "venus", "mars",
15
15
  "jupiter", "saturn", "uranus", "neptune", "pluto"];
16
- /** Flat vector [w*cos(lon), w*sin(lon), ...] for the given (longitude, weight)
17
- * pairs, in order. */
16
+ /**
17
+ * Build a feature vector from explicit `(longitude, weight)` pairs: each pair
18
+ * contributes a weighted unit-circle point `[w·cos(lon), w·sin(lon)]`. The
19
+ * low-level primitive behind {@link chartFeatures}; most callers want that.
20
+ *
21
+ * @param weightedLons `[longitudeDeg, weight]` pairs, in the order they should
22
+ * appear in the vector.
23
+ * @returns A flat vector, two entries per pair.
24
+ */
18
25
  export function featureVector(weightedLons) {
19
26
  const out = [];
20
27
  for (const [lon, w] of weightedLons) {
@@ -23,7 +30,15 @@ export function featureVector(weightedLons) {
23
30
  }
24
31
  return out;
25
32
  }
26
- /** Cosine similarity of two feature vectors, in [-1, 1]. */
33
+ /**
34
+ * Cosine similarity of two feature vectors, in `[-1, 1]`. For vectors from
35
+ * {@link chartFeatures} this is a weighted mean of `cos(Δlongitude)` per body:
36
+ * `1` when the configurations coincide, falling off as bodies diverge.
37
+ *
38
+ * @param a First feature vector.
39
+ * @param b Second feature vector (compared over the shorter length).
40
+ * @returns Similarity in `[-1, 1]`; `0` if either vector is all zeros.
41
+ */
27
42
  export function cosineSimilarity(a, b) {
28
43
  let dot = 0, na = 0, nb = 0;
29
44
  const n = Math.min(a.length, b.length);
@@ -36,7 +51,26 @@ export function cosineSimilarity(a, b) {
36
51
  return 0;
37
52
  return dot / (Math.sqrt(na) * Math.sqrt(nb));
38
53
  }
39
- /** Feature vector for the sky at jdUt over an ordered set of bodies. */
54
+ /**
55
+ * Encode the sky at an instant as a feature vector: each body's ecliptic
56
+ * longitude becomes a weighted unit-circle point. The deterministic substrate
57
+ * for matching and searching chart configurations — compare two with
58
+ * {@link cosineSimilarity}, or rank a time range against one with
59
+ * {@link searchConfigurations}.
60
+ *
61
+ * @param engine The engine used to evaluate positions.
62
+ * @param jdUt Julian Day in UT.
63
+ * @param opts `bodies` (ordered; defaults to the ten major bodies), per-body
64
+ * `weights`, and `zodiac` (tropical by default).
65
+ * @returns A flat vector `[w·cos(lon), w·sin(lon), ...]`, two entries per body
66
+ * in `bodies` order.
67
+ * @example
68
+ * ```ts
69
+ * const target = chartFeatures(engine, julianDay(2000, 1, 1));
70
+ * const now = chartFeatures(engine, julianDay(2025, 6, 1));
71
+ * cosineSimilarity(now, target); // 1 = identical configuration
72
+ * ```
73
+ */
40
74
  export function chartFeatures(engine, jdUt, opts = {}) {
41
75
  const bodies = opts.bodies ?? DEFAULT_BODIES;
42
76
  const zodiac = opts.zodiac ?? "tropical";
@@ -46,12 +80,40 @@ export function chartFeatures(engine, jdUt, opts = {}) {
46
80
  ]);
47
81
  return featureVector(wl);
48
82
  }
49
- /** Similarity between the sky at jdUt and a target feature vector. */
83
+ /**
84
+ * Similarity between the sky at `jdUt` and a target feature vector — shorthand
85
+ * for `cosineSimilarity(chartFeatures(engine, jdUt, opts), target)`. The scoring
86
+ * function {@link searchConfigurations} maximizes.
87
+ *
88
+ * @param engine The engine used to evaluate positions.
89
+ * @param jdUt Julian Day in UT.
90
+ * @param target A target feature vector from {@link chartFeatures}.
91
+ * @param opts {@link FeatureOptions} — must match those used to build `target`.
92
+ * @returns Cosine similarity in `[-1, 1]`.
93
+ */
50
94
  export function configurationFit(engine, jdUt, target, opts = {}) {
51
95
  return cosineSimilarity(chartFeatures(engine, jdUt, opts), target);
52
96
  }
53
- /** Rank the instants in [start, end] by how closely the sky resembles `target`
54
- * (a feature vector), best first. Realization search over the feature space. */
97
+ /**
98
+ * Rank the instants in `[start, end]` by how closely the sky resembles a
99
+ * `target` feature vector, best first — a realization search over the feature
100
+ * space. Build `target` with {@link chartFeatures} (e.g. from a natal chart).
101
+ *
102
+ * @param engine The engine used to evaluate positions.
103
+ * @param target A target feature vector from {@link chartFeatures}.
104
+ * @param opts `start`/`end` (Julian Days, UT) and `step` (days) define the
105
+ * scan, `limit` caps the results, plus the {@link FeatureOptions} (`bodies`,
106
+ * `weights`, `zodiac`) — which must match those used to build `target`.
107
+ * @returns Ranked `{ jd, score }` moments, highest similarity first.
108
+ * @example
109
+ * ```ts
110
+ * const natal = chartFeatures(engine, julianDay(1990, 6, 10, 14, 30));
111
+ * const matches = searchConfigurations(engine, natal, {
112
+ * start: julianDay(2025, 1, 1), end: julianDay(2026, 1, 1), step: 1, limit: 5,
113
+ * });
114
+ * matches[0].jd; // best-matching instant
115
+ * ```
116
+ */
55
117
  export function searchConfigurations(engine, target, opts) {
56
118
  return rankMoments({ start: opts.start, end: opts.end, step: opts.step, limit: opts.limit }, (jd) => configurationFit(engine, jd, target, opts));
57
119
  }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * astroengine firdaria -- the Persian/medieval system of planetary time-lord
3
+ * periods (firdariyyat).
4
+ *
5
+ * Life divides into nine periods totalling 75 years: the seven planets in the
6
+ * firdaria order, then the two lunar nodes. A day chart begins with the Sun, a
7
+ * night chart with the Moon; both follow the same cycle (Sun, Venus, Mercury,
8
+ * Moon, Saturn, Jupiter, Mars) and close with the North and South Nodes. Each
9
+ * planetary period splits into seven equal sub-periods led by the seven planets
10
+ * from that period's lord; node periods have no sub-divisions. Pure time
11
+ * arithmetic on the natal moment and the chart's sect. Mirrors the Python
12
+ * reference (astroengine/firdaria.py); the golden fixtures pin the two together.
13
+ */
14
+ import { Engine } from "./chart.js";
15
+ /** The firdaria cycle of the seven planets. */
16
+ export declare const FIRDARIA_ORDER: readonly ["sun", "venus", "mercury", "moon", "saturn", "jupiter", "mars"];
17
+ /** Period length in years for each of the seven planets. */
18
+ export declare const FIRDARIA_YEARS: Record<(typeof FIRDARIA_ORDER)[number], number>;
19
+ /** The two nodes close the sequence with no sub-periods (70 + 5 = 75 years). */
20
+ export declare const NODE_PERIODS: ReadonlyArray<readonly [string, number]>;
21
+ /** The nine major firdaria periods in order, as `[lord, years]` pairs. */
22
+ export declare function firdariaSequence(day: boolean): Array<[string, number]>;
23
+ export interface FirdariaSub {
24
+ lord: string;
25
+ start: number;
26
+ end: number;
27
+ }
28
+ export interface FirdariaPeriod {
29
+ lord: string;
30
+ years: number;
31
+ start: number;
32
+ end: number;
33
+ sub: FirdariaSub[];
34
+ }
35
+ /** The full firdaria timeline from birth. */
36
+ export declare function firdaria(day: boolean, natalJd: number, yearLength?: number): FirdariaPeriod[];
37
+ /** The major and sub firdar lord active at `targetJd`; both null outside the
38
+ * 75-year span. */
39
+ export declare function firdariaActive(day: boolean, natalJd: number, targetJd: number, yearLength?: number): {
40
+ major: string | null;
41
+ sub: string | null;
42
+ };
43
+ /** The active firdar at `targetJd`, taking the chart's sect from the natal
44
+ * moment and place. */
45
+ export declare function firdariaAt(engine: Engine, natalJd: number, targetJd: number, lat: number, lonEast: number, yearLength?: number): {
46
+ day: boolean;
47
+ major: string | null;
48
+ sub: string | null;
49
+ };
@@ -0,0 +1,62 @@
1
+ import { TROPICAL_YEAR, isDayChart } from "./derived.js";
2
+ /** The firdaria cycle of the seven planets. */
3
+ export const FIRDARIA_ORDER = [
4
+ "sun", "venus", "mercury", "moon", "saturn", "jupiter", "mars",
5
+ ];
6
+ /** Period length in years for each of the seven planets. */
7
+ export const FIRDARIA_YEARS = {
8
+ sun: 10, venus: 8, mercury: 13, moon: 9, saturn: 11, jupiter: 12, mars: 7,
9
+ };
10
+ /** The two nodes close the sequence with no sub-periods (70 + 5 = 75 years). */
11
+ export const NODE_PERIODS = [
12
+ ["north_node", 3], ["south_node", 2],
13
+ ];
14
+ /** The nine major firdaria periods in order, as `[lord, years]` pairs. */
15
+ export function firdariaSequence(day) {
16
+ const start = day ? 0 : FIRDARIA_ORDER.indexOf("moon");
17
+ const planets = [];
18
+ for (let i = 0; i < 7; i++) {
19
+ const lord = FIRDARIA_ORDER[(start + i) % 7];
20
+ planets.push([lord, FIRDARIA_YEARS[lord]]);
21
+ }
22
+ return [...planets, ...NODE_PERIODS.map((p) => [p[0], p[1]])];
23
+ }
24
+ /** The full firdaria timeline from birth. */
25
+ export function firdaria(day, natalJd, yearLength = TROPICAL_YEAR) {
26
+ const out = [];
27
+ let t = natalJd;
28
+ for (const [lord, years] of firdariaSequence(day)) {
29
+ const span = years * yearLength;
30
+ const major = { lord, years, start: t, end: t + span, sub: [] };
31
+ const li = FIRDARIA_ORDER.indexOf(lord);
32
+ if (li >= 0) {
33
+ const subSpan = span / 7;
34
+ let st = t;
35
+ for (let k = 0; k < 7; k++) {
36
+ const sl = FIRDARIA_ORDER[(li + k) % 7];
37
+ major.sub.push({ lord: sl, start: st, end: st + subSpan });
38
+ st += subSpan;
39
+ }
40
+ }
41
+ out.push(major);
42
+ t += span;
43
+ }
44
+ return out;
45
+ }
46
+ /** The major and sub firdar lord active at `targetJd`; both null outside the
47
+ * 75-year span. */
48
+ export function firdariaActive(day, natalJd, targetJd, yearLength = TROPICAL_YEAR) {
49
+ for (const major of firdaria(day, natalJd, yearLength)) {
50
+ if (major.start <= targetJd && targetJd < major.end) {
51
+ const sub = major.sub.find((s) => s.start <= targetJd && targetJd < s.end);
52
+ return { major: major.lord, sub: sub ? sub.lord : null };
53
+ }
54
+ }
55
+ return { major: null, sub: null };
56
+ }
57
+ /** The active firdar at `targetJd`, taking the chart's sect from the natal
58
+ * moment and place. */
59
+ export function firdariaAt(engine, natalJd, targetJd, lat, lonEast, yearLength = TROPICAL_YEAR) {
60
+ const day = isDayChart(engine, natalJd, lat, lonEast);
61
+ return { day, ...firdariaActive(day, natalJd, targetJd, yearLength) };
62
+ }
@@ -41,9 +41,18 @@ export declare function housesPolichPage(armc: number, phi: number, eps: number)
41
41
  /** Vehlow: equal houses with the ASC at the middle of house 1. */
42
42
  export declare function housesVehlow(armc: number, phi: number, eps: number): number[];
43
43
  /**
44
- * Placidus cusps via the classic iterative scheme. Semi-arc derivation:
45
- * for ALL four intermediate cusps RA = ARMC + offset + f*AD with
46
- * AD = asin(tan(phi) tan(dec)); offsets 30/60/120/150, f = 1/3,2/3,2/3,1/3.
47
- * Undefined above the polar circles (as Placidus itself is).
44
+ * Placidus house cusps via the classic iterative semi-arc scheme: for all four
45
+ * intermediate cusps RA = ARMC + offset + f·AD with AD = asin(tan φ · tan δ);
46
+ * offsets 30/60/120/150, f = 1/3, 2/3, 2/3, 1/3. Undefined above the polar
47
+ * circles, as Placidus itself is.
48
+ *
49
+ * Low-level: {@link Engine.chart} calls this for you when the house system is
50
+ * `"placidus"`, falling back to whole-sign near the poles. Inputs and outputs
51
+ * are in **radians**.
52
+ *
53
+ * @param armc Right ascension of the MC, in radians.
54
+ * @param phi Geographic latitude, in radians.
55
+ * @param eps Obliquity of the ecliptic, in radians.
56
+ * @returns The twelve cusp longitudes in radians, house 1 (Ascendant) first.
48
57
  */
49
58
  export declare function housesPlacidus(armc: number, phi: number, eps: number): number[];
@@ -257,10 +257,19 @@ export function housesVehlow(armc, phi, eps) {
257
257
  return Array.from({ length: 12 }, (_, i) => mod(asc - 15 * DEG + i * 30 * DEG, TWO_PI));
258
258
  }
259
259
  /**
260
- * Placidus cusps via the classic iterative scheme. Semi-arc derivation:
261
- * for ALL four intermediate cusps RA = ARMC + offset + f*AD with
262
- * AD = asin(tan(phi) tan(dec)); offsets 30/60/120/150, f = 1/3,2/3,2/3,1/3.
263
- * Undefined above the polar circles (as Placidus itself is).
260
+ * Placidus house cusps via the classic iterative semi-arc scheme: for all four
261
+ * intermediate cusps RA = ARMC + offset + f·AD with AD = asin(tan φ · tan δ);
262
+ * offsets 30/60/120/150, f = 1/3, 2/3, 2/3, 1/3. Undefined above the polar
263
+ * circles, as Placidus itself is.
264
+ *
265
+ * Low-level: {@link Engine.chart} calls this for you when the house system is
266
+ * `"placidus"`, falling back to whole-sign near the poles. Inputs and outputs
267
+ * are in **radians**.
268
+ *
269
+ * @param armc Right ascension of the MC, in radians.
270
+ * @param phi Geographic latitude, in radians.
271
+ * @param eps Obliquity of the ecliptic, in radians.
272
+ * @returns The twelve cusp longitudes in radians, house 1 (Ascendant) first.
264
273
  */
265
274
  export function housesPlacidus(armc, phi, eps) {
266
275
  const cusp = (offsetDeg, f) => {
@@ -15,3 +15,12 @@ export * from "./astrocartography.js";
15
15
  export * from "./ephemeris.js";
16
16
  export * from "./features.js";
17
17
  export * from "./compiler.js";
18
+ export * from "./lots.js";
19
+ export * from "./profections.js";
20
+ export * from "./firdaria.js";
21
+ export * from "./releasing.js";
22
+ export * from "./vedic.js";
23
+ export * from "./directions.js";
24
+ export * from "./vargas.js";
25
+ export * from "./yogini.js";
26
+ export * from "./yogas.js";
package/dist/src/index.js CHANGED
@@ -15,3 +15,12 @@ export * from "./astrocartography.js";
15
15
  export * from "./ephemeris.js";
16
16
  export * from "./features.js";
17
17
  export * from "./compiler.js";
18
+ export * from "./lots.js";
19
+ export * from "./profections.js";
20
+ export * from "./firdaria.js";
21
+ export * from "./releasing.js";
22
+ export * from "./vedic.js";
23
+ export * from "./directions.js";
24
+ export * from "./vargas.js";
25
+ export * from "./yogini.js";
26
+ export * from "./yogas.js";
@@ -0,0 +1,18 @@
1
+ import { Engine, Zodiac } from "./chart.js";
2
+ /** The seven Hermetic lots, in their conventional order. */
3
+ export declare const HERMETIC_LOTS: readonly ["fortune", "spirit", "eros", "necessity", "courage", "victory", "nemesis"];
4
+ export type HermeticLot = (typeof HERMETIC_LOTS)[number];
5
+ /** Lot of Fortune: Asc + Moon - Sun by day, Asc + Sun - Moon by night. */
6
+ export declare function lotFortune(asc: number, sun: number, moon: number, day: boolean): number;
7
+ /** Lot of Spirit (the reverse of Fortune): Asc + Sun - Moon by day. */
8
+ export declare function lotSpirit(asc: number, sun: number, moon: number, day: boolean): number;
9
+ /** The seven Hermetic lots from the Ascendant, sect, and the seven planets'
10
+ * longitudes (degrees). Pure arithmetic. */
11
+ export declare function hermeticLots(asc: number, day: boolean, sun: number, moon: number, mercury: number, venus: number, mars: number, jupiter: number, saturn: number): Record<HermeticLot, number>;
12
+ export interface ChartLots extends Record<HermeticLot, number> {
13
+ /** True when the Sun is above the horizon (a diurnal chart). */
14
+ day: boolean;
15
+ }
16
+ /** The seven Hermetic lots of a chart: compute the Ascendant and sect, then the
17
+ * lots from the seven planets' longitudes. */
18
+ export declare function lots(engine: Engine, jdUt: number, lat: number, lonEast: number, zodiac?: Zodiac): ChartLots;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * astroengine lots -- Hellenistic lots (Arabic parts), sect-aware.
3
+ *
4
+ * A lot is an arc cast from the Ascendant equal to the arc between two chart
5
+ * points, reversing direction between a day and a night chart. Arithmetic on
6
+ * apparent longitudes already checked against Swiss Ephemeris. Mirrors the
7
+ * Python reference (astroengine/lots.py); the golden fixtures pin the two
8
+ * together. Fortune and Spirit are symmetric about the Ascendant, so
9
+ * `(fortune + spirit) === 2 * asc` (mod 360).
10
+ */
11
+ import { mod } from "./core.js";
12
+ import { isDayChart } from "./derived.js";
13
+ /** The seven Hermetic lots, in their conventional order. */
14
+ export const HERMETIC_LOTS = [
15
+ "fortune", "spirit", "eros", "necessity", "courage", "victory", "nemesis",
16
+ ];
17
+ /** Asc + (a - b) by day, Asc + (b - a) by night, wrapped to [0, 360). */
18
+ function lot(asc, a, b, day) {
19
+ return mod(asc + (day ? a - b : b - a), 360);
20
+ }
21
+ /** Lot of Fortune: Asc + Moon - Sun by day, Asc + Sun - Moon by night. */
22
+ export function lotFortune(asc, sun, moon, day) {
23
+ return lot(asc, moon, sun, day);
24
+ }
25
+ /** Lot of Spirit (the reverse of Fortune): Asc + Sun - Moon by day. */
26
+ export function lotSpirit(asc, sun, moon, day) {
27
+ return lot(asc, sun, moon, day);
28
+ }
29
+ /** The seven Hermetic lots from the Ascendant, sect, and the seven planets'
30
+ * longitudes (degrees). Pure arithmetic. */
31
+ export function hermeticLots(asc, day, sun, moon, mercury, venus, mars, jupiter, saturn) {
32
+ const fortune = lotFortune(asc, sun, moon, day);
33
+ const spirit = lotSpirit(asc, sun, moon, day);
34
+ return {
35
+ fortune,
36
+ spirit,
37
+ eros: lot(asc, venus, spirit, day),
38
+ necessity: lot(asc, fortune, mercury, day),
39
+ courage: lot(asc, fortune, mars, day),
40
+ victory: lot(asc, jupiter, spirit, day),
41
+ nemesis: lot(asc, fortune, saturn, day),
42
+ };
43
+ }
44
+ /** The seven Hermetic lots of a chart: compute the Ascendant and sect, then the
45
+ * lots from the seven planets' longitudes. */
46
+ export function lots(engine, jdUt, lat, lonEast, zodiac = "tropical") {
47
+ const asc = engine.chartAt(jdUt, lat, lonEast, { zodiac }).angles.asc;
48
+ const day = isDayChart(engine, jdUt, lat, lonEast);
49
+ const lon = (b) => engine.longitude(b, jdUt, { zodiac });
50
+ const h = hermeticLots(asc, day, lon("sun"), lon("moon"), lon("mercury"), lon("venus"), lon("mars"), lon("jupiter"), lon("saturn"));
51
+ return { day, ...h };
52
+ }
@@ -19,8 +19,22 @@ export interface Pheno {
19
19
  diameter: number;
20
20
  magnitude: number;
21
21
  }
22
- /** Phase angle (deg), illuminated fraction, elongation (deg), apparent
23
- * diameter (deg), apparent magnitude. */
22
+ /**
23
+ * Photometric and apparent-geometry quantities for a body at an instant: its
24
+ * phase angle, illuminated fraction, elongation from the Sun, apparent disc
25
+ * diameter, and apparent visual magnitude.
26
+ *
27
+ * @param engine The engine used to evaluate positions.
28
+ * @param body A body with known physical dimensions (Sun, Moon, the planets).
29
+ * @param jdUt Julian Day (UT).
30
+ * @returns A {@link Pheno}: `phaseAngle` (deg), `phase` (lit fraction `0`–`1`),
31
+ * `elongation` (deg), `diameter` (deg), and `magnitude`.
32
+ * @throws Error if `body` has no photometric data.
33
+ * @example
34
+ * ```ts
35
+ * pheno(engine, "venus", julianDay(2025, 6, 1)).phase; // illuminated fraction
36
+ * ```
37
+ */
24
38
  export declare function pheno(engine: Engine, body: BodyId, jdUt: number): Pheno;
25
39
  /** Apparent minus mean solar time, minutes (Meeus ch. 28). */
26
40
  export declare function equationOfTime(engine: Engine, jdUt: number): number;
package/dist/src/pheno.js CHANGED
@@ -66,8 +66,22 @@ function magnitude(body, a, r, dlt, jde, lonDeg, latDeg) {
66
66
  return x - 1.01;
67
67
  }
68
68
  }
69
- /** Phase angle (deg), illuminated fraction, elongation (deg), apparent
70
- * diameter (deg), apparent magnitude. */
69
+ /**
70
+ * Photometric and apparent-geometry quantities for a body at an instant: its
71
+ * phase angle, illuminated fraction, elongation from the Sun, apparent disc
72
+ * diameter, and apparent visual magnitude.
73
+ *
74
+ * @param engine The engine used to evaluate positions.
75
+ * @param body A body with known physical dimensions (Sun, Moon, the planets).
76
+ * @param jdUt Julian Day (UT).
77
+ * @returns A {@link Pheno}: `phaseAngle` (deg), `phase` (lit fraction `0`–`1`),
78
+ * `elongation` (deg), `diameter` (deg), and `magnitude`.
79
+ * @throws Error if `body` has no photometric data.
80
+ * @example
81
+ * ```ts
82
+ * pheno(engine, "venus", julianDay(2025, 6, 1)).phase; // illuminated fraction
83
+ * ```
84
+ */
71
85
  export function pheno(engine, body, jdUt) {
72
86
  if (DIAMETER_KM[body] === undefined) {
73
87
  throw new Error(`pheno not available for '${body}'`);
@@ -0,0 +1,27 @@
1
+ import { Engine, Zodiac } from "./chart.js";
2
+ /** Traditional (domicile) ruler of each sign, Aries..Pisces. */
3
+ export declare const SIGN_RULERS: readonly ["mars", "venus", "mercury", "moon", "sun", "mercury", "venus", "mars", "jupiter", "saturn", "saturn", "jupiter"];
4
+ /** Traditional (domicile) ruler of a sign index (0 = Aries). */
5
+ export declare function signRuler(sign: number): string;
6
+ export interface ProfectedSign {
7
+ sign: string;
8
+ sign_index: number;
9
+ /** 1-based whole-sign house from the natal Ascendant. */
10
+ house: number;
11
+ /** Traditional (domicile) lord of the profected sign. */
12
+ lord: string;
13
+ }
14
+ /** The whole-sign profection `steps` signs after the Ascendant sign. */
15
+ export declare function profectedSign(ascSign: number, steps: number): ProfectedSign;
16
+ export interface Profection {
17
+ age_years: number;
18
+ /** 1-based month within the profection year. */
19
+ month: number;
20
+ annual: ProfectedSign;
21
+ monthly: ProfectedSign;
22
+ }
23
+ /** Annual and monthly profection at `targetJd` for a natal Ascendant sign. */
24
+ export declare function profection(ascSign: number, natalJd: number, targetJd: number, yearLength?: number): Profection;
25
+ /** Profection from a natal chart: take the Ascendant sign from the natal chart,
26
+ * then profect to `targetJd`. */
27
+ export declare function profectionAt(engine: Engine, natalJd: number, targetJd: number, lat: number, lonEast: number, zodiac?: Zodiac, yearLength?: number): Profection;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * astroengine profections -- annual and monthly profections, a Hellenistic
3
+ * time-lord technique.
4
+ *
5
+ * The Ascendant advances one whole sign per year of life; the profected sign's
6
+ * traditional (domicile) ruler is the lord of the year. Within the year the
7
+ * monthly profection advances one further sign per 1/12 of the year. Pure
8
+ * arithmetic on a date difference and the natal Ascendant sign. Mirrors the
9
+ * Python reference (astroengine/profections.py); the golden fixtures pin the
10
+ * two together. Whole-sign frame: the sign N signs after the Ascendant is the
11
+ * (N+1)th house. The profection year is a fixed length (the tropical year by
12
+ * default); birthday-exact profections would key off the solar return.
13
+ */
14
+ import { mod } from "./core.js";
15
+ import { SIGNS } from "./chart.js";
16
+ import { TROPICAL_YEAR } from "./derived.js";
17
+ /** Traditional (domicile) ruler of each sign, Aries..Pisces. */
18
+ export const SIGN_RULERS = [
19
+ "mars", "venus", "mercury", "moon", "sun", "mercury",
20
+ "venus", "mars", "jupiter", "saturn", "saturn", "jupiter",
21
+ ];
22
+ /** Traditional (domicile) ruler of a sign index (0 = Aries). */
23
+ export function signRuler(sign) {
24
+ return SIGN_RULERS[mod(sign, 12)];
25
+ }
26
+ /** The whole-sign profection `steps` signs after the Ascendant sign. */
27
+ export function profectedSign(ascSign, steps) {
28
+ const sign = mod(ascSign + steps, 12);
29
+ return { sign: SIGNS[sign], sign_index: sign, house: mod(steps, 12) + 1, lord: signRuler(sign) };
30
+ }
31
+ /** Annual and monthly profection at `targetJd` for a natal Ascendant sign. */
32
+ export function profection(ascSign, natalJd, targetJd, yearLength = TROPICAL_YEAR) {
33
+ const age = (targetJd - natalJd) / yearLength;
34
+ const years = Math.floor(age);
35
+ const month = Math.floor((age - years) * 12); // 0..11
36
+ return {
37
+ age_years: years,
38
+ month: month + 1,
39
+ annual: profectedSign(ascSign, years),
40
+ monthly: profectedSign(ascSign, years + month),
41
+ };
42
+ }
43
+ /** Profection from a natal chart: take the Ascendant sign from the natal chart,
44
+ * then profect to `targetJd`. */
45
+ export function profectionAt(engine, natalJd, targetJd, lat, lonEast, zodiac = "tropical", yearLength = TROPICAL_YEAR) {
46
+ const asc = engine.chartAt(natalJd, lat, lonEast, { zodiac }).angles.asc;
47
+ const ascSign = mod(Math.floor(asc / 30), 12);
48
+ return profection(ascSign, natalJd, targetJd, yearLength);
49
+ }