dsh-metaphysics 0.57.0 → 0.59.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.
- package/CHANGELOG.md +171 -0
- package/README.md +415 -372
- package/client.almanac.js +60 -58
- package/client.bazi.js +113 -103
- package/client.chenggu.js +60 -58
- package/client.convert.js +60 -58
- package/client.daliuren.js +121 -102
- package/client.fengshui.js +59 -57
- package/client.hepan.js +112 -102
- package/client.js +412 -288
- package/client.liuyao.js +121 -102
- package/client.mbti.js +60 -58
- package/client.name.js +46 -44
- package/client.numerology.js +60 -58
- package/client.qimen.js +119 -100
- package/client.star.js +60 -58
- package/client.tarot.js +113 -94
- package/client.trend.js +109 -99
- package/client.xiaoliuren.js +67 -56
- package/client.zeri.js +60 -58
- package/client.ziwei.js +113 -103
- package/engine/bridge.js +211 -0
- package/engine/citations.js +109 -6
- package/engine/diff.js +54 -0
- package/engine/event-prompt.js +238 -0
- package/engine/guide.js +53 -0
- package/engine/hepan.js +43 -0
- package/engine/index.js +1736 -1659
- package/engine/interpret-knowledge.js +20 -8
- package/engine/interpret.js +1904 -1889
- package/engine/limits.js +118 -0
- package/engine/liuyao.js +4 -0
- package/engine/qimen-prompt.js +303 -0
- package/engine/trace.js +18 -18
- package/engine/yingqi.js +208 -0
- package/engine/zeri.js +89 -13
- package/index.js +18 -2
- package/package.json +7 -2
- package//345/221/275/347/220/206/346/216/222/347/233/230.html +11035 -9304
package/engine/index.js
CHANGED
|
@@ -1,1659 +1,1736 @@
|
|
|
1
|
-
// 统一入口:解析参数、分派到各体系、生成可读摘要
|
|
2
|
-
import { buildBazi } from './bazi.js';
|
|
3
|
-
import { buildLiuyao, tossLines, bodyLabels, yongshenOf, yongshenStrength, fushenAccess, hexagramStrength, jinTuiPower, yingqiWindow } from './liuyao.js';
|
|
4
|
-
import { buildZiwei, BRIGHTNESS, flowOf, asOfParts, ZIWEI_SCHOOL_DEFAULTS } from './ziwei.js';
|
|
5
|
-
import { drawTarot } from './tarot.js';
|
|
6
|
-
import { buildMeihua } from './meihua.js';
|
|
7
|
-
import { buildDaliuren } from './daliuren.js';
|
|
8
|
-
import { buildHepan } from './hepan.js';
|
|
9
|
-
import { buildZiweiHepan } from './ziwei-hepan.js';
|
|
10
|
-
import { buildQimen, buildSnapshot } from './qimen.js';
|
|
11
|
-
import { geocodeAddress, resolveAddress } from './geo.js';
|
|
12
|
-
import { toJDN, ganzhiName, dayGanzhiIndex } from './calendar.js';
|
|
13
|
-
import { randomOf, isSeed } from './random.js';
|
|
14
|
-
import { encodeHandle, decodeHandle, resolveHandle } from './handle.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
*
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
'
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
+
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
: ''
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
: ''
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
(r.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
(() => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
: '
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
rows.push(`
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
+ (l.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
: '
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
r.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
`※
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
return [
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
...(
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
];
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
if (r.
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
//
|
|
422
|
-
const
|
|
423
|
-
if (
|
|
424
|
-
const
|
|
425
|
-
?
|
|
426
|
-
: '
|
|
427
|
-
out.push(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
if (
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if (
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if (system === '
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
if (system === '
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
//
|
|
827
|
-
if (args.
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
}
|
|
857
|
-
//
|
|
858
|
-
if (
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
if (i.
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
return { ...result, precision: dt.precision, summary:
|
|
1021
|
-
}
|
|
1022
|
-
if (system === '
|
|
1023
|
-
const dt = parseDateTime(args.datetime);
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
: args.
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
});
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
let
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
if (
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
//
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
if (
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
}
|
|
1230
|
-
const
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
const
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
].join('
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
'
|
|
1406
|
-
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
const
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
const
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
export
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1
|
+
// 统一入口:解析参数、分派到各体系、生成可读摘要
|
|
2
|
+
import { buildBazi } from './bazi.js';
|
|
3
|
+
import { buildLiuyao, tossLines, bodyLabels, yongshenOf, yongshenStrength, fushenAccess, hexagramStrength, jinTuiPower, yingqiWindow } from './liuyao.js';
|
|
4
|
+
import { buildZiwei, BRIGHTNESS, flowOf, asOfParts, ZIWEI_SCHOOL_DEFAULTS } from './ziwei.js';
|
|
5
|
+
import { drawTarot } from './tarot.js';
|
|
6
|
+
import { buildMeihua } from './meihua.js';
|
|
7
|
+
import { buildDaliuren } from './daliuren.js';
|
|
8
|
+
import { buildHepan, tripleOf } from './hepan.js';
|
|
9
|
+
import { buildZiweiHepan } from './ziwei-hepan.js';
|
|
10
|
+
import { buildQimen, buildSnapshot } from './qimen.js';
|
|
11
|
+
import { geocodeAddress, resolveAddress } from './geo.js';
|
|
12
|
+
import { toJDN, ganzhiName, dayGanzhiIndex } from './calendar.js';
|
|
13
|
+
import { randomOf, isSeed } from './random.js';
|
|
14
|
+
import { encodeHandle, decodeHandle, resolveHandle } from './handle.js';
|
|
15
|
+
import { askLink, parseAskLink, formOf, restNote, askPackages } from './bridge.js';
|
|
16
|
+
import { parseRevision, displayValue } from './revise.js';
|
|
17
|
+
import { interpret, DIRECTIONS, DIRECTION_DEFAULT } from './interpret.js';
|
|
18
|
+
import { followup, askPrompt } from './followup.js';
|
|
19
|
+
import { crossCheck, crossText, crossSection } from './crosscheck.js';
|
|
20
|
+
import { pastSignals, verifyFit } from './verify.js';
|
|
21
|
+
import { rectifyBirth } from './rectify.js';
|
|
22
|
+
import { buildAlmanac } from './almanac.js';
|
|
23
|
+
import { buildZeri } from './zeri.js';
|
|
24
|
+
import { buildChenggu } from './chenggu.js';
|
|
25
|
+
import { buildXiaoliuren } from './xiaoliuren.js';
|
|
26
|
+
import { buildStar } from './star.js';
|
|
27
|
+
import { buildNumerology } from './numerology.js';
|
|
28
|
+
import { buildMbti, mbtiPaper } from './mbti.js';
|
|
29
|
+
import { buildConvert } from './convert.js';
|
|
30
|
+
import { classicsFor, classicsText, suiyunClassicOf, classicOf, CLASSIC_BOOKS } from './classics.js';
|
|
31
|
+
import { diffObjects, summarize, sortDiffs, labelOf, rootOf, categoryOf, formatValue, CALIBER_SWITCHES, viewRows } from './diff.js';
|
|
32
|
+
import { canonicalJson, fingerprintOf } from './fingerprint.js';
|
|
33
|
+
import { buildTrend } from './trend.js';
|
|
34
|
+
import { buildFengshui } from './fengshui.js';
|
|
35
|
+
import { buildName } from './name.js';
|
|
36
|
+
import { buildTrace, traceMarkdown, SYSTEM_LABEL } from './trace.js';
|
|
37
|
+
import { fail } from './fail.js';
|
|
38
|
+
import { assertCivil } from './validate.js';
|
|
39
|
+
|
|
40
|
+
export { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf };
|
|
41
|
+
|
|
42
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
43
|
+
|
|
44
|
+
const GENDERS = ['male', 'female'];
|
|
45
|
+
|
|
46
|
+
function daysInMonth(year, month) {
|
|
47
|
+
if (month === 2) {
|
|
48
|
+
const leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
49
|
+
return leap ? 29 : 28;
|
|
50
|
+
}
|
|
51
|
+
return [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 性别是必填项:它决定大运/大限的顺逆,缺省按某一方排等于给出一张错盘。
|
|
56
|
+
* 之前用 `=== 'female' ? 'female' : 'male'` 兜底,静默把未填性别的人按男命排。
|
|
57
|
+
*/
|
|
58
|
+
function requireGender(value, who = '') {
|
|
59
|
+
if (GENDERS.includes(value)) return value;
|
|
60
|
+
const got = value === undefined || value === null || value === '' ? '(未提供)' : `「${value}」`;
|
|
61
|
+
throw fail('E_INPUT', `${who ? who + '的' : ''}gender 必须是 male 或 female${got};性别决定大运/大限顺逆,不能靠默认值`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ZIWEI_SCHOOL_KEYS = [
|
|
65
|
+
'brightnessSchool', 'gengYearSihua', 'tianmaSchool', 'tiankongSchool', 'jiekongSchool',
|
|
66
|
+
'kuiyueSchool', 'tianshiTianshang', 'changshengSchool', 'lateZishi', 'shenzhuSchool',
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
/** 紫微流派参数原样透传;非法值交给引擎按默认口径回退 */
|
|
70
|
+
function ziweiSchoolArgs(args) {
|
|
71
|
+
const out = {};
|
|
72
|
+
for (const key of ZIWEI_SCHOOL_KEYS) {
|
|
73
|
+
if (args[key] !== undefined) out[key] = args[key];
|
|
74
|
+
}
|
|
75
|
+
if (args.schools && typeof args.schools === 'object') out.schools = args.schools;
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 解析 'YYYY-MM-DDTHH:mm' / 'YYYY-MM-DD HH:mm' / 'YYYY-MM-DD' */
|
|
80
|
+
export function parseDateTime(text) {
|
|
81
|
+
if (typeof text !== 'string' || !text.trim()) {
|
|
82
|
+
throw fail('E_INPUT', '缺少 datetime(公历日期时间),例如 1990-05-12T14:30');
|
|
83
|
+
}
|
|
84
|
+
const m = text.trim().match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T\s](\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?$/);
|
|
85
|
+
if (!m) throw fail('E_INPUT', `无法解析 datetime:「${text}」,请用 1990-05-12T14:30 这种格式`);
|
|
86
|
+
const out = {
|
|
87
|
+
year: Number(m[1]), month: Number(m[2]), day: Number(m[3]),
|
|
88
|
+
hour: Number(m[4] ?? 0), minute: Number(m[5] ?? 0),
|
|
89
|
+
};
|
|
90
|
+
if (out.month < 1 || out.month > 12) throw fail('E_INPUT', `月份超出范围:${out.month}`);
|
|
91
|
+
// 只校验 1..31 会让 1990-02-30、2026-04-31 这类日期静默滚到下个月,整盘错位且无人察觉
|
|
92
|
+
const maxDay = daysInMonth(out.year, out.month);
|
|
93
|
+
if (out.day < 1 || out.day > maxDay) {
|
|
94
|
+
throw fail('E_INPUT', `日期不存在:${out.year}-${out.month}-${out.day}(${out.year} 年 ${out.month} 月只有 ${maxDay} 天)`);
|
|
95
|
+
}
|
|
96
|
+
if (out.hour > 23 || out.minute > 59) throw fail('E_INPUT', `时间超出范围:${out.hour}:${out.minute}`);
|
|
97
|
+
// ΔT(地球自转改正)表只覆盖 1900–2100,超出后按端点值外推,精度不可保证
|
|
98
|
+
if (out.year < 1600 || out.year > 2400) {
|
|
99
|
+
throw fail('E_RANGE', `年份 ${out.year} 超出可计算范围(1600–2400)`);
|
|
100
|
+
}
|
|
101
|
+
if (out.year < 1900 || out.year > 2100) {
|
|
102
|
+
out.precision = {
|
|
103
|
+
level: 'approx',
|
|
104
|
+
note: `ΔT 模型仅覆盖 1900–2100,${out.year} 年按端点值外推,节气与朔望时刻可能有分钟级偏差,月柱在交节前后需复核`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function baziText(r) {
|
|
111
|
+
const P = r.pillars;
|
|
112
|
+
const line = (label, p) =>
|
|
113
|
+
`${label}:${p.ganzhi} ${p.stemTenGod ? `十神 ${p.stemTenGod} ` : ''}藏干 ${p.hidden.map((h) => `${h.stem}(${h.tenGod})`).join(' ')} 纳音 ${p.nayin}`
|
|
114
|
+
+ ` 地势 ${p.dishi} 自坐 ${p.zizuo} 空亡 ${p.kongwang.join('')}`
|
|
115
|
+
+ (p.shensha && p.shensha.length ? ` 神煞 ${p.shensha.join('、')}` : '');
|
|
116
|
+
const wuxing = Object.entries(r.wuxing).map(([k, v]) => `${k}${v}`).join(' ');
|
|
117
|
+
const palaces = r.palaces
|
|
118
|
+
? `【胎元】${r.palaces.taiyuan.ganzhi} 胎息 ${r.palaces.taixi ? r.palaces.taixi.ganzhi : '—'} 命宫 ${r.palaces.minggong.ganzhi} 身宫 ${r.palaces.shengong.ganzhi}`
|
|
119
|
+
: '';
|
|
120
|
+
const dayun = r.dayun.list.slice(0, 8)
|
|
121
|
+
.map((d) => `${d.startAge}岁 ${d.ganzhi}(${d.stemTenGod})${d.luck ? `·${d.luck.tier}` : ''}${d.relations && d.relations.length ? `[${d.relations.join('')}]` : ''}`).join(' ');
|
|
122
|
+
// 交运时刻(日/时辰级):旧结果没有 dayun.jiao,缺省就只说「起运」,不硬凑
|
|
123
|
+
const jiao = r.dayun.jiao && r.dayun.jiao.at
|
|
124
|
+
? `;交运 ${r.dayun.jiao.at}(${r.dayun.jiao.frame},距${r.dayun.jiao.from.name} ${r.dayun.jiao.spanDays} 天,三日折一年)`
|
|
125
|
+
: '';
|
|
126
|
+
// 阈值 20 分钟:与 NAOJ 历要项对拍实测,本引擎节气最多偏早 13.5 分钟(Meeus 低精度上限约 14 分钟)
|
|
127
|
+
const near = r.jieqi.nearBoundaryMinutes < 20
|
|
128
|
+
? `\n⚠ 出生时刻距「节」仅 ${r.jieqi.nearBoundaryMinutes} 分钟,而节气时刻本身有最多约 14 分钟的偏差,月柱与年柱建议人工复核。`
|
|
129
|
+
: '';
|
|
130
|
+
const SHORT = { year: '年', month: '月', day: '日', hour: '时' };
|
|
131
|
+
const shenshaLine = r.shensha && r.shensha.groups && r.shensha.groups.length
|
|
132
|
+
? `神煞(${r.shensha.count} 见):` + r.shensha.groups.map((g) =>
|
|
133
|
+
`${g.cat}|` + g.items.map((i) => `${i.name}(${i.pillars.map((k) => SHORT[k]).join('')})`).join('、')).join(';')
|
|
134
|
+
: '神煞:—';
|
|
135
|
+
const ry = r.renyuan;
|
|
136
|
+
const rySrc = classicOf('smt-renyuan');
|
|
137
|
+
const renyuanLine = ry
|
|
138
|
+
? `人元:${ry.month}月第${ry.day}日,${ry.active}${ry.activeElement}司权(${ry.segments.map((s) => `${s.stem}${s.from}–${s.to}`).join(' ')})${ry.beyond ? ';已过 30 日表,按本气算' : ''}${rySrc ? `|原文见《${CLASSIC_BOOKS[rySrc.book].name}·${rySrc.chapter}》` : ''}`
|
|
139
|
+
: '';
|
|
140
|
+
const ysd = (r.frame && r.frame.yongshen && r.frame.yongshen.disputed) || [];
|
|
141
|
+
const disText = ysd.length
|
|
142
|
+
? ` 有争议:${ysd.map((d) => `${d.element}(${d.favorRole};${d.climate}盘调候忌${d.element})`).join('、')}`
|
|
143
|
+
: '';
|
|
144
|
+
const disEls = new Set(ysd.map((d) => d.element));
|
|
145
|
+
const ysn = (r.frame && r.frame.yongshen && r.frame.yongshen.neutral) || [];
|
|
146
|
+
const neuText = ysn.length
|
|
147
|
+
? ` 中性偏忌:${ysn.map((n) => `${n.element} ${n.weight} 分`).join('、')}(近零,不作实忌)`
|
|
148
|
+
: '';
|
|
149
|
+
const gejuLine = r.frame ? [
|
|
150
|
+
`格局:${r.frame.primary ? `${r.frame.primary.name}(${r.frame.primary.evidence})` : '—'}`,
|
|
151
|
+
` ${r.frame.geju.map((g) => `${g.name}[${g.kind}${g.status ? `·${g.status}` : ''}]`).join(' ')}`,
|
|
152
|
+
`用神:喜${r.frame.yongshen.favor.join('')}(${r.frame.yongshen.favorTenGods.join('、')}) 忌${r.frame.yongshen.avoid.join('')}(${r.frame.yongshen.avoidTenGods.join('、')})${disText}${neuText} ${r.frame.yongshen.method}`,
|
|
153
|
+
` ${r.frame.yongshen.detail}`,
|
|
154
|
+
`寒暖:${r.climate ? `${r.climate.label}(${(r.climate.evidence || []).join('、')})${r.climate.need ? ` 调候取${r.climate.need.stem}${r.climate.need.element} 忌${r.climate.avoidElement}${disEls.has(r.climate.avoidElement) ? '(有争议)' : ''}` : ''}` : '—'}` +
|
|
155
|
+
(r.frame.yongshen.qt ? ` 穷通:${r.frame.yongshen.qt.month}${r.frame.yongshen.qt.stem}取${r.frame.yongshen.qt.yong.join('、')}(首取${r.frame.yongshen.qt.primary})` : ''),
|
|
156
|
+
`十神权重:${Object.entries(r.frame.godStats.groups).map(([k, v]) => `${k}${v}`).join(' ')}`,
|
|
157
|
+
] : [];
|
|
158
|
+
return [
|
|
159
|
+
`【四柱】${P.year.ganzhi} ${P.month.ganzhi} ${P.day.ganzhi} ${P.hour.ganzhi}`,
|
|
160
|
+
`日主:${r.dayMaster.stem}${r.dayMaster.element}(${r.dayMaster.yinYang}) 空亡:${r.kongwang.join('')}`,
|
|
161
|
+
line('年柱', P.year), line('月柱', P.month), line('日柱', P.day), line('时柱', P.hour),
|
|
162
|
+
`五行:${wuxing}`,
|
|
163
|
+
...(palaces ? [palaces] : []),
|
|
164
|
+
`日主强弱:${r.strength.level}(${r.strength.score}) ${r.strength.detail}`,
|
|
165
|
+
...gejuLine,
|
|
166
|
+
`天干关系:${(r.relations && r.relations.stems.length) ? r.relations.stems.join(' ') : '—'}`,
|
|
167
|
+
`地支关系:${(r.relations && r.relations.branches.length) ? r.relations.branches.join(' ') : '—'}`,
|
|
168
|
+
shenshaLine,
|
|
169
|
+
`真太阳时:${r.solarTime.trueSolarText}(${r.input.geo ? r.input.geo.note + ';' : ''}${r.solarTime.note}) 晚子时:${r.input.lateZi ? '23:00 后算次日' : '23:00 后仍算当日'}`,
|
|
170
|
+
`节气:上一节 ${r.jieqi.previous.name} ${r.jieqi.previous.time} 下一节 ${r.jieqi.next.name} ${r.jieqi.next.time}`,
|
|
171
|
+
...(renyuanLine ? [renyuanLine] : []),
|
|
172
|
+
`大运:${r.dayun.rule}(${r.dayun.direction}),${r.dayun.startAge.years}岁${r.dayun.startAge.months}个月起运${jiao}`,
|
|
173
|
+
` ${dayun}`,
|
|
174
|
+
`流年:${r.liunian.slice(0, 10).map((l) => `${l.year}${l.ganzhi}(${l.stemTenGod})${l.luck ? `·${l.luck.tier}` : ''}`).join(' ')}`,
|
|
175
|
+
(() => {
|
|
176
|
+
const hits = r.liunian.filter((l) => l.suiyun && l.suiyun.signals && l.suiyun.signals.length);
|
|
177
|
+
if (!hits.length) return '岁运提示:近十年无岁运并临、运年反吟或大运/流年伏吟、反吟(只标干支,不判吉凶)';
|
|
178
|
+
const label = (s) => `${s.type}${(s.type.startsWith('流年') || s.type.startsWith('大运')) && s.target ? `(${s.target})` : ''}`;
|
|
179
|
+
const e = suiyunClassicOf(r);
|
|
180
|
+
const src = e ? `|古籍见《${CLASSIC_BOOKS[e.book].name}·${e.chapter}》` : '';
|
|
181
|
+
return '岁运提示(只标干支变动,不判吉凶):' + hits
|
|
182
|
+
.map((l) => `${l.year}${l.ganzhi} ${l.suiyun.signals.map(label).join('/')}`).join(';') + src;
|
|
183
|
+
})(),
|
|
184
|
+
'(格局、用神、行运吉凶为扶抑+简化调候的量化提示,非断语。)',
|
|
185
|
+
].join('\n') + near;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function ziweiText(r) {
|
|
189
|
+
// 闰月:日期按「闰X月」原样写;换算过起盘月时再附一句,免得把 21 日写成「闰5月21日」
|
|
190
|
+
const rawMonth = r.lunar.monthRaw || r.lunar.month;
|
|
191
|
+
const lunarText = `${r.lunar.year}年${r.lunar.isLeap ? `闰${rawMonth}` : r.lunar.month}月${r.lunar.day}日`
|
|
192
|
+
+ (r.lunar.isLeap && r.lunar.month !== rawMonth ? `(按${r.lunar.month}月起盘)` : '');
|
|
193
|
+
// 只在显式改动流派时补一行,默认盘不增加噪音
|
|
194
|
+
const schoolLine = (() => {
|
|
195
|
+
if (!r.schools) return '';
|
|
196
|
+
const labels = {
|
|
197
|
+
brightnessSchool: '亮度', gengYearSihua: '庚干四化', tianmaSchool: '天马', tiankongSchool: '天空',
|
|
198
|
+
jiekongSchool: '截空', kuiyueSchool: '魁钺', tianshiTianshang: '天伤天使',
|
|
199
|
+
changshengSchool: '长生', lateZishi: '晚子时', shenzhuSchool: '身主',
|
|
200
|
+
};
|
|
201
|
+
const diff = Object.entries(labels)
|
|
202
|
+
.filter(([key]) => r.schools[key] && r.schools[key] !== ZIWEI_SCHOOL_DEFAULTS[key])
|
|
203
|
+
.map(([key, label]) => `${label} ${r.schools[key]}`);
|
|
204
|
+
return diff.length ? `【流派】${diff.join(' ')}` : '';
|
|
205
|
+
})();
|
|
206
|
+
const starText = (stars) => (stars.length
|
|
207
|
+
? stars.map((s) => `${s.name}${s.mutagen ? `化${s.mutagen}` : ''}`).join(' ')
|
|
208
|
+
: '—');
|
|
209
|
+
const shenshaText = (s) =>
|
|
210
|
+
`神煞:长生(${s.changsheng}) 博士(${s.boshi}) 将前(${s.jiangqian}) 岁前(${s.suiqi})`;
|
|
211
|
+
const rows = [];
|
|
212
|
+
for (const p of r.palaces) {
|
|
213
|
+
const mark = p.isSoul ? '【命】' : p.isBody ? '【身】' : ' ';
|
|
214
|
+
rows.push(` ${p.palace} ${p.branch} ${p.ganzhi} ${mark} 大限${String(p.daxian.start).padStart(2)}-${p.daxian.end} 小限${String(p.xiaoxian.age).padStart(2)} ${starText(p.stars)}`);
|
|
215
|
+
rows.push(` ${shenshaText(p.shensha)}`);
|
|
216
|
+
}
|
|
217
|
+
const liunian = (r.liunian ?? []).slice(0, 6).map((l) =>
|
|
218
|
+
` ${l.year} ${l.ganzhi} ${String(l.age).padStart(2)}岁 命宫${l.lifePalace.branch}(${l.lifePalace.palace}) 流禄${l.liulu} 流羊${l.liuyang} 流陀${l.liutuo} | ${l.mutagen.map((m) => m.replace('化', '')).join(' ')}`
|
|
219
|
+
+ (l.mutagenHits ? ` 四化落宫 ${l.mutagenHits.map((m) => `${m.star}${m.type}→${m.palace || '未上盘'}`).join('、')}` : '')
|
|
220
|
+
+ (l.sanfangMainStars ? ` 三方主星 ${l.sanfangMainStars.join('')}` : ''));
|
|
221
|
+
const patterns = (r.patterns ?? []).length
|
|
222
|
+
? r.patterns.map((p) => `${p.name}(${p.tone === 'good' ? '吉格' : p.tone === 'bad' ? '忌格' : '中'}:${p.evidence})`).join(';')
|
|
223
|
+
: '无明显格局(按内置规则)';
|
|
224
|
+
const daxian = (r.daxianList ?? []).slice(0, 8).map((d) =>
|
|
225
|
+
` ${d.branch}宫(${d.palace})${d.ages[0]}-${d.ages[1]}岁 ${d.ganzhi} 会${d.mainStars.join('') || '—'} 化 ${d.mutagenHits.map((m) => `${m.star}${m.type}→${m.palace || '未上盘'}`).join('、')}`);
|
|
226
|
+
// 命宫四化飞星:宫干化出的星落在哪一宫,一路串下去成环为止
|
|
227
|
+
const flyLine = (() => {
|
|
228
|
+
const fc = r.flyChains;
|
|
229
|
+
if (!fc) return '';
|
|
230
|
+
const one = (t) => {
|
|
231
|
+
const ch = fc[t];
|
|
232
|
+
if (!ch || !ch.steps.length) return '';
|
|
233
|
+
return `${t}:${ch.path.join('→')}${ch.closed ? '(成环)' : ''}`;
|
|
234
|
+
};
|
|
235
|
+
const text = ['禄', '权', '科', '忌'].map(one).filter(Boolean).join(' ');
|
|
236
|
+
return text ? `【命宫飞星】${text}` : '';
|
|
237
|
+
})();
|
|
238
|
+
// 三方四正一起飞:四宫的 16 条飞化按落宫归堆,只看会聚
|
|
239
|
+
const flyCompositeLine = (() => {
|
|
240
|
+
const fc = r.flyComposite;
|
|
241
|
+
if (!fc || !Array.isArray(fc.sources) || !fc.sources.length) return '';
|
|
242
|
+
const groups = fc.labels.length ? fc.labels.join(' · ') : '无同宫会聚';
|
|
243
|
+
const detail = ['禄', '权', '科', '忌'].map((t) => {
|
|
244
|
+
const list = ((fc.byType && fc.byType[t]) || []).filter((x) => !x.self);
|
|
245
|
+
const tos = [...new Set(list.map((x) => String(x.to).replace(/宫$/, '')))];
|
|
246
|
+
return tos.length ? `${t}→${tos.join('/')}` : '';
|
|
247
|
+
}).filter(Boolean).join(' ');
|
|
248
|
+
return `【三方四正飞化】${fc.sanfang.join('·')}:${groups}${detail ? ` ${detail}` : ''}`;
|
|
249
|
+
})();
|
|
250
|
+
return [
|
|
251
|
+
`【农历】${lunarText}(月首 ${r.lunar.monthStart}) 时支:${r.hourBranch} 年干支:${r.yearGanzhi}`,
|
|
252
|
+
`【时刻】${r.solarTime ? `${r.solarTime.civilText} → 真太阳时 ${r.solarTime.trueSolarText}${r.input.geo ? '(' + r.input.geo.note + ')' : ''}` : ''}`,
|
|
253
|
+
`【命宫】${r.soul.branch}(${r.soul.ganzhi}·${r.soul.nayin}) 【身宫】${r.body.branch}(落${r.body.lands}) 【五行局】${r.bureau.name}`,
|
|
254
|
+
`【紫微】${r.ziwei} 【天府】${r.tianfu} 【命主】${r.lifeMaster} 【身主】${r.bodyMaster}${r.bodyMasterNote ? `(${r.bodyMasterSchool === 'common' ? '子午皆火星' : '子铃午火'})` : ''}`,
|
|
255
|
+
`【大限】${r.daxian.rule},${r.daxian.startAge}岁起 【小限】${r.xiaoxianRule.direction}`,
|
|
256
|
+
schoolLine,
|
|
257
|
+
`【昌曲】文昌${r.changqu.wenchang.branch}(${r.changqu.wenchang.palace}${r.changqu.wenchang.mutagen ? '·化' + r.changqu.wenchang.mutagen : ''}) 文曲${r.changqu.wenqu.branch}(${r.changqu.wenqu.palace}${r.changqu.wenqu.mutagen ? '·化' + r.changqu.wenqu.mutagen : ''})`,
|
|
258
|
+
` ${r.changqu.relation} 入命三方:${r.changqu.inSoulSanfang ? '是' : '否'} 吸收面:${r.changqu.absorb.label}(${r.changqu.absorb.reason})`,
|
|
259
|
+
flyLine,
|
|
260
|
+
flyCompositeLine,
|
|
261
|
+
'十二宫:',
|
|
262
|
+
...rows,
|
|
263
|
+
'大限(按起运年龄排序,前 8 限,宫干四化落宫):',
|
|
264
|
+
...daxian,
|
|
265
|
+
'流年(前 6 年,完整 12 年在结构化数据里):',
|
|
266
|
+
...liunian,
|
|
267
|
+
`【格局】${patterns}`,
|
|
268
|
+
r.patternSummary ? `【结构统计】吉格 ${r.patternSummary.good.length}、中性 ${r.patternSummary.neutral.length}、忌格 ${r.patternSummary.bad.length}${r.patternSummary.broken.length ? `、破格 ${r.patternSummary.broken.length}(${r.patternSummary.broken.join('、')})` : ''};${r.patternSummary.note}` : '',
|
|
269
|
+
r.lunar.note ? `注:${r.lunar.note}` : '',
|
|
270
|
+
].filter(Boolean).join('\n');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const STATE_ORDER = ['旺', '相', '休', '囚', '死'];
|
|
274
|
+
/** 门/星的月令五态,按 旺相休囚死 顺序拼成一行 */
|
|
275
|
+
function stateLine(r) {
|
|
276
|
+
const st = r.states;
|
|
277
|
+
const mo = r.month;
|
|
278
|
+
if (!st || !mo) return '无月令数据';
|
|
279
|
+
const fmt = (g) => STATE_ORDER.filter((k) => g && Array.isArray(g[k]) && g[k].length)
|
|
280
|
+
.map((k) => `${k} ${g[k].join('/')}`).join(' ');
|
|
281
|
+
return `月令 ${mo.branch}(${mo.element}) 门 ${fmt(st.gate) || '—'} 星 ${fmt(st.star) || '—'} 干 ${fmt(st.stem) || '—'}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function qimenText(r) {
|
|
285
|
+
const P = r.palaces;
|
|
286
|
+
const cell = (x) => `${x.palace}宫 ${x.star || '—'}/${x.gate || '—'}/${x.god || '—'}${x.kong ? ' 空' : ''}${x.ma ? ' 马' : ''}`;
|
|
287
|
+
return [
|
|
288
|
+
`【四柱】${r.siZhu.year} ${r.siZhu.month} ${r.siZhu.day} ${r.siZhu.time}`,
|
|
289
|
+
`【局数】${r.ju} 节气 ${r.jieqi}(${r.jieqiTime})`,
|
|
290
|
+
`【值符】${r.zhiFu.star}(${r.zhiFu.gong}宫) 【值使】${r.zhiShi.gate}(${r.zhiShi.gong}宫) 旬首 ${r.xunshou}`,
|
|
291
|
+
`【空亡】${r.kongWang.join('') || '—'} 驿马 ${r.ma.zhi || '—'}(${r.ma.gong || '—'}宫)`,
|
|
292
|
+
`【宫位条件】击刑 ${(r.conditions && r.conditions.jiXing || []).map((x) => `${x.palace}宫${x.stem}`).join('、') || '无'} 三奇入墓 ${(r.conditions && r.conditions.ruMu || []).map((x) => `${x.palace}宫${x.stem}`).join('、') || '无'} 六仪入墓 ${(r.conditions && r.conditions.liuYiRuMu || []).map((x) => `${x.palace}宫${x.stem}`).join('、') || '无'}`,
|
|
293
|
+
`【十干克应】${[...P, r.centerPalace].filter((p) => p && p.ganYing).map((p) => `${p.palace}宫 ${p.ganYing.tian}加${p.ganYing.di} ${p.ganYing.name}`).join(' ') || '—'}`,
|
|
294
|
+
`【旺衰】${stateLine(r)}`,
|
|
295
|
+
`【九宫】${P.map(cell).join(' ')}`,
|
|
296
|
+
`※ 晚子时:${r.lateZiHourMode === 'next-day' ? '23:00 后归次日' : '23:00 后仍归当日'}(可用 lateZiHourMode / lateZi 指定)`,
|
|
297
|
+
`※ ${r.rules[r.rules.length - 1]}`,
|
|
298
|
+
].join('\n');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function tripleText(t) {
|
|
302
|
+
if (!t || !Array.isArray(t.pairs) || !t.pairs.length) return [];
|
|
303
|
+
const lines = [
|
|
304
|
+
'【三方合盘】三对两两并列,不合成「三人总分」',
|
|
305
|
+
...t.pairs.map((p) => `· ${p.a}–${p.b}:${p.level || '—'}(${p.total == null ? '—' : `${p.total}/100`})`),
|
|
306
|
+
];
|
|
307
|
+
if (t.best) {
|
|
308
|
+
// 差 1–2 分时点名「最顺/最需磨合」是假精确:三对分数接近就直说接近,不排名
|
|
309
|
+
if (t.spread >= 3) {
|
|
310
|
+
lines.push(` 最顺:${t.best.a}–${t.best.b}(${t.best.total}/100)`
|
|
311
|
+
+ (t.hardest ? `;最需磨合:${t.hardest.a}–${t.hardest.b}(${t.hardest.total}/100)` : ''));
|
|
312
|
+
} else {
|
|
313
|
+
lines.push(` 三对分数接近(最高与最低相差 ${t.spread} 分),不排名`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (t.focus && t.focus.spread >= 3) lines.push(` 差异最大的一维:${t.focus.label}(三对相差 ${t.focus.spread} 分)`);
|
|
317
|
+
if (t.note) lines.push(`※ ${t.note}`);
|
|
318
|
+
return lines;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function hepanText(r) {
|
|
322
|
+
if (r.chart === 'ziwei') {
|
|
323
|
+
const d = (x) => `${x.label}:${x.score >= 0 ? '+' : ''}${x.score} ${x.text}`;
|
|
324
|
+
return [
|
|
325
|
+
`【紫微合盘】${r.score.level}(${r.score.total}/100)`,
|
|
326
|
+
`你 命宫 ${r.a.soul.branch}(${r.a.soulStars.join('、') || '无主星'}) 夫妻 ${r.a.spouseStars.join('、') || '无主星'} ${r.a.bureau}`,
|
|
327
|
+
`对方 命宫 ${r.b.soul.branch}(${r.b.soulStars.join('、') || '无主星'}) 夫妻 ${r.b.spouseStars.join('、') || '无主星'} ${r.b.bureau}`,
|
|
328
|
+
...r.dimensions.map(d),
|
|
329
|
+
...r.interactions.map((h) => ` · ${h.text}`),
|
|
330
|
+
...tripleText(r.triple),
|
|
331
|
+
...r.notes.map((n) => `※ ${n}`),
|
|
332
|
+
].join('\n');
|
|
333
|
+
}
|
|
334
|
+
const d = (x) => `${x.label}:${x.score >= 0 ? '+' : ''}${x.score} ${x.text}`;
|
|
335
|
+
const solarLine = (side) => {
|
|
336
|
+
if (!side || !side.solarNote) return '—';
|
|
337
|
+
return `${side.longitude == null ? '未含经度修正' : `${side.longitude}°E`}(${side.solarNote})`;
|
|
338
|
+
};
|
|
339
|
+
return [
|
|
340
|
+
`【合盘】${r.score.level}(${r.score.total}/100)`,
|
|
341
|
+
`你 ${r.a.pillars.join(' ')} 日主 ${r.a.dayMaster} 日支 ${r.a.dayBranch} ${r.a.strength || ''}`,
|
|
342
|
+
`对方 ${r.b.pillars.join(' ')} 日主 ${r.b.dayMaster} 日支 ${r.b.dayBranch} ${r.b.strength || ''}`,
|
|
343
|
+
`真太阳时:你 ${solarLine(r.a)};对方 ${solarLine(r.b)}`,
|
|
344
|
+
...r.dimensions.map(d),
|
|
345
|
+
...(r.detail ? [
|
|
346
|
+
`属相:${r.detail.yearRelation.label}——${r.detail.yearRelation.text}`,
|
|
347
|
+
`日干:${r.detail.stems.text}`,
|
|
348
|
+
...(r.detail.crossBranches && r.detail.crossBranches.length
|
|
349
|
+
? [`跨盘地支:${r.detail.crossBranches.map((c) => `${c.name}(${c.a}${c.b})`).join('、')}`]
|
|
350
|
+
: []),
|
|
351
|
+
`年命纳音:${r.detail.nayin.a} / ${r.detail.nayin.b}(${r.detail.nayin.label})——${r.detail.nayin.text}`,
|
|
352
|
+
`吉凶象:吉 ${r.detail.tally.good} / 凶 ${r.detail.tally.bad}(${r.detail.tally.lean};分档只数象,总分以上面三维度为准)`,
|
|
353
|
+
] : []),
|
|
354
|
+
...tripleText(r.triple),
|
|
355
|
+
...r.notes.map((n) => `※ ${n}`),
|
|
356
|
+
].join('\n');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function daliurenText(r) {
|
|
360
|
+
const sc = r.sanChuan;
|
|
361
|
+
const one = (c) => `${c.zhi}(${c.jiang}${c.liuQin}${c.xunKong ? '空' : ''})`;
|
|
362
|
+
return [
|
|
363
|
+
`【日时】${r.dayGanZhi}日 ${r.hourZhi}时(${r.dayNight}贵) 月将 ${r.yueJiang.name}${r.yueJiang.zhi} 节气 ${r.jieqi.name} ${r.lateZi === false ? '晚子时不换日' : '晚子时换日'}`,
|
|
364
|
+
`【三传】初 ${one(sc.chu)} → 中 ${one(sc.zhong)} → 末 ${one(sc.mo)}`,
|
|
365
|
+
`【四课】${r.siKe.map((k) => `${k.position}课 ${k.shang}上${k.xia}下(${k.relation}·${k.jiang})`).join(' ')}`,
|
|
366
|
+
`【天地盘】${r.pan.map((p) => `${p.di}:${p.tian}(${p.jiang})`).join(' ')}`,
|
|
367
|
+
`【格局】${r.geJu.name}·${r.geJu.detail}`,
|
|
368
|
+
...((r.shensha && r.shensha.length) ? [`【神煞】${r.shensha.map((x) => `${x.name}${x.branch}`).join(' ')}`] : []),
|
|
369
|
+
].join('\n');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function meihuaText(r) {
|
|
373
|
+
const lines = [
|
|
374
|
+
`【起卦】${r.source}`,
|
|
375
|
+
`【本卦】${r.ben.name}(上${r.ben.upper} 下${r.ben.lower}) 动爻:${r.movingName}`,
|
|
376
|
+
`【互卦】${r.hu.name} 【变卦】${r.bian.name} 【错卦】${r.cuo.name} 【综卦】${r.zong.name}`,
|
|
377
|
+
`【体用】体 ${r.bodyUse.body.trigram}${r.bodyUse.body.element} 用 ${r.bodyUse.use.trigram}${r.bodyUse.use.element} ${r.bodyUse.level}`,
|
|
378
|
+
` ${r.bodyUse.relation}`,
|
|
379
|
+
];
|
|
380
|
+
const bars = r.lines.slice().reverse().map((l) =>
|
|
381
|
+
` ${l.name} ${l.yinYang === '阳' ? '━━━━━' : '━━ ━━'}${l.moving ? ' ←动' : ''}`);
|
|
382
|
+
return [...lines, '【爻】', ...bars].join('\n');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function tarotText(r) {
|
|
386
|
+
const lines = [
|
|
387
|
+
r.question ? `【所问】${r.question}` : '(未填写提问)',
|
|
388
|
+
`【牌阵】${r.spread}${r.castAt ? ` 抽牌于 ${r.castAt}` : ''}`,
|
|
389
|
+
];
|
|
390
|
+
for (const c of r.cards) {
|
|
391
|
+
lines.push(` ${c.position}:${c.name}(${c.orientation}) ${c.keywords.join('、')}`);
|
|
392
|
+
}
|
|
393
|
+
if (r.reading && r.reading.length) {
|
|
394
|
+
lines.push('【解读】');
|
|
395
|
+
for (const p of r.reading) lines.push(` ${p}`);
|
|
396
|
+
}
|
|
397
|
+
return lines.join('\n');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function liuyaoText(r) {
|
|
401
|
+
const fmt = (hex) => {
|
|
402
|
+
const rows = hex.lines.slice().reverse().map((l) => {
|
|
403
|
+
const mark = l.position === hex.shi ? '世' : l.position === hex.ying ? '应' : ' ';
|
|
404
|
+
return ` ${l.name} ${mark} ${l.symbol} ${l.ganzhi}${l.element} ${l.liuqin}/${l.liushen}`;
|
|
405
|
+
});
|
|
406
|
+
return [`${hex.name}(${hex.palace}·${hex.palaceElement})`, ...rows].join('\n');
|
|
407
|
+
};
|
|
408
|
+
const out = [
|
|
409
|
+
`起卦:${r.cast.notation.join('、')}(自下而上)`,
|
|
410
|
+
`日辰:${r.dayGanZhi.name}`,
|
|
411
|
+
r.xunShou ? `旬首:${r.xunShou} 旬空:${(r.xunKong || []).join('') || '—'}` : '',
|
|
412
|
+
r.question ? `所问:${r.question}` : '',
|
|
413
|
+
'本卦:',
|
|
414
|
+
fmt(r.ben),
|
|
415
|
+
];
|
|
416
|
+
if (r.bian) out.push(`动爻:第 ${r.cast.movingPositions.join('、')} 爻`, '变卦(六亲以变卦自身宫论):', fmt(r.bian));
|
|
417
|
+
else out.push('六爻安静,无变卦。');
|
|
418
|
+
// 结果里已带 labels;只有旧数据或裸对象调用时才现算一份
|
|
419
|
+
const body = (r.body && Array.isArray(r.body.labels)) ? r.body.labels : bodyLabels(r.body);
|
|
420
|
+
out.push(`卦体:${body.length ? body.join(' · ') : '无六冲、六合、伏吟、反吟'}`);
|
|
421
|
+
// 用神:按询问方向取六亲(感情分男女、出行以世为用),不上卦时给伏神
|
|
422
|
+
const ug = r.useGod || yongshenOf(r, {});
|
|
423
|
+
if (ug && ug.liuqin) {
|
|
424
|
+
const where = ug.found.length
|
|
425
|
+
? ug.found.map((f) => `${f.position}爻${f.ganzhi}${f.element}${f.kong ? '(空)' : ''}`).join('、')
|
|
426
|
+
: (ug.fushen.length ? ug.fushen.map((f) => `伏 ${f.position}爻${f.ganzhi}${f.element}(${f.relation || '飞伏关系未知'})`).join('、') : '不上卦');
|
|
427
|
+
out.push(`用神:${ug.byShi ? '世爻' : ug.liuqin}(${ug.ask})→ ${where}${ug.side ? `(${ug.side})` : ''}`);
|
|
428
|
+
const head = ug.byShi ? '用神 世爻' : `用神 ${ug.liuqin}`;
|
|
429
|
+
const extra = ug.labels.filter((x) => x !== head && !x.startsWith(head));
|
|
430
|
+
if (extra.length) out.push(`用神联动:${extra.join(' · ')}`);
|
|
431
|
+
const strength = r.useGodStrength;
|
|
432
|
+
if (strength && strength.labels.length) {
|
|
433
|
+
const m = strength.month && strength.month.branch ? `月建 ${strength.month.branch}(${strength.month.element}) ` : '';
|
|
434
|
+
out.push(`用神旺衰:${m}${strength.labels.join(' · ')}`);
|
|
435
|
+
}
|
|
436
|
+
// 伏神出伏条件:只在用神不上卦时出现;只归条件档,不给应期
|
|
437
|
+
const chufu = r.fushenAccess;
|
|
438
|
+
if (chufu && Array.isArray(chufu.labels) && chufu.labels.length) {
|
|
439
|
+
out.push(`伏神出伏:${chufu.labels.join(' · ')}`);
|
|
440
|
+
}
|
|
441
|
+
} else {
|
|
442
|
+
out.push('用神:未取(未指明所问之事;用「询问方向」指定后即按六亲取用)');
|
|
443
|
+
}
|
|
444
|
+
// 全卦逐爻旺衰:不挑用神,六爻都给状态,世应、动爻、旬空另标
|
|
445
|
+
const hexSt = r.hexStrength;
|
|
446
|
+
if (hexSt && Array.isArray(hexSt.labels) && hexSt.labels.length) {
|
|
447
|
+
const m = hexSt.month && hexSt.month.branch
|
|
448
|
+
? `月建 ${hexSt.month.branch}(${hexSt.month.element}) `
|
|
449
|
+
: '无月建(未给起卦时间) ';
|
|
450
|
+
out.push(`全卦旺衰:${m}${hexSt.labels.join(' · ')}`);
|
|
451
|
+
}
|
|
452
|
+
// 化进/化退的力重:单列一行,免得跟卦体标签混着读
|
|
453
|
+
const jt = r.jinTuiPower;
|
|
454
|
+
if (jt && Array.isArray(jt.lines) && jt.lines.length) {
|
|
455
|
+
out.push(`进退力重:${jt.lines.map((l) => `${l.position}爻${l.from}→${l.to}${l.type}·${l.verdict}`).join(' · ')}`);
|
|
456
|
+
}
|
|
457
|
+
// 应期线索:只列地支与快慢,不给日期
|
|
458
|
+
const yq = r.yingqi;
|
|
459
|
+
if (yq && Array.isArray(yq.triggers) && yq.triggers.length) {
|
|
460
|
+
out.push(`应期线索:${yq.tempo} · ${yq.triggers.map((t) => `${t.labels.join('/')}${t.branch}`).join(' · ')}(只列地支,不出日期)`);
|
|
461
|
+
}
|
|
462
|
+
return out.filter(Boolean).join('\n');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* 工具输出必须是无损 JSON:去掉 undefined / 函数等不可序列化的值。
|
|
467
|
+
* 语义与 JSON.parse(JSON.stringify(v)) 一致(NaN→null、-0→0、Date→ISO、数组空位→null、
|
|
468
|
+
* 对象里的 undefined/函数丢键、循环引用抛错),但不建中间字符串——结果动辄几百 KB,
|
|
469
|
+
* 每次 compute 都序列化一遍纯属浪费。
|
|
470
|
+
*/
|
|
471
|
+
function jsonSafe(value) {
|
|
472
|
+
const seen = new WeakSet();
|
|
473
|
+
const walk = (v, key) => {
|
|
474
|
+
if (v instanceof Number || v instanceof String || v instanceof Boolean) v = v.valueOf();
|
|
475
|
+
if (v && typeof v === 'object' && typeof v.toJSON === 'function') v = v.toJSON(key);
|
|
476
|
+
switch (typeof v) {
|
|
477
|
+
case 'string':
|
|
478
|
+
case 'boolean':
|
|
479
|
+
return v;
|
|
480
|
+
case 'number':
|
|
481
|
+
return Number.isFinite(v) ? (Object.is(v, -0) ? 0 : v) : null;
|
|
482
|
+
case 'bigint':
|
|
483
|
+
throw new TypeError('Do not know how to serialize a BigInt');
|
|
484
|
+
case 'object': {
|
|
485
|
+
if (v === null) return null;
|
|
486
|
+
if (seen.has(v)) throw new TypeError('Converting circular structure to JSON');
|
|
487
|
+
seen.add(v);
|
|
488
|
+
let out;
|
|
489
|
+
if (Array.isArray(v)) {
|
|
490
|
+
out = new Array(v.length);
|
|
491
|
+
for (let i = 0; i < v.length; i++) {
|
|
492
|
+
const c = walk(v[i], String(i));
|
|
493
|
+
out[i] = c === undefined ? null : c;
|
|
494
|
+
}
|
|
495
|
+
} else {
|
|
496
|
+
out = {};
|
|
497
|
+
for (const k of Object.keys(v)) {
|
|
498
|
+
const c = walk(v[k], k);
|
|
499
|
+
if (c !== undefined) out[k] = c;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
seen.delete(v);
|
|
503
|
+
return out;
|
|
504
|
+
}
|
|
505
|
+
default:
|
|
506
|
+
return undefined;
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
return walk(value, '');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ── 影子盘:同一组归一化参数 + 同一版本 → 同一个短 ID ──────────────
|
|
513
|
+
// 只收影响盘面的参数:地址换成有效经度后不再把原文算进去,问事/方向按体系保留。
|
|
514
|
+
const SHADOW_IGNORE = new Set([
|
|
515
|
+
'shadow', 'shadowVersion', 'diff', 'trace', 'traceError', 'withInterpret', 'cross', 'verifyPast', 'rectify', 'events',
|
|
516
|
+
'geocode', 'ask', 'followup', 'caliber', 'seed', 'asOf', 'handle', 'revise',
|
|
517
|
+
]);
|
|
518
|
+
|
|
519
|
+
const num6 = (n) => {
|
|
520
|
+
if (n === undefined || n === null || n === '') return null;
|
|
521
|
+
const v = Number(n);
|
|
522
|
+
return Number.isFinite(v) ? Number(v.toFixed(6)) : null;
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
function shadowDateTime(value) {
|
|
526
|
+
if (value === undefined || value === null || value === '') return null;
|
|
527
|
+
try {
|
|
528
|
+
const d = parseDateTime(value);
|
|
529
|
+
return { y: d.year, m: d.month, d: d.day, h: d.hour, mi: d.minute, precision: d.precision };
|
|
530
|
+
} catch {
|
|
531
|
+
return String(value).trim();
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function shadowPerson(person = {}) {
|
|
536
|
+
const out = {};
|
|
537
|
+
if (person.datetime) out.datetime = shadowDateTime(person.datetime);
|
|
538
|
+
if (person.gender) out.gender = String(person.gender).toLowerCase();
|
|
539
|
+
if (person.longitude !== undefined) out.longitude = num6(person.longitude);
|
|
540
|
+
else if (person.address) out.address = String(person.address).trim();
|
|
541
|
+
if (person.lateZi !== undefined) out.lateZi = person.lateZi !== false;
|
|
542
|
+
if (person.trueSolar !== undefined) out.trueSolar = person.trueSolar !== false;
|
|
543
|
+
if (person.dst !== undefined) out.dst = person.dst !== false;
|
|
544
|
+
return out;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function qimenLateMode(args) {
|
|
548
|
+
if (args.lateZiHourMode === 'next-day' || args.lateZi === true) return 'next-day';
|
|
549
|
+
return 'same-day';
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function shadowParams(args, result) {
|
|
553
|
+
const system = String((result && result.system) || args.system || '');
|
|
554
|
+
const input = result && result.input && typeof result.input === 'object' ? result.input : null;
|
|
555
|
+
if (system === 'bazi' && input) {
|
|
556
|
+
return {
|
|
557
|
+
civil: input.civil,
|
|
558
|
+
gender: input.gender,
|
|
559
|
+
longitude: num6(input.longitude),
|
|
560
|
+
lateZi: input.lateZi !== false,
|
|
561
|
+
trueSolar: input.trueSolar !== false,
|
|
562
|
+
dst: !!(input.dst && input.dst.applied),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
if (system === 'ziwei' && input) {
|
|
566
|
+
return {
|
|
567
|
+
civil: { year: input.year, month: input.month, day: input.day, hour: input.hour, minute: input.minute },
|
|
568
|
+
gender: input.gender,
|
|
569
|
+
longitude: num6(input.longitude),
|
|
570
|
+
leapMonth: input.leapMonth || 'split',
|
|
571
|
+
leapAsNext: input.leapAsNext === true,
|
|
572
|
+
schools: input.schools || ZIWEI_SCHOOL_DEFAULTS,
|
|
573
|
+
trueSolar: input.trueSolar !== false,
|
|
574
|
+
dst: !!(input.dst && input.dst.applied),
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
if (system === 'qimen') {
|
|
578
|
+
return {
|
|
579
|
+
datetime: shadowDateTime(args.datetime),
|
|
580
|
+
juMethod: args.juMethod || 'chaibu',
|
|
581
|
+
lateZiHourMode: qimenLateMode(args),
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
if (system === 'hepan') {
|
|
585
|
+
const params = {
|
|
586
|
+
chart: args.chart === 'ziwei' ? 'ziwei' : 'bazi',
|
|
587
|
+
a: shadowPerson({ ...args, datetime: args.datetime }),
|
|
588
|
+
b: shadowPerson(args.partner || {}),
|
|
589
|
+
};
|
|
590
|
+
// 第三方进指纹:同一对加上不同的第三人,是另一组盘
|
|
591
|
+
if (args.partner2) params.c = shadowPerson(args.partner2);
|
|
592
|
+
return params;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// 其余体系:只剔除解析/附加模块参数,其余原样归一化
|
|
596
|
+
const params = {};
|
|
597
|
+
for (const [key, value] of Object.entries(args)) {
|
|
598
|
+
if (SHADOW_IGNORE.has(key) || value === undefined) continue;
|
|
599
|
+
if (key === 'datetime') params.datetime = shadowDateTime(value);
|
|
600
|
+
else if (key === 'partner') params.partner = shadowPerson(value);
|
|
601
|
+
else if (key === 'gender') params.gender = String(value).toLowerCase();
|
|
602
|
+
else if (key === 'address') continue; // 调用侧先把地址换成经度,再由 longitude 参与;这里不收原文
|
|
603
|
+
else if (typeof value === 'number') params[key] = Number.isFinite(value) ? (Object.is(value, -0) ? 0 : value) : null;
|
|
604
|
+
else if (typeof value === 'string') params[key] = value.trim();
|
|
605
|
+
else params[key] = value;
|
|
606
|
+
}
|
|
607
|
+
if (system === 'daliuren' && params.lateZi === undefined) params.lateZi = true;
|
|
608
|
+
if (system === 'tarot' && params.count === undefined) params.count = 3;
|
|
609
|
+
if (system === 'meihua' && params.mode === undefined) params.mode = 'time';
|
|
610
|
+
if (system === 'xiaoliuren' && params.mode === undefined) params.mode = 'time';
|
|
611
|
+
if (system === 'convert' && params.mode === undefined) params.mode = 'solar2lunar';
|
|
612
|
+
if (system === 'fengshui' && params.action === undefined) params.action = 'natal';
|
|
613
|
+
// 随机起卦 / 抽牌:同参数不等于同一副牌,把实际抽到的牌面 / 爻值算进来,
|
|
614
|
+
// 否则两次不同的抽牌会撞成同一个 ID(有 seed 时本来就能复现,这里只是多一层同构校验)。
|
|
615
|
+
if (system === 'tarot' && result && Array.isArray(result.cards) && result.cards.length) {
|
|
616
|
+
params.cards = result.cards.map((c) => `${c.name || ''}|${c.upright ? '正' : '逆'}`);
|
|
617
|
+
}
|
|
618
|
+
if (system === 'liuyao' && result && result.cast && Array.isArray(result.cast.lines)) {
|
|
619
|
+
params.cast = result.cast.lines.join('');
|
|
620
|
+
}
|
|
621
|
+
return params;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function shadowMeta(system, args) {
|
|
625
|
+
const seed = args.seed;
|
|
626
|
+
const seeded = !(seed === undefined || seed === null || seed === '');
|
|
627
|
+
// 六爻给定了六爻值就是确定性起卦,不需要 seed 也能复算
|
|
628
|
+
if (system === 'liuyao' && Array.isArray(args.lines) && args.lines.length === 6) {
|
|
629
|
+
return { reproducible: true };
|
|
630
|
+
}
|
|
631
|
+
if ((system === 'tarot' || system === 'liuyao') && !seeded) {
|
|
632
|
+
return { reproducible: false, reason: '未给 seed:重算会变成另一副牌 / 另一卦' };
|
|
633
|
+
}
|
|
634
|
+
if (system === 'bazi' || system === 'ziwei' || system === 'trend' || system === 'fengshui') {
|
|
635
|
+
return { reproducible: true, asOfPinned: !!args.asOf };
|
|
636
|
+
}
|
|
637
|
+
return { reproducible: true };
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* 影子盘指纹。版本优先级:显式 opts.version → 构建注入的全局 __MP_VER → dev。
|
|
642
|
+
* @returns {{id:string, baseId:string, version:string, system:string, payload:string,
|
|
643
|
+
* reproducible:boolean, asOfPinned?:boolean, reason?:string}}
|
|
644
|
+
*/
|
|
645
|
+
export function chartFingerprint(args = {}, opts = {}) {
|
|
646
|
+
const result = opts.result || null;
|
|
647
|
+
const system = String((result && result.system) || args.system || '');
|
|
648
|
+
const version = String(opts.version || (typeof globalThis !== 'undefined' && globalThis.__MP_VER) || 'dev');
|
|
649
|
+
const fp = fingerprintOf({
|
|
650
|
+
system,
|
|
651
|
+
params: shadowParams(args, result),
|
|
652
|
+
seed: args.seed,
|
|
653
|
+
asOf: args.asOf ? String(args.asOf) : null,
|
|
654
|
+
version,
|
|
655
|
+
});
|
|
656
|
+
return { ...fp, ...shadowMeta(system, args), system, version };
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const SHADOW_DIFF_IGNORE = new Set([
|
|
660
|
+
'shadow', 'trace', 'traceText', 'traceError', 'interpret', 'summary', 'classics', 'suiyunClassic', 'renyuanClassic', 'cross',
|
|
661
|
+
'past', 'rectify', 'followup', 'caliber', 'input', 'reading', 'knowledge', 'outline',
|
|
662
|
+
'sections', 'answer', 'plain', 'tables', 'glossary', 'caveats', 'lead', 'dimensions',
|
|
663
|
+
'moduleType', 'technique', 'disclaimer', 'ask', 'followups', 'knowledgeCount', 'title',
|
|
664
|
+
'systemLabel', 'handle', 'handleError', 'revision',
|
|
665
|
+
]);
|
|
666
|
+
|
|
667
|
+
function shadowChart(result) {
|
|
668
|
+
const out = {};
|
|
669
|
+
for (const [key, value] of Object.entries(result || {})) {
|
|
670
|
+
if (!SHADOW_DIFF_IGNORE.has(key)) out[key] = value;
|
|
671
|
+
}
|
|
672
|
+
return out;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* 存档用的轻量快照:只留影子盘信息与盘面字段(解析/古籍/验前事等不参与 diff)。
|
|
677
|
+
* 页面侧把它写进本地存储,下次同 baseId 重排时直接与它比对。
|
|
678
|
+
*/
|
|
679
|
+
export function shadowSnapshot(result) {
|
|
680
|
+
if (!result || !result.shadow || !result.shadow.baseId) return null;
|
|
681
|
+
return { snapshot: true, shadow: result.shadow, data: shadowChart(result) };
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* 同一 baseId 的两次结果对比;版本不同时自动落到具体字段。
|
|
686
|
+
* previous / current 可以是完整结果,也可以是 shadowSnapshot() 的存档。
|
|
687
|
+
*/
|
|
688
|
+
export function shadowDiff(previous, current, opts = {}) {
|
|
689
|
+
const a = previous && previous.shadow;
|
|
690
|
+
const b = current && current.shadow;
|
|
691
|
+
if (!a || !b) return { sameId: false, total: 0, reason: '缺少影子盘信息' };
|
|
692
|
+
if (a.baseId && b.baseId && a.baseId !== b.baseId) {
|
|
693
|
+
return { sameId: false, total: 0, fromId: a.id, toId: b.id, fromBaseId: a.baseId, toBaseId: b.baseId, reason: '基础指纹不同,不是同一张盘' };
|
|
694
|
+
}
|
|
695
|
+
const pick = (x) => (x.snapshot === true && x.data && typeof x.data === 'object' ? x.data : shadowChart(x));
|
|
696
|
+
const diffs = diffObjects(pick(previous), pick(current));
|
|
697
|
+
const roots = sortDiffs(diffs.filter((d) => rootOf(d.path))).map((d) => ({
|
|
698
|
+
path: d.path, label: labelOf(d.path), from: formatValue(d.from), to: formatValue(d.to),
|
|
699
|
+
}));
|
|
700
|
+
const counts = new Map();
|
|
701
|
+
for (const d of diffs) {
|
|
702
|
+
const cat = categoryOf(d.path) || '其他';
|
|
703
|
+
counts.set(cat, (counts.get(cat) || 0) + 1);
|
|
704
|
+
}
|
|
705
|
+
const categories = [...counts.entries()]
|
|
706
|
+
.map(([name, count]) => ({ name, count }))
|
|
707
|
+
.sort((x, y) => y.count - x.count || x.name.localeCompare(y.name, 'zh-Hans-CN'));
|
|
708
|
+
const fromVersion = opts.fromVersion || a.version || '';
|
|
709
|
+
const toVersion = opts.toVersion || b.version || '';
|
|
710
|
+
return {
|
|
711
|
+
sameId: true,
|
|
712
|
+
versionChanged: fromVersion !== toVersion,
|
|
713
|
+
fromVersion,
|
|
714
|
+
toVersion,
|
|
715
|
+
total: diffs.length,
|
|
716
|
+
summary: summarize(diffs),
|
|
717
|
+
roots,
|
|
718
|
+
categories,
|
|
719
|
+
diffs,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* 统一排盘入口
|
|
725
|
+
* @param {object} args
|
|
726
|
+
* @param {'bazi'|'ziwei'|'liuyao'} args.system
|
|
727
|
+
* @param {string} [args.datetime]
|
|
728
|
+
* @param {string} [args.handle] 上次结果里的句柄;给了它就可以只传增量参数(如 asOf、direction、system)
|
|
729
|
+
*/
|
|
730
|
+
export function compute(input = {}) {
|
|
731
|
+
// 句柄先并回参数:后面的代码只看见一份完整参数,不必到处判 handle
|
|
732
|
+
let args = resolveHandle(input);
|
|
733
|
+
// 一句话改参(C2):把「换成紫微看 2030 年事业」翻成补丁再排盘;识别不到就原样排,
|
|
734
|
+
// 绝不猜。改了哪些、没认哪些都随结果回传,便于核对。
|
|
735
|
+
let revision;
|
|
736
|
+
if (typeof args.revise === 'string' && args.revise.trim()) {
|
|
737
|
+
const rev = parseRevision(args.revise);
|
|
738
|
+
const before = { ...args };
|
|
739
|
+
args = { ...args, ...rev.patch };
|
|
740
|
+
revision = {
|
|
741
|
+
text: rev.text,
|
|
742
|
+
applied: rev.labels.map((l) => {
|
|
743
|
+
const from = before[l.key] === undefined ? null : before[l.key];
|
|
744
|
+
return { ...l, from, fromDisplay: displayValue(l.key, from) };
|
|
745
|
+
}),
|
|
746
|
+
ignored: rev.ignored,
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
// partner2: null 视同未传:先归一化再排盘,否则同一对盘会因为显式 null 多出一个句柄
|
|
750
|
+
if (args.partner2 === null) {
|
|
751
|
+
args = { ...args };
|
|
752
|
+
delete args.partner2;
|
|
753
|
+
}
|
|
754
|
+
const r = jsonSafe(computeRaw(args));
|
|
755
|
+
if (revision) r.revision = revision;
|
|
756
|
+
if (r && r.system === 'bazi') {
|
|
757
|
+
r.classics = classicsFor(r);
|
|
758
|
+
const ct = classicsText(r.classics);
|
|
759
|
+
if (ct) r.summary = `${r.summary}\n【古籍】\n${ct}`;
|
|
760
|
+
// 岁运提示的原文不占五条上限:只在盘面真有岁运信号时挂到结果上,摘要、解析层与面板各自取用
|
|
761
|
+
const syc = suiyunClassicOf(r);
|
|
762
|
+
if (syc) r.suiyunClassic = { ...syc, bookName: CLASSIC_BOOKS[syc.book].name };
|
|
763
|
+
// 人元司事的原文同样不进五条取条表:每张八字盘都有人元,随「人元司权」一节给出
|
|
764
|
+
if (r.renyuan) {
|
|
765
|
+
const ryc = classicOf('smt-renyuan');
|
|
766
|
+
if (ryc) r.renyuanClassic = { ...ryc, bookName: CLASSIC_BOOKS[ryc.book].name };
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
// 超出 ΔT 覆盖范围时把精度提示顶到摘要最前,别让远期盘悄悄出错
|
|
770
|
+
if (r && r.precision && r.precision.level === 'approx' && r.summary) {
|
|
771
|
+
r.summary = `⚠ ${r.precision.note}\n${r.summary}`;
|
|
772
|
+
}
|
|
773
|
+
// 没有经度就没有真太阳时:时柱可能整柱偏(海外出生尤其常见),必须显式告警而不是藏在括号里
|
|
774
|
+
if (r && (r.system === 'bazi' || r.system === 'ziwei') && r.input && r.input.longitude == null && r.summary) {
|
|
775
|
+
r.summary = `⚠ 未提供出生地或经度:按东八区平太阳时排盘,时柱可能与本命盘相差一个时辰;海外出生请先把当地时间换算成东八区标准时。\n${r.summary}`;
|
|
776
|
+
}
|
|
777
|
+
// 显式要求时才附加解析模块,避免默认输出过长;「所问之事」与「询问方向」一并传进去
|
|
778
|
+
if (args.withInterpret) {
|
|
779
|
+
r.interpret = interpret(r, {
|
|
780
|
+
question: typeof args.question === 'string' ? args.question : '',
|
|
781
|
+
direction: typeof args.direction === 'string' ? args.direction : 'general',
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
// 八字 × 紫微交叉验证:同一份出生信息再排另一套盘,并列两边对同一件事的说法
|
|
785
|
+
if (args.cross === true && (r.system === 'bazi' || r.system === 'ziwei')) {
|
|
786
|
+
const cdt = parseDateTime(args.datetime);
|
|
787
|
+
const common = {
|
|
788
|
+
...cdt,
|
|
789
|
+
gender: requireGender(args.gender),
|
|
790
|
+
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
791
|
+
address: typeof args.address === 'string' ? args.address : undefined,
|
|
792
|
+
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
793
|
+
trueSolar: args.trueSolar !== false,
|
|
794
|
+
dst: args.dst !== false,
|
|
795
|
+
};
|
|
796
|
+
const asOf = checkAsOf(args.asOf);
|
|
797
|
+
const other = r.system === 'bazi'
|
|
798
|
+
? buildZiwei({ ...common, leapMonth: args.leapMonth, leapAsNext: args.leapAsNext === true, ...ziweiSchoolArgs(args), asOf })
|
|
799
|
+
: buildBazi({ ...common, lateZi: args.lateZi !== false, nowYear: asOf ? asOf.year : undefined });
|
|
800
|
+
r.cross = crossCheck(r.system === 'bazi' ? r : other, r.system === 'ziwei' ? r : other);
|
|
801
|
+
if (r.interpret) {
|
|
802
|
+
r.interpret.sections.push(crossSection(r.cross));
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
// 验前事:把过去若干年的流年信号摊开(只读,不改盘)
|
|
806
|
+
if (Number.isInteger(args.verifyPast) && args.verifyPast > 0 && r.system === 'bazi') {
|
|
807
|
+
const years = Math.min(Math.max(args.verifyPast, 1), 10);
|
|
808
|
+
const from = Number(r.liunianFrom) - years;
|
|
809
|
+
const pastChart = buildBazi({
|
|
810
|
+
...parseDateTime(args.datetime),
|
|
811
|
+
gender: requireGender(args.gender),
|
|
812
|
+
lateZi: args.lateZi !== false,
|
|
813
|
+
trueSolar: args.trueSolar !== false,
|
|
814
|
+
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
815
|
+
address: typeof args.address === 'string' ? args.address : undefined,
|
|
816
|
+
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
817
|
+
dst: args.dst !== false,
|
|
818
|
+
nowYear: from,
|
|
819
|
+
});
|
|
820
|
+
r.past = {
|
|
821
|
+
rows: pastSignals(pastChart, Number(r.liunianFrom) - 1),
|
|
822
|
+
years,
|
|
823
|
+
until: Number(r.liunianFrom) - 1,
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
// 生时校正:候选时辰 × 过去事件,按固定规则打分(仅八字)
|
|
827
|
+
if (args.rectify === true && r.system === 'bazi') {
|
|
828
|
+
const base = parseDateTime(args.datetime);
|
|
829
|
+
r.rectify = rectifyBirth({
|
|
830
|
+
base: { year: base.year, month: base.month, day: base.day, minute: base.minute },
|
|
831
|
+
gender: requireGender(args.gender),
|
|
832
|
+
events: Array.isArray(args.events) ? args.events : [],
|
|
833
|
+
hours: Array.isArray(args.rectifyHours) ? args.rectifyHours : undefined,
|
|
834
|
+
lateZi: args.lateZi !== false,
|
|
835
|
+
trueSolar: args.trueSolar !== false,
|
|
836
|
+
dst: args.dst !== false,
|
|
837
|
+
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
838
|
+
address: typeof args.address === 'string' ? args.address : undefined,
|
|
839
|
+
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
// 追问:围绕已算好的盘回答「某一年 / 某方面」的具体问题
|
|
843
|
+
if (typeof args.ask === 'string' && args.ask.trim()) r.followup = followup(r, args.ask);
|
|
844
|
+
// 推导链:只读结果、不重算;生成失败不影响盘面,单独留一条错误信息
|
|
845
|
+
if (args.trace === true) {
|
|
846
|
+
try {
|
|
847
|
+
const layers = buildTrace(r, { direction: args.direction, gender: args.gender });
|
|
848
|
+
if (layers) {
|
|
849
|
+
r.trace = layers;
|
|
850
|
+
// 同一份层另出一份 Markdown:贴进对话/文档时表格不会散架,内容与 r.trace 同源
|
|
851
|
+
r.traceText = traceMarkdown(layers, { system: r.system });
|
|
852
|
+
}
|
|
853
|
+
} catch (e) {
|
|
854
|
+
r.traceError = String((e && e.message) || e);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
// 影子盘 ID:同一组归一化参数 + 同一版本 → 同一个短 ID;显式要时才挂,避免默认输出变长
|
|
858
|
+
if (args.shadow === true) {
|
|
859
|
+
try {
|
|
860
|
+
r.shadow = chartFingerprint(args, { result: r, version: args.shadowVersion });
|
|
861
|
+
} catch (e) {
|
|
862
|
+
r.shadow = { error: String((e && e.message) || e) };
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
// 排盘句柄:把这张盘的参数压成短串;下一次追问传 handle + 增量即可,不必重报出生信息
|
|
866
|
+
try {
|
|
867
|
+
const h = encodeHandle(args);
|
|
868
|
+
if (h) r.handle = h;
|
|
869
|
+
} catch (e) {
|
|
870
|
+
r.handleError = String((e && e.message) || e);
|
|
871
|
+
}
|
|
872
|
+
return r;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/** 解析与详解的紧凑文本(给模型看,不塞整块 JSON) */
|
|
876
|
+
export function interpretText(i) {
|
|
877
|
+
if (!i) return '';
|
|
878
|
+
const out = [`【${i.title || '解析与详解'}】${i.ask && i.ask.echo ? i.ask.echo : ''}`];
|
|
879
|
+
// 直答放在最前:先回答「你问的事」,再说依据,避免读者读到最后才知道结论;
|
|
880
|
+
// 没填问题时就不该喊「答所问」——改为「先看结论」,否则是一句假话
|
|
881
|
+
if (i.answer) out.push(`▶ ${i.ask && i.ask.question ? '先答所问' : '先看结论'}:${i.answer}`);
|
|
882
|
+
if (i.plain) out.push(`▶ 说人话:${i.plain}`);
|
|
883
|
+
if (i.lead) out.push(i.lead);
|
|
884
|
+
// 一页读懂:报告顶部的五条结论,先给读的人一个「整体形状」
|
|
885
|
+
if (Array.isArray(i.outline) && i.outline.length) {
|
|
886
|
+
out.push('一页读懂:' + i.outline.map((o) => `${o.label}·${o.text}`).join(' '));
|
|
887
|
+
}
|
|
888
|
+
// 解析生成失败时不能只返回一个空壳:盘面数据仍然可用,但要让调用方知道"这段没生成出来"
|
|
889
|
+
if (i.error) out.push(`⚠ 解析生成失败:${i.error}(上方盘面数据本身可用)`);
|
|
890
|
+
for (const d of i.dimensions || []) {
|
|
891
|
+
const evidence = Array.isArray(d.evidence) && d.evidence.length ? `\n 依据:${d.evidence.join(';')}${d.confidence ? ` 置信度:${d.confidence}` : ''}` : '';
|
|
892
|
+
const plain = d.plain ? `(${d.plain})` : '';
|
|
893
|
+
// 知识条目:「为什么这样断」+出处+可信度标注,是结论与依据之外的第三层
|
|
894
|
+
const know = (d.knowledge || [])
|
|
895
|
+
.map((k) => `\n 规则·${k.gist}〔${k.statusLabel}〕${k.detail} 出处:${k.source}${k.note ? ` 注:${k.note}` : ''}`)
|
|
896
|
+
.join('');
|
|
897
|
+
out.push(`${d.label}:${plain}${d.text}${d.advice ? ` 建议:${d.advice}` : ''}${evidence}${know}`);
|
|
898
|
+
}
|
|
899
|
+
for (const s of i.sections || []) out.push(`${s.label}:${s.text}`);
|
|
900
|
+
// 三张表:大运 / 流年 / 触发事件(纯文本对齐,工具输出也能直接读)
|
|
901
|
+
for (const t of i.tables || []) {
|
|
902
|
+
if (!Array.isArray(t.rows) || !t.rows.length) continue;
|
|
903
|
+
out.push(`【${t.title}】`);
|
|
904
|
+
out.push([t.columns.join(' | '), ...t.rows.map((r) => r.join(' | '))].join('\n'));
|
|
905
|
+
}
|
|
906
|
+
if (Array.isArray(i.followups) && i.followups.length) {
|
|
907
|
+
out.push('还可以这样追问:' + i.followups.map((q) => `「${q}」`).join(' '));
|
|
908
|
+
}
|
|
909
|
+
if (Array.isArray(i.glossary) && i.glossary.length) {
|
|
910
|
+
out.push('名词解释:' + i.glossary.map((g) => `${g.term}=${g.plain}`).join(' '));
|
|
911
|
+
}
|
|
912
|
+
// 能力边界:这段是给调用方(模型)看的上下文,不是给用户直接念的。
|
|
913
|
+
// 写全比只写标题有用——模型知道「为什么不答」,才不会自己补一个答案出来。
|
|
914
|
+
if (Array.isArray(i.limits) && i.limits.length) {
|
|
915
|
+
out.push('【本插件不回答什么】' + i.limits.map((l) => `${l.title}——${l.text}`).join('\n'));
|
|
916
|
+
}
|
|
917
|
+
if (i.disclaimer) out.push(i.disclaimer);
|
|
918
|
+
return out.join('\n');
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/** 对照时不参与计算的参数:它们只影响解析/附加模块,不影响盘面 */
|
|
922
|
+
// shadow / trace 是结果的派生展示,不是盘面口径;带进来会让影子盘回显把每组开关都算成「有变化」
|
|
923
|
+
const CALIBER_IGNORE_ARGS = ['cross', 'verifyPast', 'withInterpret', 'question', 'direction', 'ask', 'geocode', 'rectify', 'events', 'shadow', 'trace'];
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* 口径对照:同一组输入,只把某一个口径开关换成它的另一档,逐字段列出变化。
|
|
927
|
+
* 这一步不新增断法,也不改盘;返回根因字段、分类计数与一句话总结,供面板/独立页/工具共用。
|
|
928
|
+
*/
|
|
929
|
+
export function caliberCompare(args = {}, opts = {}) {
|
|
930
|
+
const system = opts.system || args.system;
|
|
931
|
+
const switches = opts.switches || CALIBER_SWITCHES[system] || [];
|
|
932
|
+
if (!switches.length) return { system, switches: [], affected: 0, total: 0 };
|
|
933
|
+
const base = { ...args };
|
|
934
|
+
for (const k of CALIBER_IGNORE_ARGS) delete base[k];
|
|
935
|
+
const rows = switches.map((sw) => {
|
|
936
|
+
const current = sw.read(base);
|
|
937
|
+
const alt = sw.alt(current);
|
|
938
|
+
let diffs = [];
|
|
939
|
+
let error = '';
|
|
940
|
+
let cur = null;
|
|
941
|
+
let altRes = null;
|
|
942
|
+
try {
|
|
943
|
+
cur = compute({ ...base, [sw.key]: current });
|
|
944
|
+
altRes = compute({ ...base, [sw.key]: alt });
|
|
945
|
+
diffs = diffObjects(cur, altRes);
|
|
946
|
+
} catch (e) {
|
|
947
|
+
error = (e && e.message) || String(e);
|
|
948
|
+
}
|
|
949
|
+
const roots = sortDiffs(diffs.filter((d) => rootOf(d.path))).map((d) => ({
|
|
950
|
+
path: d.path,
|
|
951
|
+
label: labelOf(d.path),
|
|
952
|
+
from: formatValue(d.from),
|
|
953
|
+
to: formatValue(d.to),
|
|
954
|
+
}));
|
|
955
|
+
const counts = new Map();
|
|
956
|
+
for (const d of diffs) {
|
|
957
|
+
const c = categoryOf(d.path) || '其他';
|
|
958
|
+
counts.set(c, (counts.get(c) || 0) + 1);
|
|
959
|
+
}
|
|
960
|
+
const categories = [...counts.entries()]
|
|
961
|
+
.map(([name, count]) => ({ name, count }))
|
|
962
|
+
.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name, 'zh-Hans-CN'));
|
|
963
|
+
return {
|
|
964
|
+
key: sw.key,
|
|
965
|
+
title: sw.title,
|
|
966
|
+
short: sw.short,
|
|
967
|
+
current,
|
|
968
|
+
alt,
|
|
969
|
+
currentLabel: sw.label(current),
|
|
970
|
+
altLabel: sw.label(alt),
|
|
971
|
+
total: diffs.length,
|
|
972
|
+
summary: error ? '' : summarize(diffs),
|
|
973
|
+
views: error ? [] : viewRows(system, cur, altRes),
|
|
974
|
+
roots,
|
|
975
|
+
categories,
|
|
976
|
+
error,
|
|
977
|
+
};
|
|
978
|
+
});
|
|
979
|
+
return {
|
|
980
|
+
system,
|
|
981
|
+
switches: rows,
|
|
982
|
+
affected: rows.filter((r) => !r.error && r.total > 0).length,
|
|
983
|
+
total: rows.reduce((n, r) => n + r.total, 0),
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const JU_METHODS = ['chaibu', 'chaoshen', 'zhirun'];
|
|
988
|
+
const TAROT_COUNTS = [1, 3, 5];
|
|
989
|
+
const LATE_ZI_MODES = ['same-day', 'next-day'];
|
|
990
|
+
|
|
991
|
+
/** asOf 只接受能解析成真实日期的字符串;非法值以前会静默回退到「此刻」 */
|
|
992
|
+
function checkAsOf(asOf) {
|
|
993
|
+
if (asOf === undefined || asOf === null || asOf === '') return undefined;
|
|
994
|
+
const parts = asOfParts(asOf);
|
|
995
|
+
if (!parts) throw fail('E_ASOF', `无法解析 asOf:「${asOf}」,请用 2026-10-22T16:00 这种格式`);
|
|
996
|
+
const { year, month, day } = parts;
|
|
997
|
+
if (!(month >= 1 && month <= 12) || !(day >= 1 && day <= daysInMonth(year, month))) {
|
|
998
|
+
throw fail('E_ASOF', `asOf 指向的日期不存在:${asOf}`);
|
|
999
|
+
}
|
|
1000
|
+
return parts;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function computeRaw(args = {}) {
|
|
1004
|
+
const system = args.system;
|
|
1005
|
+
if (system === 'bazi') {
|
|
1006
|
+
const dt = parseDateTime(args.datetime);
|
|
1007
|
+
const asOf = checkAsOf(args.asOf);
|
|
1008
|
+
const result = buildBazi({
|
|
1009
|
+
...dt,
|
|
1010
|
+
gender: requireGender(args.gender),
|
|
1011
|
+
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
1012
|
+
address: typeof args.address === 'string' ? args.address : undefined,
|
|
1013
|
+
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
1014
|
+
lateZi: args.lateZi !== false,
|
|
1015
|
+
trueSolar: args.trueSolar !== false,
|
|
1016
|
+
dst: args.dst !== false,
|
|
1017
|
+
// 流年表默认从「今年」起排;要复现历史结论就把基准年钉死
|
|
1018
|
+
nowYear: asOf ? asOf.year : undefined,
|
|
1019
|
+
});
|
|
1020
|
+
return { ...result, ...(asOf ? { asOf } : {}), precision: dt.precision, summary: baziText(result) };
|
|
1021
|
+
}
|
|
1022
|
+
if (system === 'ziwei') {
|
|
1023
|
+
const dt = parseDateTime(args.datetime);
|
|
1024
|
+
const asOf = checkAsOf(args.asOf);
|
|
1025
|
+
const result = buildZiwei({
|
|
1026
|
+
...dt,
|
|
1027
|
+
gender: requireGender(args.gender),
|
|
1028
|
+
leapMonth: args.leapMonth,
|
|
1029
|
+
leapAsNext: args.leapAsNext === true,
|
|
1030
|
+
...ziweiSchoolArgs(args),
|
|
1031
|
+
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
1032
|
+
address: typeof args.address === 'string' ? args.address : undefined,
|
|
1033
|
+
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
1034
|
+
trueSolar: args.trueSolar,
|
|
1035
|
+
dst: args.dst !== false,
|
|
1036
|
+
// 流月/流日/流时的基准时刻:默认本机此刻,可用 asOf 指定以便复现
|
|
1037
|
+
asOf,
|
|
1038
|
+
});
|
|
1039
|
+
return { ...result, precision: dt.precision, summary: ziweiText(result) };
|
|
1040
|
+
}
|
|
1041
|
+
if (system === 'tarot') {
|
|
1042
|
+
const count = args.count === undefined || args.count === null || args.count === '' ? 3 : Number(args.count);
|
|
1043
|
+
if (!TAROT_COUNTS.includes(count)) {
|
|
1044
|
+
throw fail('E_COUNT', `塔罗张数只能是 1 / 3 / 5,收到「${args.count}」`);
|
|
1045
|
+
}
|
|
1046
|
+
if (!isSeed(args.seed)) throw fail('E_INPUT', `seed 必须是数字,收到「${args.seed}」`);
|
|
1047
|
+
const asOf = checkAsOf(args.asOf);
|
|
1048
|
+
// 抽牌不看时间:castAt 只记录此刻(或 asOf),避免误用出生时间;给了 seed 就能复现同一次抽牌
|
|
1049
|
+
const result = drawTarot({ count, question: args.question, at: args.asOf, random: randomOf(args.seed) });
|
|
1050
|
+
if (args.seed !== undefined && args.seed !== null && args.seed !== '') result.seed = Number(args.seed);
|
|
1051
|
+
return { ...result, ...(asOf ? { asOf } : {}), summary: tarotText(result) };
|
|
1052
|
+
}
|
|
1053
|
+
if (system === 'daliuren') {
|
|
1054
|
+
const dt = parseDateTime(args.datetime);
|
|
1055
|
+
const lateZi = args.lateZi !== false;
|
|
1056
|
+
const result = buildDaliuren({ ...dt, lateZi });
|
|
1057
|
+
result.lateZi = lateZi;
|
|
1058
|
+
return { ...result, precision: dt.precision, summary: daliurenText(result) };
|
|
1059
|
+
}
|
|
1060
|
+
if (system === 'qimen') {
|
|
1061
|
+
const dt = parseDateTime(args.datetime);
|
|
1062
|
+
if (args.juMethod !== undefined && !JU_METHODS.includes(args.juMethod)) {
|
|
1063
|
+
throw fail('E_INPUT', `juMethod 只能是 chaibu / chaoshen / zhirun,收到「${args.juMethod}」`);
|
|
1064
|
+
}
|
|
1065
|
+
// 全插件只有"晚子时"这一件事,参数名不该按体系分裂:
|
|
1066
|
+
// lateZiHourMode 是奇门的历史参数名,lateZi 是八字/六壬的;两者互为别名。
|
|
1067
|
+
const mode = args.lateZiHourMode !== undefined
|
|
1068
|
+
? args.lateZiHourMode
|
|
1069
|
+
: args.lateZi === true ? 'next-day' : args.lateZi === false ? 'same-day' : undefined;
|
|
1070
|
+
if (mode !== undefined && !LATE_ZI_MODES.includes(mode)) {
|
|
1071
|
+
throw fail('E_INPUT', `lateZiHourMode 只能是 same-day / next-day,收到「${mode}」`);
|
|
1072
|
+
}
|
|
1073
|
+
const lateZiHourMode = mode === 'next-day' ? 'next-day' : 'same-day';
|
|
1074
|
+
const result = buildQimen({ ...dt, lateZiHourMode, juMethod: args.juMethod });
|
|
1075
|
+
result.lateZiHourMode = lateZiHourMode;
|
|
1076
|
+
return { ...result, summary: qimenText(result) };
|
|
1077
|
+
}
|
|
1078
|
+
if (system === 'hepan') {
|
|
1079
|
+
const partner = args.partner;
|
|
1080
|
+
if (!partner || typeof partner !== 'object') {
|
|
1081
|
+
throw fail('E_PARTNER', '合盘需要 partner:{ datetime, gender, address? },其中 partner.datetime 是对方的公历出生时间');
|
|
1082
|
+
}
|
|
1083
|
+
if (!partner.datetime) {
|
|
1084
|
+
throw fail('E_PARTNER', '缺少 partner.datetime(对方的公历出生时间),例如 partner: {"datetime":"1992-03-03T10:00","gender":"female"}');
|
|
1085
|
+
}
|
|
1086
|
+
const partner2 = args.partner2;
|
|
1087
|
+
if (partner2 !== undefined && partner2 !== null) {
|
|
1088
|
+
if (typeof partner2 !== 'object' || Array.isArray(partner2)) {
|
|
1089
|
+
throw fail('E_PARTNER', 'partner2 需要 { datetime, gender, address? };不想看三方就整段省略(null 视同未传)');
|
|
1090
|
+
}
|
|
1091
|
+
if (!partner2.datetime) {
|
|
1092
|
+
throw fail('E_PARTNER', '缺少 partner2.datetime(第三方的公历出生时间),例如 partner2: {"datetime":"1988-09-09T08:00","gender":"male"}');
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
const chart = args.chart === 'ziwei' ? 'ziwei' : 'bazi';
|
|
1096
|
+
// 性别决定双方大运/大限顺逆,两边都不能用默认值
|
|
1097
|
+
const sideOf = (src, who) => {
|
|
1098
|
+
const dt = parseDateTime(src.datetime);
|
|
1099
|
+
return {
|
|
1100
|
+
...dt,
|
|
1101
|
+
gender: requireGender(src.gender, who),
|
|
1102
|
+
trueSolar: src.trueSolar,
|
|
1103
|
+
lateZi: src.lateZi,
|
|
1104
|
+
dst: src.dst !== false,
|
|
1105
|
+
address: typeof src.address === 'string' ? src.address : undefined,
|
|
1106
|
+
longitude: typeof src.longitude === 'number' ? src.longitude : undefined,
|
|
1107
|
+
geo: src.geo && typeof src.geo === 'object' ? src.geo : undefined,
|
|
1108
|
+
};
|
|
1109
|
+
};
|
|
1110
|
+
const sideA = sideOf(args, '你(主方)');
|
|
1111
|
+
const sideB = sideOf(partner, '对方');
|
|
1112
|
+
const build = (x, y) => (chart === 'ziwei' ? buildZiweiHepan({ a: x, b: y }) : buildHepan({ a: x, b: y }));
|
|
1113
|
+
const result = build(sideA, sideB);
|
|
1114
|
+
if (partner2) {
|
|
1115
|
+
const sideC = sideOf(partner2, '第三方');
|
|
1116
|
+
result.triple = tripleOf([
|
|
1117
|
+
{ key: 'a-b', a: '你', b: '对方', result },
|
|
1118
|
+
{ key: 'a-c', a: '你', b: '第三方', result: build(sideA, sideC) },
|
|
1119
|
+
{ key: 'b-c', a: '对方', b: '第三方', result: build(sideB, sideC) },
|
|
1120
|
+
]);
|
|
1121
|
+
}
|
|
1122
|
+
return { ...result, summary: hepanText(result) };
|
|
1123
|
+
}
|
|
1124
|
+
if (system === 'meihua') {
|
|
1125
|
+
let result;
|
|
1126
|
+
try {
|
|
1127
|
+
result = buildMeihua({
|
|
1128
|
+
mode: args.mode === 'number' ? 'number' : 'time',
|
|
1129
|
+
datetime: args.datetime,
|
|
1130
|
+
upper: args.upper,
|
|
1131
|
+
lower: args.lower,
|
|
1132
|
+
changing: args.changing,
|
|
1133
|
+
});
|
|
1134
|
+
} catch (e) {
|
|
1135
|
+
throw fail('E_INPUT', `梅花起卦参数不完整:${e && e.message ? e.message : e}`);
|
|
1136
|
+
}
|
|
1137
|
+
return { ...result, summary: meihuaText(result) };
|
|
1138
|
+
}
|
|
1139
|
+
if (system === 'liuyao') {
|
|
1140
|
+
if (!isSeed(args.seed)) throw fail('E_INPUT', `seed 必须是数字,收到「${args.seed}」`);
|
|
1141
|
+
let lines;
|
|
1142
|
+
if (args.lines === undefined || args.lines === null) {
|
|
1143
|
+
// 没给爻值 = 随机摇卦;给了 seed 则同一次摇卦可复现
|
|
1144
|
+
lines = tossLines(randomOf(args.seed));
|
|
1145
|
+
} else {
|
|
1146
|
+
if (!Array.isArray(args.lines)) {
|
|
1147
|
+
throw fail('E_LINES', 'lines 必须是数组:自下而上 6 个爻值(6 老阴 / 7 少阳 / 8 少阴 / 9 老阳)');
|
|
1148
|
+
}
|
|
1149
|
+
if (args.lines.length !== 6) {
|
|
1150
|
+
// 以前这里会静默改随机摇卦:用户手抖少填一个,拿到的却是一张"看起来正常"的随机盘
|
|
1151
|
+
throw fail('E_LINES', `六爻需要正好 6 个爻值(自下而上),收到 ${args.lines.length} 个;留空则由引擎随机摇卦`);
|
|
1152
|
+
}
|
|
1153
|
+
lines = args.lines.map(Number);
|
|
1154
|
+
}
|
|
1155
|
+
let dayGanZhi;
|
|
1156
|
+
let asOf = null;
|
|
1157
|
+
// 六神按「起卦日」的日干起:给了 datetime 用它,给了 asOf 用 asOf,都没有才回落到本机当天
|
|
1158
|
+
const stamp = args.datetime ? parseDateTime(args.datetime) : checkAsOf(args.asOf);
|
|
1159
|
+
if (stamp) {
|
|
1160
|
+
if (!args.datetime) asOf = stamp;
|
|
1161
|
+
const g = ganzhiName(dayGanzhiIndex(toJDN(stamp.year, stamp.month, stamp.day)));
|
|
1162
|
+
dayGanZhi = { stem: g.stem, branch: g.branch, name: g.name };
|
|
1163
|
+
}
|
|
1164
|
+
let result;
|
|
1165
|
+
try {
|
|
1166
|
+
result = buildLiuyao({ lines, question: args.question, dayGanZhi });
|
|
1167
|
+
} catch (e) {
|
|
1168
|
+
throw fail('E_LINES', e && e.message ? e.message : e);
|
|
1169
|
+
}
|
|
1170
|
+
// 用神挂在结果上:摘要、面板、解析三层读同一份,避免各自取一次走散
|
|
1171
|
+
const ug = yongshenOf(result, { direction: args.direction, gender: args.gender });
|
|
1172
|
+
if (ug.liuqin) result.useGod = ug;
|
|
1173
|
+
// 月建按节气换月、与八字同源:从同一个 snapshot 取月支,取不到就只按日辰算
|
|
1174
|
+
let monthBranch = null;
|
|
1175
|
+
if (stamp) {
|
|
1176
|
+
try {
|
|
1177
|
+
const snap = buildSnapshot({ year: stamp.year, month: stamp.month, day: stamp.day, hour: stamp.hour, minute: stamp.minute });
|
|
1178
|
+
monthBranch = (snap.monthGanZhi || '')[1] || null;
|
|
1179
|
+
} catch {
|
|
1180
|
+
monthBranch = null;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
// 全卦逐爻旺衰不依赖用神:没指定所问之事也要能看整张卦的气
|
|
1184
|
+
const hexStrength = hexagramStrength(result, { monthBranch, dayBranch: result.dayGanZhi.branch });
|
|
1185
|
+
if (hexStrength) result.hexStrength = hexStrength;
|
|
1186
|
+
// 化进/化退的力重:与旺衰同一套五态/五关系,只说走不走得动
|
|
1187
|
+
const jtPower = jinTuiPower(result, { monthBranch, dayBranch: result.dayGanZhi.branch });
|
|
1188
|
+
if (jtPower) result.jinTuiPower = jtPower;
|
|
1189
|
+
if (ug.liuqin) {
|
|
1190
|
+
const strength = yongshenStrength(result, ug, { monthBranch, dayBranch: result.dayGanZhi.branch });
|
|
1191
|
+
if (strength) result.useGodStrength = strength;
|
|
1192
|
+
// 伏神出伏条件:飞伏生克叠月建日辰,只判条件、不出应期
|
|
1193
|
+
const chufu = fushenAccess(result, ug, { monthBranch, dayBranch: result.dayGanZhi.branch });
|
|
1194
|
+
if (chufu) result.fushenAccess = chufu;
|
|
1195
|
+
// 应期线索:只列逢值/逢冲/逢合与旬空的出空/冲空,再给快慢方向,不换算日期
|
|
1196
|
+
const yq = yingqiWindow(result, ug, { monthBranch });
|
|
1197
|
+
if (yq) result.yingqi = yq;
|
|
1198
|
+
}
|
|
1199
|
+
if (args.seed !== undefined && args.seed !== null && args.seed !== '') result.seed = Number(args.seed);
|
|
1200
|
+
return { ...result, ...(asOf ? { asOf } : {}), summary: liuyaoText(result) };
|
|
1201
|
+
}
|
|
1202
|
+
if (system === 'almanac') {
|
|
1203
|
+
const dt = parseDateTime(args.datetime);
|
|
1204
|
+
const result = buildAlmanac({ year: dt.year, month: dt.month, day: dt.day });
|
|
1205
|
+
return { ...result, summary: almanacText(result) };
|
|
1206
|
+
}
|
|
1207
|
+
if (system === 'zeri') {
|
|
1208
|
+
const candMode = args.candidates !== undefined && args.candidates !== null;
|
|
1209
|
+
// startDate 必须显式:择日要「从哪天起看」,缺了不能拿本机今天静默代填;候选日模式改用 candidates
|
|
1210
|
+
if (!candMode && (typeof args.startDate !== 'string' || !args.startDate.trim())) {
|
|
1211
|
+
throw fail('E_INPUT', '择日需要 startDate(公历 YYYY-MM-DD,起算日);可另给 days(1–60,默认 30)与 shengXiao(当事人属相)。只想比几天,改给 candidates: ["2026-12-01","2026-12-08"]');
|
|
1212
|
+
}
|
|
1213
|
+
let result;
|
|
1214
|
+
try {
|
|
1215
|
+
result = buildZeri({
|
|
1216
|
+
task: args.task,
|
|
1217
|
+
startDate: args.startDate,
|
|
1218
|
+
days: args.days,
|
|
1219
|
+
shengXiao: args.shengXiao,
|
|
1220
|
+
candidates: args.candidates,
|
|
1221
|
+
});
|
|
1222
|
+
} catch (e) {
|
|
1223
|
+
throw fail('E_INPUT', (e && e.message) || String(e));
|
|
1224
|
+
}
|
|
1225
|
+
return { ...result, summary: zeriText(result) };
|
|
1226
|
+
}
|
|
1227
|
+
if (system === 'chenggu') {
|
|
1228
|
+
const dt = parseDateTime(args.datetime);
|
|
1229
|
+
const hasTime = /[T\s]\d{1,2}:\d{1,2}/.test(String(args.datetime));
|
|
1230
|
+
const hour = Number.isInteger(args.hour) ? args.hour : hasTime ? dt.hour : null;
|
|
1231
|
+
if (hour === null || hour < 0 || hour > 23) {
|
|
1232
|
+
throw fail('E_INPUT', '称骨必须给时辰:datetime 带时分(如 1990-08-15T10:00),或额外给 hour(0–23)——缺时辰四柱不齐');
|
|
1233
|
+
}
|
|
1234
|
+
const result = buildChenggu({ year: dt.year, month: dt.month, day: dt.day, hour, minute: hasTime ? dt.minute : 0 });
|
|
1235
|
+
return { ...result, summary: chengguText(result) };
|
|
1236
|
+
}
|
|
1237
|
+
if (system === 'convert') {
|
|
1238
|
+
const mode = args.mode === 'lunar2solar' ? 'lunar2solar' : 'solar2lunar';
|
|
1239
|
+
let result;
|
|
1240
|
+
if (mode === 'lunar2solar') {
|
|
1241
|
+
if (args.lunarYear == null || args.lunarMonth == null || args.lunarDay == null) {
|
|
1242
|
+
throw fail('E_INPUT', '农历转公历需要 lunarYear / lunarMonth / lunarDay(闰月另加 isLeapMonth: true)');
|
|
1243
|
+
}
|
|
1244
|
+
result = buildConvert({
|
|
1245
|
+
mode,
|
|
1246
|
+
lunarYear: Number(args.lunarYear),
|
|
1247
|
+
lunarMonth: Number(args.lunarMonth),
|
|
1248
|
+
lunarDay: Number(args.lunarDay),
|
|
1249
|
+
isLeapMonth: args.isLeapMonth === true,
|
|
1250
|
+
});
|
|
1251
|
+
} else {
|
|
1252
|
+
const dt = parseDateTime(args.datetime);
|
|
1253
|
+
result = buildConvert({ mode, year: dt.year, month: dt.month, day: dt.day });
|
|
1254
|
+
}
|
|
1255
|
+
return { ...result, summary: convertText(result) };
|
|
1256
|
+
}
|
|
1257
|
+
if (system === 'xiaoliuren') {
|
|
1258
|
+
const dt = args.datetime ? parseDateTime(args.datetime) : null;
|
|
1259
|
+
const result = buildXiaoliuren({
|
|
1260
|
+
mode: args.mode === 'number' ? 'number' : 'time',
|
|
1261
|
+
date: typeof args.date === 'string' && args.date.trim() ? args.date : dt ? `${dt.year}-${dt.month}-${dt.day}` : undefined,
|
|
1262
|
+
hour: Number.isInteger(args.hour) ? args.hour : dt && /[T\s]\d{1,2}:\d{1,2}/.test(String(args.datetime)) ? dt.hour : undefined,
|
|
1263
|
+
num1: args.num1,
|
|
1264
|
+
num2: args.num2,
|
|
1265
|
+
num3: args.num3,
|
|
1266
|
+
question: args.question,
|
|
1267
|
+
});
|
|
1268
|
+
return { ...result, summary: xiaoliurenText(result) };
|
|
1269
|
+
}
|
|
1270
|
+
if (system === 'star') {
|
|
1271
|
+
const dt = parseDateTime(args.datetime);
|
|
1272
|
+
const result = buildStar({ year: dt.year, month: dt.month, day: dt.day });
|
|
1273
|
+
return { ...result, summary: starText(result) };
|
|
1274
|
+
}
|
|
1275
|
+
if (system === 'numerology') {
|
|
1276
|
+
const dt = parseDateTime(args.datetime);
|
|
1277
|
+
const result = buildNumerology({ year: dt.year, month: dt.month, day: dt.day });
|
|
1278
|
+
return { ...result, summary: numerologyText(result) };
|
|
1279
|
+
}
|
|
1280
|
+
if (system === 'mbti') {
|
|
1281
|
+
// 没给答案就先发问卷:让模型把 24 题交给用户逐题打分,而不是自己编一个类型
|
|
1282
|
+
if (args.answers === undefined || args.answers === null) {
|
|
1283
|
+
const paper = mbtiPaper();
|
|
1284
|
+
return { ...paper, summary: mbtiPaperText(paper) };
|
|
1285
|
+
}
|
|
1286
|
+
const result = buildMbti({ answers: args.answers });
|
|
1287
|
+
return { ...result, summary: mbtiText(result) };
|
|
1288
|
+
}
|
|
1289
|
+
if (system === 'trend') {
|
|
1290
|
+
const dt = parseDateTime(args.datetime);
|
|
1291
|
+
const result = buildTrend({
|
|
1292
|
+
...dt,
|
|
1293
|
+
gender: requireGender(args.gender),
|
|
1294
|
+
toAge: args.toAge,
|
|
1295
|
+
lateZi: args.lateZi !== false,
|
|
1296
|
+
trueSolar: args.trueSolar !== false,
|
|
1297
|
+
dst: args.dst !== false,
|
|
1298
|
+
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
1299
|
+
address: typeof args.address === 'string' ? args.address : undefined,
|
|
1300
|
+
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
1301
|
+
});
|
|
1302
|
+
return { ...result, precision: dt.precision, summary: trendText(result) };
|
|
1303
|
+
}
|
|
1304
|
+
if (system === 'fengshui') {
|
|
1305
|
+
const action = args.action === 'advise' ? 'advise' : args.action === 'xuankong' ? 'xuankong' : 'natal';
|
|
1306
|
+
if (action === 'xuankong') {
|
|
1307
|
+
const result = buildFengshui({
|
|
1308
|
+
action,
|
|
1309
|
+
sitting: args.sitting,
|
|
1310
|
+
facing: args.facing,
|
|
1311
|
+
...(args.period !== undefined ? { period: args.period } : {}),
|
|
1312
|
+
...(Number.isInteger(args.fortuneYear) ? { fortuneYear: args.fortuneYear } : {}),
|
|
1313
|
+
...(typeof args.sittingOffset === 'number' ? { sittingOffset: args.sittingOffset } : {}),
|
|
1314
|
+
...(typeof args.sittingDegree === 'number' ? { sittingDegree: args.sittingDegree } : {}),
|
|
1315
|
+
...(typeof args.facingOffset === 'number' ? { facingOffset: args.facingOffset } : {}),
|
|
1316
|
+
...(typeof args.facingDegree === 'number' ? { facingDegree: args.facingDegree } : {}),
|
|
1317
|
+
...(typeof args.fortuneDate === 'string' ? { fortuneDate: args.fortuneDate } : {}),
|
|
1318
|
+
...(typeof args.fortuneTime === 'string' ? { fortuneTime: args.fortuneTime } : {}),
|
|
1319
|
+
dst: args.dst !== false,
|
|
1320
|
+
});
|
|
1321
|
+
return { ...result, summary: fengshuiText(result) };
|
|
1322
|
+
}
|
|
1323
|
+
if (action === 'advise') {
|
|
1324
|
+
const result = buildFengshui({
|
|
1325
|
+
action,
|
|
1326
|
+
sitting: args.sitting,
|
|
1327
|
+
facing: args.facing,
|
|
1328
|
+
...(args.datetime ? parseDateTime(args.datetime) : {}),
|
|
1329
|
+
...(args.gender === 'male' || args.gender === 'female' ? { gender: args.gender } : {}),
|
|
1330
|
+
dst: args.dst !== false,
|
|
1331
|
+
});
|
|
1332
|
+
return { ...result, summary: fengshuiText(result) };
|
|
1333
|
+
}
|
|
1334
|
+
const dt = parseDateTime(args.datetime);
|
|
1335
|
+
const result = buildFengshui({
|
|
1336
|
+
action: 'natal',
|
|
1337
|
+
year: dt.year, month: dt.month, day: dt.day,
|
|
1338
|
+
gender: requireGender(args.gender),
|
|
1339
|
+
fortuneYear: Number.isInteger(args.fortuneYear) ? args.fortuneYear : undefined,
|
|
1340
|
+
dst: args.dst !== false,
|
|
1341
|
+
});
|
|
1342
|
+
return { ...result, summary: fengshuiText(result) };
|
|
1343
|
+
}
|
|
1344
|
+
if (system === 'name') {
|
|
1345
|
+
const action = args.action === 'generate' ? 'generate' : 'assess';
|
|
1346
|
+
const birth = args.datetime
|
|
1347
|
+
? parseDateTime(args.datetime)
|
|
1348
|
+
: (Number.isInteger(args.year) && Number.isInteger(args.month) && Number.isInteger(args.day)
|
|
1349
|
+
? {
|
|
1350
|
+
year: args.year, month: args.month, day: args.day,
|
|
1351
|
+
...(Number.isInteger(args.hour) ? { hour: args.hour } : {}),
|
|
1352
|
+
...(Number.isInteger(args.minute) ? { minute: args.minute } : {}),
|
|
1353
|
+
}
|
|
1354
|
+
: {});
|
|
1355
|
+
const result = buildName({
|
|
1356
|
+
action,
|
|
1357
|
+
surname: args.surname,
|
|
1358
|
+
givenName: args.givenName,
|
|
1359
|
+
limit: args.limit,
|
|
1360
|
+
charElements: args.charElements && typeof args.charElements === 'object' ? args.charElements : undefined,
|
|
1361
|
+
strokeOverrides: args.strokeOverrides && typeof args.strokeOverrides === 'object' ? args.strokeOverrides : undefined,
|
|
1362
|
+
surnameStrokes: args.surnameStrokes && typeof args.surnameStrokes === 'object' ? args.surnameStrokes : undefined,
|
|
1363
|
+
...birth,
|
|
1364
|
+
...(args.gender === 'male' || args.gender === 'female' ? { gender: args.gender } : {}),
|
|
1365
|
+
...(args.datetime ? {
|
|
1366
|
+
lateZi: args.lateZi !== false,
|
|
1367
|
+
trueSolar: args.trueSolar !== false,
|
|
1368
|
+
dst: args.dst !== false,
|
|
1369
|
+
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
1370
|
+
address: typeof args.address === 'string' ? args.address : undefined,
|
|
1371
|
+
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
1372
|
+
} : {}),
|
|
1373
|
+
});
|
|
1374
|
+
return { ...result, summary: nameText(result) };
|
|
1375
|
+
}
|
|
1376
|
+
if (!system) {
|
|
1377
|
+
throw fail('E_INPUT', '缺少 system,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、mbti(十六型人格问卷)、trend(人生 K 线)、fengshui(风水)、name(起名)、convert(公农历互转)。');
|
|
1378
|
+
}
|
|
1379
|
+
throw fail('E_SYSTEM', `未知体系「${system}」,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、mbti(十六型人格问卷)、trend(人生 K 线)、fengshui(风水)、name(起名)、convert(公农历互转)。`);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/** 黄历文本 */
|
|
1383
|
+
function almanacText(r) {
|
|
1384
|
+
const g = r.ganzhi;
|
|
1385
|
+
const jq = r.jieQi && r.jieQi.next ? ` 下一节:${r.jieQi.next.name} ${r.jieQi.next.date}` : '';
|
|
1386
|
+
const god = r.dayGod ? `值日:${r.dayGod.name}(${r.dayGod.type}·${r.dayGod.luck})` : '';
|
|
1387
|
+
const xiu = r.xiu ? `二十八宿:${r.xiu.name}宿(${r.xiu.luck}·${r.xiu.zheng}·${r.xiu.animal})` : '';
|
|
1388
|
+
const tai = r.taiShen ? `胎神:${r.taiShen}` : '';
|
|
1389
|
+
const pos = r.positions
|
|
1390
|
+
? `吉神方位:喜神${r.positions.xiShen || '—'} 财神${r.positions.caiShen || '—'} 福神${r.positions.fuShen ? r.positions.fuShen.dir : '—'} 阳贵${r.positions.yangGui ? r.positions.yangGui.dir : '—'} 阴贵${r.positions.yinGui ? r.positions.yinGui.dir : '—'}`
|
|
1391
|
+
: '';
|
|
1392
|
+
const lucky = r.lucky ? `吉配生肖:六合${r.lucky.liuhe || '—'} 三合${(r.lucky.sanhe || []).join('、') || '—'}` : '';
|
|
1393
|
+
const nayin = r.nayin && r.nayin.day ? `日柱纳音:${r.nayin.day.name}` : '';
|
|
1394
|
+
const hours = (r.hours || []).map((h) => `${h.zhi}${h.ganZhi.slice(0, 1)}·${h.shen}${h.luck === '吉' ? '吉' : '凶'}`).join(' ');
|
|
1395
|
+
return [
|
|
1396
|
+
`【黄历】${r.meta.date} ${r.meta.weekday} ${r.lunar.text}`,
|
|
1397
|
+
`干支:${g.year}年 ${g.month}月 ${g.day}日 属${g.shengXiao}`,
|
|
1398
|
+
...(r.meta.boundaryNote ? [` ${r.meta.boundaryNote}`] : []),
|
|
1399
|
+
`建除:${r.zhiXing}日 冲:${r.chong.text}${r.chong.sha ? `(${r.chong.sha})` : ''} ${god}`,
|
|
1400
|
+
[xiu, nayin, tai].filter(Boolean).join(' '),
|
|
1401
|
+
`宜:${r.yi.join('、')}`,
|
|
1402
|
+
`忌:${r.ji.join('、')}`,
|
|
1403
|
+
pos,
|
|
1404
|
+
lucky,
|
|
1405
|
+
hours ? `时辰吉凶:${hours}` : '',
|
|
1406
|
+
`节气:上一节 ${r.jieQi.prev ? `${r.jieQi.prev.name} ${r.jieQi.prev.date}` : '—'}${jq}`,
|
|
1407
|
+
r.dailyHexagram ? `每日一卦:${r.dailyHexagram.name} ${r.dailyHexagram.meaning || ''}` : '',
|
|
1408
|
+
`口径:${r.meta.scope}`,
|
|
1409
|
+
].filter(Boolean).join('\n');
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
/** 择日文本 */
|
|
1413
|
+
function zeriText(r) {
|
|
1414
|
+
const cand = r.compare && r.compare.mode === 'candidates';
|
|
1415
|
+
const head = cand
|
|
1416
|
+
? `【择日·${r.meta.task}】候选 ${r.meta.days} 天:${r.meta.candidates.join('、')}${r.meta.shengXiao ? ` 当事人属${r.meta.shengXiao}` : '(未给属相,未做冲煞排除)'}`
|
|
1417
|
+
: `【择日·${r.meta.task}】${r.meta.startDate} 起 ${r.meta.days} 天${r.meta.shengXiao ? ` 当事人属${r.meta.shengXiao}` : '(未给属相,未做冲煞排除)'}`;
|
|
1418
|
+
const top = r.days.slice().sort((a, b) => b.score - a.score).slice(0, cand ? 12 : 5)
|
|
1419
|
+
.map((d) => `· ${d.date} ${d.weekday} ${d.verdict}(${d.score}分) 宜${d.yi.slice(0, 3).join('/')} ${d.reasons.join(';') || '无特别加减分'}`);
|
|
1420
|
+
const bestLine = cand
|
|
1421
|
+
? (r.compare.best
|
|
1422
|
+
? `这一组里最优:${r.compare.best.date}(${r.compare.best.score}分,${r.compare.best.verdict})`
|
|
1423
|
+
+ (r.compare.best.ties.length > 1 ? `;同分:${r.compare.best.ties.filter((d) => d !== r.compare.best.date).join('、')}` : '')
|
|
1424
|
+
: '这一组都因冲属相被排除,没有可用的日子')
|
|
1425
|
+
: `推荐日:${r.best.length ? r.best.join('、') : '本期无(都不够推荐分)'}`;
|
|
1426
|
+
return [
|
|
1427
|
+
head,
|
|
1428
|
+
bestLine,
|
|
1429
|
+
`推荐日:${r.best.length ? r.best.join('、') : '无'}`,
|
|
1430
|
+
`可用日:${r.usable.length ? r.usable.join('、') : '无'}`,
|
|
1431
|
+
`回避日:${r.avoid.length ? r.avoid.slice(0, 6).join('、') : '无'}`,
|
|
1432
|
+
cand ? '候选日逐日:' : '逐日排序:',
|
|
1433
|
+
...top,
|
|
1434
|
+
...(cand ? [`比较口径:${r.compare.note}`] : []),
|
|
1435
|
+
`口径:${r.meta.scope}`,
|
|
1436
|
+
].join('\n');
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/** 称骨文本 */
|
|
1440
|
+
function chengguText(r) {
|
|
1441
|
+
const s = r.song || {};
|
|
1442
|
+
const lines = [
|
|
1443
|
+
`【称骨】${r.input.solar} ${r.lunar.text}`,
|
|
1444
|
+
`四项:${r.weights.year.label} ${r.weights.month.label} ${r.weights.day.label} ${r.weights.hour.label}`,
|
|
1445
|
+
`合计:${r.total.label}(${r.verdict.band})`,
|
|
1446
|
+
`歌诀(${r.total.label}):${s.text || '—'}`,
|
|
1447
|
+
`释义:${s.plain || '—'}`,
|
|
1448
|
+
`分档白话:${r.verdict.text}`,
|
|
1449
|
+
'口径:骨重与歌诀为通行本口径(各版本歌诀有异文),释义为白话自撰,属民俗娱乐参考。',
|
|
1450
|
+
];
|
|
1451
|
+
return lines.join('\n');
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/** 小六壬文本 */
|
|
1455
|
+
function xiaoliurenText(r) {
|
|
1456
|
+
return [
|
|
1457
|
+
`【小六壬·${r.meta.mode}】${r.meta.question ? `所问:${r.meta.question}` : ''}`,
|
|
1458
|
+
r.meta.originText,
|
|
1459
|
+
`落宫:${r.steps.join(' → ')}`,
|
|
1460
|
+
`断语:${r.result.palace}(${r.result.luck}) ${r.result.text}`,
|
|
1461
|
+
].join('\n');
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
/** 星座文本 */
|
|
1465
|
+
function starText(r) {
|
|
1466
|
+
return [
|
|
1467
|
+
`【星座】${r.meta.date} ${r.sign.name}(${r.sign.element}·守护星${r.sign.planet})`,
|
|
1468
|
+
`区间:${r.sign.range} 关键词:${r.sign.keywords} 契合:${r.sign.compatible}`,
|
|
1469
|
+
`当日基调:${r.daily.tone}`,
|
|
1470
|
+
r.daily.dims.map((d) => `· ${d.name}:${'★'.repeat(d.stars)}${'☆'.repeat(5 - d.stars)} ${d.text}`).join('\n'),
|
|
1471
|
+
`幸运色:${r.daily.luckyColor} 幸运数字:${r.daily.luckyNumber}`,
|
|
1472
|
+
'口径:当日基调由日期哈希确定性生成(同日同结果),纯娱乐向。',
|
|
1473
|
+
].join('\n');
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
/** 公农历互转文本 */
|
|
1477
|
+
function convertText(r) {
|
|
1478
|
+
const g = r.ganzhi;
|
|
1479
|
+
const dir = r.meta.mode === 'lunar2solar' ? '农历 → 公历' : '公历 → 农历';
|
|
1480
|
+
return [
|
|
1481
|
+
`【公农历转换·${dir}】${r.lunar.text} ⇄ ${r.solar.text} ${r.meta.weekday}`,
|
|
1482
|
+
`干支:${g.year}年 ${g.month}月 ${g.day}日 属${g.shengXiao}${g.springYear && g.springYear !== g.year ? `(春节界为${g.springYear}年)` : ''}`,
|
|
1483
|
+
...(r.meta.note ? [`注:${r.meta.note}`] : []),
|
|
1484
|
+
`口径:${r.meta.scope}`,
|
|
1485
|
+
].join('\n');
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
/** 生命数字文本 */
|
|
1489
|
+
function numerologyText(r) {
|
|
1490
|
+
return [
|
|
1491
|
+
`【生命数字】${r.meta.date}(西方数字学,与八字/紫微不可互证)`,
|
|
1492
|
+
...r.parts.map((p) => `${p.name}:${p.number}(${p.meaning.title}) ${p.meaning.keywords}\n 倾向:${p.meaning.tendency}\n 课题:${p.meaning.lesson}`),
|
|
1493
|
+
].join('\n');
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
/** 十六型人格文本 */
|
|
1497
|
+
function mbtiText(r) {
|
|
1498
|
+
const dims = r.dims.map((d) => {
|
|
1499
|
+
const side = d.picked === d.left ? d.leftSay : d.rightSay;
|
|
1500
|
+
const pct = Math.round(d.pct * 100);
|
|
1501
|
+
return `${d.left}/${d.right} 取 ${d.picked}(${d.strength} ${pct}%,净分 ${d.sum}) ${side}`;
|
|
1502
|
+
});
|
|
1503
|
+
const p = r.profile;
|
|
1504
|
+
const stack = Array.isArray(p.stack) && p.stack.length
|
|
1505
|
+
? p.stack.map((f, i) => `${i === 0 ? '主导 ' : ''}${f.code}`).join(' → ') + `\n ${p.stack.map((f) => `${f.code}:${f.say}`).join('\n ')}`
|
|
1506
|
+
: '';
|
|
1507
|
+
return [
|
|
1508
|
+
`【十六型人格】${r.type}(${p.title})· ${p.keywords} 已答 ${r.answered}/${r.total} 题`,
|
|
1509
|
+
`一句:${p.oneLine}`,
|
|
1510
|
+
'【四维】',
|
|
1511
|
+
...dims,
|
|
1512
|
+
`【优势】${p.strengths.join(';')}`,
|
|
1513
|
+
`【留意】${p.blindspots.join(';')}`,
|
|
1514
|
+
`【课题】${p.growth}`,
|
|
1515
|
+
`【认知功能】${stack}`,
|
|
1516
|
+
`【工作】${p.work}`,
|
|
1517
|
+
`【关系】${p.love}`,
|
|
1518
|
+
`【学习】${p.study}`,
|
|
1519
|
+
`注:${r.note}`,
|
|
1520
|
+
].join('\n');
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/** 问卷文本(还没答题时) */
|
|
1524
|
+
function mbtiPaperText(paper) {
|
|
1525
|
+
return [
|
|
1526
|
+
`【十六型人格问卷】${paper.total} 题,每题按 1(完全不像我)到 5(很像我)打分`,
|
|
1527
|
+
...paper.questions.map((q) => `${q.no}. ${q.text}`),
|
|
1528
|
+
`请把 ${paper.total} 个分数按顺序交给 answers(如 [4,2,5,3,…]);可以跳过某些题(填 null),至少答 12 题。`,
|
|
1529
|
+
`注:${paper.note}`,
|
|
1530
|
+
].join('\n');
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
/** 人生 K 线文本 */
|
|
1534
|
+
function trendText(r) {
|
|
1535
|
+
const s = r.meta.strength;
|
|
1536
|
+
const dm = r.meta.dayMaster;
|
|
1537
|
+
const dmText = dm && typeof dm === 'object' ? `${dm.stem}(${dm.element})` : String(dm || '');
|
|
1538
|
+
return [
|
|
1539
|
+
`【人生 K 线】${r.meta.birth} ${r.meta.gender} 日主 ${dmText} 旺衰 ${s.level}(${s.score})`,
|
|
1540
|
+
r.meta.boundaryNote ? `※ ${r.meta.boundaryNote}` : '',
|
|
1541
|
+
`【大运分段】${r.segments.map((g) => `${g.ganZhi} ${g.startAge}-${g.endAge}岁`).join(' ')}`,
|
|
1542
|
+
`【走势】${Array.isArray(r.sparkline) ? r.sparkline.join(' ') : r.sparkline}`,
|
|
1543
|
+
...(r.highlights && r.highlights.best && r.highlights.best.length
|
|
1544
|
+
? [`【关键年】最佳 ${r.highlights.best.join(';')}${r.highlights.worst && r.highlights.worst.length ? `\n最需留意 ${r.highlights.worst.join(';')}` : ''}`]
|
|
1545
|
+
: []),
|
|
1546
|
+
`※ ${r.disclaimer}`,
|
|
1547
|
+
].filter(Boolean).join('\n');
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/** 风水文本 */
|
|
1551
|
+
function fengshuiText(r) {
|
|
1552
|
+
if (r.meta.action === 'xuankong') {
|
|
1553
|
+
const p = r.meta.period;
|
|
1554
|
+
const cell = (palace) => {
|
|
1555
|
+
const x = r.palaces.find((y) => y.palace === palace);
|
|
1556
|
+
return `${palace}${x.yun}/${x.shan}/${x.xiang}`;
|
|
1557
|
+
};
|
|
1558
|
+
const row = (names) => names.map(cell).join(' ');
|
|
1559
|
+
const w = r.wangShanWangXiang;
|
|
1560
|
+
const jian = r.meta.jianXiang;
|
|
1561
|
+
const tf = r.timeFlying;
|
|
1562
|
+
const tfLine = tf
|
|
1563
|
+
? `【年月日时】${[['year', '年'], ['month', '月'], ['day', '日'], ['hour', '时']]
|
|
1564
|
+
.filter(([k]) => tf[k]).map(([k, label]) => `${label}${tf[k].centerName}${tf[k].fly ? `·${tf[k].fly}` : ''}`).join(' ')}${tf.meta && tf.meta.note ? `(${tf.meta.note})` : ''}`
|
|
1565
|
+
: '';
|
|
1566
|
+
return [
|
|
1567
|
+
`【玄空】${r.meta.periodLabel}${jian && jian.active ? '·替卦' : '·下卦'} 坐 ${r.meta.sitting} 向 ${r.meta.facing}`,
|
|
1568
|
+
...(jian && jian.active ? [`【替卦】${jian.note}`] : []),
|
|
1569
|
+
`【入中】山盘 ${r.meta.shanCenter.star}${r.meta.shanCenter.name}(${r.meta.shanCenter.fly},${r.meta.shanCenter.basis}${r.meta.shanCenter.mode === '替卦' ? `;原运星 ${r.meta.shanCenter.original}${r.meta.shanCenter.replaced ? ',已替' : ',用替而未替'}` : ''});向盘 ${r.meta.xiangCenter.star}${r.meta.xiangCenter.name}(${r.meta.xiangCenter.fly},${r.meta.xiangCenter.basis}${r.meta.xiangCenter.mode === '替卦' ? `;原运星 ${r.meta.xiangCenter.original}${r.meta.xiangCenter.replaced ? ',已替' : ',用替而未替'}` : ''})`,
|
|
1570
|
+
`【九宫】${row(['巽', '离', '坤'])}|${row(['震', '中', '兑'])}|${row(['艮', '坎', '乾'])}`,
|
|
1571
|
+
`【当运】山星${p}到${r.dangYun.shan.text};向星${p}到${r.dangYun.xiang.text}`,
|
|
1572
|
+
`【表法】${w.byTable === null ? '替卦不套正向表;' : ''}${w.table.length ? w.table.join('、') : '无'};${w.note}`,
|
|
1573
|
+
`【合十】山星合十 ${r.heshi.shan.join('、') || '无'};向星合十 ${r.heshi.xiang.join('、') || '无'}`,
|
|
1574
|
+
...(tfLine ? [tfLine] : []),
|
|
1575
|
+
...(r.meta.note ? [`※ ${r.meta.note}`] : []),
|
|
1576
|
+
`※ ${r.disclaimer}`,
|
|
1577
|
+
].join('\n');
|
|
1578
|
+
}
|
|
1579
|
+
if (r.meta.action === 'advise') {
|
|
1580
|
+
const match = r.mingMatch
|
|
1581
|
+
? [`【命宅配】${r.mingMatch.text}`]
|
|
1582
|
+
: [`※ ${r.meta.birthNote || '未提供生辰,未做命宅配。'}`];
|
|
1583
|
+
return [
|
|
1584
|
+
`【宅卦】坐 ${r.meta.sitting} 向 ${r.meta.facing} ${r.meta.houseGua}宅(${r.meta.houseGroup})`,
|
|
1585
|
+
`【八星】${r.houseStars.map((x) => `${x.star}·${x.direction}${x.palace ? `(${x.palace})` : ''}`).join(' ')}`,
|
|
1586
|
+
`【门】${r.layout.door}`,
|
|
1587
|
+
`【灶】${r.layout.stove}`,
|
|
1588
|
+
`【床】${r.layout.bed}`,
|
|
1589
|
+
`【厕】${r.layout.toilet}`,
|
|
1590
|
+
...match,
|
|
1591
|
+
`※ ${r.disclaimer}`,
|
|
1592
|
+
].join('\n');
|
|
1593
|
+
}
|
|
1594
|
+
const g = r.mingGua;
|
|
1595
|
+
return [
|
|
1596
|
+
`【命卦】${g.num}${g.gua}(${g.group}) 方位 ${g.direction}`,
|
|
1597
|
+
`【四吉】${r.luckyDirections.map((x) => `${x.star}·${x.direction}${x.palace ? `(${x.palace})` : ''}`).join(' ')}`,
|
|
1598
|
+
`【四凶】${r.unluckyDirections.map((x) => `${x.star}·${x.direction}`).join(' ')}`,
|
|
1599
|
+
...(r.yearlyFlying
|
|
1600
|
+
? [`【${r.meta.fortuneYear} 年九宫飞星】中宫 ${r.yearlyFlying.centerName};${r.yearlyFlying.palaces.filter((p) => p.palace !== '中').map((p) => `${p.palace}${p.name}`).join(' ')}`]
|
|
1601
|
+
: [`※ ${r.meta.note || ''}`]),
|
|
1602
|
+
`※ ${r.disclaimer}`,
|
|
1603
|
+
].filter(Boolean).join('\n');
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
/** 起名文本 */
|
|
1607
|
+
function nameText(r) {
|
|
1608
|
+
if (r.action === 'generate') {
|
|
1609
|
+
return [
|
|
1610
|
+
`【候选名】姓 ${r.surname}(${r.gender},喜用 ${r.bazi.favorable.join('、')})${r.bazi.basis}`,
|
|
1611
|
+
...r.candidates.map((c) => {
|
|
1612
|
+
const wu = c.wuge && c.wuge.available ? ` 五格 ${c.wuge.summary}` : '';
|
|
1613
|
+
const ph = c.phonetics && c.phonetics.available ? ` 音韵 ${c.phonetics.level}(${c.phonetics.tonePattern})` : '';
|
|
1614
|
+
return `${c.rank}. ${c.name} ${c.chars.map((x) => `${x.char}(${x.element})${x.meaning}`).join(' ')}${wu}${ph}`;
|
|
1615
|
+
}),
|
|
1616
|
+
...r.notes.map((n) => `※ ${n}`),
|
|
1617
|
+
].join('\n');
|
|
1618
|
+
}
|
|
1619
|
+
const srcLabel = (c) => (c.source === 'lib' ? '字库' : c.source === 'user' ? '用户指定' : '未收录');
|
|
1620
|
+
const charLine = (c) => {
|
|
1621
|
+
const el = c.element === '未收录' ? '未收录(不猜五行)' : `${c.element}(${srcLabel(c)})`;
|
|
1622
|
+
const fit = c.favorable === true ? '合喜用' : c.favorable === false ? '不合喜用' : '不判';
|
|
1623
|
+
const sug = c.suggest && c.suggest.length
|
|
1624
|
+
? ` 可换:${c.suggest.map((x) => `${x.char}(${x.element})`).join('、')}`
|
|
1625
|
+
: '';
|
|
1626
|
+
return `· ${c.char}:${el}|${fit}|${c.meaning}${sug}`;
|
|
1627
|
+
};
|
|
1628
|
+
const wu = r.wuge;
|
|
1629
|
+
const wugeLine = wu && wu.available
|
|
1630
|
+
? `【五格】${wu.strokes.map((x) => `${x.char}${x.strokes}画`).join('+')}|${wu.summary}|三才 ${wu.sancai.text}`
|
|
1631
|
+
: `【五格】${wu ? wu.reason : '未算'}`;
|
|
1632
|
+
const ph = r.phonetics;
|
|
1633
|
+
const phLine = ph && ph.available
|
|
1634
|
+
? `【音韵】${ph.chars.map((x) => `${x.char}${x.py}${x.tone}`).join(' ')}|声调 ${ph.tonePattern}|${ph.level}${ph.issues.length ? `:${ph.issues.map((i) => i.text).join(';')}` : ''}`
|
|
1635
|
+
: `【音韵】${ph ? ph.reason : '未算'}`;
|
|
1636
|
+
return [
|
|
1637
|
+
`【名字评估】${r.fullName}`,
|
|
1638
|
+
...r.chars.map(charLine),
|
|
1639
|
+
r.bazi ? `【八字】日主 ${r.bazi.dayMaster && r.bazi.dayMaster.stem ? `${r.bazi.dayMaster.stem}(${r.bazi.dayMaster.element})` : r.bazi.dayMaster},旺衰 ${r.bazi.strength.level}(${r.bazi.strength.score}),${r.bazi.basis}` : '',
|
|
1640
|
+
wugeLine,
|
|
1641
|
+
phLine,
|
|
1642
|
+
`【结论】${r.verdict}`,
|
|
1643
|
+
...r.notes.map((n) => `※ ${n}`),
|
|
1644
|
+
].filter(Boolean).join('\n');
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
/** 给模型的紧凑文本(去掉大字段,保留摘要;带解析时附上解析) */
|
|
1648
|
+
export function renderText(result) {
|
|
1649
|
+
const base = result.summary ?? JSON.stringify(result, null, 2);
|
|
1650
|
+
const parts = [base];
|
|
1651
|
+
if (result.shadow && result.shadow.id) {
|
|
1652
|
+
const s = result.shadow;
|
|
1653
|
+
parts.push(`【影子盘】${s.id}(同参数同 ID;版本 ${s.version})`
|
|
1654
|
+
+ `\n 基础指纹 ${s.baseId}(不含版本,跨版本可比对)`
|
|
1655
|
+
+ (s.reproducible === false ? `\n ⚠ 不可复算:${s.reason}` : '')
|
|
1656
|
+
+ (s.reproducible === true && s.asOfPinned === false && (s.system === 'bazi' || s.system === 'ziwei') ? '\n 提示:未固定 asOf,流年/流月随「此刻」变化;要比对请传 asOf。' : ''));
|
|
1657
|
+
}
|
|
1658
|
+
// 一句话改参的回显:改了哪些、没认哪些,都摆出来让人核对,而不是悄悄按新参数排
|
|
1659
|
+
if (result.revision && ((result.revision.applied || []).length || (result.revision.ignored || []).length)) {
|
|
1660
|
+
const rev = result.revision;
|
|
1661
|
+
const rows = (rev.applied || []).map((a) => `${a.label}:${a.fromDisplay == null ? '(未设)' : a.fromDisplay} → ${a.display}`);
|
|
1662
|
+
parts.push(`【改参】${rev.text}${rows.length ? `\n ${rows.join(';')}` : '\n 没有识别到可改的参数,仍按原参数排盘'}`
|
|
1663
|
+
+ ((rev.ignored || []).length ? `\n 未处理:${rev.ignored.join(';')}` : ''));
|
|
1664
|
+
}
|
|
1665
|
+
// 句柄放进文本:模型看到的是这段摘要,不给它就没法在下一轮只传增量
|
|
1666
|
+
if (typeof result.handle === 'string' && result.handle) {
|
|
1667
|
+
parts.push(`【排盘句柄】${result.handle}\n 追问同一张盘:传 handle + 增量参数(asOf / direction / question,或换 system 看另一套盘),不必重报出生信息。`);
|
|
1668
|
+
}
|
|
1669
|
+
if (Array.isArray(result.trace) && result.trace.length) {
|
|
1670
|
+
parts.push(`【推导链】${SYSTEM_LABEL[result.system] || result.system} · ${result.trace.length} 层\n`
|
|
1671
|
+
+ result.trace.map((t, i) => `${i + 1}. ${t.field}|${t.result}\n 规则:${t.rule} 出处:${t.source}`).join('\n'));
|
|
1672
|
+
}
|
|
1673
|
+
if (result.interpret) {
|
|
1674
|
+
const extra = interpretText(result.interpret);
|
|
1675
|
+
if (extra) parts.push(extra);
|
|
1676
|
+
}
|
|
1677
|
+
if (result.cross) parts.push(`【八字 × 紫微 交叉验证】\n${crossText(result.cross)}`);
|
|
1678
|
+
if (result.caliber && Array.isArray(result.caliber.switches) && result.caliber.switches.length) {
|
|
1679
|
+
const c = result.caliber;
|
|
1680
|
+
parts.push('【口径对照】同一组输入,只换一个口径(不判哪套更准,也不改盘面)\n'
|
|
1681
|
+
+ c.switches.map((s) => {
|
|
1682
|
+
if (s.error) return `· ${s.title}:未能对照(${s.error})`;
|
|
1683
|
+
if (!s.total) return `· ${s.title}:${s.currentLabel} → ${s.altLabel},本盘无变化`;
|
|
1684
|
+
const changed = (Array.isArray(s.views) ? s.views : []).filter((v) => v.changed);
|
|
1685
|
+
const side = changed.length
|
|
1686
|
+
? `\n 并排:${changed.map((v) => `${v.label} ${v.current} → ${v.alt}`).join(';')}`
|
|
1687
|
+
: '';
|
|
1688
|
+
return `· ${s.title}:${s.currentLabel} → ${s.altLabel}\n ${s.summary}${side}`;
|
|
1689
|
+
}).join('\n'));
|
|
1690
|
+
}
|
|
1691
|
+
if (result.followup) {
|
|
1692
|
+
const f = result.followup;
|
|
1693
|
+
parts.push(`【追问】${f.question}\n${f.answer}${f.evidence && f.evidence.length ? `\n 依据:${f.evidence.join(';')} 置信度:${f.confidence}` : ''}`);
|
|
1694
|
+
}
|
|
1695
|
+
// 验前事:过去几年的信号(只读);贴合度由用户在面板里勾选后算,这里只列事实
|
|
1696
|
+
if (result.past && Array.isArray(result.past.rows) && result.past.rows.length) {
|
|
1697
|
+
parts.push(`【验前事】过去 ${result.past.years} 年的流年信号(只读,不改盘)\n` +
|
|
1698
|
+
result.past.rows.map((r) => `· ${r.year} ${r.ganzhi} · ${r.theme}(${r.tier}${r.tenGod ? `,${r.tenGod}` : ''})`).join('\n') +
|
|
1699
|
+
'\n用法:在面板里勾「准 / 不准 / 不确定」后算贴合度;贴合度低时先核对出生时间、时区与晚子时口径。');
|
|
1700
|
+
}
|
|
1701
|
+
// 生时校正:候选时辰的排序与命中数(固定规则,只缩小范围)
|
|
1702
|
+
if (result.rectify && Array.isArray(result.rectify.candidates)) {
|
|
1703
|
+
const rc = result.rectify;
|
|
1704
|
+
parts.push(`【生时校正】候选时辰 ${rc.candidates.length} 个(固定规则打分,只缩小范围)\n` +
|
|
1705
|
+
rc.candidates.slice(0, 8).map((c) => `· ${c.label} 时柱 ${c.hourGanzhi} / 日柱 ${c.dayGanzhi} · 命中 ${c.hits}/${c.total}`).join('\n') +
|
|
1706
|
+
`\n${rc.note}`);
|
|
1707
|
+
}
|
|
1708
|
+
return parts.filter(Boolean).join('\n\n');
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/** 对外暴露:地址解析(异步,含联网) */
|
|
1712
|
+
export { geocodeAddress, resolveAddress };
|
|
1713
|
+
|
|
1714
|
+
export { buildBazi, buildLiuyao, tossLines };
|
|
1715
|
+
export { followup, askPrompt };
|
|
1716
|
+
export { pastSignals, verifyFit, rectifyBirth };
|
|
1717
|
+
export { crossCheck, crossText, crossSection };
|
|
1718
|
+
export { buildAlmanac, buildZeri, buildChenggu, buildXiaoliuren, buildStar, buildNumerology, buildConvert };
|
|
1719
|
+
export { buildTrend, buildFengshui, buildName };
|
|
1720
|
+
export { buildTrace, traceMarkdown };
|
|
1721
|
+
export { encodeHandle, decodeHandle, resolveHandle };
|
|
1722
|
+
// C3 对话桥替代:深链编解码、表单还原、三档复制包(面板与独立页共用一份)
|
|
1723
|
+
export { askLink, parseAskLink, formOf, restNote, askPackages };
|
|
1724
|
+
// 问事解读提示:把一张盘打包成「先答他所问、不堆术语、回到现实核对」的提示,供面板一键复制
|
|
1725
|
+
// (本仓 esbuild 不接受 `export {} from`,先静态 import 再 export)
|
|
1726
|
+
import { buildEventPrompt, ASK_KINDS } from './event-prompt.js';
|
|
1727
|
+
export { buildEventPrompt, ASK_KINDS };
|
|
1728
|
+
|
|
1729
|
+
|
|
1730
|
+
|
|
1731
|
+
|
|
1732
|
+
|
|
1733
|
+
|
|
1734
|
+
|
|
1735
|
+
|
|
1736
|
+
|