chart2txt 0.5.2 → 0.6.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 (39) hide show
  1. package/dist/chart2txt.min.js +1 -1
  2. package/dist/config/ChartSettings.d.ts +1 -0
  3. package/dist/config/ChartSettings.js +1 -0
  4. package/dist/constants.js +4 -2
  5. package/dist/core/aspectPatterns.d.ts +5 -0
  6. package/dist/core/aspectPatterns.js +444 -0
  7. package/dist/core/aspects.js +94 -9
  8. package/dist/core/astrology.d.ts +8 -2
  9. package/dist/core/astrology.js +23 -6
  10. package/dist/core/dignities.d.ts +27 -0
  11. package/dist/core/dignities.js +136 -0
  12. package/dist/core/dispositors.d.ts +22 -0
  13. package/dist/core/dispositors.js +143 -0
  14. package/dist/core/signDistributions.d.ts +31 -0
  15. package/dist/core/signDistributions.js +150 -0
  16. package/dist/formatters/text/sections/angles.js +4 -4
  17. package/dist/formatters/text/sections/aspectPatterns.d.ts +7 -0
  18. package/dist/formatters/text/sections/aspectPatterns.js +175 -0
  19. package/dist/formatters/text/sections/aspects.js +7 -4
  20. package/dist/formatters/text/sections/birthdata.js +1 -1
  21. package/dist/formatters/text/sections/dispositors.d.ts +7 -0
  22. package/dist/formatters/text/sections/dispositors.js +20 -0
  23. package/dist/formatters/text/sections/houseOverlays.js +10 -34
  24. package/dist/formatters/text/sections/houses.d.ts +6 -0
  25. package/dist/formatters/text/sections/houses.js +36 -0
  26. package/dist/formatters/text/sections/planets.js +11 -26
  27. package/dist/formatters/text/sections/signDistributions.d.ts +25 -0
  28. package/dist/formatters/text/sections/signDistributions.js +67 -0
  29. package/dist/formatters/text/textFormatter.js +37 -3
  30. package/dist/types.d.ts +61 -0
  31. package/dist/utils/formatting.d.ts +6 -0
  32. package/dist/utils/formatting.js +13 -0
  33. package/dist/utils/houseCalculations.d.ts +13 -0
  34. package/dist/utils/houseCalculations.js +65 -0
  35. package/dist/utils/precision.d.ts +49 -0
  36. package/dist/utils/precision.js +71 -0
  37. package/dist/utils/validation.d.ts +37 -0
  38. package/dist/utils/validation.js +181 -0
  39. package/package.json +2 -1
@@ -2,31 +2,116 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.calculateAspects = calculateAspects;
4
4
  exports.calculateMultichartAspects = calculateMultichartAspects;
