chart2txt 0.5.2 → 0.7.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 (59) hide show
  1. package/README.md +101 -34
  2. package/dist/chart2txt.d.ts +9 -0
  3. package/dist/chart2txt.js +30 -0
  4. package/dist/chart2txt.min.js +1 -1
  5. package/dist/config/ChartSettings.d.ts +13 -6
  6. package/dist/config/ChartSettings.js +36 -10
  7. package/dist/constants.d.ts +17 -2
  8. package/dist/constants.js +301 -32
  9. package/dist/core/analysis.d.ts +6 -0
  10. package/dist/core/analysis.js +235 -0
  11. package/dist/core/aspectPatterns.d.ts +10 -0
  12. package/dist/core/aspectPatterns.js +460 -0
  13. package/dist/core/aspects.d.ts +14 -11
  14. package/dist/core/aspects.js +142 -40
  15. package/dist/core/astrology.d.ts +8 -2
  16. package/dist/core/astrology.js +23 -6
  17. package/dist/core/dignities.d.ts +2 -0
  18. package/dist/core/dignities.js +71 -0
  19. package/dist/core/dispositors.d.ts +9 -0
  20. package/dist/core/dispositors.js +57 -0
  21. package/dist/core/grouping.d.ts +9 -0
  22. package/dist/core/grouping.js +45 -0
  23. package/dist/core/signDistributions.d.ts +21 -0
  24. package/dist/core/signDistributions.js +50 -0
  25. package/dist/core/stelliums.d.ts +10 -0
  26. package/dist/core/stelliums.js +108 -0
  27. package/dist/formatters/text/sections/angles.js +4 -4
  28. package/dist/formatters/text/sections/aspectPatterns.d.ts +9 -0
  29. package/dist/formatters/text/sections/aspectPatterns.js +199 -0
  30. package/dist/formatters/text/sections/aspects.d.ts +3 -6
  31. package/dist/formatters/text/sections/aspects.js +35 -49
  32. package/dist/formatters/text/sections/birthdata.js +1 -1
  33. package/dist/formatters/text/sections/dispositors.d.ts +8 -0
  34. package/dist/formatters/text/sections/dispositors.js +19 -0
  35. package/dist/formatters/text/sections/houseOverlays.d.ts +11 -6
  36. package/dist/formatters/text/sections/houseOverlays.js +38 -69
  37. package/dist/formatters/text/sections/houses.d.ts +6 -0
  38. package/dist/formatters/text/sections/houses.js +36 -0
  39. package/dist/formatters/text/sections/metadata.d.ts +2 -0
  40. package/dist/formatters/text/sections/metadata.js +54 -0
  41. package/dist/formatters/text/sections/planets.d.ts +3 -5
  42. package/dist/formatters/text/sections/planets.js +12 -38
  43. package/dist/formatters/text/sections/signDistributions.d.ts +9 -0
  44. package/dist/formatters/text/sections/signDistributions.js +21 -0
  45. package/dist/formatters/text/textFormatter.d.ts +4 -5
  46. package/dist/formatters/text/textFormatter.js +86 -112
  47. package/dist/index.d.ts +7 -4
  48. package/dist/index.js +11 -6
  49. package/dist/types.d.ts +159 -13
  50. package/dist/types.js +15 -0
  51. package/dist/utils/formatting.d.ts +10 -0
  52. package/dist/utils/formatting.js +56 -0
  53. package/dist/utils/houseCalculations.d.ts +10 -0
  54. package/dist/utils/houseCalculations.js +23 -0
  55. package/dist/utils/precision.d.ts +49 -0
  56. package/dist/utils/precision.js +71 -0
  57. package/dist/utils/validation.d.ts +37 -0
  58. package/dist/utils/validation.js +181 -0
  59. package/package.json +2 -1
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectStelliums = detectStelliums;
4
+ exports.formatStellium = formatStellium;
5
+ const astrology_1 = require("./astrology");
6
+ const houseCalculations_1 = require("../utils/houseCalculations");
7
+ const formatting_1 = require("../utils/formatting");
8
+ /**
9
+ * Convert Point to PlanetPosition
10
+ */
11
+ function pointToPlanetPosition(point, houseCusps) {
12
+ const sign = (0, astrology_1.getDegreeSign)(point.degree);
13
+ const house = houseCusps
14
+ ? (0, houseCalculations_1.getHouseForPoint)(point.degree, houseCusps) || undefined
15
+ : undefined;
16
+ return {
17
+ name: point.name,
18
+ degree: point.degree,
19
+ sign,
20
+ house,
21
+ };
22
+ }
23
+ /**
24
+ * Detect Stellium patterns (3+ planets in same sign or adjacent houses)
25
+ * This function requires house information and is specific to single-chart analysis
26
+ */
27
+ function detectStelliums(planets, houseCusps, minPlanets = 3) {
28
+ const patterns = [];
29
+ // Group by sign
30
+ const signGroups = new Map();
31
+ planets.forEach((planet) => {
32
+ const sign = (0, astrology_1.getDegreeSign)(planet.degree);
33
+ if (!signGroups.has(sign)) {
34
+ signGroups.set(sign, []);
35
+ }
36
+ signGroups.get(sign).push(planet);
37
+ });
38
+ // Check sign-based stelliums
39
+ signGroups.forEach((planetsInSign, sign) => {
40
+ if (planetsInSign.length >= minPlanets) {
41
+ const planetPositions = planetsInSign.map((p) => pointToPlanetPosition(p, houseCusps));
42
+ const houses = planetPositions
43
+ .map((p) => p.house)
44
+ .filter((h) => h !== undefined);
45
+ const degrees = planetsInSign.map((p) => p.degree);
46
+ const span = Math.max(...degrees) - Math.min(...degrees);
47
+ patterns.push({
48
+ type: 'Stellium',
49
+ planets: planetPositions,
50
+ sign,
51
+ houses: [...new Set(houses)].sort(),
52
+ span,
53
+ });
54
+ }
55
+ });
56
+ // Check house-based stelliums (if house cusps available)
57
+ if (houseCusps) {
58
+ const houseGroups = new Map();
59
+ planets.forEach((planet) => {
60
+ const house = (0, houseCalculations_1.getHouseForPoint)(planet.degree, houseCusps);
61
+ if (house) {
62
+ if (!houseGroups.has(house)) {
63
+ houseGroups.set(house, []);
64
+ }
65
+ houseGroups.get(house).push(planet);
66
+ }
67
+ });
68
+ houseGroups.forEach((planetsInHouse, house) => {
69
+ if (planetsInHouse.length >= minPlanets) {
70
+ const planetPositions = planetsInHouse.map((p) => pointToPlanetPosition(p, houseCusps));
71
+ const degrees = planetsInHouse.map((p) => p.degree);
72
+ const span = Math.max(...degrees) - Math.min(...degrees);
73
+ // Only add if not already covered by sign stellium
74
+ const existingSignStellium = patterns.find((p) => p.type === 'Stellium' &&
75
+ p.planets.some((planet) => planetPositions.some((pp) => pp.name === planet.name)));
76
+ if (!existingSignStellium) {
77
+ patterns.push({
78
+ type: 'Stellium',
79
+ planets: planetPositions,
80
+ houses: [house],
81
+ span,
82
+ });
83
+ }
84
+ }
85
+ });
86
+ }
87
+ return patterns;
88
+ }
89
+ /**
90
+ * Format a Stellium pattern for display in compact format
91
+ */
92
+ function formatStellium(pattern) {
93
+ const planetNames = pattern.planets.map((p) => p.name).join(', ');
94
+ let location = '';
95
+ if (pattern.sign) {
96
+ location = pattern.sign;
97
+ }
98
+ if (pattern.houses && pattern.houses.length > 0) {
99
+ const houseStr = pattern.houses.length === 1
100
+ ? `${(0, formatting_1.getOrdinal)(pattern.houses[0])} House`
101
+ : pattern.houses.map((h) => `${(0, formatting_1.getOrdinal)(h)} House`).join('-');
102
+ location += location ? ` (${houseStr})` : houseStr;
103
+ }
104
+ const locationStr = location ? ` in ${location}` : '';
105
+ return [
106
+ `Stellium (${pattern.span.toFixed(1)}°): ${planetNames}${locationStr}`,
107
+ ];
108
+ }
@@ -11,16 +11,16 @@ const astrology_1 = require("../../../core/astrology");
11
11
  function generateAnglesOutput(ascDegree, mcDegree) {
12
12
  const output = ['[ANGLES]'];
13
13
  if (ascDegree !== undefined) {
14
- output.push(`ASC: ${Math.floor((0, astrology_1.getDegreeInSign)(ascDegree))}° ${(0, astrology_1.getDegreeSign)(ascDegree)}`);
14
+ output.push(`Ascendant: ${Math.floor((0, astrology_1.getDegreeInSign)(ascDegree))}° ${(0, astrology_1.getDegreeSign)(ascDegree)}`);
15
15
  }
16
16
  else {
17
- output.push('ASC: Not available');
17
+ output.push('Ascendant: Not available');
18
18
  }
19
19
  if (mcDegree !== undefined) {
20
- output.push(`MC: ${Math.floor((0, astrology_1.getDegreeInSign)(mcDegree))}° ${(0, astrology_1.getDegreeSign)(mcDegree)}`);
20
+ output.push(`Midheaven: ${Math.floor((0, astrology_1.getDegreeInSign)(mcDegree))}° ${(0, astrology_1.getDegreeSign)(mcDegree)}`);
21
21
  }
22
22
  else {
23
- output.push('MC: Not available');
23
+ output.push('Midheaven: Not available');
24
24
  }
25
25
  return output;
26
26
  }
