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
package/dist/types.d.ts CHANGED
@@ -17,30 +17,176 @@ export interface ChartData {
17
17
  }
18
18
  export type MultiChartData = ChartData[];
19
19
  export declare function isMultiChartData(obj: ChartData | MultiChartData): obj is MultiChartData;
20
+ export type UnionedPoint = [Point, string];
21
+ export declare enum AspectClassification {
22
+ Major = "major",
23
+ Minor = "minor",
24
+ Esoteric = "esoteric"
25
+ }
26
+ export declare enum PlanetCategory {
27
+ Luminaries = "luminaries",
28
+ Personal = "personal",
29
+ Social = "social",
30
+ Outer = "outer",
31
+ Angles = "angles"
32
+ }
20
33
  export interface Aspect {
21
34
  name: string;
22
35
  angle: number;
23
36
  orb: number;
37
+ classification?: AspectClassification;
38
+ }
39
+ export type AspectStrength = 'tight' | 'moderate' | 'wide';
40
+ export interface AspectStrengthThresholds {
41
+ tight: number;
42
+ moderate: number;
24
43
  }
25
44
  export interface AspectData {
26
45
  planetA: string;
27
46
  planetB: string;
47
+ p1ChartName?: string;
48
+ p2ChartName?: string;
28
49
  aspectType: string;
29
50
  orb: number;
51
+ application?: 'applying' | 'separating' | 'exact';
30
52
  }
