chart2txt 0.7.1 → 0.8.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.
@@ -0,0 +1,1253 @@
1
+ "use strict";
2
+ /**
3
+ * humandesign2txt
4
+ * Converts Human Design chart data to human-readable text for LLM consumption.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ALL_CENTERS = exports.STRATEGY_BY_TYPE = exports.INCARNATION_CROSSES = exports.PROFILE_NAMES = exports.CHANNELS = exports.GATE_CENTERS = exports.GATE_NAMES = exports.GATES = void 0;
8
+ exports.longitudeToGateLine = longitudeToGateLine;
9
+ exports.oppositeGate = oppositeGate;
10
+ exports.calculateActivations = calculateActivations;
11
+ exports.getAllGates = getAllGates;
12
+ exports.getActiveChannels = getActiveChannels;
13
+ exports.getCenterStatus = getCenterStatus;
14
+ exports.calculateType = calculateType;
15
+ exports.calculateAuthority = calculateAuthority;
16
+ exports.calculateDefinition = calculateDefinition;
17
+ exports.getDefinitionIslands = getDefinitionIslands;
18
+ exports.getHangingGates = getHangingGates;
19
+ exports.calculateProfile = calculateProfile;
20
+ exports.getIncarnationCross = getIncarnationCross;
21
+ exports.humandesign2txt = humandesign2txt;
22
+ exports.analyzePartnership = analyzePartnership;
23
+ exports.buildChart = buildChart;
24
+ exports.humandesignPartnership2txt = humandesignPartnership2txt;
25
+ // ============================================================================
26
+ // CONSTANTS
27
+ // ============================================================================
28
+ /**
29
+ * The 64 gates in I Ching wheel order (starting from 0° Aries after 58° adjustment)
30
+ */
31
+ exports.GATES = [
32
+ 41, 19, 13, 49, 30, 55, 37, 63, 22, 36, 25, 17, 21, 51, 42, 3,
33
+ 27, 24, 2, 23, 8, 20, 16, 35, 45, 12, 15, 52, 39, 53, 62, 56,
34
+ 31, 33, 7, 4, 29, 59, 40, 64, 47, 6, 46, 18, 48, 57, 32, 50,
35
+ 28, 44, 1, 43, 14, 34, 9, 5, 26, 11, 10, 58, 38, 54, 61, 60
36
+ ];
37
+ /**
38
+ * Gate names (I Ching / Human Design names)
39
+ */
40
+ exports.GATE_NAMES = {
41
+ 1: 'Self-Expression',
42
+ 2: 'Direction of the Self',
43
+ 3: 'Ordering',
44
+ 4: 'Formulization',
45
+ 5: 'Fixed Rhythms',
46
+ 6: 'Friction',
47
+ 7: 'The Role of the Self',
48
+ 8: 'Contribution',
49
+ 9: 'Focus',
50
+ 10: 'Behavior of the Self',
51
+ 11: 'Ideas',
52
+ 12: 'Caution',
53
+ 13: 'The Listener',
54
+ 14: 'Power Skills',
55
+ 15: 'Extremes',
56
+ 16: 'Skills',
57
+ 17: 'Opinions',
58
+ 18: 'Correction',
59
+ 19: 'Wanting',
60
+ 20: 'The Now',
61
+ 21: 'The Hunter',
62
+ 22: 'Openness',
63
+ 23: 'Assimilation',
64
+ 24: 'Rationalization',
65
+ 25: 'Innocence',
66
+ 26: 'The Egoist',
67
+ 27: 'Caring',
68
+ 28: 'The Game Player',
69
+ 29: 'Perseverance',
70
+ 30: 'Feelings',
71
+ 31: 'Influence',
72
+ 32: 'Continuity',
73
+ 33: 'Privacy',
74
+ 34: 'Power',
75
+ 35: 'Change',
76
+ 36: 'Crisis',
77
+ 37: 'Friendship',
78
+ 38: 'The Fighter',
79
+ 39: 'Provocation',
80
+ 40: 'Aloneness',
81
+ 41: 'Contraction',
82
+ 42: 'Growth',
83
+ 43: 'Insight',
84
+ 44: 'Alertness',
85
+ 45: 'The Gatherer',
86
+ 46: 'Determination',
87
+ 47: 'Realization',
88
+ 48: 'Depth',
89
+ 49: 'Principles',
90
+ 50: 'Values',
91
+ 51: 'Shock',
92
+ 52: 'Stillness',
93
+ 53: 'Beginnings',
94
+ 54: 'Ambition',
95
+ 55: 'Spirit',
96
+ 56: 'Stimulation',
97
+ 57: 'Intuition',
98
+ 58: 'Vitality',
99
+ 59: 'Sexuality',
100
+ 60: 'Limitation',
101
+ 61: 'Mystery',
102
+ 62: 'Details',
103
+ 63: 'Doubt',
104
+ 64: 'Confusion'
105
+ };
106
+ /**
107
+ * Which center each gate belongs to
108
+ */
109
+ exports.GATE_CENTERS = {
110
+ // Head Center
111
+ 64: 'Head', 61: 'Head', 63: 'Head',
112
+ // Ajna Center
113
+ 47: 'Ajna', 24: 'Ajna', 4: 'Ajna', 17: 'Ajna', 43: 'Ajna', 11: 'Ajna',
114
+ // Throat Center
115
+ 62: 'Throat', 23: 'Throat', 56: 'Throat', 35: 'Throat', 12: 'Throat',
116
+ 45: 'Throat', 33: 'Throat', 8: 'Throat', 31: 'Throat', 20: 'Throat',
117
+ 16: 'Throat',
118
+ // G Center (Self)
119
+ 7: 'G Center', 1: 'G Center', 13: 'G Center', 25: 'G Center', 46: 'G Center',
120
+ 2: 'G Center', 15: 'G Center', 10: 'G Center',
121
+ // Heart/Ego Center
122
+ 21: 'Ego', 40: 'Ego', 26: 'Ego', 51: 'Ego',
123
+ // Solar Plexus Center
124
+ 36: 'Solar Plexus', 22: 'Solar Plexus', 37: 'Solar Plexus', 6: 'Solar Plexus',
125
+ 49: 'Solar Plexus', 55: 'Solar Plexus', 30: 'Solar Plexus',
126
+ // Sacral Center
127
+ 34: 'Sacral', 5: 'Sacral', 14: 'Sacral', 29: 'Sacral', 59: 'Sacral',
128
+ 9: 'Sacral', 3: 'Sacral', 42: 'Sacral', 27: 'Sacral',
129
+ // Spleen Center
130
+ 48: 'Spleen', 57: 'Spleen', 44: 'Spleen', 50: 'Spleen', 32: 'Spleen',
131
+ 28: 'Spleen', 18: 'Spleen',
132
+ // Root Center
133
+ 58: 'Root', 38: 'Root', 54: 'Root', 53: 'Root', 60: 'Root', 52: 'Root',
134
+ 19: 'Root', 39: 'Root', 41: 'Root'
135
+ };
136
+ /**
137
+ * All 36 channels with their gate pairs, names, and connected centers
138
+ */
139
+ exports.CHANNELS = [
140
+ // Head to Ajna
141
+ { gates: [64, 47], name: 'Abstraction', centers: ['Head', 'Ajna'] },
142
+ { gates: [61, 24], name: 'Awareness', centers: ['Head', 'Ajna'] },
143
+ { gates: [63, 4], name: 'Logic', centers: ['Head', 'Ajna'] },
144
+ // Ajna to Throat
145
+ { gates: [17, 62], name: 'Acceptance', centers: ['Ajna', 'Throat'] },
146
+ { gates: [43, 23], name: 'Structuring', centers: ['Ajna', 'Throat'] },
147
+ { gates: [11, 56], name: 'Curiosity', centers: ['Ajna', 'Throat'] },
148
+ // G Center to Throat
149
+ { gates: [7, 31], name: 'The Alpha', centers: ['G Center', 'Throat'] },
150
+ { gates: [1, 8], name: 'Inspiration', centers: ['G Center', 'Throat'] },
151
+ { gates: [13, 33], name: 'The Prodigal', centers: ['G Center', 'Throat'] },
152
+ { gates: [10, 20], name: 'Awakening', centers: ['G Center', 'Throat'] },
153
+ // G Center to Sacral
154
+ { gates: [15, 5], name: 'Rhythm', centers: ['G Center', 'Sacral'] },
155
+ { gates: [2, 14], name: 'The Beat', centers: ['G Center', 'Sacral'] },
156
+ { gates: [46, 29], name: 'Discovery', centers: ['G Center', 'Sacral'] },
157
+ // G Center to Spleen
158
+ { gates: [10, 57], name: 'Perfected Form', centers: ['G Center', 'Spleen'] },
159
+ // G Center to Ego
160
+ { gates: [25, 51], name: 'Initiation', centers: ['G Center', 'Ego'] },
161
+ // Ego to Throat
162
+ { gates: [21, 45], name: 'Money', centers: ['Ego', 'Throat'] },
163
+ // Ego to Solar Plexus
164
+ { gates: [37, 40], name: 'Community', centers: ['Ego', 'Solar Plexus'] },
165
+ // Ego to Spleen
166
+ { gates: [26, 44], name: 'Surrender', centers: ['Ego', 'Spleen'] },
167
+ // Sacral to Throat
168
+ { gates: [34, 20], name: 'Charisma', centers: ['Sacral', 'Throat'] },
169
+ // Sacral to Spleen
170
+ { gates: [34, 57], name: 'Power', centers: ['Sacral', 'Spleen'] },
171
+ { gates: [27, 50], name: 'Preservation', centers: ['Sacral', 'Spleen'] },
172
+ // Sacral to Solar Plexus
173
+ { gates: [59, 6], name: 'Intimacy', centers: ['Sacral', 'Solar Plexus'] },
174
+ // Sacral to Root
175
+ { gates: [42, 53], name: 'Maturation', centers: ['Sacral', 'Root'] },
176
+ { gates: [3, 60], name: 'Mutation', centers: ['Sacral', 'Root'] },
177
+ { gates: [9, 52], name: 'Concentration', centers: ['Sacral', 'Root'] },
178
+ // Spleen to Throat
179
+ { gates: [16, 48], name: 'The Wavelength', centers: ['Spleen', 'Throat'] },
180
+ { gates: [57, 20], name: 'The Brainwave', centers: ['Spleen', 'Throat'] },
181
+ // Spleen to Root
182
+ { gates: [18, 58], name: 'Judgment', centers: ['Spleen', 'Root'] },
183
+ { gates: [28, 38], name: 'Struggle', centers: ['Spleen', 'Root'] },
184
+ { gates: [32, 54], name: 'Transformation', centers: ['Spleen', 'Root'] },
185
+ // Solar Plexus to Throat
186
+ { gates: [12, 22], name: 'Openness', centers: ['Solar Plexus', 'Throat'] },
187
+ { gates: [35, 36], name: 'Transitoriness', centers: ['Solar Plexus', 'Throat'] },
188
+ // Solar Plexus to Root
189
+ { gates: [19, 49], name: 'Synthesis', centers: ['Solar Plexus', 'Root'] },
190
+ { gates: [39, 55], name: 'Emoting', centers: ['Solar Plexus', 'Root'] },
191
+ { gates: [41, 30], name: 'Recognition', centers: ['Solar Plexus', 'Root'] },
192
+ // Sacral to G Center (via 10-34, often grouped with Sacral-Spleen)
193
+ { gates: [34, 10], name: 'Exploration', centers: ['Sacral', 'G Center'] }
194
+ ];
195
+ /**
196
+ * Profile names by line combination
197
+ */
198
+ exports.PROFILE_NAMES = {
199
+ '1/3': 'Investigator/Martyr',
200
+ '1/4': 'Investigator/Opportunist',
201
+ '2/4': 'Hermit/Opportunist',
202
+ '2/5': 'Hermit/Heretic',
203
+ '3/5': 'Martyr/Heretic',
204
+ '3/6': 'Martyr/Role Model',
205
+ '4/6': 'Opportunist/Role Model',
206
+ '4/1': 'Opportunist/Investigator',
207
+ '5/1': 'Heretic/Investigator',
208
+ '5/2': 'Heretic/Hermit',
209
+ '6/2': 'Role Model/Hermit',
210
+ '6/3': 'Role Model/Martyr'
211
+ };
212
+ /**
213
+ * Incarnation Cross names by Sun gate (simplified - Right Angle crosses)
214
+ * Format: { gateNumber: "Cross Name" }
215
+ */
216
+ exports.INCARNATION_CROSSES = {
217
+ 1: 'The Sphinx',
218
+ 2: 'The Sphinx',
219
+ 3: 'Laws',
220
+ 4: 'Explanation',
221
+ 5: 'Consciousness',
222
+ 6: 'Eden',
223
+ 7: 'The Sphinx',
224
+ 8: 'Contagion',
225
+ 9: 'Planning',
226
+ 10: 'Vessel of Love',
227
+ 11: 'Eden',
228
+ 12: 'Eden',
229
+ 13: 'The Sphinx',
230
+ 14: 'Contagion',
231
+ 15: 'Vessel of Love',
232
+ 16: 'Planning',
233
+ 17: 'Service',
234
+ 18: 'Service',
235
+ 19: 'The Four Ways',
236
+ 20: 'The Sleeping Phoenix',
237
+ 21: 'Tension',
238
+ 22: 'Rulership',
239
+ 23: 'Explanation',
240
+ 24: 'The Four Ways',
241
+ 25: 'Vessel of Love',
242
+ 26: 'Rulership',
243
+ 27: 'The Unexpected',
244
+ 28: 'The Unexpected',
245
+ 29: 'Contagion',
246
+ 30: 'Contagion',
247
+ 31: 'The Unexpected',
248
+ 32: 'Maya',
249
+ 33: 'The Four Ways',
250
+ 34: 'The Sleeping Phoenix',
251
+ 35: 'Consciousness',
252
+ 36: 'Eden',
253
+ 37: 'Planning',
254
+ 38: 'Tension',
255
+ 39: 'Tension',
256
+ 40: 'Planning',
257
+ 41: 'The Unexpected',
258
+ 42: 'Maya',
259
+ 43: 'Explanation',
260
+ 44: 'The Four Ways',
261
+ 45: 'Rulership',
262
+ 46: 'Vessel of Love',
263
+ 47: 'Rulership',
264
+ 48: 'Tension',
265
+ 49: 'Explanation',
266
+ 50: 'Laws',
267
+ 51: 'Penetration',
268
+ 52: 'Service',
269
+ 53: 'Penetration',
270
+ 54: 'Penetration',
271
+ 55: 'The Sleeping Phoenix',
272
+ 56: 'Laws',
273
+ 57: 'Penetration',
274
+ 58: 'Service',
275
+ 59: 'The Sleeping Phoenix',
276
+ 60: 'Laws',
277
+ 61: 'Maya',
278
+ 62: 'Maya',
279
+ 63: 'Consciousness',
280
+ 64: 'Consciousness'
281
+ };
282
+ /**
283
+ * Strategy by Type
284
+ */
285
+ exports.STRATEGY_BY_TYPE = {
286
+ 'Generator': 'Wait to Respond',
287
+ 'Manifesting Generator': 'Wait to Respond',
288
+ 'Manifestor': 'Inform Before Acting',
289
+ 'Projector': 'Wait for the Invitation',
290
+ 'Reflector': 'Wait a Lunar Cycle'
291
+ };
292
+ /**
293
+ * All 9 centers
294
+ */
295
+ exports.ALL_CENTERS = [
296
+ 'Head', 'Ajna', 'Throat', 'G Center', 'Ego', 'Solar Plexus', 'Sacral', 'Spleen', 'Root'
297
+ ];
298
+ // ============================================================================
299
+ // CALCULATION FUNCTIONS
300
+ // ============================================================================
301
+ /**
302
+ * Convert a planetary longitude to gate and line
303
+ */
304
+ function longitudeToGateLine(longitude) {
305
+ const adjustment = 58.0;
306
+ const adjustedLongitude = (longitude + adjustment) % 360;
307
+ const percentage = adjustedLongitude / 360;
308
+ const gateIndex = Math.floor(percentage * 64);
309
+ const gate = exports.GATES[gateIndex];
310
+ // Line calculation: 384 = 64 gates * 6 lines
311
+ const linePosition = (percentage * 384) % 6;
312
+ const line = Math.floor(linePosition) + 1;
313
+ return { gate, line };
314
+ }
315
+ /**
316
+ * Get the opposite gate (180° across the wheel)
317
+ */
318
+ function oppositeGate(gate) {
319
+ const index = exports.GATES.indexOf(gate);
320
+ const oppositeIndex = (index + 32) % 64;
321
+ return exports.GATES[oppositeIndex];
322
+ }
323
+ /**
324
+ * Calculate all activations from planetary positions
325
+ */
326
+ function calculateActivations(planets) {
327
+ const activations = [];
328
+ for (const planet of planets) {
329
+ const { gate, line } = longitudeToGateLine(planet.longitude);
330
+ activations.push({ planet: planet.name, gate, line });
331
+ // Add Earth (opposite of Sun) and South Node (opposite of North Node)
332
+ if (planet.name === 'Sun') {
333
+ const earthGate = oppositeGate(gate);
334
+ const earthLine = line; // Same line as Sun
335
+ activations.push({ planet: 'Earth', gate: earthGate, line: earthLine });
336
+ }
337
+ else if (planet.name === 'North Node') {
338
+ const southNodeGate = oppositeGate(gate);
339
+ const southNodeLine = line;
340
+ activations.push({ planet: 'South Node', gate: southNodeGate, line: southNodeLine });
341
+ }
342
+ }
343
+ return activations;
344
+ }
345
+ /**
346
+ * Get all unique gates from activations, tracking their sources
347
+ */
348
+ function getAllGates(personalityActivations, designActivations) {
349
+ const gates = new Map();
350
+ for (const activation of personalityActivations) {
351
+ const existing = gates.get(activation.gate);
352
+ if (existing) {
353
+ if (!existing.sources.includes('Personality')) {
354
+ existing.sources.push('Personality');
355
+ }
356
+ }
357
+ else {
358
+ gates.set(activation.gate, {
359
+ center: exports.GATE_CENTERS[activation.gate],
360
+ sources: ['Personality']
361
+ });
362
+ }
363
+ }
364
+ for (const activation of designActivations) {
365
+ const existing = gates.get(activation.gate);
366
+ if (existing) {
367
+ if (!existing.sources.includes('Design')) {
368
+ existing.sources.push('Design');
369
+ }
370
+ }
371
+ else {
372
+ gates.set(activation.gate, {
373
+ center: exports.GATE_CENTERS[activation.gate],
374
+ sources: ['Design']
375
+ });
376
+ }
377
+ }
378
+ return gates;
379
+ }
380
+ /**
381
+ * Determine which channels are active (both gates present)
382
+ */
383
+ function getActiveChannels(allGates) {
384
+ const activeChannels = [];
385
+ for (const channel of exports.CHANNELS) {
386
+ const [gate1, gate2] = channel.gates;
387
+ if (allGates.has(gate1) && allGates.has(gate2)) {
388
+ activeChannels.push(channel);
389
+ }
390
+ }
391
+ return activeChannels;
392
+ }
393
+ /**
394
+ * Determine center status: Defined, Undefined, or Open
395
+ */
396
+ function getCenterStatus(activeChannels, allGates) {
397
+ const definedCenters = new Set();
398
+ const centersWithGates = new Set();
399
+ // Centers are defined if they have at least one complete channel
400
+ for (const channel of activeChannels) {
401
+ definedCenters.add(channel.centers[0]);
402
+ definedCenters.add(channel.centers[1]);
403
+ }
404
+ // Track which centers have any gates (for undefined vs open)
405
+ for (const [, info] of allGates) {
406
+ centersWithGates.add(info.center);
407
+ }
408
+ const defined = [];
409
+ const undefined = [];
410
+ const open = [];
411
+ for (const center of exports.ALL_CENTERS) {
412
+ if (definedCenters.has(center)) {
413
+ defined.push(center);
414
+ }
415
+ else if (centersWithGates.has(center)) {
416
+ undefined.push(center);
417
+ }
418
+ else {
419
+ open.push(center);
420
+ }
421
+ }
422
+ return { defined, undefined, open };
423
+ }
424
+ /**
425
+ * Check if there's a motor connected to Throat
426
+ */
427
+ function hasMotorToThroat(activeChannels, definedCenters) {
428
+ const motors = ['Sacral', 'Solar Plexus', 'Ego', 'Root'];
429
+ // Build a graph of connected centers
430
+ const connections = new Map();
431
+ for (const center of exports.ALL_CENTERS) {
432
+ connections.set(center, new Set());
433
+ }
434
+ for (const channel of activeChannels) {
435
+ connections.get(channel.centers[0]).add(channel.centers[1]);
436
+ connections.get(channel.centers[1]).add(channel.centers[0]);
437
+ }
438
+ // BFS from each motor to see if it reaches Throat
439
+ for (const motor of motors) {
440
+ if (!definedCenters.includes(motor))
441
+ continue;
442
+ const visited = new Set();
443
+ const queue = [motor];
444
+ while (queue.length > 0) {
445
+ const current = queue.shift();
446
+ if (current === 'Throat')
447
+ return true;
448
+ if (visited.has(current))
449
+ continue;
450
+ visited.add(current);
451
+ for (const neighbor of connections.get(current) || []) {
452
+ if (!visited.has(neighbor)) {
453
+ queue.push(neighbor);
454
+ }
455
+ }
456
+ }
457
+ }
458
+ return false;
459
+ }
460
+ /**
461
+ * Calculate Human Design Type
462
+ */
463
+ function calculateType(definedCenters, activeChannels) {
464
+ const hasSacral = definedCenters.includes('Sacral');
465
+ const motorToThroat = hasMotorToThroat(activeChannels, definedCenters);
466
+ if (hasSacral) {
467
+ return motorToThroat ? 'Manifesting Generator' : 'Generator';
468
+ }
469
+ if (motorToThroat) {
470
+ return 'Manifestor';
471
+ }
472
+ if (definedCenters.length === 0) {
473
+ return 'Reflector';
474
+ }
475
+ return 'Projector';
476
+ }
477
+ /**
478
+ * Calculate Inner Authority
479
+ */
480
+ function calculateAuthority(definedCenters, activeChannels) {
481
+ // Check in order of hierarchy
482
+ if (definedCenters.includes('Solar Plexus')) {
483
+ return 'Emotional (Solar Plexus)';
484
+ }
485
+ if (definedCenters.includes('Sacral')) {
486
+ return 'Sacral';
487
+ }
488
+ if (definedCenters.includes('Spleen')) {
489
+ return 'Splenic';
490
+ }
491
+ // Ego authority requires Ego connected to Throat or G Center
492
+ if (definedCenters.includes('Ego')) {
493
+ const egoToThroat = activeChannels.some(ch => ch.centers.includes('Ego') && ch.centers.includes('Throat'));
494
+ const egoToG = activeChannels.some(ch => ch.centers.includes('Ego') && ch.centers.includes('G Center'));
495
+ if (egoToThroat || egoToG) {
496
+ return 'Ego';
497
+ }
498
+ }
499
+ // Self-Projected: G Center connected to Throat
500
+ if (definedCenters.includes('G Center')) {
501
+ const gToThroat = activeChannels.some(ch => ch.centers.includes('G Center') && ch.centers.includes('Throat'));
502
+ if (gToThroat) {
503
+ return 'Self-Projected';
504
+ }
505
+ }
506
+ // Mental/Environment authority (Head/Ajna defined, or just Ajna to Throat)
507
+ if (definedCenters.includes('Ajna') || definedCenters.includes('Head')) {
508
+ return 'Mental (Outer Authority)';
509
+ }
510
+ // Lunar authority for Reflectors
511
+ return 'Lunar';
512
+ }
513
+ /**
514
+ * Calculate Definition type (how centers are connected)
515
+ */
516
+ function calculateDefinition(activeChannels, definedCenters) {
517
+ if (definedCenters.length === 0) {
518
+ return 'None';
519
+ }
520
+ const islands = getDefinitionIslands(activeChannels, definedCenters);
521
+ switch (islands.length) {
522
+ case 1: return 'Single Definition';
523
+ case 2: return 'Split Definition';
524
+ case 3: return 'Triple Split Definition';
525
+ case 4: return 'Quadruple Split Definition';
526
+ default: return `${islands.length}-Split Definition`;
527
+ }
528
+ }
529
+ /**
530
+ * Get definition islands - groups of connected defined centers
531
+ */
532
+ function getDefinitionIslands(activeChannels, definedCenters) {
533
+ if (definedCenters.length === 0) {
534
+ return [];
535
+ }
536
+ // Build connection groups using Union-Find approach
537
+ const parent = new Map();
538
+ for (const center of definedCenters) {
539
+ parent.set(center, center);
540
+ }
541
+ function find(x) {
542
+ if (parent.get(x) !== x) {
543
+ parent.set(x, find(parent.get(x)));
544
+ }
545
+ return parent.get(x);
546
+ }
547
+ function union(x, y) {
548
+ const px = find(x);
549
+ const py = find(y);
550
+ if (px !== py) {
551
+ parent.set(px, py);
552
+ }
553
+ }
554
+ // Connect centers through channels
555
+ for (const channel of activeChannels) {
556
+ const [c1, c2] = channel.centers;
557
+ if (parent.has(c1) && parent.has(c2)) {
558
+ union(c1, c2);
559
+ }
560
+ }
561
+ // Group centers by their root
562
+ const islandMap = new Map();
563
+ for (const center of definedCenters) {
564
+ const root = find(center);
565
+ if (!islandMap.has(root)) {
566
+ islandMap.set(root, []);
567
+ }
568
+ islandMap.get(root).push(center);
569
+ }
570
+ // Return as array of arrays, sorted by first center in each island
571
+ return Array.from(islandMap.values()).sort((a, b) => {
572
+ const indexA = exports.ALL_CENTERS.indexOf(a[0]);
573
+ const indexB = exports.ALL_CENTERS.indexOf(b[0]);
574
+ return indexA - indexB;
575
+ });
576
+ }
577
+ /**
578
+ * Get hanging gates - gates that are not part of a complete channel
579
+ */
580
+ function getHangingGates(allGates, activeChannels) {
581
+ // Get all gates that are part of complete channels
582
+ const channelGates = new Set();
583
+ for (const channel of activeChannels) {
584
+ channelGates.add(channel.gates[0]);
585
+ channelGates.add(channel.gates[1]);
586
+ }
587
+ // Find gates that are NOT in complete channels
588
+ const hangingGates = [];
589
+ for (const [gate, info] of allGates) {
590
+ if (!channelGates.has(gate)) {
591
+ hangingGates.push({ gate, center: info.center });
592
+ }
593
+ }
594
+ // Sort by gate number
595
+ return hangingGates.sort((a, b) => a.gate - b.gate);
596
+ }
597
+ /**
598
+ * Calculate Profile from personality and design sun lines
599
+ */
600
+ function calculateProfile(personalitySunLine, designSunLine) {
601
+ return `${personalitySunLine}/${designSunLine}`;
602
+ }
603
+ /**
604
+ * Get Incarnation Cross
605
+ */
606
+ function getIncarnationCross(personalitySunGate, personalityEarthGate, designSunGate, designEarthGate, personalitySunLine, designSunLine) {
607
+ const crossName = exports.INCARNATION_CROSSES[personalitySunGate] || 'Unknown';
608
+ // Determine cross type based on lines
609
+ let crossType;
610
+ if (personalitySunLine < 4) {
611
+ crossType = 'Right Angle Cross';
612
+ }
613
+ else if (personalitySunLine >= 5) {
614
+ crossType = 'Left Angle Cross';
615
+ }
616
+ else {
617
+ // Line 4 with specific design line combinations
618
+ crossType = designSunLine < 4 ? 'Juxtaposition Cross' : 'Left Angle Cross';
619
+ }
620
+ return `${crossType} of ${crossName} (${personalitySunGate}/${personalityEarthGate} | ${designSunGate}/${designEarthGate})`;
621
+ }
622
+ /**
623
+ * Main entry point: Convert Human Design API response to formatted text
624
+ */
625
+ function humandesign2txt(apiResponse, options = {}) {
626
+ const { personality, design } = apiResponse;
627
+ // Calculate activations
628
+ const personalityActivations = calculateActivations(personality.planets);
629
+ const designActivations = calculateActivations(design.planets);
630
+ // Get all gates
631
+ const allGates = getAllGates(personalityActivations, designActivations);
632
+ // Get active channels
633
+ const activeChannels = getActiveChannels(allGates);
634
+ // Get center status
635
+ const { defined, undefined: undefinedCenters, open } = getCenterStatus(activeChannels, allGates);
636
+ // Calculate Type, Authority, Definition
637
+ const type = calculateType(defined, activeChannels);
638
+ const strategy = exports.STRATEGY_BY_TYPE[type];
639
+ const authority = calculateAuthority(defined, activeChannels);
640
+ const definition = calculateDefinition(activeChannels, defined);
641
+ const definitionIslands = getDefinitionIslands(activeChannels, defined);
642
+ // Calculate hanging gates
643
+ const hangingGates = getHangingGates(allGates, activeChannels);
644
+ // Calculate Profile
645
+ const personalitySun = personalityActivations.find(a => a.planet === 'Sun');
646
+ const designSun = designActivations.find(a => a.planet === 'Sun');
647
+ const profile = calculateProfile(personalitySun.line, designSun.line);
648
+ const profileName = exports.PROFILE_NAMES[profile] || '';
649
+ // Get Incarnation Cross
650
+ const personalityEarth = personalityActivations.find(a => a.planet === 'Earth');
651
+ const designEarth = designActivations.find(a => a.planet === 'Earth');
652
+ const incarnationCross = getIncarnationCross(personalitySun.gate, personalityEarth.gate, designSun.gate, designEarth.gate, personalitySun.line, designSun.line);
653
+ // Build the chart object
654
+ const chart = {
655
+ name: options.name || 'Chart',
656
+ location: options.location || `${personality.location.latitude}, ${personality.location.longitude}`,
657
+ date: personality.date,
658
+ time: personality.time,
659
+ type,
660
+ strategy,
661
+ authority,
662
+ definition,
663
+ definitionIslands,
664
+ profile,
665
+ profileName,
666
+ incarnationCross,
667
+ definedCenters: defined,
668
+ undefinedCenters,
669
+ openCenters: open,
670
+ activeChannels,
671
+ hangingGates,
672
+ allGates,
673
+ personalityActivations,
674
+ designActivations
675
+ };
676
+ // Format to text
677
+ return formatHumanDesignToText(chart);
678
+ }
679
+ // ============================================================================
680
+ // TEXT FORMATTER
681
+ // ============================================================================
682
+ function formatHumanDesignToText(chart) {
683
+ const lines = [];
684
+ // Metadata
685
+ lines.push('[METADATA]');
686
+ lines.push('chart_type: human_design');
687
+ lines.push('');
688
+ // Chart header
689
+ lines.push(`[CHART: ${chart.name}]`);
690
+ lines.push(`[BIRTHDATA] ${chart.location} | ${chart.date} | ${chart.time}`);
691
+ lines.push('');
692
+ // Type section
693
+ lines.push('[TYPE]');
694
+ lines.push(`Type: ${chart.type}`);
695
+ lines.push(`Strategy: ${chart.strategy}`);
696
+ lines.push(`Authority: ${chart.authority}`);
697
+ lines.push(`Definition: ${chart.definition}`);
698
+ if (chart.definitionIslands.length > 1) {
699
+ const islandStrs = chart.definitionIslands.map(island => `[${island.join('+')}]`);
700
+ lines.push(`Definition Islands: ${islandStrs.join(' + ')}`);
701
+ }
702
+ lines.push(`Profile: ${chart.profile}${chart.profileName ? ` (${chart.profileName})` : ''}`);
703
+ lines.push(`Incarnation Cross: ${chart.incarnationCross}`);
704
+ lines.push('');
705
+ // Centers
706
+ lines.push('[CENTERS]');
707
+ if (chart.definedCenters.length > 0) {
708
+ lines.push(`Defined: ${chart.definedCenters.join(', ')}`);
709
+ }
710
+ if (chart.undefinedCenters.length > 0) {
711
+ lines.push(`Undefined: ${chart.undefinedCenters.join(', ')}`);
712
+ }
713
+ if (chart.openCenters.length > 0) {
714
+ lines.push(`Open: ${chart.openCenters.join(', ')}`);
715
+ }
716
+ lines.push('');
717
+ // Channels
718
+ lines.push('[CHANNELS]');
719
+ if (chart.activeChannels.length > 0) {
720
+ for (const channel of chart.activeChannels) {
721
+ lines.push(`${channel.gates[0]}-${channel.gates[1]} (${channel.name}): ${channel.centers[0]} ↔ ${channel.centers[1]}`);
722
+ }
723
+ }
724
+ else {
725
+ lines.push('None');
726
+ }
727
+ lines.push('');
728
+ // Hanging Gates
729
+ lines.push('[HANGING GATES]');
730
+ lines.push('(Gates not part of a complete channel)');
731
+ if (chart.hangingGates.length > 0) {
732
+ for (const hg of chart.hangingGates) {
733
+ lines.push(`${hg.gate}: ${exports.GATE_NAMES[hg.gate]} | ${hg.center}`);
734
+ }
735
+ }
736
+ else {
737
+ lines.push('None');
738
+ }
739
+ lines.push('');
740
+ // Gates
741
+ lines.push('[GATES]');
742
+ const sortedGates = Array.from(chart.allGates.entries()).sort((a, b) => a[0] - b[0]);
743
+ for (const [gate, info] of sortedGates) {
744
+ const sourcesLabel = info.sources.length === 2 ? 'Both' : info.sources[0];
745
+ lines.push(`${gate}: ${exports.GATE_NAMES[gate]} | ${info.center} (${sourcesLabel})`);
746
+ }
747
+ lines.push('');
748
+ // Personality Activations
749
+ lines.push('[PERSONALITY ACTIVATIONS]');
750
+ const pSun = chart.personalityActivations.find(a => a.planet === 'Sun');
751
+ const pEarth = chart.personalityActivations.find(a => a.planet === 'Earth');
752
+ const pMoon = chart.personalityActivations.find(a => a.planet === 'Moon');
753
+ const pNorth = chart.personalityActivations.find(a => a.planet === 'North Node');
754
+ const pSouth = chart.personalityActivations.find(a => a.planet === 'South Node');
755
+ const pMercury = chart.personalityActivations.find(a => a.planet === 'Mercury');
756
+ const pVenus = chart.personalityActivations.find(a => a.planet === 'Venus');
757
+ const pMars = chart.personalityActivations.find(a => a.planet === 'Mars');
758
+ const pJupiter = chart.personalityActivations.find(a => a.planet === 'Jupiter');
759
+ const pSaturn = chart.personalityActivations.find(a => a.planet === 'Saturn');
760
+ const pUranus = chart.personalityActivations.find(a => a.planet === 'Uranus');
761
+ const pNeptune = chart.personalityActivations.find(a => a.planet === 'Neptune');
762
+ const pPluto = chart.personalityActivations.find(a => a.planet === 'Pluto');
763
+ lines.push(`Sun: ${pSun.gate}.${pSun.line} Earth: ${pEarth.gate}.${pEarth.line}`);
764
+ lines.push(`Moon: ${pMoon.gate}.${pMoon.line}`);
765
+ lines.push(`North Node: ${pNorth.gate}.${pNorth.line} South Node: ${pSouth.gate}.${pSouth.line}`);
766
+ lines.push(`Mercury: ${pMercury.gate}.${pMercury.line} Venus: ${pVenus.gate}.${pVenus.line} Mars: ${pMars.gate}.${pMars.line}`);
767
+ lines.push(`Jupiter: ${pJupiter.gate}.${pJupiter.line} Saturn: ${pSaturn.gate}.${pSaturn.line}`);
768
+ lines.push(`Uranus: ${pUranus.gate}.${pUranus.line} Neptune: ${pNeptune.gate}.${pNeptune.line} Pluto: ${pPluto.gate}.${pPluto.line}`);
769
+ lines.push('');
770
+ // Design Activations
771
+ lines.push('[DESIGN ACTIVATIONS]');
772
+ const dSun = chart.designActivations.find(a => a.planet === 'Sun');
773
+ const dEarth = chart.designActivations.find(a => a.planet === 'Earth');
774
+ const dMoon = chart.designActivations.find(a => a.planet === 'Moon');
775
+ const dNorth = chart.designActivations.find(a => a.planet === 'North Node');
776
+ const dSouth = chart.designActivations.find(a => a.planet === 'South Node');
777
+ const dMercury = chart.designActivations.find(a => a.planet === 'Mercury');
778
+ const dVenus = chart.designActivations.find(a => a.planet === 'Venus');
779
+ const dMars = chart.designActivations.find(a => a.planet === 'Mars');
780
+ const dJupiter = chart.designActivations.find(a => a.planet === 'Jupiter');
781
+ const dSaturn = chart.designActivations.find(a => a.planet === 'Saturn');
782
+ const dUranus = chart.designActivations.find(a => a.planet === 'Uranus');
783
+ const dNeptune = chart.designActivations.find(a => a.planet === 'Neptune');
784
+ const dPluto = chart.designActivations.find(a => a.planet === 'Pluto');
785
+ lines.push(`Sun: ${dSun.gate}.${dSun.line} Earth: ${dEarth.gate}.${dEarth.line}`);
786
+ lines.push(`Moon: ${dMoon.gate}.${dMoon.line}`);
787
+ lines.push(`North Node: ${dNorth.gate}.${dNorth.line} South Node: ${dSouth.gate}.${dSouth.line}`);
788
+ lines.push(`Mercury: ${dMercury.gate}.${dMercury.line} Venus: ${dVenus.gate}.${dVenus.line} Mars: ${dMars.gate}.${dMars.line}`);
789
+ lines.push(`Jupiter: ${dJupiter.gate}.${dJupiter.line} Saturn: ${dSaturn.gate}.${dSaturn.line}`);
790
+ lines.push(`Uranus: ${dUranus.gate}.${dUranus.line} Neptune: ${dNeptune.gate}.${dNeptune.line} Pluto: ${dPluto.gate}.${dPluto.line}`);
791
+ return lines.join('\n');
792
+ }
793
+ // ============================================================================
794
+ // PARTNERSHIP ANALYSIS FUNCTIONS
795
+ // ============================================================================
796
+ /**
797
+ * Analyze the relationship between two Human Design charts
798
+ */
799
+ function analyzePartnership(chart1, chart2) {
800
+ const electromagneticChannels = [];
801
+ const companionshipChannels = [];
802
+ const dominanceChannels = [];
803
+ const compromiseChannels = [];
804
+ // Get all gates for each person
805
+ const person1Gates = new Set(chart1.allGates.keys());
806
+ const person2Gates = new Set(chart2.allGates.keys());
807
+ // Get channels for each person
808
+ const person1ChannelGates = new Set();
809
+ for (const ch of chart1.activeChannels) {
810
+ person1ChannelGates.add(`${ch.gates[0]}-${ch.gates[1]}`);
811
+ }
812
+ const person2ChannelGates = new Set();
813
+ for (const ch of chart2.activeChannels) {
814
+ person2ChannelGates.add(`${ch.gates[0]}-${ch.gates[1]}`);
815
+ }
816
+ // Analyze each potential channel
817
+ for (const channel of exports.CHANNELS) {
818
+ const [gateA, gateB] = channel.gates;
819
+ const channelKey = `${gateA}-${gateB}`;
820
+ const p1HasGateA = person1Gates.has(gateA);
821
+ const p1HasGateB = person1Gates.has(gateB);
822
+ const p2HasGateA = person2Gates.has(gateA);
823
+ const p2HasGateB = person2Gates.has(gateB);
824
+ const p1HasChannel = p1HasGateA && p1HasGateB;
825
+ const p2HasChannel = p2HasGateA && p2HasGateB;
826
+ // Companionship: Both have the full channel
827
+ if (p1HasChannel && p2HasChannel) {
828
+ companionshipChannels.push({
829
+ channel,
830
+ type: 'companionship',
831
+ person1Gates: [gateA, gateB],
832
+ person2Gates: [gateA, gateB],
833
+ description: `Both ${chart1.name} and ${chart2.name} have the complete ${channel.name} channel - deep mutual understanding in this area`
834
+ });
835
+ }
836
+ // Electromagnetic: Together they complete a channel neither has alone
837
+ else if (!p1HasChannel && !p2HasChannel) {
838
+ // Check if together they form the channel
839
+ const togetherHasA = p1HasGateA || p2HasGateA;
840
+ const togetherHasB = p1HasGateB || p2HasGateB;
841
+ if (togetherHasA && togetherHasB) {
842
+ // One contributes gate A, other contributes gate B (or both contribute to both)
843
+ const p1Contributes = [];
844
+ const p2Contributes = [];
845
+ if (p1HasGateA)
846
+ p1Contributes.push(gateA);
847
+ if (p1HasGateB)
848
+ p1Contributes.push(gateB);
849
+ if (p2HasGateA)
850
+ p2Contributes.push(gateA);
851
+ if (p2HasGateB)
852
+ p2Contributes.push(gateB);
853
+ // True electromagnetic: each person contributes at least one unique gate
854
+ const p1Unique = p1Contributes.filter(g => !p2Contributes.includes(g));
855
+ const p2Unique = p2Contributes.filter(g => !p1Contributes.includes(g));
856
+ if (p1Unique.length > 0 && p2Unique.length > 0) {
857
+ electromagneticChannels.push({
858
+ channel,
859
+ type: 'electromagnetic',
860
+ person1Gates: p1Contributes,
861
+ person2Gates: p2Contributes,
862
+ description: `${chart1.name} brings gate ${p1Unique.join(', ')}, ${chart2.name} brings gate ${p2Unique.join(', ')} - magnetic attraction`
863
+ });
864
+ }
865
+ // Compromise: Both have hanging gates that connect
866
+ else if (p1Contributes.length > 0 && p2Contributes.length > 0) {
867
+ compromiseChannels.push({
868
+ channel,
869
+ type: 'compromise',
870
+ person1Gates: p1Contributes,
871
+ person2Gates: p2Contributes,
872
+ description: `Both contribute to the ${channel.name} channel but share gate(s) - requires compromise`
873
+ });
874
+ }
875
+ }
876
+ }
877
+ // Dominance: One has full channel, other has hanging gate(s)
878
+ else if (p1HasChannel && !p2HasChannel && (p2HasGateA || p2HasGateB)) {
879
+ const p2Gates = [];
880
+ if (p2HasGateA)
881
+ p2Gates.push(gateA);
882
+ if (p2HasGateB)
883
+ p2Gates.push(gateB);
884
+ dominanceChannels.push({
885
+ channel,
886
+ type: 'dominance',
887
+ person1Gates: [gateA, gateB],
888
+ person2Gates: p2Gates,
889
+ description: `${chart1.name} has the complete ${channel.name} channel and leads; ${chart2.name} has gate ${p2Gates.join(', ')}`
890
+ });
891
+ }
892
+ else if (p2HasChannel && !p1HasChannel && (p1HasGateA || p1HasGateB)) {
893
+ const p1Gates = [];
894
+ if (p1HasGateA)
895
+ p1Gates.push(gateA);
896
+ if (p1HasGateB)
897
+ p1Gates.push(gateB);
898
+ dominanceChannels.push({
899
+ channel,
900
+ type: 'dominance',
901
+ person1Gates: p1Gates,
902
+ person2Gates: [gateA, gateB],
903
+ description: `${chart2.name} has the complete ${channel.name} channel and leads; ${chart1.name} has gate ${p1Gates.join(', ')}`
904
+ });
905
+ }
906
+ }
907
+ // Calculate composite chart (combined gates and channels)
908
+ const compositeGates = new Map();
909
+ for (const [gate, info] of chart1.allGates) {
910
+ compositeGates.set(gate, { center: info.center, sources: [chart1.name] });
911
+ }
912
+ for (const [gate, info] of chart2.allGates) {
913
+ const existing = compositeGates.get(gate);
914
+ if (existing) {
915
+ existing.sources.push(chart2.name);
916
+ }
917
+ else {
918
+ compositeGates.set(gate, { center: info.center, sources: [chart2.name] });
919
+ }
920
+ }
921
+ const compositeChannels = getActiveChannels(compositeGates);
922
+ const compositeCenterStatus = getCenterStatus(compositeChannels, compositeGates);
923
+ const compositeType = calculateType(compositeCenterStatus.defined, compositeChannels);
924
+ const compositeStrategy = exports.STRATEGY_BY_TYPE[compositeType];
925
+ const compositeDefinition = calculateDefinition(compositeChannels, compositeCenterStatus.defined);
926
+ const compositeDefinitionIslands = getDefinitionIslands(compositeChannels, compositeCenterStatus.defined);
927
+ // Calculate channel breakdown
928
+ const person1ChannelKeys = new Set(chart1.activeChannels.map(ch => `${ch.gates[0]}-${ch.gates[1]}`));
929
+ const person2ChannelKeys = new Set(chart2.activeChannels.map(ch => `${ch.gates[0]}-${ch.gates[1]}`));
930
+ const channelsPerson1Only = [];
931
+ const channelsPerson2Only = [];
932
+ const channelsOnlyTogether = [];
933
+ for (const channel of compositeChannels) {
934
+ const key = `${channel.gates[0]}-${channel.gates[1]}`;
935
+ const p1Has = person1ChannelKeys.has(key);
936
+ const p2Has = person2ChannelKeys.has(key);
937
+ if (p1Has && !p2Has) {
938
+ channelsPerson1Only.push(channel);
939
+ }
940
+ else if (p2Has && !p1Has) {
941
+ channelsPerson2Only.push(channel);
942
+ }
943
+ else if (!p1Has && !p2Has) {
944
+ // Neither has it individually - only together
945
+ channelsOnlyTogether.push(channel);
946
+ }
947
+ // If both have it (companionship), it's not unique to anyone
948
+ }
949
+ // Categorize gates
950
+ const sharedGates = [];
951
+ const uniqueGatesPerson1 = [];
952
+ const uniqueGatesPerson2 = [];
953
+ for (const [gate, info] of compositeGates) {
954
+ const gateConnection = {
955
+ gate,
956
+ gateName: exports.GATE_NAMES[gate],
957
+ center: info.center,
958
+ type: info.sources.length === 2 ? 'shared' :
959
+ info.sources[0] === chart1.name ? 'unique_person1' : 'unique_person2'
960
+ };
961
+ if (gateConnection.type === 'shared') {
962
+ sharedGates.push(gateConnection);
963
+ }
964
+ else if (gateConnection.type === 'unique_person1') {
965
+ uniqueGatesPerson1.push(gateConnection);
966
+ }
967
+ else {
968
+ uniqueGatesPerson2.push(gateConnection);
969
+ }
970
+ }
971
+ return {
972
+ person1: chart1,
973
+ person2: chart2,
974
+ electromagneticChannels,
975
+ companionshipChannels,
976
+ dominanceChannels,
977
+ compromiseChannels,
978
+ compositeDefinedCenters: compositeCenterStatus.defined,
979
+ compositeUndefinedCenters: compositeCenterStatus.undefined,
980
+ compositeOpenCenters: compositeCenterStatus.open,
981
+ compositeChannels,
982
+ compositeType,
983
+ compositeStrategy,
984
+ compositeDefinition,
985
+ compositeDefinitionIslands,
986
+ channelsPerson1Only,
987
+ channelsPerson2Only,
988
+ channelsOnlyTogether,
989
+ sharedGates,
990
+ uniqueGatesPerson1,
991
+ uniqueGatesPerson2
992
+ };
993
+ }
994
+ /**
995
+ * Build a HumanDesignChart object from API response
996
+ */
997
+ function buildChart(apiResponse, options = {}) {
998
+ const { personality, design } = apiResponse;
999
+ const personalityActivations = calculateActivations(personality.planets);
1000
+ const designActivations = calculateActivations(design.planets);
1001
+ const allGates = getAllGates(personalityActivations, designActivations);
1002
+ const activeChannels = getActiveChannels(allGates);
1003
+ const { defined, undefined: undefinedCenters, open } = getCenterStatus(activeChannels, allGates);
1004
+ const type = calculateType(defined, activeChannels);
1005
+ const strategy = exports.STRATEGY_BY_TYPE[type];
1006
+ const authority = calculateAuthority(defined, activeChannels);
1007
+ const definition = calculateDefinition(activeChannels, defined);
1008
+ const definitionIslands = getDefinitionIslands(activeChannels, defined);
1009
+ const hangingGates = getHangingGates(allGates, activeChannels);
1010
+ const personalitySun = personalityActivations.find(a => a.planet === 'Sun');
1011
+ const designSun = designActivations.find(a => a.planet === 'Sun');
1012
+ const profile = calculateProfile(personalitySun.line, designSun.line);
1013
+ const profileName = exports.PROFILE_NAMES[profile] || '';
1014
+ const personalityEarth = personalityActivations.find(a => a.planet === 'Earth');
1015
+ const designEarth = designActivations.find(a => a.planet === 'Earth');
1016
+ const incarnationCross = getIncarnationCross(personalitySun.gate, personalityEarth.gate, designSun.gate, designEarth.gate, personalitySun.line, designSun.line);
1017
+ return {
1018
+ name: options.name || 'Chart',
1019
+ location: options.location || `${personality.location.latitude}, ${personality.location.longitude}`,
1020
+ date: personality.date,
1021
+ time: personality.time,
1022
+ type,
1023
+ strategy,
1024
+ authority,
1025
+ definition,
1026
+ definitionIslands,
1027
+ profile,
1028
+ profileName,
1029
+ incarnationCross,
1030
+ definedCenters: defined,
1031
+ undefinedCenters,
1032
+ openCenters: open,
1033
+ activeChannels,
1034
+ hangingGates,
1035
+ allGates,
1036
+ personalityActivations,
1037
+ designActivations
1038
+ };
1039
+ }
1040
+ /**
1041
+ * Main entry point for partnership analysis: Convert two Human Design API responses to formatted text
1042
+ */
1043
+ function humandesignPartnership2txt(apiResponse1, apiResponse2, options = {}) {
1044
+ const chart1 = buildChart(apiResponse1, {
1045
+ name: options.person1Name || 'Person 1',
1046
+ location: options.person1Location
1047
+ });
1048
+ const chart2 = buildChart(apiResponse2, {
1049
+ name: options.person2Name || 'Person 2',
1050
+ location: options.person2Location
1051
+ });
1052
+ const partnership = analyzePartnership(chart1, chart2);
1053
+ return formatPartnershipToText(partnership);
1054
+ }
1055
+ // ============================================================================
1056
+ // PARTNERSHIP TEXT FORMATTER
1057
+ // ============================================================================
1058
+ function formatPartnershipToText(partnership) {
1059
+ const lines = [];
1060
+ const { person1, person2 } = partnership;
1061
+ // Metadata
1062
+ lines.push('[METADATA]');
1063
+ lines.push('chart_type: human_design_partnership');
1064
+ lines.push('');
1065
+ // Individual Chart Summaries
1066
+ lines.push(`[CHART: ${person1.name}]`);
1067
+ lines.push(`[BIRTHDATA] ${person1.location} | ${person1.date} | ${person1.time}`);
1068
+ lines.push(`Type: ${person1.type} | Strategy: ${person1.strategy} | Authority: ${person1.authority}`);
1069
+ lines.push(`Definition: ${person1.definition} | Profile: ${person1.profile}${person1.profileName ? ` (${person1.profileName})` : ''}`);
1070
+ if (person1.definitionIslands.length > 1) {
1071
+ const islandStrs = person1.definitionIslands.map(island => `[${island.join('+')}]`);
1072
+ lines.push(`Definition Islands: ${islandStrs.join(' + ')}`);
1073
+ }
1074
+ lines.push(`Defined Centers: ${person1.definedCenters.join(', ') || 'None'}`);
1075
+ if (person1.hangingGates.length > 0) {
1076
+ const hangingStr = person1.hangingGates.map(hg => `${hg.gate} (${hg.center})`).join(', ');
1077
+ lines.push(`Hanging Gates: ${hangingStr}`);
1078
+ }
1079
+ lines.push('');
1080
+ lines.push(`[CHART: ${person2.name}]`);
1081
+ lines.push(`[BIRTHDATA] ${person2.location} | ${person2.date} | ${person2.time}`);
1082
+ lines.push(`Type: ${person2.type} | Strategy: ${person2.strategy} | Authority: ${person2.authority}`);
1083
+ lines.push(`Definition: ${person2.definition} | Profile: ${person2.profile}${person2.profileName ? ` (${person2.profileName})` : ''}`);
1084
+ if (person2.definitionIslands.length > 1) {
1085
+ const islandStrs = person2.definitionIslands.map(island => `[${island.join('+')}]`);
1086
+ lines.push(`Definition Islands: ${islandStrs.join(' + ')}`);
1087
+ }
1088
+ lines.push(`Defined Centers: ${person2.definedCenters.join(', ') || 'None'}`);
1089
+ if (person2.hangingGates.length > 0) {
1090
+ const hangingStr = person2.hangingGates.map(hg => `${hg.gate} (${hg.center})`).join(', ');
1091
+ lines.push(`Hanging Gates: ${hangingStr}`);
1092
+ }
1093
+ lines.push('');
1094
+ // Partnership Analysis Header
1095
+ lines.push(`[PARTNERSHIP: ${person1.name} & ${person2.name}]`);
1096
+ lines.push('');
1097
+ // Composite Overview
1098
+ lines.push('[COMPOSITE OVERVIEW]');
1099
+ lines.push(`Composite Type: ${partnership.compositeType}`);
1100
+ lines.push(`Composite Strategy: ${partnership.compositeStrategy}`);
1101
+ lines.push(`Composite Definition: ${partnership.compositeDefinition}`);
1102
+ if (partnership.compositeDefinitionIslands.length > 1) {
1103
+ const islandStrs = partnership.compositeDefinitionIslands.map(island => `[${island.join('+')}]`);
1104
+ lines.push(`Composite Definition Islands: ${islandStrs.join(' + ')}`);
1105
+ }
1106
+ if (partnership.compositeDefinedCenters.length > 0) {
1107
+ lines.push(`Defined Centers Together: ${partnership.compositeDefinedCenters.join(', ')}`);
1108
+ }
1109
+ if (partnership.compositeUndefinedCenters.length > 0) {
1110
+ lines.push(`Undefined Centers Together: ${partnership.compositeUndefinedCenters.join(', ')}`);
1111
+ }
1112
+ if (partnership.compositeOpenCenters.length > 0) {
1113
+ lines.push(`Open Centers Together: ${partnership.compositeOpenCenters.join(', ')}`);
1114
+ }
1115
+ lines.push('');
1116
+ // Electromagnetic Connections (Attraction)
1117
+ lines.push('[ELECTROMAGNETIC CONNECTIONS]');
1118
+ lines.push('(Channels completed together - magnetic attraction and chemistry)');
1119
+ if (partnership.electromagneticChannels.length > 0) {
1120
+ for (const conn of partnership.electromagneticChannels) {
1121
+ lines.push(`${conn.channel.gates[0]}-${conn.channel.gates[1]} (${conn.channel.name}): ${conn.channel.centers[0]} ↔ ${conn.channel.centers[1]}`);
1122
+ lines.push(` ${conn.description}`);
1123
+ }
1124
+ }
1125
+ else {
1126
+ lines.push('None');
1127
+ }
1128
+ lines.push('');
1129
+ // Companionship Connections (Understanding)
1130
+ lines.push('[COMPANIONSHIP CONNECTIONS]');
1131
+ lines.push('(Channels both have individually - deep mutual understanding)');
1132
+ if (partnership.companionshipChannels.length > 0) {
1133
+ for (const conn of partnership.companionshipChannels) {
1134
+ lines.push(`${conn.channel.gates[0]}-${conn.channel.gates[1]} (${conn.channel.name}): ${conn.channel.centers[0]} ↔ ${conn.channel.centers[1]}`);
1135
+ lines.push(` ${conn.description}`);
1136
+ }
1137
+ }
1138
+ else {
1139
+ lines.push('None');
1140
+ }
1141
+ lines.push('');
1142
+ // Dominance Connections (Leadership)
1143
+ lines.push('[DOMINANCE CONNECTIONS]');
1144
+ lines.push('(One person has full channel, the other has hanging gate - one naturally leads)');
1145
+ if (partnership.dominanceChannels.length > 0) {
1146
+ for (const conn of partnership.dominanceChannels) {
1147
+ lines.push(`${conn.channel.gates[0]}-${conn.channel.gates[1]} (${conn.channel.name}): ${conn.channel.centers[0]} ↔ ${conn.channel.centers[1]}`);
1148
+ lines.push(` ${conn.description}`);
1149
+ }
1150
+ }
1151
+ else {
1152
+ lines.push('None');
1153
+ }
1154
+ lines.push('');
1155
+ // Compromise Connections
1156
+ lines.push('[COMPROMISE CONNECTIONS]');
1157
+ lines.push('(Both have hanging gates in same channel - requires negotiation)');
1158
+ if (partnership.compromiseChannels.length > 0) {
1159
+ for (const conn of partnership.compromiseChannels) {
1160
+ lines.push(`${conn.channel.gates[0]}-${conn.channel.gates[1]} (${conn.channel.name}): ${conn.channel.centers[0]} ↔ ${conn.channel.centers[1]}`);
1161
+ lines.push(` ${conn.description}`);
1162
+ }
1163
+ }
1164
+ else {
1165
+ lines.push('None');
1166
+ }
1167
+ lines.push('');
1168
+ // Shared Gates
1169
+ lines.push('[SHARED GATES]');
1170
+ lines.push('(Gates both people have - common ground)');
1171
+ if (partnership.sharedGates.length > 0) {
1172
+ const sortedShared = [...partnership.sharedGates].sort((a, b) => a.gate - b.gate);
1173
+ for (const g of sortedShared) {
1174
+ lines.push(`${g.gate}: ${g.gateName} | ${g.center}`);
1175
+ }
1176
+ }
1177
+ else {
1178
+ lines.push('None');
1179
+ }
1180
+ lines.push('');
1181
+ // Unique Gates
1182
+ lines.push(`[UNIQUE GATES: ${person1.name}]`);
1183
+ lines.push(`(Gates only ${person1.name} brings to the relationship)`);
1184
+ if (partnership.uniqueGatesPerson1.length > 0) {
1185
+ const sorted = [...partnership.uniqueGatesPerson1].sort((a, b) => a.gate - b.gate);
1186
+ for (const g of sorted) {
1187
+ lines.push(`${g.gate}: ${g.gateName} | ${g.center}`);
1188
+ }
1189
+ }
1190
+ else {
1191
+ lines.push('None');
1192
+ }
1193
+ lines.push('');
1194
+ lines.push(`[UNIQUE GATES: ${person2.name}]`);
1195
+ lines.push(`(Gates only ${person2.name} brings to the relationship)`);
1196
+ if (partnership.uniqueGatesPerson2.length > 0) {
1197
+ const sorted = [...partnership.uniqueGatesPerson2].sort((a, b) => a.gate - b.gate);
1198
+ for (const g of sorted) {
1199
+ lines.push(`${g.gate}: ${g.gateName} | ${g.center}`);
1200
+ }
1201
+ }
1202
+ else {
1203
+ lines.push('None');
1204
+ }
1205
+ lines.push('');
1206
+ // Channel Breakdown
1207
+ lines.push(`[CHANNELS: ${person1.name} Only]`);
1208
+ lines.push(`(Channels only ${person1.name} has)`);
1209
+ if (partnership.channelsPerson1Only.length > 0) {
1210
+ for (const channel of partnership.channelsPerson1Only) {
1211
+ lines.push(`${channel.gates[0]}-${channel.gates[1]} (${channel.name}): ${channel.centers[0]} ↔ ${channel.centers[1]}`);
1212
+ }
1213
+ }
1214
+ else {
1215
+ lines.push('None');
1216
+ }
1217
+ lines.push('');
1218
+ lines.push(`[CHANNELS: ${person2.name} Only]`);
1219
+ lines.push(`(Channels only ${person2.name} has)`);
1220
+ if (partnership.channelsPerson2Only.length > 0) {
1221
+ for (const channel of partnership.channelsPerson2Only) {
1222
+ lines.push(`${channel.gates[0]}-${channel.gates[1]} (${channel.name}): ${channel.centers[0]} ↔ ${channel.centers[1]}`);
1223
+ }
1224
+ }
1225
+ else {
1226
+ lines.push('None');
1227
+ }
1228
+ lines.push('');
1229
+ lines.push('[CHANNELS: Only Together]');
1230
+ lines.push('(Channels formed only when both are together - neither has individually)');
1231
+ if (partnership.channelsOnlyTogether.length > 0) {
1232
+ for (const channel of partnership.channelsOnlyTogether) {
1233
+ lines.push(`${channel.gates[0]}-${channel.gates[1]} (${channel.name}): ${channel.centers[0]} ↔ ${channel.centers[1]}`);
1234
+ }
1235
+ }
1236
+ else {
1237
+ lines.push('None');
1238
+ }
1239
+ lines.push('');
1240
+ // Composite Channels (all channels when together)
1241
+ lines.push('[COMPOSITE CHANNELS]');
1242
+ lines.push('(All channels active when together)');
1243
+ if (partnership.compositeChannels.length > 0) {
1244
+ for (const channel of partnership.compositeChannels) {
1245
+ lines.push(`${channel.gates[0]}-${channel.gates[1]} (${channel.name}): ${channel.centers[0]} ↔ ${channel.centers[1]}`);
1246
+ }
1247
+ }
1248
+ else {
1249
+ lines.push('None');
1250
+ }
1251
+ return lines.join('\n');
1252
+ }
1253
+ exports.default = humandesign2txt;