@@ -0,0 +1,9 @@
1
+ import { AspectPattern } from '../../../types';
2
+ /**
3
+ * Generates the [ASPECT PATTERNS] section of the chart output.
4
+ * @param patterns Array of detected aspect patterns
5
+ * @param customTitle Optional custom title for the section
6
+ * @param showChartNames Whether to show chart names for planets (false for single charts, true for multi-chart)
7
+ * @returns An array of strings for the output.
8
+ */
9
+ export declare function generateAspectPatternsOutput(patterns: AspectPattern[], customTitle?: string, showChartNames?: boolean): string[];
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateAspectPatternsOutput = generateAspectPatternsOutput;
4
+ const astrology_1 = require("../../../core/astrology");
5
+ const formatting_1 = require("../../../utils/formatting");
6
+ /**
7
+ * Format a planet position for display
8
+ */
9
+ function formatPlanetPosition(planet, includeHouse = false, showChartNames = true) {
10
+ const degInSign = Math.floor((0, astrology_1.getDegreeInSign)(planet.degree));
11
+ const houseStr = includeHouse && planet.house ? ` (${(0, formatting_1.getOrdinal)(planet.house)} house)` : '';
12
+ const chartPrefix = showChartNames && planet.chartName ? `${planet.chartName}'s ` : '';
13
+ return `${chartPrefix}${planet.name} ${degInSign}° ${planet.sign}${houseStr}`;
14
+ }
15
+ /**
16
+ * Format a T-Square pattern in compact format
17
+ */
18
+ function formatTSquare(pattern, showChartNames = true) {
19
+ if (pattern.type !== 'T-Square')
20
+ return [];
21
+ const apex = formatPlanetPosition(pattern.apex, false, showChartNames);
22
+ const opp1 = formatPlanetPosition(pattern.opposition[0], false, showChartNames);
23
+ const opp2 = formatPlanetPosition(pattern.opposition[1], false, showChartNames);
24
+ return [
25
+ `T-Square (${pattern.mode}, ${pattern.averageOrb.toFixed(1)}°): ${apex} ← ${opp1} ↔ ${opp2}`,
26
+ ];
27
+ }
28
+ /**
29
+ * Format a Grand Trine pattern in compact format
30
+ */
31
+ function formatGrandTrine(pattern, showChartNames = true) {
32
+ if (pattern.type !== 'Grand Trine')
33
+ return [];
34
+ const planets = pattern.planets
35
+ .map((p) => formatPlanetPosition(p, false, showChartNames))
36
+ .join(' △ ');
37
+ return [
38
+ `Grand Trine (${pattern.element}, ${pattern.averageOrb.toFixed(1)}°): ${planets}`,
39
+ ];
40
+ }
41
+ /**
42
+ * Format a Grand Cross pattern in compact format
43
+ */
44
+ function formatGrandCross(pattern, showChartNames = true) {
45
+ if (pattern.type !== 'Grand Cross')
46
+ return [];
47
+ const planets = pattern.planets
48
+ .map((p) => formatPlanetPosition(p, false, showChartNames))
49
+ .join(' ⨯ ');
50
+ return [
51
+ `Grand Cross (${pattern.mode}, ${pattern.averageOrb.toFixed(1)}°): ${planets}`,
52
+ ];
53
+ }
54
+ /**
55
+ * Format a Yod pattern in compact format
56
+ */
57
+ function formatYod(pattern, showChartNames = true) {
58
+ if (pattern.type !== 'Yod')
59
+ return [];
60
+ const apex = formatPlanetPosition(pattern.apex, false, showChartNames);
61
+ const base1 = formatPlanetPosition(pattern.base[0], false, showChartNames);
62
+ const base2 = formatPlanetPosition(pattern.base[1], false, showChartNames);
63
+ return [
64
+ `Yod (${pattern.averageOrb.toFixed(1)}°): ${apex} ← ${base1} • ${base2}`,
65
+ ];
66
+ }
67
+ /**
68
+ * Format a Mystic Rectangle pattern in compact format
69
+ */
70
+ function formatMysticRectangle(pattern, showChartNames = true) {
71
+ if (pattern.type !== 'Mystic Rectangle')
72
+ return [];
73
+ const opp1_1 = formatPlanetPosition(pattern.oppositions[0][0], false, showChartNames);
74
+ const opp1_2 = formatPlanetPosition(pattern.oppositions[0][1], false, showChartNames);
75
+ const opp2_1 = formatPlanetPosition(pattern.oppositions[1][0], false, showChartNames);
76
+ const opp2_2 = formatPlanetPosition(pattern.oppositions[1][1], false, showChartNames);
77
+ return [
78
+ `Mystic Rectangle (${pattern.averageOrb.toFixed(1)}°): ${opp1_1} ↔ ${opp1_2} | ${opp2_1} ↔ ${opp2_2}`,
79
+ ];
80
+ }
81
+ /**
82
+ * Format a Kite pattern in compact format
83
+ */
84
+ function formatKite(pattern, showChartNames = true) {
85
+ if (pattern.type !== 'Kite')
86
+ return [];
87
+ const grandTrineStr = pattern.grandTrine
88
+ .map((p) => formatPlanetPosition(p, false, showChartNames))
89
+ .join(' △ ');
90
+ const oppositionPlanet = formatPlanetPosition(pattern.opposition, false, showChartNames);
91
+ // Get the element from the first planet's sign for context
92
+ const element = pattern.grandTrine[0].sign
93
+ ? getElementFromSign(pattern.grandTrine[0].sign)
94
+ : '';
95
+ const elementStr = element ? `${element}, ` : '';
96
+ return [
97
+ `Kite (${elementStr}${pattern.averageOrb.toFixed(1)}°): [${grandTrineStr}] ← ${oppositionPlanet}`,
98
+ ];
99
+ }
100
+ /**
101
+ * Helper function to get element from sign
102
+ */
103
+ function getElementFromSign(sign) {
104
+ const fireSigns = ['Aries', 'Leo', 'Sagittarius'];
105
+ const earthSigns = ['Taurus', 'Virgo', 'Capricorn'];
106
+ const airSigns = ['Gemini', 'Libra', 'Aquarius'];
107
+ const waterSigns = ['Cancer', 'Scorpio', 'Pisces'];
108
+ if (fireSigns.includes(sign))
109
+ return 'Fire';
110
+ if (earthSigns.includes(sign))
111
+ return 'Earth';
112
+ if (airSigns.includes(sign))
113
+ return 'Air';
114
+ if (waterSigns.includes(sign))
115
+ return 'Water';
116
+ return '';
117
+ }
118
+ /**
119
+ * Check if a Grand Trine is part of any Kite pattern within the same analysis context
120
+ * Uses exact planet identity matching (chart name + planet name + degree)
121
+ */
122
+ function isGrandTrinePartOfKite(grandTrine, patterns) {
123
+ if (grandTrine.type !== 'Grand Trine')
124
+ return false;
125
+ return patterns.some((pattern) => {
126
+ if (pattern.type !== 'Kite')
127
+ return false;
128
+ // Create unique identifiers for exact planet identity matching
129
+ const createPlanetId = (p) => `${p.chartName || ''}-${p.name}-${p.degree}`;
130
+ const kiteGrandTrinePlanets = pattern.grandTrine.map(createPlanetId);
131
+ const grandTrinePlanets = grandTrine.planets.map(createPlanetId);
132
+ // Check if the sets of planets are identical (same planets, same count)
133
+ return (kiteGrandTrinePlanets.length === grandTrinePlanets.length &&
134
+ kiteGrandTrinePlanets.every((kp) => grandTrinePlanets.includes(kp)));
135
+ });
136
+ }
137
+ /**
138
+ * Generates the [ASPECT PATTERNS] section of the chart output.
139
+ * @param patterns Array of detected aspect patterns
140
+ * @param customTitle Optional custom title for the section
141
+ * @param showChartNames Whether to show chart names for planets (false for single charts, true for multi-chart)
142
+ * @returns An array of strings for the output.
143
+ */
144
+ function generateAspectPatternsOutput(patterns, customTitle, showChartNames = true) {
145
+ const output = [
146
+ customTitle ? `[ASPECT PATTERNS: ${customTitle}]` : '[ASPECT PATTERNS]',
147
+ ];
148
+ if (patterns.length === 0) {
149
+ output.push('No T-Squares detected.');
150
+ output.push('No Grand Trines detected.');
151
+ return output;
152
+ }
153
+ // Filter out Grand Trines that are part of Kites to avoid duplication
154
+ const filteredPatterns = patterns.filter((pattern) => {
155
+ if (pattern.type === 'Grand Trine') {
156
+ return !isGrandTrinePartOfKite(pattern, patterns);
157
+ }
158
+ return true;
159
+ });
160
+ // Sort patterns by type for consistent output
161
+ const sortOrder = [
162
+ 'T-Square',
163
+ 'Grand Trine',
164
+ 'Grand Cross',
165
+ 'Yod',
166
+ 'Mystic Rectangle',
167
+ 'Kite',
168
+ ];
169
+ const sortedPatterns = filteredPatterns.sort((a, b) => {
170
+ return sortOrder.indexOf(a.type) - sortOrder.indexOf(b.type);
171
+ });
172
+ sortedPatterns.forEach((pattern) => {
173
+ switch (pattern.type) {
174
+ case 'T-Square':
175
+ output.push(...formatTSquare(pattern, showChartNames));
176
+ break;
177
+ case 'Grand Trine':
178
+ output.push(...formatGrandTrine(pattern, showChartNames));
179
+ break;
180
+ case 'Grand Cross':
181
+ output.push(...formatGrandCross(pattern, showChartNames));
182
+ break;
183
+ case 'Yod':
184
+ output.push(...formatYod(pattern, showChartNames));
185
+ break;
186
+ case 'Mystic Rectangle':
187
+ output.push(...formatMysticRectangle(pattern, showChartNames));
188
+ break;
189
+ case 'Kite':
190
+ output.push(...formatKite(pattern, showChartNames));
191
+ break;
192
+ }
193
+ });
194
+ // Remove trailing empty line
195
+ if (output[output.length - 1] === '') {
196
+ output.pop();
197
+ }
198
+ return output;
199
+ }
@@ -1,14 +1,11 @@
1
1
  import { AspectData } from '../../../types';
