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
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateElementDistributionOutput = generateElementDistributionOutput;
4
+ exports.generateModalityDistributionOutput = generateModalityDistributionOutput;
5
+ exports.generatePolarityOutput = generatePolarityOutput;
6
+ const signDistributions_1 = require("../../../core/signDistributions");
7
+ /**
8
+ * Generates the [ELEMENT DISTRIBUTION] section of the chart output.
9
+ * @param planets Array of planet points.
10
+ * @param chartName Optional chart name for the header.
11
+ * @param ascendant Optional ascendant degree to include in analysis.
12
+ * @returns An array of strings for the output.
13
+ */
14
+ function generateElementDistributionOutput(planets, chartName, ascendant) {
15
+ const header = chartName
16
+ ? `[ELEMENT DISTRIBUTION: ${chartName}]`
17
+ : '[ELEMENT DISTRIBUTION]';
18
+ const output = [header];
19
+ if (planets.length === 0) {
20
+ output.push('No planets available for element analysis.');
21
+ return output;
22
+ }
23
+ const distributions = (0, signDistributions_1.analyzeSignDistributions)(planets, ascendant);
24
+ const formattedElements = (0, signDistributions_1.formatElementDistribution)(distributions.elements);
25
+ output.push(...formattedElements);
26
+ return output;
27
+ }
28
+ /**
29
+ * Generates the [MODALITY DISTRIBUTION] section of the chart output.
30
+ * @param planets Array of planet points.
31
+ * @param chartName Optional chart name for the header.
32
+ * @param ascendant Optional ascendant degree to include in analysis.
33
+ * @returns An array of strings for the output.
34
+ */
35
+ function generateModalityDistributionOutput(planets, chartName, ascendant) {
36
+ const header = chartName
37
+ ? `[MODALITY DISTRIBUTION: ${chartName}]`
38
+ : '[MODALITY DISTRIBUTION]';
39
+ const output = [header];
40
+ if (planets.length === 0) {
41
+ output.push('No planets available for modality analysis.');
42
+ return output;
43
+ }
44
+ const distributions = (0, signDistributions_1.analyzeSignDistributions)(planets, ascendant);
45
+ const formattedModalities = (0, signDistributions_1.formatModalityDistribution)(distributions.modalities);
46
+ output.push(...formattedModalities);
47
+ return output;
48
+ }
49
+ /**
50
+ * Generates the [POLARITY] section of the chart output.
51
+ * @param planets Array of planet points.
52
+ * @param chartName Optional chart name for the header.
53
+ * @param ascendant Optional ascendant degree to include in analysis.
54
+ * @returns An array of strings for the output.
55
+ */
56
+ function generatePolarityOutput(planets, chartName, ascendant) {
57
+ const header = chartName ? `[POLARITY: ${chartName}]` : '[POLARITY]';
58
+ const output = [header];
59
+ if (planets.length === 0) {
60
+ output.push('No planets available for polarity analysis.');
61
+ return output;
62
+ }
63
+ const distributions = (0, signDistributions_1.analyzeSignDistributions)(planets, ascendant);
64
+ const formattedPolarities = (0, signDistributions_1.formatPolarityDistribution)(distributions.polarities);
65
+ output.push(...formattedPolarities);
66
+ return output;
67
+ }
@@ -3,23 +3,52 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.formatChartToText = formatChartToText;
4
4
  const types_1 = require("../../types");
5
5
  const ChartSettings_1 = require("../../config/ChartSettings");
6
+ const validation_1 = require("../../utils/validation");
6
7
  const aspects_1 = require("../../core/aspects");
8
+ const aspectPatterns_1 = require("../../core/aspectPatterns");
9
+ /**
10
+ * Helper function to get all points (planets + angles) from a chart for aspect calculation
11
+ */
12
+ function getAllPointsFromChart(chartData) {
13
+ const allPoints = [...chartData.planets];
14
+ if (chartData.ascendant !== undefined) {
15
+ allPoints.push({ name: 'Ascendant', degree: chartData.ascendant });
16
+ }
17
+ if (chartData.midheaven !== undefined) {
18
+ allPoints.push({ name: 'Midheaven', degree: chartData.midheaven });
19
+ }
20
+ return allPoints;
21
+ }
7
22
  const metadata_1 = require("./sections/metadata");