5
+ const astrology_1 = require("./astrology");
6
+ const precision_1 = require("../utils/precision");
7
+ /**
8
+ * Gets the expected sign difference for a given aspect angle
9
+ * @param aspectAngle The aspect angle in degrees
10
+ * @returns The expected sign difference
11
+ */
12
+ function getExpectedSignDifference(aspectAngle) {
13
+ // Normalize aspect angle to 0-180 range for sign difference calculation
14
+ const normalizedAngle = aspectAngle <= 180 ? aspectAngle : 360 - aspectAngle;
15
+ // Calculate how many 30-degree signs this aspect spans
16
+ switch (normalizedAngle) {
17
+ case 0:
18
+ return 0; // Conjunction: same sign
19
+ case 30:
20
+ return 1; // Semi-sextile: 1 sign apart
21
+ case 60:
22
+ return 2; // Sextile: 2 signs apart
23
+ case 90:
24
+ return 3; // Square: 3 signs apart
25
+ case 120:
26
+ return 4; // Trine: 4 signs apart
27
+ case 150:
28
+ return 5; // Quincunx: 5 signs apart
29
+ case 180:
30
+ return 6; // Opposition: 6 signs apart
31
+ default:
32
+ // For non-standard aspects, calculate based on 30-degree segments
33
+ return Math.round(normalizedAngle / 30);
34
+ }
35
+ }
36
+ /**
37
+ * Determines if an aspect is applying or separating based on planet speeds
38
+ * @param planetA First planet
39
+ * @param planetB Second planet
40
+ * @param aspectAngle The aspect angle (0, 60, 90, 120, 180, etc.)
41
+ * @returns 'applying', 'separating', or 'exact'
42
+ */
43
+ function determineAspectApplication(planetA, planetB, aspectAngle) {
44
+ // If either planet doesn't have speed data, we can't determine application
45
+ if (planetA.speed === undefined || planetB.speed === undefined) {
46
+ return 'exact';
47
+ }
48
+ const speedA = planetA.speed;
49
+ const speedB = planetB.speed;
50
+ // Calculate current angular distance (handle wraparound properly)
51
+ const degreeA = (0, astrology_1.normalizeDegree)(planetA.degree);
52
+ const degreeB = (0, astrology_1.normalizeDegree)(planetB.degree);
53
+ let currentDistance = Math.abs(degreeA - degreeB);
54
+ if (currentDistance > 180) {
55
+ currentDistance = 360 - currentDistance;
56
+ }
57
+ // If very close to exact, consider it exact
58
+ const orbFromExact = Math.abs(currentDistance - aspectAngle);
59
+ if ((0, precision_1.isExactAspect)(orbFromExact)) {
60
+ return 'exact';
61
+ }
62
+ // Calculate relative speed (how fast the angle between planets is changing)
63
+ const relativeSpeed = speedA - speedB;
64
+ if (relativeSpeed === 0) {
65
+ return 'exact'; // Planets moving at same speed
66
+ }
67
+ // Use a small, consistent time increment (e.g., 0.1 days) rather than degree-based increment
68
+ const timeIncrement = 0.1; // days
69
+ // Calculate future positions after the same time period for both planets
70
+ const futureA = (0, astrology_1.normalizeDegree)(degreeA + speedA * timeIncrement);
71
+ const futureB = (0, astrology_1.normalizeDegree)(degreeB + speedB * timeIncrement);
72
+ // Calculate current and future angular distances for this aspect
73
+ const currentAspectDistance = Math.abs(currentDistance - aspectAngle);
74
+ let futureSeparation = Math.abs(futureA - futureB);
75
+ if (futureSeparation > 180) {
76
+ futureSeparation = 360 - futureSeparation;
77
+ }
78
+ const futureAspectDistance = Math.abs(futureSeparation - aspectAngle);
79
+ // If future distance to exact aspect is smaller, it's applying
80
+ // If future distance to exact aspect is larger, it's separating
81
+ const isApplying = futureAspectDistance < currentAspectDistance;
82
+ return isApplying ? 'applying' : 'separating';
83
+ }
5
84
  function findTightestAspect(aspectDefinitions, planetA, planetB, skipOutOfSignAspects) {
6
- let diff = Math.abs(planetA.degree - planetB.degree);
85
+ const degreeA = (0, precision_1.roundDegrees)((0, astrology_1.normalizeDegree)(planetA.degree));
86
+ const degreeB = (0, precision_1.roundDegrees)((0, astrology_1.normalizeDegree)(planetB.degree));
87
+ let diff = Math.abs(degreeA - degreeB);
7
88
  if (diff > 180)
8
89
  diff = 360 - diff;
9
90
  let tightestAspect = null;
10
91
  for (const aspectType of aspectDefinitions) {
11
- const orb = Math.abs(diff - aspectType.angle);
92
+ const orb = (0, precision_1.roundDegrees)(Math.abs(diff - aspectType.angle));
12
93
  if (skipOutOfSignAspects) {
13
- const planetASign = Math.floor(planetA.degree / 30);
14
- const planetBSign = Math.floor(planetB.degree / 30);
15
- const aspectSignDiff = Math.floor(aspectType.angle / 30);
16
- let signDiff = Math.abs(planetASign - planetBSign);
17
- if (signDiff > 6)
18
- signDiff = 12 - signDiff;
19
- if (signDiff !== aspectSignDiff) {
94
+ const planetASign = Math.floor(degreeA / 30);
95
+ const planetBSign = Math.floor(degreeB / 30);
96
+ // Calculate expected sign difference for this aspect
97
+ // For major aspects: 0° = 0 signs, 60° = 2 signs, 90° = 3 signs, 120° = 4 signs, 180° = 6 signs
98
+ const expectedSignDiff = getExpectedSignDifference(aspectType.angle);
99
+ let actualSignDiff = Math.abs(planetASign - planetBSign);
100
+ if (actualSignDiff > 6)
101
+ actualSignDiff = 12 - actualSignDiff;
102
+ if (actualSignDiff !== expectedSignDiff) {
20
103
  continue;
21
104
  }
22
105
  }
23
106
  if (orb <= aspectType.orb) {
24
107
  if (!tightestAspect || orb < tightestAspect.orb) {
108
+ const application = determineAspectApplication(planetA, planetB, aspectType.angle);
25
109
  tightestAspect = {
26
110
  planetA: planetA.name,
27
111
  planetB: planetB.name,
28
112
  aspectType: aspectType.name,
29
113
  orb,
114
+ application,
30
115
  };
31
116
  }
32
117
  }
@@ -1,12 +1,18 @@
1
+ /**
2
+ * Normalizes a degree value to the 0-359.999... range.
3
+ * @param degree The degree value to normalize.
4
+ * @returns The normalized degree value.
5
+ */
6
+ export declare function normalizeDegree(degree: number): number;
1
7
  /**
2
8
  * Determines the zodiac sign for a given degree.
3
- * @param degree The absolute degree (0-359.99...).
9
+ * @param degree The absolute degree (any value, will be normalized).
4
10
  * @returns The zodiac sign name.
5
11
  */
6
12
  export declare function getDegreeSign(degree: number): string;
7
13
  /**
8
14
  * Calculates the degree within its 30-degree sign (0-29.99...).
9
- * @param degree The absolute degree (0-359.99...).
15
+ * @param degree The absolute degree (any value, will be normalized).
10
16
  * @returns The degree within the sign.
11
17
  */
12
18
  export declare function getDegreeInSign(degree: number): number;
@@ -1,27 +1,44 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeDegree = normalizeDegree;
3
4
  exports.getDegreeSign = getDegreeSign;
4
5
  exports.getDegreeInSign = getDegreeInSign;
5
6
  const constants_1 = require("../constants");
7
+ /**
8
+ * Normalizes a degree value to the 0-359.999... range.
9
+ * @param degree The degree value to normalize.
10
+ * @returns The normalized degree value.
11
+ */
12
+ function normalizeDegree(degree) {
13
+ if (!isFinite(degree)) {
14
+ throw new Error(`Invalid degree value: ${degree}`);
15
+ }
16
+ let normalized = degree % 360;
17
+ if (normalized < 0) {
18
+ normalized += 360;
19
+ }
20
+ return normalized;
21
+ }
6
22
  /**
7
23
  * Determines the zodiac sign for a given degree.
8
- * @param degree The absolute degree (0-359.99...).
24
+ * @param degree The absolute degree (any value, will be normalized).
9
25
  * @returns The zodiac sign name.
10
26
  */
11
27
  function getDegreeSign(degree) {
12
- const signIndex = Math.floor(degree / 30) % 12;
28
+ const normalizedDegree = normalizeDegree(degree);
29
+ const signIndex = Math.floor(normalizedDegree / 30);
13
30
  if (signIndex < 0 || signIndex >= constants_1.ZODIAC_SIGNS.length) {
14
- // This should ideally not happen with degree % 12 logic if degree is positive
15
- console.error(`Invalid sign index computed: ${signIndex} for degree ${degree}`);
31
+ console.error(`Invalid sign index computed: ${signIndex} for normalized degree ${normalizedDegree}`);
16
32
  return 'Unknown Sign';
17
33
  }
18
34
  return constants_1.ZODIAC_SIGNS[signIndex];
19
35
  }
20
36
  /**
21
37
  * Calculates the degree within its 30-degree sign (0-29.99...).
22
- * @param degree The absolute degree (0-359.99...).
38
+ * @param degree The absolute degree (any value, will be normalized).
23
39
  * @returns The degree within the sign.
24
40
  */
25
41
  function getDegreeInSign(degree) {
26
- return degree % 30;
42
+ const normalizedDegree = normalizeDegree(degree);
43
+ return normalizedDegree % 30;
27
44
  }
@@ -0,0 +1,27 @@
1
+ import { Point } from '../types';
2
+ export interface DignityInfo {
3
+ rulers: string[];
4
+ exaltation?: string;
5
+ detriment?: string;
6
+ fall?: string;
7
+ }
8
+ /**
9
+ * Gets the essential dignities for a planet in a specific sign
10
+ * @param planetName Name of the planet
11
+ * @param sign The zodiac sign
12
+ * @returns Array of dignity descriptions
13
+ */
14
+ export declare function getPlanetDignities(planetName: string, sign: string): string[];
15
+ /**
16
+ * Gets the ruler(s) of a zodiac sign
17
+ * @param sign The zodiac sign
18
+ * @returns Array of ruling planets
19
+ */
20
+ export declare function getSignRulers(sign: string): string[];
21
+ /**
22
+ * Formats planet dignities for display
23
+ * @param planet The planet point
24
+ * @param houseCusps Array of house cusps (optional)
25
+ * @returns Formatted string with dignities
26
+ */
27
+ export declare function formatPlanetWithDignities(planet: Point, houseCusps?: number[]): string;
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPlanetDignities = getPlanetDignities;
4
+ exports.getSignRulers = getSignRulers;
5
+ exports.formatPlanetWithDignities = formatPlanetWithDignities;
6
+ const astrology_1 = require("./astrology");
7
+ // Essential dignity mappings
8
+ const SIGN_DIGNITIES = {
9
+ Aries: {
10
+ rulers: ['Mars'],
11
+ exaltation: 'Sun',
12
+ detriment: 'Venus',
13
+ fall: 'Saturn',
14
+ },
15
+ Taurus: {
16
+ rulers: ['Venus'],
17
+ exaltation: 'Moon',
18
+ detriment: 'Mars',
19
+ fall: 'Uranus',
20
+ },
21
+ Gemini: {
22
+ rulers: ['Mercury'],
23
+ detriment: 'Jupiter',
24
+ },
25
+ Cancer: {
26
+ rulers: ['Moon'],
27
+ exaltation: 'Jupiter',
28
+ detriment: 'Saturn',
29
+ fall: 'Mars',
30
+ },
31
+ Leo: {
32
+ rulers: ['Sun'],
33
+ detriment: 'Saturn',
34
+ fall: 'Neptune',
35
+ },
36
+ Virgo: {
37
+ rulers: ['Mercury'],
38
+ exaltation: 'Mercury',
39
+ detriment: 'Jupiter',
40
+ fall: 'Venus',
41
+ },
42
+ Libra: {
43
+ rulers: ['Venus'],
44
+ exaltation: 'Saturn',
45
+ detriment: 'Mars',
46
+ fall: 'Sun',
47
+ },
48
+ Scorpio: {
49
+ rulers: ['Mars'],
50
+ detriment: 'Venus',
51
+ fall: 'Moon',
52
+ },
53
+ Sagittarius: {
54
+ rulers: ['Jupiter'],
55
+ detriment: 'Mercury',
56
+ },
57
+ Capricorn: {
58
+ rulers: ['Saturn'],
59
+ exaltation: 'Mars',
60
+ detriment: 'Moon',
61
+ fall: 'Jupiter',
62
+ },
63
+ Aquarius: {
64
+ rulers: ['Saturn'],
65
+ detriment: 'Sun',
66
+ fall: 'Neptune',
67
+ },
68
+ Pisces: {
69
+ rulers: ['Jupiter'],
70
+ exaltation: 'Venus',
71
+ detriment: 'Mercury',
72
+ fall: 'Mercury',
73
+ },
74
+ };
75
+ /**
76
+ * Gets the essential dignities for a planet in a specific sign
77
+ * @param planetName Name of the planet
78
+ * @param sign The zodiac sign
79
+ * @returns Array of dignity descriptions
80
+ */
81
+ function getPlanetDignities(planetName, sign) {
82
+ const dignities = [];
83
+ const normalizedSign = sign.trim();
84
+ const signInfo = SIGN_DIGNITIES[normalizedSign];
85
+ if (!signInfo)
86
+ return dignities;
87
+ // Check for rulership (domicile)
88
+ if (signInfo.rulers.includes(planetName)) {
89
+ dignities.push(`Domicile`);
90
+ }
91
+ // Check for exaltation
92
+ if (signInfo.exaltation === planetName) {
93
+ dignities.push(`Exaltation`);
94
+ }
95
+ // Check for detriment
96
+ if (signInfo.detriment === planetName) {
97
+ dignities.push(`Detriment`);
98
+ }
99
+ // Check for fall
100
+ if (signInfo.fall && signInfo.fall === planetName) {
101
+ dignities.push(`Fall`);
102
+ }
103
+ return dignities;
104
+ }
105
+ /**
106
+ * Gets the ruler(s) of a zodiac sign
107
+ * @param sign The zodiac sign
108
+ * @returns Array of ruling planets
109
+ */
110
+ function getSignRulers(sign) {
111
+ const normalizedSign = sign.trim();
112
+ const signInfo = SIGN_DIGNITIES[normalizedSign];
113
+ return signInfo ? signInfo.rulers : [];
114
+ }
115
+ /**
116
+ * Formats planet dignities for display
117
+ * @param planet The planet point
118
+ * @param houseCusps Array of house cusps (optional)
119
+ * @returns Formatted string with dignities
120
+ */
121
+ function formatPlanetWithDignities(planet, houseCusps) {
122
+ const sign = (0, astrology_1.getDegreeSign)(planet.degree);
123
+ const dignities = getPlanetDignities(planet.name, sign);
124
+ const rulers = getSignRulers(sign);
125
+ let dignitiesStr = '';
126
+ if (dignities.length > 0 && dignities.includes('Domicile')) {
127
+ dignitiesStr = `[${dignities.join(', ')}]`;
128
+ }
129
+ else if (dignities.length > 0 && rulers.length > 0) {
130
+ dignitiesStr = `[${dignities.join(', ')} | Ruler: ${rulers.join(', ')}]`;
131
+ }
132
+ else if (rulers.length > 0) {
133
+ dignitiesStr = `[Ruler: ${rulers.join(', ')}]`;
134
+ }
135
+ return dignitiesStr;
136
+ }
@@ -0,0 +1,22 @@
1
+ import { Point } from '../types';
2
+ export interface DispositorChain {
3
+ planet: string;
4
+ disposedBy: string[];
5
+ disposes: string[];
6
+ }
7
+ export interface DispositorAnalysis {
8
+ chains: DispositorChain[];
9
+ finalDispositors: string[];
10
+ }
11
+ /**
12
+ * Builds dispositor chains for all planets
13
+ * @param planets Array of planet points
14
+ * @returns Complete dispositor analysis
15
+ */
16
+ export declare function analyzeDispositors(planets: Point[]): DispositorAnalysis;
17
+ /**
18
+ * Formats the dispositor analysis for display
19
+ * @param analysis The dispositor analysis
20
+ * @returns Array of formatted strings
21
+ */
22
+ export declare function formatDispositorAnalysis(analysis: DispositorAnalysis): string[];
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeDispositors = analyzeDispositors;
4
+ exports.formatDispositorAnalysis = formatDispositorAnalysis;
5
+ const astrology_1 = require("./astrology");
6
+ const dignities_1 = require("./dignities");
7
+ /**
8
+ * Builds dispositor chains for all planets
9
+ * @param planets Array of planet points
10
+ * @returns Complete dispositor analysis
11
+ */
12
+ function analyzeDispositors(planets) {
13
+ const dispositorMap = new Map();
14
+ const disposedByMap = new Map();
15
+ // Build the dispositor relationships
16
+ planets.forEach((planet) => {
17
+ const sign = (0, astrology_1.getDegreeSign)(planet.degree);
18
+ const rulers = (0, dignities_1.getSignRulers)(sign);
19
+ dispositorMap.set(planet.name, rulers);
20
+ rulers.forEach((ruler) => {
21
+ if (!disposedByMap.has(ruler)) {
22
+ disposedByMap.set(ruler, []);
23
+ }
24
+ disposedByMap.get(ruler).push(planet.name);
25
+ });
26
+ });
27
+ // Find final dispositors - these are the roots of dispositor trees
28
+ const finalDispositors = [];
29
+ const planetNames = new Set(planets.map((p) => p.name));
30
+ planets.forEach((planet) => {
31
+ const rulers = dispositorMap.get(planet.name) || [];
32
+ const isRuledBySelf = rulers.includes(planet.name);
33
+ const isRuledByPlanetNotInChart = rulers.length > 0 && !rulers.some((ruler) => planetNames.has(ruler));
34
+ // A planet is a final dispositor if:
35
+ // 1. It rules itself (traditional dignity)
36
+ // 2. It's ruled by a planet not in the chart
37
+ if (isRuledBySelf || isRuledByPlanetNotInChart) {
38
+ finalDispositors.push(planet.name);
39
+ }
40
+ });
41
+ // Build chains
42
+ const chains = planets.map((planet) => ({
43
+ planet: planet.name,
44
+ disposedBy: dispositorMap.get(planet.name) || [],
45
+ disposes: disposedByMap.get(planet.name) || [],
46
+ }));
47
+ return {
48
+ chains,
49
+ finalDispositors,
50
+ };
51
+ }
52
+ /**
53
+ * Detect cycles in dispositor chains
54
+ */
55
+ function findDispositorCycles(chains) {
56
+ const cycles = [];
57
+ const visited = new Set();
58
+ const path = new Set();
59
+ function dfs(planet, currentPath) {
60
+ if (path.has(planet)) {
61
+ // Found a cycle
62
+ const cycleStart = currentPath.indexOf(planet);
63
+ if (cycleStart !== -1) {
64
+ const cycle = currentPath.slice(cycleStart);
65
+ cycle.push(planet); // Complete the cycle
66
+ if (cycle.length > 2) {
67
+ cycles.push(cycle);
68
+ }
69
+ }
70
+ return;
71
+ }
72
+ if (visited.has(planet))
73
+ return;
74
+ visited.add(planet);
75
+ path.add(planet);
76
+ currentPath.push(planet);
77
+ const chain = chains.find((c) => c.planet === planet);
78
+ if (chain) {
79
+ // Follow the dispositor chain (only follow planets in the chart)
80
+ const inChartRulers = chain.disposedBy.filter((ruler) => chains.some((c) => c.planet === ruler));
81
+ for (const ruler of inChartRulers) {
82
+ dfs(ruler, [...currentPath]);
83
+ }
84
+ }
85
+ path.delete(planet);
86
+ currentPath.pop();
87
+ }
88
+ // Start DFS from each planet
89
+ chains.forEach((chain) => {
90
+ if (!visited.has(chain.planet)) {
91
+ dfs(chain.planet, []);
92
+ }
93
+ });
94
+ return cycles;
95
+ }
96
+ /**
97
+ * Builds the full dispositor chain for a planet
98
+ * @param planet Starting planet
99
+ * @param chains All dispositor chains
100
+ * @param pathSoFar Array to track the current path (prevents infinite loops)
101
+ * @returns Array of planets in the chain (excluding the starting planet)
102
+ */
103
+ function buildDispositorChain(planet, chains, pathSoFar = []) {
104
+ const chain = chains.find((c) => c.planet === planet);
105
+ if (!chain || chain.disposedBy.length === 0) {
106
+ return ['(final)'];
107
+ }
108
+ // Get the first in-chart ruler
109
+ const inChartRulers = chain.disposedBy.filter((ruler) => chains.some((c) => c.planet === ruler));
110
+ if (inChartRulers.length === 0) {
111
+ return ['(final)'];
112
+ }
113
+ const ruler = inChartRulers[0];
114
+ // If the ruler is the same as the planet (self-ruler), it's final
115
+ if (ruler === planet) {
116
+ return ['(final)'];
117
+ }
118
+ // If we've seen this ruler in the current path, we have a cycle
119
+ if (pathSoFar.includes(ruler)) {
120
+ return ['(cycle)'];
121
+ }
122
+ // Continue the chain
123
+ const newPath = [...pathSoFar, planet];
124
+ const nextChain = buildDispositorChain(ruler, chains, newPath);
125
+ // If the next chain ends in (final), this whole chain ends in (final)
126
+ // If the next chain ends in (cycle), this chain also ends in (cycle)
127
+ return [ruler, ...nextChain];
128
+ }
129
+ /**
130
+ * Formats the dispositor analysis for display
131
+ * @param analysis The dispositor analysis
132
+ * @returns Array of formatted strings
133
+ */
134
+ function formatDispositorAnalysis(analysis) {
135
+ const output = [];
136
+ // Build and format chains for each planet
137
+ analysis.chains.forEach((chainInfo) => {
138
+ const chainRest = buildDispositorChain(chainInfo.planet, analysis.chains);
139
+ const chainString = chainRest.join(' → ');
140
+ output.push(`${chainInfo.planet} → ${chainString}`);
141
+ });
142
+ return output;
143
+ }
@@ -0,0 +1,31 @@
1
+ import { Point } from '../types';
2
+ export interface SignDistributions {
3
+ elements: Record<string, string[]>;
4
+ modalities: Record<string, number>;
5
+ polarities: Record<string, number>;
6
+ }
7
+ /**
8
+ * Analyzes the distribution of planets across elements, modalities, and polarities
9
+ * @param planets Array of planet points
10
+ * @param includeAscendant Optional ascendant degree to include in analysis
11
+ * @returns Sign distribution analysis
12
+ */
13
+ export declare function analyzeSignDistributions(planets: Point[], includeAscendant?: number): SignDistributions;
14
+ /**
15
+ * Formats element distribution for display
16
+ * @param elements Element distribution data
17
+ * @returns Array of formatted strings
18
+ */
19
+ export declare function formatElementDistribution(elements: Record<string, string[]>): string[];
20
+ /**
21
+ * Formats modality distribution for display
22
+ * @param modalities Modality distribution data
23
+ * @returns Array of formatted strings
24
+ */
25
+ export declare function formatModalityDistribution(modalities: Record<string, number>): string[];
26
+ /**
27
+ * Formats polarity distribution for display
28
+ * @param polarities Polarity distribution data
29
+ * @returns Array of formatted strings
30
+ */
31
+ export declare function formatPolarityDistribution(polarities: Record<string, number>): string[];