chart2txt 0.6.0 → 0.7.1
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 +103 -34
- package/dist/chart2txt.d.ts +9 -0
- package/dist/chart2txt.js +30 -0
- package/dist/chart2txt.min.js +1 -1
- package/dist/config/ChartSettings.d.ts +10 -6
- package/dist/config/ChartSettings.js +22 -11
- package/dist/constants.d.ts +17 -2
- package/dist/constants.js +303 -34
- package/dist/core/analysis.d.ts +6 -0
- package/dist/core/analysis.js +237 -0
- package/dist/core/aspectPatterns.d.ts +8 -3
- package/dist/core/aspectPatterns.js +234 -218
- package/dist/core/aspects.d.ts +14 -11
- package/dist/core/aspects.js +49 -32
- package/dist/core/dignities.d.ts +2 -27
- package/dist/core/dignities.js +56 -121
- package/dist/core/dispositors.d.ts +7 -19
- package/dist/core/dispositors.js +152 -126
- package/dist/core/grouping.d.ts +9 -0
- package/dist/core/grouping.js +45 -0
- package/dist/core/signDistributions.d.ts +20 -30
- package/dist/core/signDistributions.js +25 -122
- package/dist/core/stelliums.d.ts +10 -0
- package/dist/core/stelliums.js +108 -0
- package/dist/formatters/text/sections/aspectPatterns.d.ts +3 -1
- package/dist/formatters/text/sections/aspectPatterns.js +118 -94
- package/dist/formatters/text/sections/aspects.d.ts +3 -6
- package/dist/formatters/text/sections/aspects.js +35 -52
- package/dist/formatters/text/sections/dispositors.d.ts +4 -3
- package/dist/formatters/text/sections/dispositors.js +12 -8
- package/dist/formatters/text/sections/houseOverlays.d.ts +11 -6
- package/dist/formatters/text/sections/houseOverlays.js +37 -44
- package/dist/formatters/text/sections/metadata.d.ts +2 -0
- package/dist/formatters/text/sections/metadata.js +54 -0
- package/dist/formatters/text/sections/planets.d.ts +3 -5
- package/dist/formatters/text/sections/planets.js +11 -22
- package/dist/formatters/text/sections/signDistributions.d.ts +9 -25
- package/dist/formatters/text/sections/signDistributions.js +9 -55
- package/dist/formatters/text/textFormatter.d.ts +4 -5
- package/dist/formatters/text/textFormatter.js +86 -142
- package/dist/index.d.ts +7 -4
- package/dist/index.js +11 -6
- package/dist/types.d.ts +102 -15
- package/dist/types.js +15 -0
- package/dist/utils/formatting.d.ts +4 -0
- package/dist/utils/formatting.js +43 -0
- package/dist/utils/houseCalculations.d.ts +10 -13
- package/dist/utils/houseCalculations.js +15 -57
- package/package.json +1 -1
|
@@ -2,31 +2,70 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.detectAspectPatterns = detectAspectPatterns;
|
|
4
4
|
const astrology_1 = require("./astrology");
|
|
5
|
-
const houseCalculations_1 = require("../utils/houseCalculations");
|
|
6
5
|
/**
|
|
7
|
-
*
|
|
6
|
+
* Create a consistent key for planet+chart combinations
|
|
8
7
|
*/
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
const degree2 = (0, astrology_1.normalizeDegree)(planet2.degree);
|
|
12
|
-
let diff = Math.abs(degree1 - degree2);
|
|
13
|
-
if (diff > 180)
|
|
14
|
-
diff = 360 - diff;
|
|
15
|
-
return Math.abs(diff - aspectAngle);
|
|
8
|
+
function createPlanetKey(planetName, chartName) {
|
|
9
|
+
return chartName ? `${planetName}-${chartName}` : planetName;
|
|
16
10
|
}
|
|
17
11
|
/**
|
|
18
|
-
*
|
|
12
|
+
* Create a lookup map for aspect relationships between planets
|
|
19
13
|
*/
|
|
20
|
-
function
|
|
14
|
+
function createAspectLookup(aspects) {
|
|
15
|
+
const lookup = new Map();
|
|
16
|
+
aspects.forEach((aspect) => {
|
|
17
|
+
const keyA = createPlanetKey(aspect.planetA, aspect.p1ChartName);
|
|
18
|
+
const keyB = createPlanetKey(aspect.planetB, aspect.p2ChartName);
|
|
19
|
+
if (!lookup.has(keyA)) {
|
|
20
|
+
lookup.set(keyA, new Map());
|
|
21
|
+
}
|
|
22
|
+
if (!lookup.has(keyB)) {
|
|
23
|
+
lookup.set(keyB, new Map());
|
|
24
|
+
}
|
|
25
|
+
lookup.get(keyA).set(keyB, aspect);
|
|
26
|
+
lookup.get(keyB).set(keyA, aspect);
|
|
27
|
+
});
|
|
28
|
+
return lookup;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Convert Point to PlanetPosition for aspect patterns
|
|
32
|
+
* Note: House information is optional for aspect patterns (except stelliums which are handled separately)
|
|
33
|
+
*/
|
|
34
|
+
function pointToPlanetPosition(point, houseCusps, chartName) {
|
|
21
35
|
const sign = (0, astrology_1.getDegreeSign)(point.degree);
|
|
22
|
-
|
|
23
|
-
|
|
36
|
+
// For multi-chart patterns, house information may not be meaningful
|
|
37
|
+
// Only calculate house if houseCusps are provided and we're dealing with a single-chart context
|
|
38
|
+
const house = houseCusps && houseCusps.length === 12
|
|
39
|
+
? (() => {
|
|
40
|
+
// Simple house calculation without importing the utility
|
|
41
|
+
const normalizedDegree = point.degree % 360;
|
|
42
|
+
for (let i = 0; i < 12; i++) {
|
|
43
|
+
const currentCusp = houseCusps[i];
|
|
44
|
+
const nextCusp = houseCusps[(i + 1) % 12];
|
|
45
|
+
if (nextCusp > currentCusp) {
|
|
46
|
+
// Normal case: house doesn't cross 0°
|
|
47
|
+
if (normalizedDegree >= currentCusp &&
|
|
48
|
+
normalizedDegree < nextCusp) {
|
|
49
|
+
return i + 1;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// House crosses 0° (e.g., 350° to 20°)
|
|
54
|
+
if (normalizedDegree >= currentCusp ||
|
|
55
|
+
normalizedDegree < nextCusp) {
|
|
56
|
+
return i + 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
})()
|
|
24
62
|
: undefined;
|
|
25
63
|
return {
|
|
26
64
|
name: point.name,
|
|
27
65
|
degree: point.degree,
|
|
28
66
|
sign,
|
|
29
67
|
house,
|
|
68
|
+
chartName,
|
|
30
69
|
};
|
|
31
70
|
}
|
|
32
71
|
/**
|
|
@@ -35,7 +74,6 @@ function pointToPlanetPosition(point, houseCusps) {
|
|
|
35
74
|
function getSignModality(sign) {
|
|
36
75
|
const cardinal = ['Aries', 'Cancer', 'Libra', 'Capricorn'];
|
|
37
76
|
const fixed = ['Taurus', 'Leo', 'Scorpio', 'Aquarius'];
|
|
38
|
-
const mutable = ['Gemini', 'Virgo', 'Sagittarius', 'Pisces'];
|
|
39
77
|
if (cardinal.includes(sign))
|
|
40
78
|
return 'Cardinal';
|
|
41
79
|
if (fixed.includes(sign))
|
|
@@ -49,7 +87,6 @@ function getSignElement(sign) {
|
|
|
49
87
|
const fire = ['Aries', 'Leo', 'Sagittarius'];
|
|
50
88
|
const earth = ['Taurus', 'Virgo', 'Capricorn'];
|
|
51
89
|
const air = ['Gemini', 'Libra', 'Aquarius'];
|
|
52
|
-
const water = ['Cancer', 'Scorpio', 'Pisces'];
|
|
53
90
|
if (fire.includes(sign))
|
|
54
91
|
return 'Fire';
|
|
55
92
|
if (earth.includes(sign))
|
|
@@ -59,34 +96,54 @@ function getSignElement(sign) {
|
|
|
59
96
|
return 'Water';
|
|
60
97
|
}
|
|
61
98
|
/**
|
|
62
|
-
* Check if two planets
|
|
99
|
+
* Check if two planets have a specific aspect type using the pre-calculated aspects
|
|
63
100
|
*/
|
|
64
|
-
function
|
|
65
|
-
const
|
|
66
|
-
|
|
101
|
+
function hasSpecificAspect(p1, p2, aspectType, aspectLookup) {
|
|
102
|
+
const key1 = createPlanetKey(p1[0].name, p1[1]);
|
|
103
|
+
const key2 = createPlanetKey(p2[0].name, p2[1]);
|
|
104
|
+
const planet1Aspects = aspectLookup.get(key1);
|
|
105
|
+
if (!planet1Aspects)
|
|
106
|
+
return false;
|
|
107
|
+
const aspectData = planet1Aspects.get(key2);
|
|
108
|
+
return aspectData !== undefined && aspectData.aspectType === aspectType;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get aspect data between two planets if it exists
|
|
112
|
+
*/
|
|
113
|
+
function getAspectBetween(p1, p2, aspectLookup) {
|
|
114
|
+
const key1 = createPlanetKey(p1[0].name, p1[1]);
|
|
115
|
+
const key2 = createPlanetKey(p2[0].name, p2[1]);
|
|
116
|
+
const planet1Aspects = aspectLookup.get(key1);
|
|
117
|
+
if (!planet1Aspects)
|
|
118
|
+
return undefined;
|
|
119
|
+
return planet1Aspects.get(key2);
|
|
67
120
|
}
|
|
68
121
|
/**
|
|
69
122
|
* Detect T-Square patterns
|
|
70
123
|
*/
|
|
71
|
-
function detectTSquares(
|
|
124
|
+
function detectTSquares(unionedPoints, aspectLookup, houseCusps) {
|
|
72
125
|
const patterns = [];
|
|
73
|
-
for (let i = 0; i <
|
|
74
|
-
for (let j = i + 1; j <
|
|
126
|
+
for (let i = 0; i < unionedPoints.length; i++) {
|
|
127
|
+
for (let j = i + 1; j < unionedPoints.length; j++) {
|
|
75
128
|
// Check for opposition
|
|
76
|
-
if (
|
|
129
|
+
if (hasSpecificAspect(unionedPoints[i], unionedPoints[j], 'opposition', aspectLookup)) {
|
|
77
130
|
// Look for a third planet that squares both
|
|
78
|
-
for (let k = 0; k <
|
|
131
|
+
for (let k = 0; k < unionedPoints.length; k++) {
|
|
79
132
|
if (k === i || k === j)
|
|
80
133
|
continue;
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
134
|
+
if (hasSpecificAspect(unionedPoints[i], unionedPoints[k], 'square', aspectLookup) &&
|
|
135
|
+
hasSpecificAspect(unionedPoints[j], unionedPoints[k], 'square', aspectLookup)) {
|
|
136
|
+
const [pApex, cApex] = unionedPoints[k];
|
|
137
|
+
const [pOpp1, cOpp1] = unionedPoints[i];
|
|
138
|
+
const [pOpp2, cOpp2] = unionedPoints[j];
|
|
139
|
+
const apex = pointToPlanetPosition(pApex, houseCusps, cApex);
|
|
140
|
+
const opp1 = pointToPlanetPosition(pOpp1, houseCusps, cOpp1);
|
|
141
|
+
const opp2 = pointToPlanetPosition(pOpp2, houseCusps, cOpp2);
|
|
142
|
+
// Get actual orbs from pre-calculated aspects
|
|
143
|
+
const oppAspect = getAspectBetween(unionedPoints[i], unionedPoints[j], aspectLookup);
|
|
144
|
+
const square1Aspect = getAspectBetween(unionedPoints[i], unionedPoints[k], aspectLookup);
|
|
145
|
+
const square2Aspect = getAspectBetween(unionedPoints[j], unionedPoints[k], aspectLookup);
|
|
146
|
+
const averageOrb = (oppAspect.orb + square1Aspect.orb + square2Aspect.orb) / 3;
|
|
90
147
|
// Determine modality from apex planet
|
|
91
148
|
const mode = getSignModality(apex.sign);
|
|
92
149
|
patterns.push({
|
|
@@ -106,22 +163,26 @@ function detectTSquares(planets, houseCusps, maxOrb = 8) {
|
|
|
106
163
|
/**
|
|
107
164
|
* Detect Grand Trine patterns
|
|
108
165
|
*/
|
|
109
|
-
function detectGrandTrines(
|
|
166
|
+
function detectGrandTrines(unionedPoints, aspectLookup, houseCusps) {
|
|
110
167
|
const patterns = [];
|
|
111
|
-
for (let i = 0; i <
|
|
112
|
-
for (let j = i + 1; j <
|
|
113
|
-
for (let k = j + 1; k <
|
|
168
|
+
for (let i = 0; i < unionedPoints.length; i++) {
|
|
169
|
+
for (let j = i + 1; j < unionedPoints.length; j++) {
|
|
170
|
+
for (let k = j + 1; k < unionedPoints.length; k++) {
|
|
114
171
|
// Check if all three planets form trines with each other
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
172
|
+
if (hasSpecificAspect(unionedPoints[i], unionedPoints[j], 'trine', aspectLookup) &&
|
|
173
|
+
hasSpecificAspect(unionedPoints[j], unionedPoints[k], 'trine', aspectLookup) &&
|
|
174
|
+
hasSpecificAspect(unionedPoints[k], unionedPoints[i], 'trine', aspectLookup)) {
|
|
175
|
+
const [p1, c1] = unionedPoints[i];
|
|
176
|
+
const [p2, c2] = unionedPoints[j];
|
|
177
|
+
const [p3, c3] = unionedPoints[k];
|
|
178
|
+
const planet1 = pointToPlanetPosition(p1, houseCusps, c1);
|
|
179
|
+
const planet2 = pointToPlanetPosition(p2, houseCusps, c2);
|
|
180
|
+
const planet3 = pointToPlanetPosition(p3, houseCusps, c3);
|
|
181
|
+
// Get actual orbs from pre-calculated aspects
|
|
182
|
+
const trine1Aspect = getAspectBetween(unionedPoints[i], unionedPoints[j], aspectLookup);
|
|
183
|
+
const trine2Aspect = getAspectBetween(unionedPoints[j], unionedPoints[k], aspectLookup);
|
|
184
|
+
const trine3Aspect = getAspectBetween(unionedPoints[k], unionedPoints[i], aspectLookup);
|
|
185
|
+
const averageOrb = (trine1Aspect.orb + trine2Aspect.orb + trine3Aspect.orb) / 3;
|
|
125
186
|
// Determine element from the planets (should be same element for proper grand trine)
|
|
126
187
|
const element = getSignElement(planet1.sign);
|
|
127
188
|
patterns.push({
|
|
@@ -136,121 +197,60 @@ function detectGrandTrines(planets, houseCusps, maxOrb = 8) {
|
|
|
136
197
|
}
|
|
137
198
|
return patterns;
|
|
138
199
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Detect Stellium patterns (3+ planets in same sign or adjacent houses)
|
|
141
|
-
*/
|
|
142
|
-
function detectStelliums(planets, houseCusps, minPlanets = 3) {
|
|
143
|
-
const patterns = [];
|
|
144
|
-
// Group by sign
|
|
145
|
-
const signGroups = new Map();
|
|
146
|
-
planets.forEach((planet) => {
|
|
147
|
-
const sign = (0, astrology_1.getDegreeSign)(planet.degree);
|
|
148
|
-
if (!signGroups.has(sign)) {
|
|
149
|
-
signGroups.set(sign, []);
|
|
150
|
-
}
|
|
151
|
-
signGroups.get(sign).push(planet);
|
|
152
|
-
});
|
|
153
|
-
// Check sign-based stelliums
|
|
154
|
-
signGroups.forEach((planetsInSign, sign) => {
|
|
155
|
-
if (planetsInSign.length >= minPlanets) {
|
|
156
|
-
const planetPositions = planetsInSign.map((p) => pointToPlanetPosition(p, houseCusps));
|
|
157
|
-
const houses = planetPositions
|
|
158
|
-
.map((p) => p.house)
|
|
159
|
-
.filter((h) => h !== undefined);
|
|
160
|
-
const degrees = planetsInSign.map((p) => p.degree);
|
|
161
|
-
const span = Math.max(...degrees) - Math.min(...degrees);
|
|
162
|
-
patterns.push({
|
|
163
|
-
type: 'Stellium',
|
|
164
|
-
planets: planetPositions,
|
|
165
|
-
sign,
|
|
166
|
-
houses: [...new Set(houses)].sort(),
|
|
167
|
-
span,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
// Check house-based stelliums (if house cusps available)
|
|
172
|
-
if (houseCusps) {
|
|
173
|
-
const houseGroups = new Map();
|
|
174
|
-
planets.forEach((planet) => {
|
|
175
|
-
const house = (0, houseCalculations_1.getHouseForPoint)(planet.degree, houseCusps);
|
|
176
|
-
if (house) {
|
|
177
|
-
if (!houseGroups.has(house)) {
|
|
178
|
-
houseGroups.set(house, []);
|
|
179
|
-
}
|
|
180
|
-
houseGroups.get(house).push(planet);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
houseGroups.forEach((planetsInHouse, house) => {
|
|
184
|
-
if (planetsInHouse.length >= minPlanets) {
|
|
185
|
-
const planetPositions = planetsInHouse.map((p) => pointToPlanetPosition(p, houseCusps));
|
|
186
|
-
const degrees = planetsInHouse.map((p) => p.degree);
|
|
187
|
-
const span = Math.max(...degrees) - Math.min(...degrees);
|
|
188
|
-
// Only add if not already covered by sign stellium
|
|
189
|
-
const existingSignStellium = patterns.find((p) => p.type === 'Stellium' &&
|
|
190
|
-
p.planets.some((planet) => planetPositions.some((pp) => pp.name === planet.name)));
|
|
191
|
-
if (!existingSignStellium) {
|
|
192
|
-
patterns.push({
|
|
193
|
-
type: 'Stellium',
|
|
194
|
-
planets: planetPositions,
|
|
195
|
-
houses: [house],
|
|
196
|
-
span,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
return patterns;
|
|
203
|
-
}
|
|
204
200
|
/**
|
|
205
201
|
* Detect Grand Cross patterns
|
|
206
202
|
*/
|
|
207
|
-
function detectGrandCrosses(
|
|
203
|
+
function detectGrandCrosses(unionedPoints, aspectLookup, houseCusps) {
|
|
208
204
|
const patterns = [];
|
|
209
|
-
for (let i = 0; i <
|
|
210
|
-
for (let j = i + 1; j <
|
|
211
|
-
for (let k = j + 1; k <
|
|
212
|
-
for (let l = k + 1; l <
|
|
205
|
+
for (let i = 0; i < unionedPoints.length; i++) {
|
|
206
|
+
for (let j = i + 1; j < unionedPoints.length; j++) {
|
|
207
|
+
for (let k = j + 1; k < unionedPoints.length; k++) {
|
|
208
|
+
for (let l = k + 1; l < unionedPoints.length; l++) {
|
|
209
|
+
const group = [
|
|
210
|
+
unionedPoints[i],
|
|
211
|
+
unionedPoints[j],
|
|
212
|
+
unionedPoints[k],
|
|
213
|
+
unionedPoints[l],
|
|
214
|
+
];
|
|
213
215
|
// Check if planets form two oppositions and four squares
|
|
214
216
|
const pairs = [
|
|
215
|
-
[
|
|
216
|
-
[
|
|
217
|
+
[0, 1],
|
|
218
|
+
[2, 3],
|
|
217
219
|
];
|
|
218
220
|
const otherPairs = [
|
|
219
|
-
[
|
|
220
|
-
[
|
|
221
|
-
[
|
|
222
|
-
[
|
|
221
|
+
[0, 2],
|
|
222
|
+
[1, 3],
|
|
223
|
+
[0, 3],
|
|
224
|
+
[1, 2],
|
|
223
225
|
];
|
|
224
226
|
// Check for two oppositions
|
|
225
227
|
let oppositions = 0;
|
|
226
228
|
let squares = 0;
|
|
229
|
+
const aspectData = [];
|
|
227
230
|
pairs.forEach(([a, b]) => {
|
|
228
|
-
if (
|
|
231
|
+
if (hasSpecificAspect(group[a], group[b], 'opposition', aspectLookup)) {
|
|
229
232
|
oppositions++;
|
|
233
|
+
aspectData.push(getAspectBetween(group[a], group[b], aspectLookup));
|
|
230
234
|
}
|
|
231
235
|
});
|
|
232
236
|
otherPairs.forEach(([a, b]) => {
|
|
233
|
-
if (
|
|
237
|
+
if (hasSpecificAspect(group[a], group[b], 'square', aspectLookup)) {
|
|
234
238
|
squares++;
|
|
239
|
+
aspectData.push(getAspectBetween(group[a], group[b], aspectLookup));
|
|
235
240
|
}
|
|
236
241
|
});
|
|
237
242
|
if (oppositions === 2 && squares === 4) {
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
otherPairs.forEach(([a, b]) => {
|
|
250
|
-
totalOrb += calculateOrb(planets[a], planets[b], 90);
|
|
251
|
-
aspectCount++;
|
|
252
|
-
});
|
|
253
|
-
const averageOrb = totalOrb / aspectCount;
|
|
243
|
+
const [p1, c1] = group[0];
|
|
244
|
+
const [p2, c2] = group[1];
|
|
245
|
+
const [p3, c3] = group[2];
|
|
246
|
+
const [p4, c4] = group[3];
|
|
247
|
+
const planet1 = pointToPlanetPosition(p1, houseCusps, c1);
|
|
248
|
+
const planet2 = pointToPlanetPosition(p2, houseCusps, c2);
|
|
249
|
+
const planet3 = pointToPlanetPosition(p3, houseCusps, c3);
|
|
250
|
+
const planet4 = pointToPlanetPosition(p4, houseCusps, c4);
|
|
251
|
+
// Calculate average orb from actual aspect data
|
|
252
|
+
const totalOrb = aspectData.reduce((sum, aspect) => sum + aspect.orb, 0);
|
|
253
|
+
const averageOrb = totalOrb / aspectData.length;
|
|
254
254
|
const mode = getSignModality(planet1.sign); // Determine from first planet
|
|
255
255
|
patterns.push({
|
|
256
256
|
type: 'Grand Cross',
|
|
@@ -268,25 +268,30 @@ function detectGrandCrosses(planets, houseCusps, maxOrb = 8) {
|
|
|
268
268
|
/**
|
|
269
269
|
* Detect Yod patterns (two quincunxes to apex planet and one sextile between base planets)
|
|
270
270
|
*/
|
|
271
|
-
function detectYods(
|
|
271
|
+
function detectYods(unionedPoints, aspectLookup, houseCusps) {
|
|
272
272
|
const patterns = [];
|
|
273
|
-
for (let i = 0; i <
|
|
274
|
-
for (let j = i + 1; j <
|
|
273
|
+
for (let i = 0; i < unionedPoints.length; i++) {
|
|
274
|
+
for (let j = i + 1; j < unionedPoints.length; j++) {
|
|
275
275
|
// Check for sextile between base planets
|
|
276
|
-
if (
|
|
276
|
+
if (hasSpecificAspect(unionedPoints[i], unionedPoints[j], 'sextile', aspectLookup)) {
|
|
277
277
|
// Look for apex planet that forms quincunxes with both
|
|
278
|
-
for (let k = 0; k <
|
|
278
|
+
for (let k = 0; k < unionedPoints.length; k++) {
|
|
279
279
|
if (k === i || k === j)
|
|
280
280
|
continue;
|
|
281
|
-
if (
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
const
|
|
285
|
-
const
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
|
|
281
|
+
if (hasSpecificAspect(unionedPoints[i], unionedPoints[k], 'quincunx', aspectLookup) &&
|
|
282
|
+
hasSpecificAspect(unionedPoints[j], unionedPoints[k], 'quincunx', aspectLookup)) {
|
|
283
|
+
const [pApex, cApex] = unionedPoints[k];
|
|
284
|
+
const [pBase1, cBase1] = unionedPoints[i];
|
|
285
|
+
const [pBase2, cBase2] = unionedPoints[j];
|
|
286
|
+
const apex = pointToPlanetPosition(pApex, houseCusps, cApex);
|
|
287
|
+
const base1 = pointToPlanetPosition(pBase1, houseCusps, cBase1);
|
|
288
|
+
const base2 = pointToPlanetPosition(pBase2, houseCusps, cBase2);
|
|
289
|
+
// Get actual orbs from pre-calculated aspects
|
|
290
|
+
const sextileAspect = getAspectBetween(unionedPoints[i], unionedPoints[j], aspectLookup);
|
|
291
|
+
const quincunx1Aspect = getAspectBetween(unionedPoints[i], unionedPoints[k], aspectLookup);
|
|
292
|
+
const quincunx2Aspect = getAspectBetween(unionedPoints[j], unionedPoints[k], aspectLookup);
|
|
293
|
+
const averageOrb = (sextileAspect.orb + quincunx1Aspect.orb + quincunx2Aspect.orb) /
|
|
294
|
+
3;
|
|
290
295
|
patterns.push({
|
|
291
296
|
type: 'Yod',
|
|
292
297
|
apex,
|
|
@@ -303,84 +308,88 @@ function detectYods(planets, houseCusps, maxOrb = 5) {
|
|
|
303
308
|
/**
|
|
304
309
|
* Detect Mystic Rectangle patterns (two oppositions with sextiles and trines)
|
|
305
310
|
*/
|
|
306
|
-
function detectMysticRectangles(
|
|
311
|
+
function detectMysticRectangles(unionedPoints, aspectLookup, houseCusps) {
|
|
307
312
|
const patterns = [];
|
|
308
|
-
for (let i = 0; i <
|
|
309
|
-
for (let j = i + 1; j <
|
|
310
|
-
for (let k = j + 1; k <
|
|
311
|
-
for (let l = k + 1; l <
|
|
313
|
+
for (let i = 0; i < unionedPoints.length; i++) {
|
|
314
|
+
for (let j = i + 1; j < unionedPoints.length; j++) {
|
|
315
|
+
for (let k = j + 1; k < unionedPoints.length; k++) {
|
|
316
|
+
for (let l = k + 1; l < unionedPoints.length; l++) {
|
|
317
|
+
const group = [
|
|
318
|
+
unionedPoints[i],
|
|
319
|
+
unionedPoints[j],
|
|
320
|
+
unionedPoints[k],
|
|
321
|
+
unionedPoints[l],
|
|
322
|
+
];
|
|
312
323
|
// Check for two oppositions and appropriate sextiles/trines
|
|
313
324
|
const combinations = [
|
|
314
325
|
{
|
|
315
326
|
oppositions: [
|
|
316
|
-
[
|
|
317
|
-
[
|
|
327
|
+
[0, 1],
|
|
328
|
+
[2, 3],
|
|
318
329
|
],
|
|
319
330
|
sextiles: [
|
|
320
|
-
[
|
|
321
|
-
[
|
|
322
|
-
[
|
|
323
|
-
[
|
|
331
|
+
[0, 2],
|
|
332
|
+
[0, 3],
|
|
333
|
+
[1, 2],
|
|
334
|
+
[1, 3],
|
|
324
335
|
],
|
|
325
336
|
},
|
|
326
337
|
{
|
|
327
338
|
oppositions: [
|
|
328
|
-
[
|
|
329
|
-
[
|
|
339
|
+
[0, 2],
|
|
340
|
+
[1, 3],
|
|
330
341
|
],
|
|
331
342
|
sextiles: [
|
|
332
|
-
[
|
|
333
|
-
[
|
|
334
|
-
[
|
|
335
|
-
[
|
|
343
|
+
[0, 1],
|
|
344
|
+
[0, 3],
|
|
345
|
+
[2, 1],
|
|
346
|
+
[2, 3],
|
|
336
347
|
],
|
|
337
348
|
},
|
|
338
349
|
{
|
|
339
350
|
oppositions: [
|
|
340
|
-
[
|
|
341
|
-
[
|
|
351
|
+
[0, 3],
|
|
352
|
+
[1, 2],
|
|
342
353
|
],
|
|
343
354
|
sextiles: [
|
|
344
|
-
[
|
|
345
|
-
[
|
|
346
|
-
[
|
|
347
|
-
[
|
|
355
|
+
[0, 1],
|
|
356
|
+
[0, 2],
|
|
357
|
+
[3, 1],
|
|
358
|
+
[3, 2],
|
|
348
359
|
],
|
|
349
360
|
},
|
|
350
361
|
];
|
|
351
362
|
for (const combo of combinations) {
|
|
352
363
|
let validOppositions = 0;
|
|
353
364
|
let validSextiles = 0;
|
|
365
|
+
const aspectData = [];
|
|
354
366
|
combo.oppositions.forEach(([a, b]) => {
|
|
355
|
-
if (
|
|
367
|
+
if (hasSpecificAspect(group[a], group[b], 'opposition', aspectLookup)) {
|
|
356
368
|
validOppositions++;
|
|
369
|
+
aspectData.push(getAspectBetween(group[a], group[b], aspectLookup));
|
|
357
370
|
}
|
|
358
371
|
});
|
|
359
372
|
combo.sextiles.forEach(([a, b]) => {
|
|
360
|
-
|
|
361
|
-
|
|
373
|
+
const sextileAspect = getAspectBetween(group[a], group[b], aspectLookup);
|
|
374
|
+
if (sextileAspect &&
|
|
375
|
+
(sextileAspect.aspectType === 'sextile' ||
|
|
376
|
+
sextileAspect.aspectType === 'trine')) {
|
|
362
377
|
validSextiles++;
|
|
378
|
+
aspectData.push(sextileAspect);
|
|
363
379
|
}
|
|
364
380
|
});
|
|
365
381
|
if (validOppositions === 2 && validSextiles === 4) {
|
|
366
|
-
const
|
|
367
|
-
const
|
|
368
|
-
const
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
combo.sextiles.forEach(([a, b]) => {
|
|
378
|
-
const sextileOrb = calculateOrb(planets[a], planets[b], 60);
|
|
379
|
-
const trineOrb = calculateOrb(planets[a], planets[b], 120);
|
|
380
|
-
totalOrb += Math.min(sextileOrb, trineOrb);
|
|
381
|
-
aspectCount++;
|
|
382
|
-
});
|
|
383
|
-
const averageOrb = totalOrb / aspectCount;
|
|
382
|
+
const [p1, c1] = group[combo.oppositions[0][0]];
|
|
383
|
+
const [p2, c2] = group[combo.oppositions[0][1]];
|
|
384
|
+
const [p3, c3] = group[combo.oppositions[1][0]];
|
|
385
|
+
const [p4, c4] = group[combo.oppositions[1][1]];
|
|
386
|
+
const pos1 = pointToPlanetPosition(p1, houseCusps, c1);
|
|
387
|
+
const pos2 = pointToPlanetPosition(p2, houseCusps, c2);
|
|
388
|
+
const pos3 = pointToPlanetPosition(p3, houseCusps, c3);
|
|
389
|
+
const pos4 = pointToPlanetPosition(p4, houseCusps, c4);
|
|
390
|
+
// Calculate average orb from actual aspect data
|
|
391
|
+
const totalOrb = aspectData.reduce((sum, aspect) => sum + aspect.orb, 0);
|
|
392
|
+
const averageOrb = totalOrb / aspectData.length;
|
|
384
393
|
patterns.push({
|
|
385
394
|
type: 'Mystic Rectangle',
|
|
386
395
|
oppositions: [
|
|
@@ -400,21 +409,23 @@ function detectMysticRectangles(planets, houseCusps, maxOrb = 8) {
|
|
|
400
409
|
/**
|
|
401
410
|
* Detect Kite patterns (Grand Trine with one opposition)
|
|
402
411
|
*/
|
|
403
|
-
function detectKites(
|
|
412
|
+
function detectKites(unionedPoints, aspectLookup, houseCusps) {
|
|
404
413
|
const patterns = [];
|
|
405
|
-
const grandTrines = detectGrandTrines(
|
|
414
|
+
const grandTrines = detectGrandTrines(unionedPoints, aspectLookup, houseCusps);
|
|
406
415
|
grandTrines.forEach((grandTrine) => {
|
|
407
416
|
// For each planet in the grand trine, look for opposition to another planet
|
|
408
417
|
grandTrine.planets.forEach((trinePoint) => {
|
|
409
|
-
|
|
410
|
-
const
|
|
418
|
+
unionedPoints.forEach((unionedPoint) => {
|
|
419
|
+
const [planet, chartName] = unionedPoint;
|
|
420
|
+
const isPartOfTrine = grandTrine.planets.some((tp) => tp.name === planet.name && tp.chartName === chartName);
|
|
411
421
|
if (!isPartOfTrine) {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
const
|
|
422
|
+
// Find the original UnionedPoint for this trine point
|
|
423
|
+
const trineUnionedPoint = unionedPoints.find(([p, c]) => p.name === trinePoint.name && c === trinePoint.chartName);
|
|
424
|
+
if (trineUnionedPoint &&
|
|
425
|
+
hasSpecificAspect(trineUnionedPoint, unionedPoint, 'opposition', aspectLookup)) {
|
|
426
|
+
const oppositionPlanet = pointToPlanetPosition(planet, houseCusps, chartName);
|
|
427
|
+
const oppositionAspect = getAspectBetween(trineUnionedPoint, unionedPoint, aspectLookup);
|
|
428
|
+
const averageOrb = (grandTrine.averageOrb + oppositionAspect.orb) / 2;
|
|
418
429
|
patterns.push({
|
|
419
430
|
type: 'Kite',
|
|
420
431
|
grandTrine: grandTrine.planets,
|
|
@@ -429,16 +440,21 @@ function detectKites(planets, houseCusps, maxOrb = 8) {
|
|
|
429
440
|
return patterns;
|
|
430
441
|
}
|
|
431
442
|
/**
|
|
432
|
-
* Main function to detect
|
|
443
|
+
* Main function to detect aspect patterns (excluding stelliums which are handled separately)
|
|
444
|
+
* This function works with both single-chart and multi-chart scenarios
|
|
445
|
+
* @param planets Array of planets to analyze
|
|
446
|
+
* @param aspects Pre-calculated aspects between planets
|
|
447
|
+
* @param houseCusps Optional house cusps for single-chart reference
|
|
448
|
+
* @param planetChartMap Optional mapping from planet name to chart name for multichart ownership context
|
|
433
449
|
*/
|
|
434
|
-
function detectAspectPatterns(
|
|
450
|
+
function detectAspectPatterns(unionedPoints, aspects, houseCusps) {
|
|
435
451
|
const patterns = [];
|
|
436
|
-
|
|
437
|
-
patterns.push(...
|
|
438
|
-
patterns.push(...
|
|
439
|
-
patterns.push(...detectGrandCrosses(
|
|
440
|
-
patterns.push(...detectYods(
|
|
441
|
-
patterns.push(...detectMysticRectangles(
|
|
442
|
-
patterns.push(...detectKites(
|
|
452
|
+
const aspectLookup = createAspectLookup(aspects);
|
|
453
|
+
patterns.push(...detectTSquares(unionedPoints, aspectLookup, houseCusps));
|
|
454
|
+
patterns.push(...detectGrandTrines(unionedPoints, aspectLookup, houseCusps));
|
|
455
|
+
patterns.push(...detectGrandCrosses(unionedPoints, aspectLookup, houseCusps));
|
|
456
|
+
patterns.push(...detectYods(unionedPoints, aspectLookup, houseCusps));
|
|
457
|
+
patterns.push(...detectMysticRectangles(unionedPoints, aspectLookup, houseCusps));
|
|
458
|
+
patterns.push(...detectKites(unionedPoints, aspectLookup, houseCusps));
|
|
443
459
|
return patterns;
|
|
444
460
|
}
|