8
23
  const chartHeader_1 = require("./sections/chartHeader");
9
24
  const birthdata_1 = require("./sections/birthdata");
10
25
  const angles_1 = require("./sections/angles");
26
+ const houses_1 = require("./sections/houses");
11
27
  const planets_1 = require("./sections/planets");
28
+ const dispositors_1 = require("./sections/dispositors");
12
29
  const aspects_2 = require("./sections/aspects");
30
+ const aspectPatterns_2 = require("./sections/aspectPatterns");
13
31
  const houseOverlays_1 = require("./sections/houseOverlays");
32
+ const signDistributions_1 = require("./sections/signDistributions");
14
33
  const processSingleChartOutput = (settings, chartData, chartTitlePrefix) => {
15
34
  const outputLines = [];
16
35
  outputLines.push(...(0, chartHeader_1.generateChartHeaderOutput)(chartData.name, chartTitlePrefix));
17
36
  outputLines.push(...(0, birthdata_1.generateBirthdataOutput)(chartData.location, chartData.timestamp, settings));
18
37
  outputLines.push(...(0, angles_1.generateAnglesOutput)(chartData.ascendant, chartData.midheaven));
38
+ outputLines.push(...(0, houses_1.generateHousesOutput)(chartData.houseCusps));
19
39
  outputLines.push(...(0, planets_1.generatePlanetsOutput)(chartData.planets, chartData.houseCusps, settings));
20
- const aspects = (0, aspects_1.calculateAspects)(settings.aspectDefinitions, chartData.planets, settings.skipOutOfSignAspects);
40
+ outputLines.push(...(0, dispositors_1.generateDispositorsOutput)(chartData.planets));
41
+ outputLines.push(...(0, signDistributions_1.generateElementDistributionOutput)(chartData.planets, undefined, chartData.ascendant));
42
+ outputLines.push(...(0, signDistributions_1.generateModalityDistributionOutput)(chartData.planets, undefined, chartData.ascendant));
43
+ outputLines.push(...(0, signDistributions_1.generatePolarityOutput)(chartData.planets, undefined, chartData.ascendant));
44
+ const aspects = (0, aspects_1.calculateAspects)(settings.aspectDefinitions, getAllPointsFromChart(chartData), settings.skipOutOfSignAspects);
21
45
  // For single chart, p1ChartName and p2ChartName are not needed for aspect string generation
22
46
  outputLines.push(...(0, aspects_2.generateAspectsOutput)('[ASPECTS]', aspects, settings));
47
+ // Detect and display aspect patterns (if enabled)
48
+ if (settings.includeAspectPatterns) {
49
+ const aspectPatterns = (0, aspectPatterns_1.detectAspectPatterns)(chartData.planets, chartData.houseCusps);
50
+ outputLines.push(...(0, aspectPatterns_2.generateAspectPatternsOutput)(aspectPatterns));
51
+ }
23
52
  outputLines.push('');
24
53
  return outputLines;
25
54
  };
