caelus 0.19.0 → 0.20.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/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/interpret.d.ts +52 -0
- package/dist/src/interpret.js +80 -0
- package/dist/src/interpretation-enrich.d.ts +32 -0
- package/dist/src/interpretation-enrich.js +63 -0
- package/dist/src/interpretation.d.ts +117 -2
- package/dist/src/interpretation.js +217 -2
- package/dist/src/relational.d.ts +54 -0
- package/dist/src/relational.js +103 -0
- package/package.json +1 -1
package/dist/src/index.d.ts
CHANGED
|
@@ -29,7 +29,9 @@ export * from "./rajayoga.js";
|
|
|
29
29
|
export * from "./patterns.js";
|
|
30
30
|
export * from "./signature.js";
|
|
31
31
|
export * from "./interpretation.js";
|
|
32
|
+
export * from "./interpretation-enrich.js";
|
|
32
33
|
export * from "./interpret.js";
|
|
34
|
+
export * from "./relational.js";
|
|
33
35
|
export * from "./brief.js";
|
|
34
36
|
export * from "./provenance.js";
|
|
35
37
|
export * from "./anchored.js";
|
package/dist/src/index.js
CHANGED
|
@@ -29,7 +29,9 @@ export * from "./rajayoga.js";
|
|
|
29
29
|
export * from "./patterns.js";
|
|
30
30
|
export * from "./signature.js";
|
|
31
31
|
export * from "./interpretation.js";
|
|
32
|
+
export * from "./interpretation-enrich.js";
|
|
32
33
|
export * from "./interpret.js";
|
|
34
|
+
export * from "./relational.js";
|
|
33
35
|
export * from "./brief.js";
|
|
34
36
|
export * from "./provenance.js";
|
|
35
37
|
export * from "./anchored.js";
|
package/dist/src/interpret.d.ts
CHANGED
|
@@ -74,6 +74,58 @@ export declare function hasLot(filter?: {
|
|
|
74
74
|
sign?: string;
|
|
75
75
|
house?: number;
|
|
76
76
|
}): Selector;
|
|
77
|
+
/** Matches transit-to-natal aspect atoms. */
|
|
78
|
+
export declare function hasTransit(filter?: {
|
|
79
|
+
transit?: string;
|
|
80
|
+
natal?: string;
|
|
81
|
+
aspect?: string;
|
|
82
|
+
phase?: string;
|
|
83
|
+
minStrength?: number;
|
|
84
|
+
}): Selector;
|
|
85
|
+
/** Matches synastry aspect or house-overlay atoms. */
|
|
86
|
+
export declare function hasSynastry(filter?: {
|
|
87
|
+
mode?: "aspect" | "overlay";
|
|
88
|
+
a?: string;
|
|
89
|
+
b?: string;
|
|
90
|
+
aspect?: string;
|
|
91
|
+
body?: string;
|
|
92
|
+
partner?: "a" | "b";
|
|
93
|
+
house?: number;
|
|
94
|
+
}): Selector;
|
|
95
|
+
/** Matches a composite midpoint placement. */
|
|
96
|
+
export declare function hasComposite(filter?: {
|
|
97
|
+
body?: string;
|
|
98
|
+
sign?: string;
|
|
99
|
+
}): Selector;
|
|
100
|
+
/** Matches an active time-lord period. */
|
|
101
|
+
export declare function hasTimelord(filter?: {
|
|
102
|
+
system?: "profection" | "zr" | "firdaria" | "dasha";
|
|
103
|
+
level?: string;
|
|
104
|
+
lord?: string;
|
|
105
|
+
}): Selector;
|
|
106
|
+
/** Matches finer essential-dignity facts (term, face, triplicity, almuten). */
|
|
107
|
+
export declare function hasDignityFine(filter?: {
|
|
108
|
+
facet?: "term" | "face" | "triplicity" | "almuten";
|
|
109
|
+
body?: string;
|
|
110
|
+
ruler?: string;
|
|
111
|
+
}): Selector;
|
|
112
|
+
/** Matches a nakshatra placement. */
|
|
113
|
+
export declare function hasNakshatra(filter?: {
|
|
114
|
+
body?: string;
|
|
115
|
+
name?: string;
|
|
116
|
+
lord?: string;
|
|
117
|
+
}): Selector;
|
|
118
|
+
/** Matches a varga (divisional chart) placement. */
|
|
119
|
+
export declare function hasVarga(filter?: {
|
|
120
|
+
division?: number;
|
|
121
|
+
body?: string;
|
|
122
|
+
sign?: string;
|
|
123
|
+
}): Selector;
|
|
124
|
+
/** Matches a classical yoga. */
|
|
125
|
+
export declare function hasYoga(filter?: {
|
|
126
|
+
yoga?: string;
|
|
127
|
+
body?: string;
|
|
128
|
+
}): Selector;
|
|
77
129
|
/** Matches only when every selector matches; returns the union of their atoms. */
|
|
78
130
|
export declare function matchAll(...sels: Selector[]): Selector;
|
|
79
131
|
/** Matches when any selector matches; returns the atoms from those that did. */
|
package/dist/src/interpret.js
CHANGED
|
@@ -74,6 +74,86 @@ export function hasLot(filter = {}) {
|
|
|
74
74
|
&& (filter.sign === undefined || a.sign === filter.sign)
|
|
75
75
|
&& (filter.house === undefined || a.house === filter.house)));
|
|
76
76
|
}
|
|
77
|
+
/** Matches transit-to-natal aspect atoms. */
|
|
78
|
+
export function hasTransit(filter = {}) {
|
|
79
|
+
return (ctx) => hit(ctx.atoms.filter((a) => {
|
|
80
|
+
if (a.kind !== "transit")
|
|
81
|
+
return false;
|
|
82
|
+
if (filter.transit !== undefined && a.transit !== filter.transit)
|
|
83
|
+
return false;
|
|
84
|
+
if (filter.natal !== undefined && a.natal !== filter.natal)
|
|
85
|
+
return false;
|
|
86
|
+
if (filter.aspect !== undefined && a.aspect !== filter.aspect)
|
|
87
|
+
return false;
|
|
88
|
+
if (filter.phase !== undefined && a.phase !== filter.phase)
|
|
89
|
+
return false;
|
|
90
|
+
if (filter.minStrength !== undefined && a.strength < filter.minStrength)
|
|
91
|
+
return false;
|
|
92
|
+
return true;
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
/** Matches synastry aspect or house-overlay atoms. */
|
|
96
|
+
export function hasSynastry(filter = {}) {
|
|
97
|
+
return (ctx) => hit(ctx.atoms.filter((a) => {
|
|
98
|
+
if (a.kind !== "synastry")
|
|
99
|
+
return false;
|
|
100
|
+
if (filter.mode !== undefined && a.mode !== filter.mode)
|
|
101
|
+
return false;
|
|
102
|
+
if (filter.a !== undefined && a.a !== filter.a)
|
|
103
|
+
return false;
|
|
104
|
+
if (filter.b !== undefined && a.b !== filter.b)
|
|
105
|
+
return false;
|
|
106
|
+
if (filter.aspect !== undefined && a.aspect !== filter.aspect)
|
|
107
|
+
return false;
|
|
108
|
+
if (filter.body !== undefined && a.body !== filter.body)
|
|
109
|
+
return false;
|
|
110
|
+
if (filter.partner !== undefined && a.partner !== filter.partner)
|
|
111
|
+
return false;
|
|
112
|
+
if (filter.house !== undefined && a.house !== filter.house)
|
|
113
|
+
return false;
|
|
114
|
+
return true;
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
/** Matches a composite midpoint placement. */
|
|
118
|
+
export function hasComposite(filter = {}) {
|
|
119
|
+
return (ctx) => hit(ctx.atoms.filter((a) => a.kind === "composite"
|
|
120
|
+
&& (filter.body === undefined || a.body === filter.body)
|
|
121
|
+
&& (filter.sign === undefined || a.sign === filter.sign)));
|
|
122
|
+
}
|
|
123
|
+
/** Matches an active time-lord period. */
|
|
124
|
+
export function hasTimelord(filter = {}) {
|
|
125
|
+
return (ctx) => hit(ctx.atoms.filter((a) => a.kind === "timelord"
|
|
126
|
+
&& (filter.system === undefined || a.system === filter.system)
|
|
127
|
+
&& (filter.level === undefined || a.level === filter.level)
|
|
128
|
+
&& (filter.lord === undefined || a.lord === filter.lord)));
|
|
129
|
+
}
|
|
130
|
+
/** Matches finer essential-dignity facts (term, face, triplicity, almuten). */
|
|
131
|
+
export function hasDignityFine(filter = {}) {
|
|
132
|
+
return (ctx) => hit(ctx.atoms.filter((a) => a.kind === "dignity"
|
|
133
|
+
&& (filter.facet === undefined || a.facet === filter.facet)
|
|
134
|
+
&& (filter.body === undefined || a.body === filter.body)
|
|
135
|
+
&& (filter.ruler === undefined || a.ruler === filter.ruler)));
|
|
136
|
+
}
|
|
137
|
+
/** Matches a nakshatra placement. */
|
|
138
|
+
export function hasNakshatra(filter = {}) {
|
|
139
|
+
return (ctx) => hit(ctx.atoms.filter((a) => a.kind === "nakshatra"
|
|
140
|
+
&& (filter.body === undefined || a.body === filter.body)
|
|
141
|
+
&& (filter.name === undefined || a.name === filter.name)
|
|
142
|
+
&& (filter.lord === undefined || a.lord === filter.lord)));
|
|
143
|
+
}
|
|
144
|
+
/** Matches a varga (divisional chart) placement. */
|
|
145
|
+
export function hasVarga(filter = {}) {
|
|
146
|
+
return (ctx) => hit(ctx.atoms.filter((a) => a.kind === "varga"
|
|
147
|
+
&& (filter.division === undefined || a.division === filter.division)
|
|
148
|
+
&& (filter.body === undefined || a.body === filter.body)
|
|
149
|
+
&& (filter.sign === undefined || a.sign === filter.sign)));
|
|
150
|
+
}
|
|
151
|
+
/** Matches a classical yoga. */
|
|
152
|
+
export function hasYoga(filter = {}) {
|
|
153
|
+
return (ctx) => hit(ctx.atoms.filter((a) => a.kind === "yoga"
|
|
154
|
+
&& (filter.yoga === undefined || a.yoga === filter.yoga)
|
|
155
|
+
&& (filter.body === undefined || a.bodies.includes(filter.body))));
|
|
156
|
+
}
|
|
77
157
|
// ----------------------------------------------------------------- combinators
|
|
78
158
|
/** Matches only when every selector matches; returns the union of their atoms. */
|
|
79
159
|
export function matchAll(...sels) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enrich an {@link interpretationContext} with diachronic and Vedic facts at a
|
|
3
|
+
* target instant — transits, time-lords, nakshatras, vargas, yogas.
|
|
4
|
+
*/
|
|
5
|
+
import { type Chart, type Engine, type Zodiac } from "./chart.js";
|
|
6
|
+
import type { ContextOptions } from "./interpretation.js";
|
|
7
|
+
export interface EnrichTarget {
|
|
8
|
+
jd: number;
|
|
9
|
+
lat: number;
|
|
10
|
+
lonEast: number;
|
|
11
|
+
zodiac?: Zodiac;
|
|
12
|
+
}
|
|
13
|
+
export interface EnrichFlags {
|
|
14
|
+
/** Project transit-to-natal aspects. Default true. */
|
|
15
|
+
transits?: boolean;
|
|
16
|
+
/** Project profection, ZR, firdaria, dasha. Default true. */
|
|
17
|
+
timelords?: boolean;
|
|
18
|
+
/** Project nakshatras, D9, yogas. Default true when chart zodiac is sidereal. */
|
|
19
|
+
vedic?: boolean;
|
|
20
|
+
/** Max orb for transit aspects. Default 3. */
|
|
21
|
+
transitOrb?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build {@link ContextOptions} extras for a natal chart evaluated at `target`.
|
|
25
|
+
* Merge the result into `interpretationContext(chart, { ...base, ...extras })`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function enrichContextOptions(engine: Engine, chart: Chart, target: EnrichTarget, flags?: EnrichFlags): Pick<ContextOptions, "transits" | "timelords" | "vedic">;
|
|
28
|
+
/** Synastry and composite atoms for two natal charts (A is the projection base). */
|
|
29
|
+
export declare function enrichSynastryOptions(engine: Engine, chartA: Chart, chartB: Chart, opts?: {
|
|
30
|
+
orb?: number;
|
|
31
|
+
zodiac?: Zodiac;
|
|
32
|
+
}): Pick<ContextOptions, "synastry" | "composite">;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enrich an {@link interpretationContext} with diachronic and Vedic facts at a
|
|
3
|
+
* target instant — transits, time-lords, nakshatras, vargas, yogas.
|
|
4
|
+
*/
|
|
5
|
+
import { BODIES } from "./chart.js";
|
|
6
|
+
import { firdariaAt } from "./firdaria.js";
|
|
7
|
+
import { profectionAt } from "./profections.js";
|
|
8
|
+
import { zrAt } from "./releasing.js";
|
|
9
|
+
import { compositePlacements, synastryAspects, synastryOverlays, transitAspects, } from "./relational.js";
|
|
10
|
+
import { vimshottariAt } from "./vedic.js";
|
|
11
|
+
import { yogasAt } from "./yogas.js";
|
|
12
|
+
/**
|
|
13
|
+
* Build {@link ContextOptions} extras for a natal chart evaluated at `target`.
|
|
14
|
+
* Merge the result into `interpretationContext(chart, { ...base, ...extras })`.
|
|
15
|
+
*/
|
|
16
|
+
export function enrichContextOptions(engine, chart, target, flags = {}) {
|
|
17
|
+
const zodiac = target.zodiac ?? chart.zodiac;
|
|
18
|
+
const { lat, lonEast } = target;
|
|
19
|
+
const out = {};
|
|
20
|
+
if (flags.transits !== false) {
|
|
21
|
+
out.transits = transitAspects(chart, engine, target.jd, {
|
|
22
|
+
maxOrb: flags.transitOrb ?? 3, zodiac,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (flags.timelords !== false) {
|
|
26
|
+
const prof = profectionAt(engine, chart.jdUt, target.jd, lat, lonEast, zodiac);
|
|
27
|
+
const zr = zrAt(engine, chart.jdUt, target.jd, lat, lonEast);
|
|
28
|
+
const fir = firdariaAt(engine, chart.jdUt, target.jd, lat, lonEast);
|
|
29
|
+
const dasha = vimshottariAt(engine, chart.jdUt, target.jd, "sidereal:lahiri");
|
|
30
|
+
out.timelords = {
|
|
31
|
+
profection: prof,
|
|
32
|
+
zr: {
|
|
33
|
+
l1: zr.l1, l2: zr.l2, l3: zr.l3, l4: zr.l4, lot: zr.lot,
|
|
34
|
+
},
|
|
35
|
+
firdaria: { major: fir.major, sub: fir.sub, day: fir.day },
|
|
36
|
+
dasha: {
|
|
37
|
+
maha: dasha.maha, antar: dasha.antar ?? null,
|
|
38
|
+
pratyantar: dasha.pratyantar ?? null, moon_nakshatra: dasha.moon_nakshatra,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const wantVedic = flags.vedic ?? zodiac.startsWith("sidereal");
|
|
43
|
+
if (wantVedic) {
|
|
44
|
+
out.vedic = {
|
|
45
|
+
nakshatraBodies: ["moon", "sun", "mars", "mercury", "jupiter", "venus", "saturn"],
|
|
46
|
+
vargas: [9],
|
|
47
|
+
yogas: yogasAt(engine, chart.jdUt, lat, lonEast, "sidereal:lahiri"),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
/** Synastry and composite atoms for two natal charts (A is the projection base). */
|
|
53
|
+
export function enrichSynastryOptions(engine, chartA, chartB, opts = {}) {
|
|
54
|
+
const orb = opts.orb ?? 4;
|
|
55
|
+
const zodiac = opts.zodiac ?? chartA.zodiac;
|
|
56
|
+
return {
|
|
57
|
+
synastry: {
|
|
58
|
+
aspects: synastryAspects(chartA, chartB, orb),
|
|
59
|
+
overlays: synastryOverlays(chartA, chartB),
|
|
60
|
+
},
|
|
61
|
+
composite: compositePlacements(engine, chartA.jdUt, chartB.jdUt, BODIES, zodiac),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -3,8 +3,10 @@ import type { AspectPhase } from "./electional.js";
|
|
|
3
3
|
import { ChartPattern } from "./patterns.js";
|
|
4
4
|
import { ChartSignature } from "./signature.js";
|
|
5
5
|
import type { Realm, Certainty } from "./provenance.js";
|
|
6
|
+
import type { Profection } from "./profections.js";
|
|
7
|
+
import type { CompositePlacement, SynastryAspectHit, SynastryOverlays, TransitHit } from "./relational.js";
|
|
6
8
|
/** Atom kinds in an {@link InterpretationContext}. */
|
|
7
|
-
export type FactKind = "placement" | "aspect" | "pattern" | "signature" | "angle" | "dispositor" | "reception" | "star" | "lot";
|
|
9
|
+
export type FactKind = "placement" | "aspect" | "pattern" | "signature" | "angle" | "dispositor" | "reception" | "star" | "lot" | "transit" | "synastry" | "composite" | "timelord" | "dignity" | "nakshatra" | "varga" | "yoga";
|
|
8
10
|
interface FactAtomBase {
|
|
9
11
|
/** Stable, content-addressable id, e.g. `"placement:mars"` or
|
|
10
12
|
* `"aspect:mars~saturn:square"`. Interpretations cite this. */
|
|
@@ -89,7 +91,66 @@ export interface LotAtom extends FactAtomBase {
|
|
|
89
91
|
signDeg: number;
|
|
90
92
|
house: number;
|
|
91
93
|
}
|
|
92
|
-
export
|
|
94
|
+
export interface TransitAtom extends FactAtomBase {
|
|
95
|
+
kind: "transit";
|
|
96
|
+
transit: string;
|
|
97
|
+
natal: string;
|
|
98
|
+
aspect: string;
|
|
99
|
+
orb: number;
|
|
100
|
+
phase: AspectPhase;
|
|
101
|
+
strength: number;
|
|
102
|
+
natalHouse: number;
|
|
103
|
+
}
|
|
104
|
+
export interface SynastryAtom extends FactAtomBase {
|
|
105
|
+
kind: "synastry";
|
|
106
|
+
mode: "aspect" | "overlay";
|
|
107
|
+
a?: string;
|
|
108
|
+
b?: string;
|
|
109
|
+
aspect?: string;
|
|
110
|
+
orb?: number;
|
|
111
|
+
strength?: number;
|
|
112
|
+
body?: string;
|
|
113
|
+
partner?: "a" | "b";
|
|
114
|
+
house?: number;
|
|
115
|
+
}
|
|
116
|
+
export interface CompositeAtom extends FactAtomBase {
|
|
117
|
+
kind: "composite";
|
|
118
|
+
body: string;
|
|
119
|
+
sign: string;
|
|
120
|
+
signDeg: number;
|
|
121
|
+
}
|
|
122
|
+
export interface TimelordAtom extends FactAtomBase {
|
|
123
|
+
kind: "timelord";
|
|
124
|
+
system: "profection" | "zr" | "firdaria" | "dasha";
|
|
125
|
+
level: string;
|
|
126
|
+
lord: string;
|
|
127
|
+
sign?: string;
|
|
128
|
+
}
|
|
129
|
+
export interface DignityAtom extends FactAtomBase {
|
|
130
|
+
kind: "dignity";
|
|
131
|
+
facet: "term" | "face" | "triplicity" | "almuten";
|
|
132
|
+
body: string;
|
|
133
|
+
ruler?: string;
|
|
134
|
+
}
|
|
135
|
+
export interface NakshatraAtom extends FactAtomBase {
|
|
136
|
+
kind: "nakshatra";
|
|
137
|
+
body: string;
|
|
138
|
+
name: string;
|
|
139
|
+
pada: number;
|
|
140
|
+
lord: string;
|
|
141
|
+
}
|
|
142
|
+
export interface VargaAtom extends FactAtomBase {
|
|
143
|
+
kind: "varga";
|
|
144
|
+
division: number;
|
|
145
|
+
body: string;
|
|
146
|
+
sign: string;
|
|
147
|
+
}
|
|
148
|
+
export interface YogaAtom extends FactAtomBase {
|
|
149
|
+
kind: "yoga";
|
|
150
|
+
yoga: string;
|
|
151
|
+
planets: string[];
|
|
152
|
+
}
|
|
153
|
+
export type FactAtom = PlacementAtom | AspectAtom | PatternAtom | SignatureAtom | AngleAtom | DispositorAtom | ReceptionAtom | StarAtom | LotAtom | TransitAtom | SynastryAtom | CompositeAtom | TimelordAtom | DignityAtom | NakshatraAtom | VargaAtom | YogaAtom;
|
|
93
154
|
/** A chart as a flat, ranked list of {@link FactAtom}s. */
|
|
94
155
|
export interface InterpretationContext {
|
|
95
156
|
jdUt: number;
|
|
@@ -129,6 +190,18 @@ export interface SalienceWeights {
|
|
|
129
190
|
star: number;
|
|
130
191
|
/** Added to a Hermetic lot (the Part of Fortune and its companions). */
|
|
131
192
|
lot: number;
|
|
193
|
+
/** Added to a transit-to-natal aspect. */
|
|
194
|
+
transit: number;
|
|
195
|
+
/** Added to a synastry aspect or house overlay. */
|
|
196
|
+
synastry: number;
|
|
197
|
+
/** Added to a composite midpoint placement. */
|
|
198
|
+
composite: number;
|
|
199
|
+
/** Added to an active time-lord period. */
|
|
200
|
+
timelord: number;
|
|
201
|
+
/** Added to a finer essential-dignity fact. */
|
|
202
|
+
dignityFine: number;
|
|
203
|
+
/** Added to a nakshatra / varga / yoga fact. */
|
|
204
|
+
vedic: number;
|
|
132
205
|
}
|
|
133
206
|
export declare const DEFAULT_SALIENCE: SalienceWeights;
|
|
134
207
|
export interface ContextOptions {
|
|
@@ -159,6 +232,48 @@ export interface ContextOptions {
|
|
|
159
232
|
signDeg: number;
|
|
160
233
|
house: number;
|
|
161
234
|
}[];
|
|
235
|
+
/** Transit-to-natal hits, e.g. from {@link transitAspects}. */
|
|
236
|
+
transits?: TransitHit[];
|
|
237
|
+
/** Synastry aspects and/or house overlays between two charts. */
|
|
238
|
+
synastry?: {
|
|
239
|
+
aspects?: SynastryAspectHit[];
|
|
240
|
+
overlays?: SynastryOverlays;
|
|
241
|
+
};
|
|
242
|
+
/** Composite midpoint placements, e.g. from {@link compositePlacements}. */
|
|
243
|
+
composite?: CompositePlacement[];
|
|
244
|
+
/** Active time-lord periods at a target instant (caller-supplied). */
|
|
245
|
+
timelords?: {
|
|
246
|
+
profection?: Profection;
|
|
247
|
+
zr?: {
|
|
248
|
+
l1: string;
|
|
249
|
+
l2: string;
|
|
250
|
+
l3: string;
|
|
251
|
+
l4: string;
|
|
252
|
+
lot?: string;
|
|
253
|
+
};
|
|
254
|
+
firdaria?: {
|
|
255
|
+
major: string | null;
|
|
256
|
+
sub: string | null;
|
|
257
|
+
day?: boolean;
|
|
258
|
+
};
|
|
259
|
+
dasha?: {
|
|
260
|
+
maha: string;
|
|
261
|
+
antar?: string | null;
|
|
262
|
+
pratyantar?: string | null;
|
|
263
|
+
moon_nakshatra?: string;
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
/** Vedic structure facts (caller-supplied or auto from sidereal chart). */
|
|
267
|
+
vedic?: {
|
|
268
|
+
/** Project nakshatras for these bodies from the chart longitudes. */
|
|
269
|
+
nakshatraBodies?: string[];
|
|
270
|
+
/** Project varga D-n for these bodies (default `[9]` when set true). */
|
|
271
|
+
vargas?: number[] | true;
|
|
272
|
+
yogas?: {
|
|
273
|
+
yoga: string;
|
|
274
|
+
planets: string[];
|
|
275
|
+
}[];
|
|
276
|
+
};
|
|
162
277
|
}
|
|
163
278
|
/**
|
|
164
279
|
* Project a {@link Chart} into a ranked list of {@link FactAtom}s -- the
|
|
@@ -23,7 +23,9 @@ import { mod } from "./core.js";
|
|
|
23
23
|
import { SIGNS, DOMICILE, EXALTATION } from "./chart.js";
|
|
24
24
|
import { detectPatterns } from "./patterns.js";
|
|
25
25
|
import { chartSignature } from "./signature.js";
|
|
26
|
-
import { TRIPLICITY } from "./dignity-score.js";
|
|
26
|
+
import { TRIPLICITY, dignityScore, almuten } from "./dignity-score.js";
|
|
27
|
+
import { nakshatra } from "./vedic.js";
|
|
28
|
+
import { varga } from "./vargas.js";
|
|
27
29
|
const LUMINARIES = new Set(["sun", "moon"]);
|
|
28
30
|
const ANGULAR_HOUSES = new Set([1, 4, 7, 10]);
|
|
29
31
|
const HARD_ASPECTS = new Set(["conjunction", "square", "opposition"]);
|
|
@@ -53,7 +55,8 @@ const DIGNITY_RANK = { domicile: 3, exaltation: 2, triplicity: 1 };
|
|
|
53
55
|
export const DEFAULT_SALIENCE = {
|
|
54
56
|
base: 1, luminary: 1.5, angular: 1, chartRuler: 1,
|
|
55
57
|
dignity: 0.5, hardAspect: 1, pattern: 4, dispositor: 0.5, reception: 2,
|
|
56
|
-
star: 2, lot: 2,
|
|
58
|
+
star: 2, lot: 2, transit: 1.5, synastry: 1, composite: 0.8, timelord: 2,
|
|
59
|
+
dignityFine: 0.4, vedic: 1,
|
|
57
60
|
};
|
|
58
61
|
/** How much to keep of a time-sensitive atom's salience at each certainty -- the
|
|
59
62
|
* Moon and the angles move fastest, so an uncertain instant trusts them least. */
|
|
@@ -254,6 +257,218 @@ export function interpretationContext(chart, opts = {}) {
|
|
|
254
257
|
text: `Lot of ${title(l.lot)} in ${l.sign}, house ${l.house}`,
|
|
255
258
|
});
|
|
256
259
|
}
|
|
260
|
+
// Finer essential dignities: term, face, triplicity held, almuten of each degree.
|
|
261
|
+
const chartSect = sunHouse !== undefined && sunHouse >= 7 ? "day" : "night";
|
|
262
|
+
for (const body of CLASSICAL) {
|
|
263
|
+
const p = chart.bodies[body];
|
|
264
|
+
if (!p)
|
|
265
|
+
continue;
|
|
266
|
+
const ds = dignityScore(body, p.lon, chartSect);
|
|
267
|
+
const alm = almuten(p.lon, chartSect);
|
|
268
|
+
let sal = w.base + w.dignityFine;
|
|
269
|
+
if (LUMINARIES.has(body))
|
|
270
|
+
sal += w.luminary;
|
|
271
|
+
atoms.push({
|
|
272
|
+
id: `term:${body}:${ds.term_ruler}`, kind: "dignity", bodies: [body], salience: sal,
|
|
273
|
+
facet: "term", body, ruler: ds.term_ruler,
|
|
274
|
+
text: `${title(body)} in the term of ${title(ds.term_ruler)}`
|
|
275
|
+
+ (ds.term > 0 ? " (holds term dignity)" : ""),
|
|
276
|
+
});
|
|
277
|
+
atoms.push({
|
|
278
|
+
id: `face:${body}:${ds.face_ruler}`, kind: "dignity", bodies: [body], salience: sal,
|
|
279
|
+
facet: "face", body, ruler: ds.face_ruler,
|
|
280
|
+
text: `${title(body)} in the face of ${title(ds.face_ruler)}`
|
|
281
|
+
+ (ds.face > 0 ? " (holds face dignity)" : ""),
|
|
282
|
+
});
|
|
283
|
+
if (ds.triplicity > 0) {
|
|
284
|
+
atoms.push({
|
|
285
|
+
id: `triplicity:${body}`, kind: "dignity", bodies: [body],
|
|
286
|
+
salience: sal + w.dignity, facet: "triplicity", body,
|
|
287
|
+
text: `${title(body)} holds ${chartSect} triplicity`,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
atoms.push({
|
|
291
|
+
id: `almuten:${body}:${alm.planet}`, kind: "dignity", bodies: [body],
|
|
292
|
+
salience: sal + (alm.planet === body ? w.dignity : 0),
|
|
293
|
+
facet: "almuten", body, ruler: alm.planet,
|
|
294
|
+
text: `${title(alm.planet)} is almuten of ${title(body)}'s degree`,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
// Transit-to-natal aspects (caller-supplied).
|
|
298
|
+
for (const t of opts.transits ?? []) {
|
|
299
|
+
let salience = w.base + w.transit + t.strength;
|
|
300
|
+
if (HARD_ASPECTS.has(t.aspect))
|
|
301
|
+
salience += w.hardAspect;
|
|
302
|
+
if (LUMINARIES.has(t.transit) || LUMINARIES.has(t.natal))
|
|
303
|
+
salience += w.luminary;
|
|
304
|
+
atoms.push({
|
|
305
|
+
id: `transit:${t.transit}~natal_${t.natal}:${t.aspect}`, kind: "transit",
|
|
306
|
+
bodies: [t.transit, t.natal], salience,
|
|
307
|
+
transit: t.transit, natal: t.natal, aspect: t.aspect, orb: t.orb,
|
|
308
|
+
phase: t.phase, strength: t.strength, natalHouse: t.natalHouse,
|
|
309
|
+
text: `Transiting ${title(t.transit)} ${t.aspect} natal ${title(t.natal)} `
|
|
310
|
+
+ `(${t.phase}, orb ${t.orb.toFixed(1)}°, natal house ${t.natalHouse})`,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
// Synastry: inter-chart aspects and house overlays.
|
|
314
|
+
for (const s of opts.synastry?.aspects ?? []) {
|
|
315
|
+
let salience = w.base + w.synastry + s.strength;
|
|
316
|
+
if (HARD_ASPECTS.has(s.aspect))
|
|
317
|
+
salience += w.hardAspect;
|
|
318
|
+
if (LUMINARIES.has(s.a) || LUMINARIES.has(s.b))
|
|
319
|
+
salience += w.luminary;
|
|
320
|
+
atoms.push({
|
|
321
|
+
id: `synastry:${s.a}~b_${s.b}:${s.aspect}`, kind: "synastry",
|
|
322
|
+
bodies: [s.a, s.b], salience, mode: "aspect",
|
|
323
|
+
a: s.a, b: s.b, aspect: s.aspect, orb: s.orb, strength: s.strength,
|
|
324
|
+
text: `${title(s.a)} ${s.aspect} partner's ${title(s.b)} (orb ${s.orb.toFixed(1)}°)`,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
const overlays = opts.synastry?.overlays;
|
|
328
|
+
if (overlays) {
|
|
329
|
+
for (const [body, house] of Object.entries(overlays.aInB)) {
|
|
330
|
+
atoms.push({
|
|
331
|
+
id: `synastry:overlay:a:${body}:house:${house}`, kind: "synastry",
|
|
332
|
+
bodies: [body], salience: w.base + w.synastry, mode: "overlay",
|
|
333
|
+
body, partner: "a", house,
|
|
334
|
+
text: `${title(body)} falls in partner's house ${house}`,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
for (const [body, house] of Object.entries(overlays.bInA)) {
|
|
338
|
+
atoms.push({
|
|
339
|
+
id: `synastry:overlay:b:${body}:house:${house}`, kind: "synastry",
|
|
340
|
+
bodies: [body], salience: w.base + w.synastry, mode: "overlay",
|
|
341
|
+
body, partner: "b", house,
|
|
342
|
+
text: `Partner's ${title(body)} falls in house ${house}`,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Composite midpoint placements.
|
|
347
|
+
for (const c of opts.composite ?? []) {
|
|
348
|
+
atoms.push({
|
|
349
|
+
id: `composite:${c.body}`, kind: "composite", bodies: [c.body],
|
|
350
|
+
salience: w.base + w.composite + (LUMINARIES.has(c.body) ? w.luminary : 0),
|
|
351
|
+
body: c.body, sign: c.sign, signDeg: c.signDeg,
|
|
352
|
+
text: `Composite ${title(c.body)} in ${c.sign}`,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
// Time-lords: profection, zodiacal releasing, firdaria, dasha.
|
|
356
|
+
const tl = opts.timelords;
|
|
357
|
+
if (tl?.profection) {
|
|
358
|
+
const pf = tl.profection;
|
|
359
|
+
atoms.push({
|
|
360
|
+
id: `profection:year:${pf.annual.sign.toLowerCase()}:${pf.annual.lord}`, kind: "timelord",
|
|
361
|
+
bodies: [pf.annual.lord], salience: w.base + w.timelord, system: "profection",
|
|
362
|
+
level: "year", lord: pf.annual.lord, sign: pf.annual.sign,
|
|
363
|
+
text: `Annual profection: ${pf.annual.sign} (house ${pf.annual.house}), lord ${title(pf.annual.lord)}`,
|
|
364
|
+
});
|
|
365
|
+
atoms.push({
|
|
366
|
+
id: `profection:month:${pf.monthly.sign.toLowerCase()}:${pf.monthly.lord}`, kind: "timelord",
|
|
367
|
+
bodies: [pf.monthly.lord], salience: w.base + w.timelord * 0.7, system: "profection",
|
|
368
|
+
level: "month", lord: pf.monthly.lord, sign: pf.monthly.sign,
|
|
369
|
+
text: `Monthly profection: ${pf.monthly.sign} (house ${pf.monthly.house}), lord ${title(pf.monthly.lord)}`,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
if (tl?.zr) {
|
|
373
|
+
const zrWeight = { l1: 1, l2: 0.75, l3: 0.5, l4: 0.35 };
|
|
374
|
+
const zrLevels = [
|
|
375
|
+
["l1", tl.zr.l1], ["l2", tl.zr.l2], ["l3", tl.zr.l3], ["l4", tl.zr.l4],
|
|
376
|
+
];
|
|
377
|
+
for (const [level, sign] of zrLevels) {
|
|
378
|
+
const signIdx = SIGNS.indexOf(sign);
|
|
379
|
+
const lord = signIdx >= 0 ? SIGN_RULER[signIdx] : "";
|
|
380
|
+
atoms.push({
|
|
381
|
+
id: `zr:${level}:${sign.toLowerCase()}:${lord}`, kind: "timelord",
|
|
382
|
+
bodies: lord ? [lord] : [], salience: w.base + w.timelord * (zrWeight[level] ?? 0.5),
|
|
383
|
+
system: "zr", level, lord, sign,
|
|
384
|
+
text: `Zodiacal releasing ${level.toUpperCase()}: ${sign}`
|
|
385
|
+
+ (lord ? `, lord ${title(lord)}` : "")
|
|
386
|
+
+ (tl.zr.lot ? ` (from Lot of ${title(tl.zr.lot)})` : ""),
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (tl?.firdaria?.major) {
|
|
391
|
+
atoms.push({
|
|
392
|
+
id: `firdaria:major:${tl.firdaria.major}`, kind: "timelord",
|
|
393
|
+
bodies: [tl.firdaria.major], salience: w.base + w.timelord, system: "firdaria",
|
|
394
|
+
level: "major", lord: tl.firdaria.major,
|
|
395
|
+
text: `Firdaria major period: ${title(tl.firdaria.major)}`
|
|
396
|
+
+ (tl.firdaria.day !== undefined ? ` (${tl.firdaria.day ? "day" : "night"} chart)` : ""),
|
|
397
|
+
});
|
|
398
|
+
if (tl.firdaria.sub) {
|
|
399
|
+
atoms.push({
|
|
400
|
+
id: `firdaria:sub:${tl.firdaria.sub}`, kind: "timelord",
|
|
401
|
+
bodies: [tl.firdaria.sub], salience: w.base + w.timelord * 0.7, system: "firdaria",
|
|
402
|
+
level: "sub", lord: tl.firdaria.sub,
|
|
403
|
+
text: `Firdaria sub-period: ${title(tl.firdaria.sub)}`,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (tl?.dasha?.maha) {
|
|
408
|
+
const d = tl.dasha;
|
|
409
|
+
atoms.push({
|
|
410
|
+
id: `dasha:maha:${d.maha}`, kind: "timelord", bodies: [d.maha],
|
|
411
|
+
salience: w.base + w.timelord, system: "dasha", level: "maha", lord: d.maha,
|
|
412
|
+
text: `Vimshottari mahadasha: ${title(d.maha)}`
|
|
413
|
+
+ (d.moon_nakshatra ? ` (Moon in ${d.moon_nakshatra})` : ""),
|
|
414
|
+
});
|
|
415
|
+
if (d.antar) {
|
|
416
|
+
atoms.push({
|
|
417
|
+
id: `dasha:antar:${d.antar}`, kind: "timelord", bodies: [d.antar],
|
|
418
|
+
salience: w.base + w.timelord * 0.8, system: "dasha", level: "antar", lord: d.antar,
|
|
419
|
+
text: `Vimshottari antardasha: ${title(d.antar)}`,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
if (d.pratyantar) {
|
|
423
|
+
atoms.push({
|
|
424
|
+
id: `dasha:pratyantar:${d.pratyantar}`, kind: "timelord", bodies: [d.pratyantar],
|
|
425
|
+
salience: w.base + w.timelord * 0.6, system: "dasha", level: "pratyantar", lord: d.pratyantar,
|
|
426
|
+
text: `Vimshottari pratyantardasha: ${title(d.pratyantar)}`,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// Vedic: nakshatras, vargas, yogas.
|
|
431
|
+
const vedic = opts.vedic;
|
|
432
|
+
if (vedic) {
|
|
433
|
+
const nakBodies = vedic.nakshatraBodies
|
|
434
|
+
?? ["moon", "sun", "mars", "mercury", "jupiter", "venus", "saturn"];
|
|
435
|
+
for (const body of nakBodies) {
|
|
436
|
+
const p = chart.bodies[body];
|
|
437
|
+
if (!p)
|
|
438
|
+
continue;
|
|
439
|
+
const nak = nakshatra(p.lon);
|
|
440
|
+
let salience = w.base + w.vedic;
|
|
441
|
+
if (body === "moon")
|
|
442
|
+
salience += w.luminary;
|
|
443
|
+
atoms.push({
|
|
444
|
+
id: `nakshatra:${body}:${nak.name.replace(/\s+/g, "_")}`, kind: "nakshatra",
|
|
445
|
+
bodies: [body], salience, body, name: nak.name, pada: nak.pada, lord: nak.lord,
|
|
446
|
+
text: `${title(body)} in ${nak.name} (pada ${nak.pada}, lord ${title(nak.lord)})`,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
const vargaDivs = vedic.vargas === true ? [9] : (vedic.vargas ?? []);
|
|
450
|
+
for (const n of vargaDivs) {
|
|
451
|
+
for (const body of nakBodies) {
|
|
452
|
+
const p = chart.bodies[body];
|
|
453
|
+
if (!p)
|
|
454
|
+
continue;
|
|
455
|
+
const v = varga(p.lon, n);
|
|
456
|
+
atoms.push({
|
|
457
|
+
id: `varga:d${n}:${body}:${v.sign.toLowerCase()}`, kind: "varga",
|
|
458
|
+
bodies: [body], salience: w.base + w.vedic, division: n, body, sign: v.sign,
|
|
459
|
+
text: `${title(body)} D${n} (${v.sign})`,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
for (const y of vedic.yogas ?? []) {
|
|
464
|
+
atoms.push({
|
|
465
|
+
id: `yoga:${y.yoga.replace(/\s+/g, "_")}`, kind: "yoga",
|
|
466
|
+
bodies: y.planets, salience: w.base + w.timelord * 0.5 + w.vedic,
|
|
467
|
+
yoga: y.yoga, planets: y.planets,
|
|
468
|
+
text: `Yoga ${y.yoga} (${y.planets.map(title).join(", ")})`,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
257
472
|
// An inexact instant trusts the fast-moving facts least.
|
|
258
473
|
const prov = opts.provenance;
|
|
259
474
|
if (prov?.certainty && prov.certainty !== "exact") {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type BodyId, type Chart, type Engine, type Zodiac } from "./chart.js";
|
|
2
|
+
import { type AspectPhase } from "./electional.js";
|
|
3
|
+
/** A transiting body aspecting a natal point. */
|
|
4
|
+
export interface TransitHit {
|
|
5
|
+
transit: string;
|
|
6
|
+
natal: string;
|
|
7
|
+
aspect: string;
|
|
8
|
+
orb: number;
|
|
9
|
+
phase: AspectPhase;
|
|
10
|
+
strength: number;
|
|
11
|
+
/** Natal house the transiting body occupies (by natal cusps). */
|
|
12
|
+
natalHouse: number;
|
|
13
|
+
}
|
|
14
|
+
/** An inter-chart aspect between person A's body and person B's body. */
|
|
15
|
+
export interface SynastryAspectHit {
|
|
16
|
+
a: string;
|
|
17
|
+
b: string;
|
|
18
|
+
aspect: string;
|
|
19
|
+
orb: number;
|
|
20
|
+
strength: number;
|
|
21
|
+
}
|
|
22
|
+
export interface SynastryOverlays {
|
|
23
|
+
/** Person A's bodies in person B's houses. */
|
|
24
|
+
aInB: Record<string, number>;
|
|
25
|
+
/** Person B's bodies in person A's houses. */
|
|
26
|
+
bInA: Record<string, number>;
|
|
27
|
+
}
|
|
28
|
+
/** A composite-chart body placement (midpoint method). */
|
|
29
|
+
export interface CompositePlacement {
|
|
30
|
+
body: string;
|
|
31
|
+
lon: number;
|
|
32
|
+
sign: string;
|
|
33
|
+
signDeg: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Transiting bodies aspecting a natal chart at `transitJd`. Natal longitudes are
|
|
37
|
+
* fixed; phase reflects the transiting body's motion toward the natal point.
|
|
38
|
+
*/
|
|
39
|
+
export declare function transitAspects(natal: Chart, engine: Engine, transitJd: number, opts?: {
|
|
40
|
+
maxOrb?: number;
|
|
41
|
+
zodiac?: Zodiac;
|
|
42
|
+
orbs?: Record<string, number>;
|
|
43
|
+
bodies?: BodyId[];
|
|
44
|
+
}): TransitHit[];
|
|
45
|
+
/**
|
|
46
|
+
* Inter-chart aspects between two natal charts (static snapshot; both speeds 0).
|
|
47
|
+
*/
|
|
48
|
+
export declare function synastryAspects(chartA: Chart, chartB: Chart, maxOrb?: number, orbs?: Record<string, number>): SynastryAspectHit[];
|
|
49
|
+
/** House overlays both ways between two charts. */
|
|
50
|
+
export declare function synastryOverlays(chartA: Chart, chartB: Chart): SynastryOverlays;
|
|
51
|
+
/**
|
|
52
|
+
* Midpoint-composite placements for `bodies` from two birth instants.
|
|
53
|
+
*/
|
|
54
|
+
export declare function compositePlacements(engine: Engine, jdA: number, jdB: number, bodies?: BodyId[], zodiac?: Zodiac): CompositePlacement[];
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine relational -- diachronic and two-chart derivations for the
|
|
3
|
+
* interpretation layer: transits vs natal, synastry, composite midpoints.
|
|
4
|
+
*
|
|
5
|
+
* Pure geometry on validated positions; no interpretation. Mirrors the MCP
|
|
6
|
+
* transits/synastry/composite tools but returns structured hits the fact
|
|
7
|
+
* projection can turn into citable atoms.
|
|
8
|
+
*/
|
|
9
|
+
import { mod } from "./core.js";
|
|
10
|
+
import { ASPECTS, BODIES, DEFAULT_ORBS, NOT_ASPECTABLE, SIGNS, } from "./chart.js";
|
|
11
|
+
import { aspectPhase } from "./electional.js";
|
|
12
|
+
import { compositeLongitudes } from "./derived.js";
|
|
13
|
+
function houseIndex(lon, cusps) {
|
|
14
|
+
for (let i = 0; i < 12; i++) {
|
|
15
|
+
if (mod(lon - cusps[i], 360) < mod(cusps[(i + 1) % 12] - cusps[i], 360))
|
|
16
|
+
return i + 1;
|
|
17
|
+
}
|
|
18
|
+
return 12;
|
|
19
|
+
}
|
|
20
|
+
function aspectHits(lonA, speedA, labelA, lonB, speedB, labelB, maxOrb, orbs) {
|
|
21
|
+
const sep = Math.abs(mod(lonA - lonB + 180, 360) - 180);
|
|
22
|
+
const out = [];
|
|
23
|
+
for (const [name, angle] of Object.entries(ASPECTS)) {
|
|
24
|
+
const limit = Math.min(maxOrb, orbs[name] ?? maxOrb);
|
|
25
|
+
const orb = Math.abs(sep - angle);
|
|
26
|
+
if (orb > limit)
|
|
27
|
+
continue;
|
|
28
|
+
const orbRounded = Math.round(orb * 100) / 100;
|
|
29
|
+
out.push({
|
|
30
|
+
a: labelA, b: labelB, aspect: name, orb: orbRounded,
|
|
31
|
+
phase: aspectPhase(lonA, speedA, lonB, speedB, angle),
|
|
32
|
+
strength: Math.max(0, 1 - orbRounded / limit),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Transiting bodies aspecting a natal chart at `transitJd`. Natal longitudes are
|
|
39
|
+
* fixed; phase reflects the transiting body's motion toward the natal point.
|
|
40
|
+
*/
|
|
41
|
+
export function transitAspects(natal, engine, transitJd, opts = {}) {
|
|
42
|
+
const maxOrb = opts.maxOrb ?? 3;
|
|
43
|
+
const orbs = opts.orbs ?? DEFAULT_ORBS;
|
|
44
|
+
const zodiac = opts.zodiac ?? natal.zodiac;
|
|
45
|
+
const bodies = opts.bodies ?? BODIES;
|
|
46
|
+
const natalBodies = bodies.filter((b) => natal.bodies[b] && !NOT_ASPECTABLE.has(b));
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const tb of bodies) {
|
|
49
|
+
if (NOT_ASPECTABLE.has(tb))
|
|
50
|
+
continue;
|
|
51
|
+
const tp = engine.position(tb, transitJd, { zodiac });
|
|
52
|
+
const natalHouse = houseIndex(tp.lon, natal.cusps);
|
|
53
|
+
for (const nb of natalBodies) {
|
|
54
|
+
const nLon = natal.bodies[nb].lon;
|
|
55
|
+
for (const hit of aspectHits(tp.lon, tp.speed, tb, nLon, 0, nb, maxOrb, orbs)) {
|
|
56
|
+
out.push({
|
|
57
|
+
transit: hit.a, natal: hit.b, aspect: hit.aspect,
|
|
58
|
+
orb: hit.orb, phase: hit.phase, strength: hit.strength, natalHouse,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Inter-chart aspects between two natal charts (static snapshot; both speeds 0).
|
|
67
|
+
*/
|
|
68
|
+
export function synastryAspects(chartA, chartB, maxOrb = 4, orbs = DEFAULT_ORBS) {
|
|
69
|
+
const bodies = BODIES.filter((b) => chartA.bodies[b] && chartB.bodies[b] && !NOT_ASPECTABLE.has(b));
|
|
70
|
+
const out = [];
|
|
71
|
+
for (const ba of bodies) {
|
|
72
|
+
const la = chartA.bodies[ba].lon;
|
|
73
|
+
for (const bb of bodies) {
|
|
74
|
+
const lb = chartB.bodies[bb].lon;
|
|
75
|
+
for (const hit of aspectHits(la, 0, ba, lb, 0, bb, maxOrb, orbs)) {
|
|
76
|
+
out.push({ a: hit.a, b: hit.b, aspect: hit.aspect, orb: hit.orb, strength: hit.strength });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
/** House overlays both ways between two charts. */
|
|
83
|
+
export function synastryOverlays(chartA, chartB) {
|
|
84
|
+
const bodies = BODIES.filter((b) => chartA.bodies[b] && chartB.bodies[b]);
|
|
85
|
+
const aInB = {};
|
|
86
|
+
const bInA = {};
|
|
87
|
+
for (const b of bodies) {
|
|
88
|
+
aInB[b] = houseIndex(chartA.bodies[b].lon, chartB.cusps);
|
|
89
|
+
bInA[b] = houseIndex(chartB.bodies[b].lon, chartA.cusps);
|
|
90
|
+
}
|
|
91
|
+
return { aInB, bInA };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Midpoint-composite placements for `bodies` from two birth instants.
|
|
95
|
+
*/
|
|
96
|
+
export function compositePlacements(engine, jdA, jdB, bodies = BODIES, zodiac = "tropical") {
|
|
97
|
+
const lons = compositeLongitudes(engine, jdA, jdB, bodies, zodiac);
|
|
98
|
+
return bodies.map((body) => {
|
|
99
|
+
const lon = mod(lons[body], 360);
|
|
100
|
+
const signIdx = Math.floor(lon / 30) % 12;
|
|
101
|
+
return { body, lon, sign: SIGNS[signIdx], signDeg: mod(lon, 30) };
|
|
102
|
+
});
|
|
103
|
+
}
|