2
- import { ChartSettings } from '../../../config/ChartSettings';
3
2
  /**
4
- * Generates aspect sections (e.g., [ASPECTS], [PLANET-PLANET ASPECTS], [TRANSIT ASPECTS: Name]).
3
+ * Generates aspect sections from a pre-grouped map of aspects.
5
4
  * @param title The main title for this aspect block (e.g., "[ASPECTS]").
6
- * @param aspects Array of calculated aspect data.
7
- * @param settings The chart settings, containing aspect categories.
5
+ * @param groupedAspects A map of category names to aspect data arrays.
8
6
  * @param p1ChartName Optional: Name of the first chart/entity for synastry/transit aspects.
9
7
  * @param p2ChartName Optional: Name of the second chart/entity for synastry aspects.
10
8
  * @param p2IsTransit Optional: Boolean indicating if p2 represents transiting points.
11
9
  * @returns An array of strings for the output.
12
10
  */
13
- export declare function generateAspectsOutput(title: string, aspects: AspectData[], settings: ChartSettings, p1ChartName?: string, p2ChartName?: string, // For synastry, this is the second person's name. For transits, it could be "Current" or the transit chart name.
14
- p2IsTransit?: boolean): string[];
11
+ export declare function generateAspectsOutput(title: string, groupedAspects?: Map<string, AspectData[]>, p1ChartName?: string, p2ChartName?: string, p2IsTransit?: boolean): string[];
@@ -2,63 +2,49 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateAspectsOutput = generateAspectsOutput;
4
4
  /**
5
- * Generates aspect sections (e.g., [ASPECTS], [PLANET-PLANET ASPECTS], [TRANSIT ASPECTS: Name]).
5
+ * Generates the output for a single aspect line.
6
+ * @param asp The aspect data.
7
+ * @param p1ChartName Optional name of the first chart.
8
+ * @param p2ChartName Optional name of the second chart.
9
+ * @param p2IsTransit Optional flag if the second chart is for transits.
10
+ * @returns A formatted string for the aspect.
11
+ */
12
+ function formatAspectLine(asp, p1ChartName, p2ChartName, p2IsTransit = false) {
13
+ const p1NameStr = p1ChartName
14
+ ? `${p1ChartName}'s ${asp.planetA}`
15
+ : asp.planetA;
16
+ let p2NameStr = asp.planetB;
17
+ if (p2IsTransit) {
18
+ p2NameStr = `transiting ${asp.planetB}`;
19
+ }
20
+ else if (p2ChartName) {
21
+ p2NameStr = `${p2ChartName}'s ${asp.planetB}`;
22
+ }
23
+ const applicationStr = asp.application && asp.application !== 'exact'
24
+ ? ` (${asp.application})`
25
+ : '';
26
+ return `${p1NameStr} ${asp.aspectType} ${p2NameStr}: ${asp.orb.toFixed(1)}°${applicationStr}`;
27
+ }
28
+ /**
29
+ * Generates aspect sections from a pre-grouped map of aspects.
6
30
  * @param title The main title for this aspect block (e.g., "[ASPECTS]").
7
- * @param aspects Array of calculated aspect data.
8
- * @param settings The chart settings, containing aspect categories.
31
+ * @param groupedAspects A map of category names to aspect data arrays.
9
32
  * @param p1ChartName Optional: Name of the first chart/entity for synastry/transit aspects.
10
33
  * @param p2ChartName Optional: Name of the second chart/entity for synastry aspects.
11
34
  * @param p2IsTransit Optional: Boolean indicating if p2 represents transiting points.
12
35
  * @returns An array of strings for the output.
13
36
  */