31
- export interface AspectCategory {
53
+ export interface PlanetPosition {
32
54
  name: string;
33
- minOrb?: number;
34
- maxOrb: number;
35
- }
36
- export interface Settings {
37
- includeSignDegree: boolean;
38
- includeAscendant: boolean;
39
- houseSystemName: string;
40
- includeHouseDegree: boolean;
41
- aspectDefinitions: Aspect[];
42
- aspectCategories: AspectCategory[];
43
- skipOutOfSignAspects: boolean;
44
- dateFormat: string;
55
+ degree: number;
56
+ sign: string;
57
+ speed?: number;
58
+ house?: number;
59
+ chartName?: string;
60
+ }
61
+ export interface TSquare {
62
+ type: 'T-Square';
63
+ apex: PlanetPosition;
64
+ opposition: [PlanetPosition, PlanetPosition];
65
+ mode: 'Cardinal' | 'Fixed' | 'Mutable';
66
+ averageOrb: number;
67
+ }
68
+ export interface GrandTrine {
69
+ type: 'Grand Trine';
70
+ planets: [PlanetPosition, PlanetPosition, PlanetPosition];
71
+ element: 'Fire' | 'Earth' | 'Air' | 'Water';
72
+ averageOrb: number;
73
+ }
74
+ export interface Stellium {
75
+ type: 'Stellium';
76
+ planets: PlanetPosition[];
77
+ sign?: string;
78
+ houses: number[];
79
+ span: number;
80
+ }
81
+ export interface GrandCross {
82
+ type: 'Grand Cross';
83
+ planets: [PlanetPosition, PlanetPosition, PlanetPosition, PlanetPosition];
84
+ mode: 'Cardinal' | 'Fixed' | 'Mutable';
85
+ averageOrb: number;
86
+ }
87
+ export interface Yod {
88
+ type: 'Yod';
89
+ apex: PlanetPosition;
90
+ base: [PlanetPosition, PlanetPosition];
91
+ averageOrb: number;
92
+ }
93
+ export interface MysticRectangle {
94
+ type: 'Mystic Rectangle';
95
+ oppositions: [
96
+ [
97
+ PlanetPosition,
98
+ PlanetPosition
99
+ ],
100
+ [
101
+ PlanetPosition,
102
+ PlanetPosition
103
+ ]
104
+ ];
105
+ averageOrb: number;
106
+ }
107
+ export interface Kite {
108
+ type: 'Kite';
109
+ grandTrine: [PlanetPosition, PlanetPosition, PlanetPosition];
110
+ opposition: PlanetPosition;
111
+ averageOrb: number;
112
+ }
113
+ export type AspectPattern = TSquare | GrandTrine | Stellium | GrandCross | Yod | MysticRectangle | Kite;
114
+ export interface ChartAnalysis {
115
+ chart: ChartData;
116
+ placements: {
117
+ planets: PlanetPosition[];
118
+ [key: string]: any;
119
+ };
120
+ aspects: AspectData[];
121
+ groupedAspects?: Map<string, AspectData[]>;
122
+ patterns: AspectPattern[];
123
+ stelliums: Stellium[];
124
+ signDistributions: {
125
+ elements: {
126
+ [key: string]: string[];
127
+ };
128
+ modalities: {
129
+ [key: string]: number;
130
+ };
131
+ polarities: {
132
+ [key: string]: number;
133
+ };
134
+ };
135
+ dispositors: {
136
+ [key: string]: string;
137
+ };
138
+ }
139
+ export interface PairwiseAnalysis {
140
+ chart1: ChartData;
141
+ chart2: ChartData;
142
+ synastryAspects: AspectData[];
143
+ groupedSynastryAspects?: Map<string, AspectData[]>;
144
+ compositePatterns: AspectPattern[];
145
+ houseOverlays: {
146
+ chart1InChart2Houses: {
147
+ [key: string]: number;
148
+ };
149
+ chart2InChart1Houses: {
150
+ [key: string]: number;
151
+ };
152
+ };
153
+ }
154
+ export interface GlobalAnalysis {
155
+ charts: ChartData[];
156
+ patterns: AspectPattern[];
157
+ }
158
+ export interface TransitAnalysis {
159
+ natalChart: ChartData;
160
+ transitChart: ChartData;
161
+ aspects: AspectData[];
162
+ groupedAspects?: Map<string, AspectData[]>;
163
+ patterns: AspectPattern[];
164
+ }
165
+ export interface AstrologicalReport {
166
+ settings: Settings;
167
+ chartAnalyses: ChartAnalysis[];
168
+ pairwiseAnalyses: PairwiseAnalysis[];
169
+ globalAnalysis?: GlobalAnalysis;
170
+ transitAnalyses: TransitAnalysis[];
171
+ globalTransitAnalysis?: GlobalAnalysis;
172
+ }
173
+ export interface AnalysisSettings {
174
+ aspectDefinitions?: Aspect[] | 'traditional' | 'modern' | 'tight' | 'wide';
175
+ skipOutOfSignAspects?: boolean;
176
+ includeAspectPatterns?: boolean;
177
+ includeSignDistributions?: boolean;
178
+ }
179
+ export interface GroupingSettings {
180
+ aspectStrengthThresholds?: AspectStrengthThresholds;
181
+ }
182
+ export interface FormattingSettings {
183
+ dateFormat?: string;
184
+ houseSystemName?: string;
185
+ }
186
+ export interface Settings extends AnalysisSettings, GroupingSettings, FormattingSettings {
45
187
  }
46
188
  export type PartialSettings = Partial<Settings>;
189
+ export type ChartDataWithInfo = {
190
+ data: ChartData;
191
+ index: number;
192
+ };
package/dist/types.js CHANGED
@@ -1,6 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PlanetCategory = exports.AspectClassification = void 0;
3
4
  exports.isMultiChartData = isMultiChartData;
4
5
  function isMultiChartData(obj) {
5
6
  return Array.isArray(obj);
6
7
  }
8
+ var AspectClassification;
9
+ (function (AspectClassification) {
10
+ AspectClassification["Major"] = "major";
11
+ AspectClassification["Minor"] = "minor";
12
+ AspectClassification["Esoteric"] = "esoteric";
13
+ })(AspectClassification || (exports.AspectClassification = AspectClassification = {}));
14
+ var PlanetCategory;
15
+ (function (PlanetCategory) {
16
+ PlanetCategory["Luminaries"] = "luminaries";
17
+ PlanetCategory["Personal"] = "personal";
18
+ PlanetCategory["Social"] = "social";
19
+ PlanetCategory["Outer"] = "outer";
20
+ PlanetCategory["Angles"] = "angles";
21
+ })(PlanetCategory || (exports.PlanetCategory = PlanetCategory = {}));
@@ -0,0 +1,10 @@
1
+ import { Point, PlanetPosition } from '../types';
2
+ /**
3
+ * Converts a number to its ordinal form (1st, 2nd, 3rd, etc.)
4
+ * @param num The number to convert
5
+ * @returns The ordinal string
6
+ */
7
+ export declare function getOrdinal(num: number): string;
8
+ export declare function getSign(degree: number): string;
9
+ export declare function getHouse(degree: number, houseCusps: number[]): number;
10
+ export declare function getPlanetPositions(planets: Point[], houseCusps?: number[]): PlanetPosition[];
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getOrdinal = getOrdinal;
4
+ exports.getSign = getSign;
5
+ exports.getHouse = getHouse;
6
+ exports.getPlanetPositions = getPlanetPositions;
7
+ const constants_1 = require("../constants");
8
+ function normalizeDegree(degree) {
9
+ return ((degree % 360) + 360) % 360;
10
+ }
11
+ /**
12
+ * Converts a number to its ordinal form (1st, 2nd, 3rd, etc.)
13
+ * @param num The number to convert
14
+ * @returns The ordinal string
15
+ */
16
+ function getOrdinal(num) {
17
+ const suffix = ['th', 'st', 'nd', 'rd'];
18
+ const v = num % 100;
19
+ return num + (suffix[(v - 20) % 10] || suffix[v] || suffix[0]);
20
+ }
21
+ function getSign(degree) {
22
+ const signIndex = Math.floor(degree / 30);
23
+ return constants_1.ZODIAC_SIGNS[signIndex];
24
+ }
25
+ function getHouse(degree, houseCusps) {
26
+ for (let i = 0; i < 12; i++) {
27
+ const cusp1 = houseCusps[i];
28
+ const cusp2 = houseCusps[(i + 1) % 12];
29
+ if (cusp1 < cusp2) {
30
+ if (degree >= cusp1 && degree < cusp2) {
31
+ return i + 1;
32
+ }
33
+ }
34
+ else {
35
+ if (degree >= cusp1 || degree < cusp2) {
36
+ return i + 1;
37
+ }
38
+ }
39
+ }
40
+ return -1; // Should not happen
41
+ }
42
+ function getPlanetPositions(planets, houseCusps) {
43
+ return planets.map((planet) => {
44
+ const normalizedDegree = normalizeDegree(planet.degree);
45
+ const position = {
46
+ name: planet.name,
47
+ degree: normalizedDegree,
48
+ sign: getSign(normalizedDegree),
49
+ speed: planet.speed,
50
+ };
51
+ if (houseCusps) {
52
+ position.house = getHouse(normalizedDegree, houseCusps);
53
+ }
54
+ return position;
55
+ });
56
+ }
@@ -0,0 +1,10 @@
1
+ import { ChartData } from '../types';
2
+ export declare function calculateHouseOverlays(chart1: ChartData, chart2: ChartData): {
3
+ chart1InChart2Houses: {
4
+ [key: string]: number;
5
+ };
6
+ chart2InChart1Houses: {
7
+ [key: string]: number;
8
+ };
9
+ };
10
+ export declare function getHouseForPoint(degree: number, houseCusps: number[]): number;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateHouseOverlays = calculateHouseOverlays;
4
+ exports.getHouseForPoint = getHouseForPoint;
5
+ const formatting_1 = require("./formatting");
6
+ function calculateHouseOverlays(chart1, chart2) {
7
+ const chart1InChart2Houses = {};
8
+ if (chart2.houseCusps) {
9
+ for (const planet of chart1.planets) {
10
+ chart1InChart2Houses[planet.name] = (0, formatting_1.getHouse)(planet.degree, chart2.houseCusps);
11
+ }
12
+ }
13
+ const chart2InChart1Houses = {};
14
+ if (chart1.houseCusps) {
15
+ for (const planet of chart2.planets) {
16
+ chart2InChart1Houses[planet.name] = (0, formatting_1.getHouse)(planet.degree, chart1.houseCusps);
17
+ }
18
+ }
19
+ return { chart1InChart2Houses, chart2InChart1Houses };
20
+ }
21
+ function getHouseForPoint(degree, houseCusps) {
22
+ return (0, formatting_1.getHouse)(degree, houseCusps);
23
+ }
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chart2txt",
3
- "version": "0.5.2",
3
+ "version": "0.7.0",
4
4
  "description": "Convert astrological chart data to human-readable text",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,6 +12,7 @@
12
12
  "test": "jest",
13
13
  "test:config": "ts-node test-configurations.ts",
14
14
  "lint": "eslint src/**/*.ts",
15
+ "lint:fix": "eslint src/**/*.ts --fix",
15
16
  "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\""
16
17
  },
17
18
  "keywords": [