@@ -31,7 +60,7 @@ const processChartPairOutput = (settings, chart1, chart2) => {
31
60
  ? 'NATAL_EVENT'
32
61
  : 'SYNASTRY';
33
62
  outputLines.push(...(0, chartHeader_1.generateChartHeaderOutput)(`${chart1.name}-${chart2.name}`, header));
34
- const synastryAspects = (0, aspects_1.calculateMultichartAspects)(settings.aspectDefinitions, chart1.planets, chart2.planets, settings.skipOutOfSignAspects);
63
+ const synastryAspects = (0, aspects_1.calculateMultichartAspects)(settings.aspectDefinitions, getAllPointsFromChart(chart1), getAllPointsFromChart(chart2), settings.skipOutOfSignAspects);
35
64
  outputLines.push(...(0, aspects_2.generateAspectsOutput)('[PLANET-PLANET ASPECTS]', synastryAspects, settings, chart1.name, chart2.name));
36
65
  outputLines.push('');
37
66
  outputLines.push(...(0, houseOverlays_1.generateHouseOverlaysOutput)(chart1, chart2, settings));
@@ -107,6 +136,11 @@ const determineChartType = (data) => {
107
136
  * @returns A string representing the full chart report.
108
137
  */
109
138
  function formatChartToText(data, partialSettings = {}) {
139
+ // Validate input data
140
+ const validationError = (0, validation_1.validateInputData)(data);
141
+ if (validationError) {
142
+ throw new Error(`Invalid chart data: ${validationError}`);
143
+ }
110
144
  const settings = new ChartSettings_1.ChartSettings(partialSettings);
111
145
  const houseSystemName = settings.houseSystemName;
112
146
  const outputLines = [];
@@ -142,7 +176,7 @@ function formatChartToText(data, partialSettings = {}) {
142
176
  outputLines.push(...processTransitChartInfoOutput(settings, transitChart));
143
177
  for (const chart of nonTransitCharts) {
144
178
  // Transit Aspects to Chart 1
145
- const transitAspectsC1 = (0, aspects_1.calculateMultichartAspects)(settings.aspectDefinitions, chart.planets, transitChart.planets, settings.skipOutOfSignAspects);
179
+ const transitAspectsC1 = (0, aspects_1.calculateMultichartAspects)(settings.aspectDefinitions, getAllPointsFromChart(chart), getAllPointsFromChart(transitChart), settings.skipOutOfSignAspects);
146
180
  outputLines.push(...(0, aspects_2.generateAspectsOutput)(`[TRANSIT ASPECTS: ${chart.name}]`, transitAspectsC1, settings, chart.name, transitChart.name, true));
147
181
  outputLines.push('');
148
182
  }
package/dist/types.d.ts CHANGED
@@ -27,12 +27,72 @@ export interface AspectData {
27
27
  planetB: string;
28
28
  aspectType: string;
29
29
  orb: number;
30
+ application?: 'applying' | 'separating' | 'exact';
30
31
  }
31
32
  export interface AspectCategory {
32
33
  name: string;
33
34
  minOrb?: number;
34
35
  maxOrb: number;
35
36
  }
37
+ export interface PlanetPosition {
38
+ name: string;
39
+ degree: number;
40
+ sign: string;
41
+ house?: number;
42
+ }
43
+ export interface TSquare {
44
+ type: 'T-Square';
45
+ apex: PlanetPosition;
46
+ opposition: [PlanetPosition, PlanetPosition];
47
+ mode: 'Cardinal' | 'Fixed' | 'Mutable';
48
+ averageOrb: number;
49
+ }
50
+ export interface GrandTrine {
51
+ type: 'Grand Trine';
52
+ planets: [PlanetPosition, PlanetPosition, PlanetPosition];
53
+ element: 'Fire' | 'Earth' | 'Air' | 'Water';
54
+ averageOrb: number;
55
+ }
56
+ export interface Stellium {
57
+ type: 'Stellium';
58
+ planets: PlanetPosition[];
59
+ sign?: string;
60
+ houses: number[];
61
+ span: number;
62
+ }
63
+ export interface GrandCross {
64
+ type: 'Grand Cross';
65
+ planets: [PlanetPosition, PlanetPosition, PlanetPosition, PlanetPosition];
66
+ mode: 'Cardinal' | 'Fixed' | 'Mutable';
67
+ averageOrb: number;
68
+ }
69
+ export interface Yod {
70
+ type: 'Yod';
71
+ apex: PlanetPosition;
72
+ base: [PlanetPosition, PlanetPosition];
73
+ averageOrb: number;
74
+ }
75
+ export interface MysticRectangle {
76
+ type: 'Mystic Rectangle';
77
+ oppositions: [
78
+ [
79
+ PlanetPosition,
80
+ PlanetPosition
81
+ ],
82
+ [
83
+ PlanetPosition,
84
+ PlanetPosition
85
+ ]
86
+ ];
87
+ averageOrb: number;
88
+ }
89
+ export interface Kite {
90
+ type: 'Kite';
91
+ grandTrine: [PlanetPosition, PlanetPosition, PlanetPosition];
92
+ opposition: PlanetPosition;
93
+ averageOrb: number;
94
+ }
95
+ export type AspectPattern = TSquare | GrandTrine | Stellium | GrandCross | Yod | MysticRectangle | Kite;
36
96
  export interface Settings {
37
97
  includeSignDegree: boolean;
38
98
  includeAscendant: boolean;
@@ -41,6 +101,7 @@ export interface Settings {
41
101
  aspectDefinitions: Aspect[];
42
102
  aspectCategories: AspectCategory[];
43
103
  skipOutOfSignAspects: boolean;
104
+ includeAspectPatterns: boolean;
44
105
  dateFormat: string;
45
106
  }
46
107
  export type PartialSettings = Partial<Settings>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Converts a number to its ordinal form (1st, 2nd, 3rd, etc.)
3
+ * @param num The number to convert
4
+ * @returns The ordinal string
5
+ */
6
+ export declare function getOrdinal(num: number): string;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getOrdinal = getOrdinal;
4
+ /**
5
+ * Converts a number to its ordinal form (1st, 2nd, 3rd, etc.)
6
+ * @param num The number to convert
7
+ * @returns The ordinal string
8
+ */
9
+ function getOrdinal(num) {
10
+ const suffix = ['th', 'st', 'nd', 'rd'];
11
+ const v = num % 100;
12
+ return num + (suffix[(v - 20) % 10] || suffix[v] || suffix[0]);
13
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Validates house cusps array for correctness
3
+ * @param houseCusps Array of 12 house cusp degrees
4
+ * @returns True if valid, false otherwise
5
+ */
6
+ export declare function validateHouseCusps(houseCusps: number[] | undefined): boolean;
7
+ /**
8
+ * Determines which house a point falls into based on house cusps
9
+ * @param pointDegree The degree of the point (will be normalized)
10
+ * @param houseCusps Array of 12 house cusp degrees
11
+ * @returns House number (1-12) or null if calculation fails
12
+ */
13
+ export declare function getHouseForPoint(pointDegree: number, houseCusps: number[] | undefined): number | null;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateHouseCusps = validateHouseCusps;
4
+ exports.getHouseForPoint = getHouseForPoint;
5
+ const astrology_1 = require("../core/astrology");
6
+ const precision_1 = require("./precision");
7
+ /**
8
+ * Validates house cusps array for correctness
9
+ * @param houseCusps Array of 12 house cusp degrees
10
+ * @returns True if valid, false otherwise
11
+ */
12
+ function validateHouseCusps(houseCusps) {
13
+ if (!houseCusps || houseCusps.length !== 12) {
14
+ return false;
15
+ }
16
+ // Check that all cusp values are finite numbers
17
+ for (const cusp of houseCusps) {
18
+ if (!isFinite(cusp)) {
19
+ return false;
20
+ }
21
+ }
22
+ return true;
23
+ }
24
+ /**
25
+ * Determines which house a point falls into based on house cusps
26
+ * @param pointDegree The degree of the point (will be normalized)
27
+ * @param houseCusps Array of 12 house cusp degrees
28
+ * @returns House number (1-12) or null if calculation fails
29
+ */
30
+ function getHouseForPoint(pointDegree, houseCusps) {
31
+ if (!validateHouseCusps(houseCusps)) {
32
+ return null;
33
+ }
34
+ if (!isFinite(pointDegree)) {
35
+ return null;
36
+ }
37
+ const normalizedPoint = (0, precision_1.roundDegrees)((0, astrology_1.normalizeDegree)(pointDegree));
38
+ const normalizedCusps = houseCusps.map((cusp) => (0, precision_1.roundDegrees)((0, astrology_1.normalizeDegree)(cusp)));
39
+ // Check if point is exactly on any cusp (within precision tolerance)
40
+ for (let i = 0; i < 12; i++) {
41
+ if ((0, precision_1.isOnCusp)(normalizedPoint, normalizedCusps[i])) {
42
+ // Point is exactly on cusp - assign to the house that starts at this cusp
43
+ return i + 1;
44
+ }
45
+ }
46
+ for (let i = 0; i < 12; i++) {
47
+ const cuspStart = normalizedCusps[i];
48
+ const cuspEnd = normalizedCusps[(i + 1) % 12];
49
+ if (cuspStart < cuspEnd) {
50
+ // Normal case: cusp doesn't cross 0° boundary
51
+ if (normalizedPoint > cuspStart && normalizedPoint < cuspEnd) {
52
+ return i + 1;
53
+ }
54
+ }
55
+ else {
56
+ // Wraparound case: cusp crosses 0°/360° boundary
57
+ if (normalizedPoint > cuspStart || normalizedPoint < cuspEnd) {
58
+ return i + 1;
59
+ }
60
+ }
61
+ }
62
+ // This should never happen if cusps properly cover 360 degrees
63
+ console.warn(`Point at ${normalizedPoint}° does not fall in any house`);
64
+ return null;
65
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Floating point precision utilities for astrological calculations
3
+ */
4
+ export declare const DEFAULT_EPSILON = 0.0001;
5
+ /**
6
+ * Compares two floating-point numbers with epsilon tolerance
7
+ * @param a First number
8
+ * @param b Second number
9
+ * @param epsilon Tolerance value (default: DEFAULT_EPSILON)
10
+ * @returns True if numbers are equal within tolerance
11
+ */
12
+ export declare function floatEquals(a: number, b: number, epsilon?: number): boolean;
13
+ /**
14
+ * Checks if a number is close to zero within epsilon tolerance
15
+ * @param value The number to check
16
+ * @param epsilon Tolerance value (default: DEFAULT_EPSILON)
17
+ * @returns True if number is close to zero
18
+ */
19
+ export declare function isNearZero(value: number, epsilon?: number): boolean;
20
+ /**
21
+ * Rounds a degree value to a reasonable precision (4 decimal places)
22
+ * This prevents accumulation of floating-point errors in calculations
23
+ * @param degrees The degree value to round
24
+ * @returns Rounded degree value
25
+ */
26
+ export declare function roundDegrees(degrees: number): number;
27
+ /**
28
+ * Compares two degree values with appropriate epsilon for astrological calculations
29
+ * @param deg1 First degree value
30
+ * @param deg2 Second degree value
31
+ * @param epsilon Tolerance in degrees (default: 0.0001°)
32
+ * @returns True if degrees are equal within tolerance
33
+ */
34
+ export declare function degreeEquals(deg1: number, deg2: number, epsilon?: number): boolean;
35
+ /**
36
+ * Checks if a planet is exactly on a house cusp within floating-point precision
37
+ * @param planetDegree Planet's degree position
38
+ * @param cuspDegree House cusp degree
39
+ * @param epsilon Tolerance in degrees (default: 0.001° = about 3.6 arc-seconds)
40
+ * @returns True if planet is on the cusp within tolerance
41
+ */
42
+ export declare function isOnCusp(planetDegree: number, cuspDegree: number, epsilon?: number): boolean;
43
+ /**
44
+ * Checks if an aspect is exact within floating-point precision
45
+ * @param actualOrb The actual orb of the aspect
46
+ * @param epsilon Tolerance in degrees (default: 0.1° = 6 arc-minutes)
47
+ * @returns True if aspect is exact within tolerance
48
+ */
49
+ export declare function isExactAspect(actualOrb: number, epsilon?: number): boolean;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ /**
3
+ * Floating point precision utilities for astrological calculations
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_EPSILON = void 0;
7
+ exports.floatEquals = floatEquals;
8
+ exports.isNearZero = isNearZero;
9
+ exports.roundDegrees = roundDegrees;
10
+ exports.degreeEquals = degreeEquals;
11
+ exports.isOnCusp = isOnCusp;
12
+ exports.isExactAspect = isExactAspect;
13
+ // Default epsilon for floating-point comparisons (about 0.0036 arc-minutes)
14
+ exports.DEFAULT_EPSILON = 1e-4;
15
+ /**
16
+ * Compares two floating-point numbers with epsilon tolerance
17
+ * @param a First number
18
+ * @param b Second number
19
+ * @param epsilon Tolerance value (default: DEFAULT_EPSILON)
20
+ * @returns True if numbers are equal within tolerance
21
+ */
22
+ function floatEquals(a, b, epsilon = exports.DEFAULT_EPSILON) {
23
+ return Math.abs(a - b) < epsilon;
24
+ }
25
+ /**
26
+ * Checks if a number is close to zero within epsilon tolerance
27
+ * @param value The number to check
28
+ * @param epsilon Tolerance value (default: DEFAULT_EPSILON)
29
+ * @returns True if number is close to zero
30
+ */
31
+ function isNearZero(value, epsilon = exports.DEFAULT_EPSILON) {
32
+ return Math.abs(value) < epsilon;
33
+ }
34
+ /**
35
+ * Rounds a degree value to a reasonable precision (4 decimal places)
36
+ * This prevents accumulation of floating-point errors in calculations
37
+ * @param degrees The degree value to round
38
+ * @returns Rounded degree value
39
+ */
40
+ function roundDegrees(degrees) {
41
+ return Math.round(degrees * 10000) / 10000;
42
+ }
43
+ /**
44
+ * Compares two degree values with appropriate epsilon for astrological calculations
45
+ * @param deg1 First degree value
46
+ * @param deg2 Second degree value
47
+ * @param epsilon Tolerance in degrees (default: 0.0001°)
48
+ * @returns True if degrees are equal within tolerance
49
+ */
50
+ function degreeEquals(deg1, deg2, epsilon = exports.DEFAULT_EPSILON) {
51
+ return floatEquals(deg1, deg2, epsilon);
52
+ }
53
+ /**
54
+ * Checks if a planet is exactly on a house cusp within floating-point precision
55
+ * @param planetDegree Planet's degree position
56
+ * @param cuspDegree House cusp degree
57
+ * @param epsilon Tolerance in degrees (default: 0.001° = about 3.6 arc-seconds)
58
+ * @returns True if planet is on the cusp within tolerance
59
+ */
60
+ function isOnCusp(planetDegree, cuspDegree, epsilon = 0.001) {
61
+ return degreeEquals(planetDegree, cuspDegree, epsilon);
62
+ }
63
+ /**
64
+ * Checks if an aspect is exact within floating-point precision
65
+ * @param actualOrb The actual orb of the aspect
66
+ * @param epsilon Tolerance in degrees (default: 0.1° = 6 arc-minutes)
67
+ * @returns True if aspect is exact within tolerance
68
+ */
69
+ function isExactAspect(actualOrb, epsilon = 0.1) {
70
+ return isNearZero(actualOrb, epsilon);
71
+ }
@@ -0,0 +1,37 @@
1
+ import { ChartData, Point, MultiChartData } from '../types';
2
+ /**
3
+ * Validates a Point object
4
+ * @param point The point to validate
5
+ * @returns Error message if invalid, null if valid
6
+ */
7
+ export declare function validatePoint(point: Point): string | null;
8
+ /**
9
+ * Validates an array of Points
10
+ * @param points The points array to validate
11
+ * @returns Error message if invalid, null if valid
12
+ */
13
+ export declare function validatePoints(points: Point[]): string | null;
14
+ /**
15
+ * Validates house cusps array
16
+ * @param houseCusps The house cusps to validate
17
+ * @returns Error message if invalid, null if valid
18
+ */
19
+ export declare function validateHouseCusps(houseCusps: number[] | undefined): string | null;
20
+ /**
21
+ * Validates a ChartData object
22
+ * @param chartData The chart data to validate
23
+ * @returns Error message if invalid, null if valid
24
+ */
25
+ export declare function validateChartData(chartData: ChartData): string | null;
26
+ /**
27
+ * Validates MultiChartData
28
+ * @param multiChartData The multi-chart data to validate
29
+ * @returns Error message if invalid, null if valid
30
+ */
31
+ export declare function validateMultiChartData(multiChartData: MultiChartData): string | null;
32
+ /**
33
+ * Validates chart data (single or multi-chart)
34
+ * @param data The data to validate
35
+ * @returns Error message if invalid, null if valid
36
+ */
37
+ export declare function validateInputData(data: ChartData | MultiChartData): string | null;
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validatePoint = validatePoint;
4
+ exports.validatePoints = validatePoints;
5
+ exports.validateHouseCusps = validateHouseCusps;
6
+ exports.validateChartData = validateChartData;
7
+ exports.validateMultiChartData = validateMultiChartData;
8
+ exports.validateInputData = validateInputData;
9
+ /**
10
+ * Validates a Point object
11
+ * @param point The point to validate
12
+ * @returns Error message if invalid, null if valid
13
+ */
14
+ function validatePoint(point) {
15
+ if (!point || typeof point !== 'object') {
16
+ return 'Point must be an object';
17
+ }
18
+ if (typeof point.name !== 'string' || point.name.trim() === '') {
19
+ return 'Point name must be a non-empty string';
20
+ }
21
+ if (typeof point.degree !== 'number' || !isFinite(point.degree)) {
22
+ return `Point ${point.name} has invalid degree value: ${point.degree}`;
23
+ }
24
+ if (point.speed !== undefined &&
25
+ (typeof point.speed !== 'number' || !isFinite(point.speed))) {
26
+ return `Point ${point.name} has invalid speed value: ${point.speed}`;
27
+ }
28
+ return null;
29
+ }
30
+ /**
31
+ * Validates an array of Points
32
+ * @param points The points array to validate
33
+ * @returns Error message if invalid, null if valid
34
+ */
35
+ function validatePoints(points) {
36
+ if (!Array.isArray(points)) {
37
+ return 'Points must be an array';
38
+ }
39
+ for (let i = 0; i < points.length; i++) {
40
+ const error = validatePoint(points[i]);
41
+ if (error) {
42
+ return `Point at index ${i}: ${error}`;
43
+ }
44
+ }
45
+ // Check for duplicate point names
46
+ const names = points.map((p) => p.name);
47
+ const duplicates = names.filter((name, index) => names.indexOf(name) !== index);
48
+ if (duplicates.length > 0) {
49
+ return `Duplicate point names found: ${duplicates.join(', ')}`;
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Validates house cusps array
55
+ * @param houseCusps The house cusps to validate
56
+ * @returns Error message if invalid, null if valid
57
+ */
58
+ function validateHouseCusps(houseCusps) {
59
+ if (houseCusps === undefined) {
60
+ return null; // Optional field
61
+ }
62
+ if (!Array.isArray(houseCusps)) {
63
+ return 'House cusps must be an array';
64
+ }
65
+ if (houseCusps.length !== 12) {
66
+ return `House cusps must contain exactly 12 values, got ${houseCusps.length}`;
67
+ }
68
+ for (let i = 0; i < houseCusps.length; i++) {
69
+ if (typeof houseCusps[i] !== 'number' || !isFinite(houseCusps[i])) {
70
+ return `House cusp ${i + 1} has invalid value: ${houseCusps[i]}`;
71
+ }
72
+ }
73
+ return null;
74
+ }
75
+ /**
76
+ * Validates a ChartData object
77
+ * @param chartData The chart data to validate
78
+ * @returns Error message if invalid, null if valid
79
+ */
80
+ function validateChartData(chartData) {
81
+ if (!chartData || typeof chartData !== 'object') {
82
+ return 'Chart data must be an object';
83
+ }
84
+ if (typeof chartData.name !== 'string' || chartData.name.trim() === '') {
85
+ return 'Chart name must be a non-empty string';
86
+ }
87
+ const pointsError = validatePoints(chartData.planets);
88
+ if (pointsError) {
89
+ return `Planets validation failed: ${pointsError}`;
90
+ }
91
+ if (chartData.ascendant !== undefined) {
92
+ if (typeof chartData.ascendant !== 'number' ||
93
+ !isFinite(chartData.ascendant)) {
94
+ return `Ascendant has invalid value: ${chartData.ascendant}`;
95
+ }
96
+ }
97
+ if (chartData.midheaven !== undefined) {
98
+ if (typeof chartData.midheaven !== 'number' ||
99
+ !isFinite(chartData.midheaven)) {
100
+ return `Midheaven has invalid value: ${chartData.midheaven}`;
101
+ }
102
+ }
103
+ const houseCuspsError = validateHouseCusps(chartData.houseCusps);
104
+ if (houseCuspsError) {
105
+ return `House cusps validation failed: ${houseCuspsError}`;
106
+ }
107
+ if (chartData.points !== undefined) {
108
+ const pointsError = validatePoints(chartData.points);
109
+ if (pointsError) {
110
+ return `Additional points validation failed: ${pointsError}`;
111
+ }
112
+ }
113
+ if (chartData.timestamp !== undefined) {
114
+ if (!(chartData.timestamp instanceof Date) ||
115
+ isNaN(chartData.timestamp.getTime())) {
116
+ return 'Timestamp must be a valid Date object';
117
+ }
118
+ }
119
+ if (chartData.location !== undefined) {
120
+ if (typeof chartData.location !== 'string') {
121
+ return 'Location must be a string';
122
+ }
123
+ }
124
+ if (chartData.chartType !== undefined) {
125
+ const validTypes = ['natal', 'event', 'transit'];
126
+ if (!validTypes.includes(chartData.chartType)) {
127
+ return `Chart type must be one of: ${validTypes.join(', ')}`;
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+ /**
133
+ * Validates MultiChartData
134
+ * @param multiChartData The multi-chart data to validate
135
+ * @returns Error message if invalid, null if valid
136
+ */
137
+ function validateMultiChartData(multiChartData) {
138
+ if (!Array.isArray(multiChartData)) {
139
+ return 'Multi-chart data must be an array';
140
+ }
141
+ if (multiChartData.length === 0) {
142
+ return 'Multi-chart data must contain at least one chart';
143
+ }
144
+ if (multiChartData.length > 10) {
145
+ return 'Multi-chart data cannot contain more than 10 charts';
146
+ }
147
+ for (let i = 0; i < multiChartData.length; i++) {
148
+ const error = validateChartData(multiChartData[i]);
149
+ if (error) {
150
+ return `Chart at index ${i} (${multiChartData[i]?.name || 'unnamed'}): ${error}`;
151
+ }
152
+ }
153
+ // Check for duplicate chart names
154
+ const names = multiChartData.map((chart) => chart.name);
155
+ const duplicates = names.filter((name, index) => names.indexOf(name) !== index);
156
+ if (duplicates.length > 0) {
157
+ return `Duplicate chart names found: ${duplicates.join(', ')}`;
158
+ }
159
+ // Validate transit charts
160
+ const transitCharts = multiChartData.filter((chart) => chart.chartType === 'transit');
161
+ if (transitCharts.length > 1) {
162
+ return 'Cannot have more than one transit chart';
163
+ }
164
+ return null;
165
+ }
166
+ /**
167
+ * Validates chart data (single or multi-chart)
168
+ * @param data The data to validate
169
+ * @returns Error message if invalid, null if valid
170
+ */
171
+ function validateInputData(data) {
172
+ if (!data) {
173
+ return 'Data is required';
174
+ }
175
+ if (Array.isArray(data)) {
176
+ return validateMultiChartData(data);
177
+ }
178
+ else {
179
+ return validateChartData(data);
180
+ }
181
+ }