14
- function generateAspectsOutput(title, aspects, settings, p1ChartName, p2ChartName, // For synastry, this is the second person's name. For transits, it could be "Current" or the transit chart name.
15
- p2IsTransit = false) {
37
+ function generateAspectsOutput(title, groupedAspects, p1ChartName, p2ChartName, p2IsTransit = false) {
16
38
  const output = [title];
17
- let aspectsFoundInAnyCategory = false;
18
- settings.aspectCategories.forEach((category) => {
19
- const categoryAspects = aspects.filter((asp) => {
20
- const orb = asp.orb;
21
- const minOrbCheck = category.minOrb === undefined ? true : orb > category.minOrb;
22
- const maxOrbCheck = orb <= category.maxOrb;
23
- return minOrbCheck && maxOrbCheck;
24
- });
25
- if (categoryAspects.length > 0) {
26
- aspectsFoundInAnyCategory = true;
27
- let orbRangeStr = `orb < ${category.maxOrb.toFixed(1)}°`;
28
- if (category.minOrb !== undefined) {
29
- // Ensure minOrb is less than maxOrb for sensible range string
30
- orbRangeStr =
31
- category.minOrb < category.maxOrb
32
- ? `orb ${category.minOrb.toFixed(1)}-${category.maxOrb.toFixed(1)}°`
33
- : `orb > ${category.minOrb.toFixed(1)}° & < ${category.maxOrb.toFixed(1)}°`; // Fallback for unusual category def
34
- }
35
- output.push(`[${category.name.toUpperCase()}: ${orbRangeStr}]`);
36
- categoryAspects.sort((a, b) => a.orb - b.orb); // Sort by orb tightness
37
- categoryAspects.forEach((asp) => {
38
- const p1NameStr = p1ChartName
39
- ? `${p1ChartName}'s ${asp.planetA}`
40
- : asp.planetA;
41
- let p2NameStr = asp.planetB;
42
- if (p2IsTransit) {
43
- // For "Transit Aspects: Alice", p1 is Alice, p2 is the transiting planet.
44
- // Example: "Alice's Mercury opposition transiting Neptune: 0.3°" - here p2ChartName is not used for the planet itself.
45
- p2NameStr = `transiting ${asp.planetB}`;
46
- }
47
- else if (p2ChartName) {
48
- // For "Synastry: Alice-Bob", "Planet-Planet Aspects"
49
- // Example: "Alice's Mercury opposition Bob's Neptune: 0.3°"
50
- p2NameStr = `${p2ChartName}'s ${asp.planetB}`;
51
- }
52
- // If neither p2IsTransit nor p2ChartName, it's a natal chart aspect, e.g. "Venus opposition Pluto: 1.2°"
53
- output.push(`${p1NameStr} ${asp.aspectType} ${p2NameStr}: ${asp.orb.toFixed(1)}°`);
54
- });
55
- }
56
- });
57
- if (!aspectsFoundInAnyCategory && aspects.length > 0) {
58
- output.push('No aspects within defined categories.');
59
- }
60
- else if (aspects.length === 0) {
39
+ if (!groupedAspects || groupedAspects.size === 0) {
61
40
  output.push('None');
41
+ return output;
62
42
  }
43
+ groupedAspects.forEach((categoryAspects, categoryName) => {
44
+ output.push(categoryName); // The category name is the pre-formatted key from the map
45
+ categoryAspects.forEach((asp) => {
46
+ output.push(formatAspectLine(asp, p1ChartName, p2ChartName, p2IsTransit));
47
+ });
48
+ });
63
49
  return output;
64
50
  }
@@ -16,6 +16,6 @@ function generateBirthdataOutput(location, timestamp, settings, sectionTitle = '
16
16
  const dateStr = (0, datetime_1.formatDateCustom)(timestamp, settings.dateFormat);
17
17
  const timeStr = (0, datetime_1.formatTime)(timestamp);
18
18
  return [
19
- `${sectionTitle} ${location || 'Unknown Location'}, ${dateStr}, ${timeStr}`,
19
+ `${sectionTitle} ${location || 'Unknown Location'} | ${dateStr} | ${timeStr}`,
20
20
  ];
21
21
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Generates the [DISPOSITOR TREE] section of the chart output.
3
+ * @param dispositors A map of planet names to their full dispositor chain string.
4
+ * @returns An array of strings for the output.
5
+ */
6
+ export declare function generateDispositorsOutput(dispositors: {
7
+ [key: string]: string;
8
+ }): string[];
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateDispositorsOutput = generateDispositorsOutput;
4
+ /**
5
+ * Generates the [DISPOSITOR TREE] section of the chart output.
6
+ * @param dispositors A map of planet names to their full dispositor chain string.
7
+ * @returns An array of strings for the output.
8
+ */
9
+ function generateDispositorsOutput(dispositors) {
10
+ const output = ['[DISPOSITOR TREE]'];
11
+ if (Object.keys(dispositors).length === 0) {
12
+ output.push('No dispositor data available.');
13
+ return output;
14
+ }
15
+ for (const planet in dispositors) {
16
+ output.push(dispositors[planet]);
17
+ }
18
+ return output;
19
+ }
@@ -1,10 +1,15 @@
1
- import { ChartData } from '../../../types';
2
- import { ChartSettings } from '../../../config/ChartSettings';
3
1
  /**
4
2
  * Generates the [HOUSE OVERLAYS] section for synastry.
5
- * @param chart1 The first chart data.
6
- * @param chart2 The second chart data.
7
- * @param settings The chart settings, containing the house system calculator.
3
+ * @param overlays The pre-calculated house overlay data.
4
+ * @param chart1Name The name of the first chart.
5
+ * @param chart2Name The name of the second chart.
8
6
  * @returns An array of strings for the output.
9
7
  */
10
- export declare function generateHouseOverlaysOutput(chart1: ChartData, chart2: ChartData, settings: ChartSettings): string[];
8
+ export declare function generateHouseOverlaysOutput(overlays: {
9
+ chart1InChart2Houses: {
10
+ [key: string]: number;
11
+ };
12
+ chart2InChart1Houses: {
13
+ [key: string]: number;
14
+ };
15
+ }, chart1Name: string, chart2Name: string): string[];
@@ -1,85 +1,54 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateHouseOverlaysOutput = generateHouseOverlaysOutput;
4
- // Helper function to determine which house a point falls into
5
- function getHouseForPoint(pointDegree, houseCusps) {
6
- if (!houseCusps || houseCusps.length !== 12) {
7
- // console.error("Invalid or missing houseCusps array for getHouseForPoint.");
8
- return 0; // Indicate failure or inability to calculate
9
- }
10
- for (let i = 0; i < 12; i++) {
11
- const cuspStart = houseCusps[i]; // Cusp of current house (e.g., for House 1, i=0, cuspStart = Cusp 1)
12
- const cuspEnd = houseCusps[(i + 1) % 12]; // Cusp of next house (e.g., for House 1, cuspEnd = Cusp 2)
13
- // Check if the point degree falls between cuspStart and cuspEnd
14
- if (cuspStart < cuspEnd) {
15
- // Normal case: e.g., Cusp1=10, Cusp2=40. Point is between 10 and 40.
16
- if (pointDegree >= cuspStart && pointDegree < cuspEnd) {
17
- return i + 1; // House number is i+1 (cusps are 0-indexed, houses 1-indexed)
18
- }
19
- }
20
- else {
21
- // Wrap-around case: e.g., Cusp12=330, Cusp1=20. Point is >=330 OR <20.
22
- if (pointDegree >= cuspStart || pointDegree < cuspEnd) {
23
- return i + 1; // House number is i+1
24
- }
25
- }
26
- }
27
- // console.warn(`Point ${pointDegree} did not fall into any house with cusps: ${houseCusps.join(', ')}.`);
28
- return 0; // Should ideally not be reached if cusps correctly cover 360 degrees
29
- }
4
+ const formatting_1 = require("../../../utils/formatting");
30
5
  /**
31
6
  * Generates the [HOUSE OVERLAYS] section for synastry.
32
- * @param chart1 The first chart data.
33
- * @param chart2 The second chart data.
34
- * @param settings The chart settings, containing the house system calculator.
7
+ * @param overlays The pre-calculated house overlay data.
8
+ * @param chart1Name The name of the first chart.
9
+ * @param chart2Name The name of the second chart.
35
10
  * @returns An array of strings for the output.
36
11
  */
37
- function generateHouseOverlaysOutput(chart1, chart2, settings) {
12
+ function generateHouseOverlaysOutput(overlays, chart1Name, chart2Name) {
38
13
  const output = ['[HOUSE OVERLAYS]'];
39
- const c1Name = chart1.name;
40
- const c2Name = chart2.name;
41
- // Chart 1's planets in Chart 2's houses
42
- if (chart2.houseCusps && chart2.houseCusps.length === 12) {
43
- output.push(`${c1Name}'s planets in ${c2Name}'s houses:`);
44
- if (chart1.planets && chart1.planets.length > 0) {
45
- chart1.planets.forEach((planet) => {
46
- const houseNumber = getHouseForPoint(planet.degree, chart2.houseCusps);
47
- if (houseNumber > 0) {
48
- output.push(`${planet.name}: House ${houseNumber}`);
49
- }
50
- else {
51
- output.push(`${planet.name}: (Could not determine house in ${c2Name})`);
52
- }
53
- });
54
- }
55
- else {
56
- output.push('(No planets listed for overlay)');
57
- }
14
+ output.push(`${chart1Name}'s planets in ${chart2Name}'s houses:`);
15
+ if (Object.keys(overlays.chart1InChart2Houses).length > 0) {
16
+ output.push(formatHouseOverlayCompact(overlays.chart1InChart2Houses));
58
17
  }
59
18
  else {
60
- output.push(`${c1Name}'s planets in ${c2Name}'s houses: (${c2Name} house cusps not available)`);
19
+ output.push(`(${chart2Name} house cusps not available)`);
61
20
  }
62
- output.push(''); // Blank line between the two overlay sections
63
- // Chart 2's planets in Chart 1's houses
64
- if (chart1.houseCusps && chart1.houseCusps.length === 12) {
65
- output.push(`${c2Name}'s planets in ${c1Name}'s houses:`);
66
- if (chart2.planets && chart2.planets.length > 0) {
67
- chart2.planets.forEach((planet) => {
68
- const houseNumber = getHouseForPoint(planet.degree, chart1.houseCusps);
69
- if (houseNumber > 0) {
70
- output.push(`${planet.name}: House ${houseNumber}`);
71
- }
72
- else {
73
- output.push(`${planet.name}: (Could not determine house in ${c1Name})`);
74
- }
75
- });
76
- }
77
- else {
78
- output.push('(No planets listed for overlay)');
79
- }
21
+ output.push(`${chart2Name}'s planets in ${chart1Name}'s houses:`);
22
+ if (Object.keys(overlays.chart2InChart1Houses).length > 0) {
23
+ output.push(formatHouseOverlayCompact(overlays.chart2InChart1Houses));
80
24
  }
81
25
  else {
82
- output.push(`${c2Name}'s planets in ${c1Name}'s houses: (${c1Name} house cusps not available)`);
26
+ output.push(`(${chart1Name} house cusps not available)`);
83
27
  }
84
28
  return output;
85
29
  }
30
+ /**
31
+ * Formats house overlays in a compact format, grouping by house.
32
+ * @param overlays Object mapping planet names to house numbers.
33
+ * @returns A compact string representation.
34
+ */
35
+ function formatHouseOverlayCompact(overlays) {
36
+ // Group planets by house
37
+ const houseGroups = {};
38
+ for (const planet in overlays) {
39
+ const house = overlays[planet];
40
+ if (!houseGroups[house]) {
41
+ houseGroups[house] = [];
42
+ }
43
+ houseGroups[house].push(planet);
44
+ }
45
+ // Sort houses numerically and format
46
+ const sortedHouses = Object.keys(houseGroups)
47
+ .map(Number)
48
+ .sort((a, b) => a - b);
49
+ const houseStrings = sortedHouses.map((house) => {
50
+ const planets = houseGroups[house].join(', ');
51
+ return `${(0, formatting_1.getOrdinal)(house)}: ${planets}`;
52
+ });
53
+ return houseStrings.join(' | ');
54
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Generates the [HOUSE CUSPS] section of the chart output.
3
+ * @param houseCusps Array of 12 house cusp degrees (0-360), or undefined if not available.
4
+ * @returns An array of strings for the output.
5
+ */
6
+ export declare function generateHousesOutput(houseCusps?: number[]): string[];