dsh-metaphysics 0.62.1 → 0.62.3
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 +63 -1
- package/README.md +2 -2
- package/client.almanac.js +69 -65
- package/client.bazi.js +122 -118
- package/client.chenggu.js +69 -65
- package/client.convert.js +71 -67
- package/client.daliuren.js +119 -115
- package/client.fengshui.js +68 -64
- package/client.hepan.js +123 -119
- package/client.js +192 -192
- package/client.liuyao.js +121 -117
- package/client.mbti.js +69 -65
- package/client.name.js +69 -65
- package/client.numerology.js +68 -64
- package/client.qimen.js +124 -120
- package/client.star.js +69 -65
- package/client.tarot.js +120 -116
- package/client.trend.js +113 -109
- package/client.xiaoliuren.js +78 -74
- package/client.zeri.js +68 -64
- package/client.ziwei.js +123 -119
- package/docs/assets/verification-badge.svg +6 -6
- package/engine/almanac.js +10 -6
- package/engine/bazi.js +10 -6
- package/engine/bool.js +195 -0
- package/engine/calendar.js +10 -2
- package/engine/chenggu.js +2 -1
- package/engine/convert.js +2 -1
- package/engine/daliuren.js +2 -1
- package/engine/diff.js +5 -3
- package/engine/fengshui.js +33 -12
- package/engine/geju.js +16 -3
- package/engine/geo.js +2 -1
- package/engine/hepan.js +4 -3
- package/engine/index.js +202 -72
- package/engine/liuyao.js +24 -5
- package/engine/mbti.js +6 -1
- package/engine/meihua.js +21 -7
- package/engine/name.js +20 -12
- package/engine/random.js +17 -3
- package/engine/rectify.js +4 -3
- package/engine/tarot.js +22 -8
- package/engine/trace.js +2 -1
- package/engine/trend.js +7 -3
- package/engine/xiaoliuren.js +2 -1
- package/engine/ziwei.js +4 -3
- package/index.js +367 -42
- package/package.json +1 -1
- package//345/221/275/347/220/206/346/216/222/347/233/230.html +629 -160
package/engine/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { buildZiweiHepan } from './ziwei-hepan.js';
|
|
|
10
10
|
import { buildQimen, buildSnapshot } from './qimen.js';
|
|
11
11
|
import { geocodeAddress, resolveAddress } from './geo.js';
|
|
12
12
|
import { toJDN, ganzhiName, dayGanzhiIndex } from './calendar.js';
|
|
13
|
+
import { bool, canonicalFlags, canonicalNumbers } from './bool.js';
|
|
13
14
|
import { randomOf, isSeed } from './random.js';
|
|
14
15
|
import { encodeHandle, decodeHandle, resolveHandle, pruneForeignKeys, ownedKeys } from './handle.js';
|
|
15
16
|
import { askLink, parseAskLink, formOf, restNote, askPackages } from './bridge.js';
|
|
@@ -35,7 +36,7 @@ import { buildFengshui } from './fengshui.js';
|
|
|
35
36
|
import { buildName } from './name.js';
|
|
36
37
|
import { buildTrace, traceMarkdown, SYSTEM_LABEL } from './trace.js';
|
|
37
38
|
import { fail } from './fail.js';
|
|
38
|
-
import { assertCivil } from './validate.js';
|
|
39
|
+
import { assertCivil, MIN_YEAR, MAX_YEAR } from './validate.js';
|
|
39
40
|
import { parseDateTime, daysInMonth } from './datetime.js';
|
|
40
41
|
|
|
41
42
|
export { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf };
|
|
@@ -45,6 +46,25 @@ const pad = (n) => String(n).padStart(2, '0');
|
|
|
45
46
|
|
|
46
47
|
const GENDERS = ['male', 'female'];
|
|
47
48
|
|
|
49
|
+
/**
|
|
50
|
+
* 时刻会改变盘面、且允许只给日期的体系:那时按 00:00 排,必须当场说清。
|
|
51
|
+
* 黄历 / 择日 / 公农历转换只以「日」为输入,缺时刻是正常用法;
|
|
52
|
+
* 六爻取日辰与月建定六神、旬空,不看钟点;梅花数字起卦也不看时间——都在名单外。
|
|
53
|
+
* 称骨与小六壬缺时辰直接报错(时辰不可代填),同样不需要这句提示。
|
|
54
|
+
*/
|
|
55
|
+
const HOUR_MATTERS = new Set(['bazi', 'ziwei', 'hepan', 'trend', 'meihua', 'daliuren', 'qimen', 'name']);
|
|
56
|
+
/** 缺时刻时那句提示里要说清的「按什么排」,以及该怎么补(hourUnknown 只对八字/趋势有定义) */
|
|
57
|
+
const NO_TIME_NOTE = {
|
|
58
|
+
bazi: { what: '时柱', advice: '知道大概几点就补上,不知道就传 hourUnknown:true(只排三柱)' },
|
|
59
|
+
ziwei: { what: '命宫与生时星曜', advice: '紫微排不出三柱,必须补上出生时刻' },
|
|
60
|
+
hepan: { what: '两侧时柱', advice: '两侧都要出生时刻,合盘才有意义' },
|
|
61
|
+
trend: { what: '时柱与起运', advice: '知道大概几点就补上,不知道就传 hourUnknown:true(按日估算)' },
|
|
62
|
+
meihua: { what: '起卦时刻', advice: '时间起卦要用真实起念时刻,请补 HH:mm' },
|
|
63
|
+
daliuren: { what: '起课时辰(月将与贵人)', advice: '请补起课时刻 HH:mm' },
|
|
64
|
+
qimen: { what: '起局时辰(旬首与局数)', advice: '请补起局时刻 HH:mm' },
|
|
65
|
+
name: { what: '时柱', advice: '请补出生时刻 HH:mm' },
|
|
66
|
+
};
|
|
67
|
+
|
|
48
68
|
/**
|
|
49
69
|
* 性别是必填项:它决定大运/大限的顺逆,缺省按某一方排等于给出一张错盘。
|
|
50
70
|
* 之前用 `=== 'female' ? 'female' : 'male'` 兜底,静默把未填性别的人按男命排。
|
|
@@ -512,14 +532,14 @@ function shadowPerson(person = {}) {
|
|
|
512
532
|
if (person.gender) out.gender = String(person.gender).toLowerCase();
|
|
513
533
|
if (person.longitude !== undefined) out.longitude = num6(person.longitude);
|
|
514
534
|
else if (person.address) out.address = String(person.address).trim();
|
|
515
|
-
if (person.lateZi !== undefined) out.lateZi = person.lateZi
|
|
516
|
-
if (person.trueSolar !== undefined) out.trueSolar = person.trueSolar
|
|
517
|
-
if (person.dst !== undefined) out.dst = person.dst
|
|
535
|
+
if (person.lateZi !== undefined) out.lateZi = bool(person.lateZi, true);
|
|
536
|
+
if (person.trueSolar !== undefined) out.trueSolar = bool(person.trueSolar, true);
|
|
537
|
+
if (person.dst !== undefined) out.dst = bool(person.dst, true);
|
|
518
538
|
return out;
|
|
519
539
|
}
|
|
520
540
|
|
|
521
541
|
function qimenLateMode(args) {
|
|
522
|
-
if (args.lateZiHourMode === 'next-day' || args.lateZi
|
|
542
|
+
if (args.lateZiHourMode === 'next-day' || bool(args.lateZi)) return 'next-day';
|
|
523
543
|
return 'same-day';
|
|
524
544
|
}
|
|
525
545
|
|
|
@@ -531,8 +551,8 @@ function shadowParams(args, result) {
|
|
|
531
551
|
civil: input.civil,
|
|
532
552
|
gender: input.gender,
|
|
533
553
|
longitude: num6(input.longitude),
|
|
534
|
-
lateZi: input.lateZi
|
|
535
|
-
trueSolar: input.trueSolar
|
|
554
|
+
lateZi: bool(input.lateZi, true),
|
|
555
|
+
trueSolar: bool(input.trueSolar, true),
|
|
536
556
|
dst: !!(input.dst && input.dst.applied),
|
|
537
557
|
};
|
|
538
558
|
}
|
|
@@ -542,9 +562,9 @@ function shadowParams(args, result) {
|
|
|
542
562
|
gender: input.gender,
|
|
543
563
|
longitude: num6(input.longitude),
|
|
544
564
|
leapMonth: input.leapMonth || 'split',
|
|
545
|
-
leapAsNext: input.leapAsNext
|
|
565
|
+
leapAsNext: bool(input.leapAsNext),
|
|
546
566
|
schools: input.schools || ZIWEI_SCHOOL_DEFAULTS,
|
|
547
|
-
trueSolar: input.trueSolar
|
|
567
|
+
trueSolar: bool(input.trueSolar, true),
|
|
548
568
|
dst: !!(input.dst && input.dst.applied),
|
|
549
569
|
};
|
|
550
570
|
}
|
|
@@ -630,7 +650,10 @@ function normalizeAsOf(value) {
|
|
|
630
650
|
const parts = asOfParts(s);
|
|
631
651
|
if (!parts) return s; // 非法值留给 checkAsOf 报错,指纹不抢答
|
|
632
652
|
const p2 = (n) => String(n).padStart(2, '0');
|
|
633
|
-
|
|
653
|
+
// 年份也要补到 4 位:asOfParts 的正则只认四位年份,0000 原样拼成 '0-01-01T12:00'
|
|
654
|
+
// 会在 checkAsOf 里被当成格式错误,报出「无法解析 asOf:「0-01-01T12:00」」这种看不懂的话。
|
|
655
|
+
const y4 = String(parts.year).padStart(4, '0');
|
|
656
|
+
return `${y4}-${p2(parts.month)}-${p2(parts.day)}T${p2(parts.hour)}:${p2(parts.minute)}`;
|
|
634
657
|
}
|
|
635
658
|
|
|
636
659
|
/**
|
|
@@ -747,6 +770,14 @@ export function compute(input = {}) {
|
|
|
747
770
|
// 称骨句柄里的 hour 会跟到八字盘上,被工具层当成误传报错。
|
|
748
771
|
args = pruneForeignKeys(args, ownedKeys(input, rev.patch));
|
|
749
772
|
}
|
|
773
|
+
// 开关归一:排盘读的、句柄记的、指纹比的是同一份真布尔。
|
|
774
|
+
// 少了这一刀,`trueSolar:"false"` 会排出「开真太阳时」的盘而句柄写 false——
|
|
775
|
+
// 同一张盘两个身份,影子盘对拍与深链回放都会错过。
|
|
776
|
+
args = canonicalFlags(args);
|
|
777
|
+
// 数字归一:`count:"3"` 与 `count:3` 是同一张盘,句柄与指纹也只能有一个身份。
|
|
778
|
+
// 放在开关之后、asOf / 经度 / verifyPast 整形之前:后面几处校验读到的是真数字,
|
|
779
|
+
// 「收到「9」」这种字面看着没错的报错也就不会再出现。
|
|
780
|
+
args = canonicalNumbers(args);
|
|
750
781
|
// asOf 在入口就归一成 canonical 串(去首尾空格、只给日期补 12:00):
|
|
751
782
|
// 排盘、句柄、影子盘指纹读同一份。以前只有指纹 trim,句柄照抄原文,
|
|
752
783
|
// '2030-01-01' 与 '2030-01-01T12:00' 出的明明是同一张盘,却各得一个 handle、一个 baseId。
|
|
@@ -821,6 +852,36 @@ ${r.summary}`;
|
|
|
821
852
|
r.summary = `时柱暂缺:起运与大运按日估算,仅供参考
|
|
822
853
|
${r.summary}`;
|
|
823
854
|
}
|
|
855
|
+
// 只给日期、没给时刻:parseDateTime 会把时分补成 00:00,于是照常排出完整四柱/起好局,
|
|
856
|
+
// 而调用方以为「日期就够了」。这类静默默认正是最该说出来的一种:盘面上看不出任何异常。
|
|
857
|
+
// 梅花数字起卦只读 upper/lower,datetime 不参与取卦,不该被这句提示打扰。
|
|
858
|
+
const ignoresClock = r && r.system === 'meihua' && args.mode === 'number';
|
|
859
|
+
const datetimeDateOnly = typeof args.datetime === 'string' && args.datetime.trim() !== ''
|
|
860
|
+
&& !/[T\s]\d{1,2}:\d{1,2}/.test(args.datetime.trim());
|
|
861
|
+
// name 还支持 year/month/day(+hour/minute) 分开给生辰:只给年月日时,
|
|
862
|
+
// buildName 内部同样按 00:00 占位,不能因为没走 datetime 就绕过这句提示。
|
|
863
|
+
const nameDateOnly = r && r.system === 'name' && !(typeof args.datetime === 'string' && args.datetime.trim())
|
|
864
|
+
&& Number.isInteger(args.year) && Number.isInteger(args.month) && Number.isInteger(args.day)
|
|
865
|
+
&& !Number.isInteger(args.hour);
|
|
866
|
+
if (r && !ignoresClock && !r.hourUnknown && !r.timeMissing && HOUR_MATTERS.has(r.system)
|
|
867
|
+
&& (datetimeDateOnly || nameDateOnly) && r.summary) {
|
|
868
|
+
r.timeMissing = true;
|
|
869
|
+
const note = NO_TIME_NOTE[r.system] || { what: '时刻', advice: '请补上时刻 HH:mm' };
|
|
870
|
+
r.summary = `⚠ 只给了日期、没有时刻:${note.what}按 00:00 暂算,与真实结果可能整档不同;${note.advice}\n${r.summary}`;
|
|
871
|
+
}
|
|
872
|
+
// 合盘的另一方同样是「只给日期」的静默 00:00,主方有提示、对方没有就等于只报了一半
|
|
873
|
+
if (r && r.system === 'hepan' && r.summary) {
|
|
874
|
+
const missing = [];
|
|
875
|
+
for (const [who, label] of [['partner', '对方'], ['partner2', '第三方']]) {
|
|
876
|
+
const person = args[who];
|
|
877
|
+
const when = person && typeof person === 'object' ? person.datetime : undefined;
|
|
878
|
+
if (typeof when === 'string' && !/[T\s]\d{1,2}:\d{1,2}/.test(when.trim())) missing.push(label);
|
|
879
|
+
}
|
|
880
|
+
if (missing.length) {
|
|
881
|
+
r.timeMissing = true;
|
|
882
|
+
r.summary = `⚠ 只给了日期、没有时刻:${missing.join('、')}的时柱按 00:00 暂算,与真实结果可能整档不同;请补出生时刻 HH:mm\n${r.summary}`;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
824
885
|
// 显式要求时才附加解析模块,避免默认输出过长;「所问之事」与「询问方向」一并传进去
|
|
825
886
|
const tier = detailOf(args.detail);
|
|
826
887
|
if (args.withInterpret) {
|
|
@@ -830,13 +891,14 @@ ${r.summary}`;
|
|
|
830
891
|
});
|
|
831
892
|
// 档位只改「回给模型的文本」,不改解析对象:面板拿到的仍是完整的 interpret。
|
|
832
893
|
// 键名不用 detail / tier:合盘结果里 r.detail 是详批对象、流年行里 r.tier 是档位,都会撞。
|
|
833
|
-
|
|
894
|
+
// 有些体系没有解析模块(interpret 为 null),此时不该留下一个没有正文的档位键。
|
|
895
|
+
if (r.interpret && tier !== 'full') r.interpretTier = tier;
|
|
834
896
|
}
|
|
835
897
|
// 八字 × 紫微交叉验证:同一份出生信息再排另一套盘,并列两边对同一件事的说法
|
|
836
898
|
// 交叉验证要再排另一套盘:时辰未知时紫微排不出来,这条支路直接跳过,
|
|
837
899
|
// 不能拿正午排一张紫微盘来「交叉」,那是两张都不可信的盘互证。
|
|
838
|
-
if (args.cross
|
|
839
|
-
if (args.cross
|
|
900
|
+
if (bool(args.cross) && bool(args.hourUnknown)) r.crossSkipped = 'HOUR_UNKNOWN';
|
|
901
|
+
if (bool(args.cross) && !bool(args.hourUnknown) && (r.system === 'bazi' || r.system === 'ziwei')) {
|
|
840
902
|
const cdt = parseDateTime(args.datetime);
|
|
841
903
|
const common = {
|
|
842
904
|
...cdt,
|
|
@@ -844,13 +906,28 @@ ${r.summary}`;
|
|
|
844
906
|
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
845
907
|
address: typeof args.address === 'string' ? args.address : undefined,
|
|
846
908
|
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
847
|
-
trueSolar: args.trueSolar
|
|
848
|
-
dst: args.dst
|
|
909
|
+
trueSolar: bool(args.trueSolar, true),
|
|
910
|
+
dst: bool(args.dst, true),
|
|
849
911
|
};
|
|
850
912
|
const asOf = checkAsOf(args.asOf);
|
|
913
|
+
// 八字显式写了 lateZi 时,交叉的紫微盘必须同口径:23:00 后一边按次日、一边按当日,
|
|
914
|
+
// 等于拿两个生日互证。没写时仍按紫微自己的 current-day 缺省,不改既有交叉结果;
|
|
915
|
+
// 显式 lateZishi(紫微历史参数名)优先。
|
|
916
|
+
const crossLateZishi = args.lateZishi !== undefined
|
|
917
|
+
? args.lateZishi
|
|
918
|
+
: args.lateZi === undefined || args.lateZi === null || args.lateZi === ''
|
|
919
|
+
? undefined
|
|
920
|
+
: bool(args.lateZi, true) ? 'next-day' : 'current-day';
|
|
851
921
|
const other = r.system === 'bazi'
|
|
852
|
-
? buildZiwei({
|
|
853
|
-
|
|
922
|
+
? buildZiwei({
|
|
923
|
+
...common,
|
|
924
|
+
leapMonth: args.leapMonth,
|
|
925
|
+
leapAsNext: bool(args.leapAsNext),
|
|
926
|
+
...ziweiSchoolArgs(args),
|
|
927
|
+
...(crossLateZishi !== undefined ? { lateZishi: crossLateZishi } : {}),
|
|
928
|
+
asOf,
|
|
929
|
+
})
|
|
930
|
+
: buildBazi({ ...common, lateZi: bool(args.lateZi, true), nowYear: asOf ? asOf.year : undefined });
|
|
854
931
|
r.cross = crossCheck(r.system === 'bazi' ? r : other, r.system === 'ziwei' ? r : other);
|
|
855
932
|
if (r.interpret) {
|
|
856
933
|
r.interpret.sections.push(crossSection(r.cross));
|
|
@@ -864,14 +941,14 @@ ${r.summary}`;
|
|
|
864
941
|
...parseDateTime(args.datetime),
|
|
865
942
|
// 与主盘同口径:主盘 hourUnknown 时内部按正午占位,过去盘若还用 00:00,
|
|
866
943
|
// 流年信号就来自子夜的盘,与用户看到的不是同一张
|
|
867
|
-
hourUnknown: args.hourUnknown
|
|
944
|
+
hourUnknown: bool(args.hourUnknown),
|
|
868
945
|
gender: requireGender(args.gender),
|
|
869
|
-
lateZi: args.lateZi
|
|
870
|
-
trueSolar: args.trueSolar
|
|
946
|
+
lateZi: bool(args.lateZi, true),
|
|
947
|
+
trueSolar: bool(args.trueSolar, true),
|
|
871
948
|
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
872
949
|
address: typeof args.address === 'string' ? args.address : undefined,
|
|
873
950
|
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
874
|
-
dst: args.dst
|
|
951
|
+
dst: bool(args.dst, true),
|
|
875
952
|
nowYear: from,
|
|
876
953
|
});
|
|
877
954
|
r.past = {
|
|
@@ -881,16 +958,16 @@ ${r.summary}`;
|
|
|
881
958
|
};
|
|
882
959
|
}
|
|
883
960
|
// 生时校正:候选时辰 × 过去事件,按固定规则打分(仅八字)
|
|
884
|
-
if (args.rectify
|
|
961
|
+
if (bool(args.rectify) && r.system === 'bazi') {
|
|
885
962
|
const base = parseDateTime(args.datetime);
|
|
886
963
|
r.rectify = rectifyBirth({
|
|
887
964
|
base: { year: base.year, month: base.month, day: base.day, minute: base.minute },
|
|
888
965
|
gender: requireGender(args.gender),
|
|
889
966
|
events: Array.isArray(args.events) ? args.events : [],
|
|
890
967
|
hours: Array.isArray(args.rectifyHours) ? args.rectifyHours : undefined,
|
|
891
|
-
lateZi: args.lateZi
|
|
892
|
-
trueSolar: args.trueSolar
|
|
893
|
-
dst: args.dst
|
|
968
|
+
lateZi: bool(args.lateZi, true),
|
|
969
|
+
trueSolar: bool(args.trueSolar, true),
|
|
970
|
+
dst: bool(args.dst, true),
|
|
894
971
|
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
895
972
|
address: typeof args.address === 'string' ? args.address : undefined,
|
|
896
973
|
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
@@ -899,7 +976,7 @@ ${r.summary}`;
|
|
|
899
976
|
// 追问:围绕已算好的盘回答「某一年 / 某方面」的具体问题
|
|
900
977
|
if (typeof args.ask === 'string' && args.ask.trim()) r.followup = followup(r, args.ask);
|
|
901
978
|
// 推导链:只读结果、不重算;生成失败不影响盘面,单独留一条错误信息
|
|
902
|
-
if (args.trace
|
|
979
|
+
if (bool(args.trace)) {
|
|
903
980
|
try {
|
|
904
981
|
const layers = buildTrace(r, { direction: args.direction, gender: args.gender });
|
|
905
982
|
if (layers) {
|
|
@@ -912,7 +989,7 @@ ${r.summary}`;
|
|
|
912
989
|
}
|
|
913
990
|
}
|
|
914
991
|
// 影子盘 ID:同一组归一化参数 + 同一版本 → 同一个短 ID;显式要时才挂,避免默认输出变长
|
|
915
|
-
if (args.shadow
|
|
992
|
+
if (bool(args.shadow)) {
|
|
916
993
|
try {
|
|
917
994
|
r.shadow = chartFingerprint(args, { result: r, version: args.shadowVersion });
|
|
918
995
|
} catch (e) {
|
|
@@ -930,11 +1007,17 @@ ${r.summary}`;
|
|
|
930
1007
|
}
|
|
931
1008
|
|
|
932
1009
|
/** 解析与详解的紧凑文本(给模型看,不塞整块 JSON)
|
|
933
|
-
* detail:'full'
|
|
934
|
-
*
|
|
1010
|
+
* detail:'full'(默认全文)/ 'lean'(只给结论、所问方向与三张表,长论述与规则出处折成指针)/
|
|
1011
|
+
* 'brief'(在 lean 上再限表行;工具层在 withInterpret 且未指定档位时用这一档)。
|
|
1012
|
+
* 省的是回给模型的文本,不改盘面、不改解析对象本身。
|
|
1013
|
+
* **不设体积上限**:brief 只做「少给哪些维度、表最多几行」这类结构性收敛,
|
|
1014
|
+
* 超长就让它超长——按字节截断会把结论、依据、边界一起腰斩,省下的体积不值这个代价。 */
|
|
935
1015
|
export function interpretText(i, detail) {
|
|
936
1016
|
if (!i) return '';
|
|
1017
|
+
const brief = detail === 'brief';
|
|
937
1018
|
const lean = detail === 'lean';
|
|
1019
|
+
const compact = brief || lean;
|
|
1020
|
+
const BRIEF_TABLE_ROWS = 8;
|
|
938
1021
|
const LONG_SECTION = 300;
|
|
939
1022
|
const out = [`【${i.title || '解析与详解'}】${i.ask && i.ask.echo ? i.ask.echo : ''}`];
|
|
940
1023
|
// 直答放在最前:先回答「你问的事」,再说依据,避免读者读到最后才知道结论;
|
|
@@ -950,12 +1033,12 @@ export function interpretText(i, detail) {
|
|
|
950
1033
|
if (i.error) out.push(`⚠ 解析生成失败:${i.error}(上方盘面数据本身可用)`);
|
|
951
1034
|
const allDims = i.dimensions || [];
|
|
952
1035
|
// 问了方向就只给那一条:其余四条在 full 档里,模型要展开随时可以再排一次
|
|
953
|
-
const dims =
|
|
1036
|
+
const dims = compact && allDims.some((d) => d && d.focused) ? allDims.filter((d) => d && d.focused) : allDims;
|
|
954
1037
|
for (const d of dims) {
|
|
955
1038
|
const evidence = Array.isArray(d.evidence) && d.evidence.length ? `\n 依据:${d.evidence.join(';')}${d.confidence ? ` 置信度:${d.confidence}` : ''}` : '';
|
|
956
1039
|
const plain = d.plain ? `(${d.plain})` : '';
|
|
957
1040
|
// 知识条目:「为什么这样断」+出处+可信度标注,是结论与依据之外的第三层
|
|
958
|
-
const know =
|
|
1041
|
+
const know = compact
|
|
959
1042
|
? ((d.knowledge || []).length ? `\n 规则 ${d.knowledge.length} 条未展开(detail:"full" 给出原文与出处)` : '')
|
|
960
1043
|
: (d.knowledge || [])
|
|
961
1044
|
.map((k) => `\n 规则·${k.gist}〔${k.statusLabel}〕${k.detail} 出处:${k.source}${k.note ? ` 注:${k.note}` : ''}`)
|
|
@@ -963,31 +1046,41 @@ export function interpretText(i, detail) {
|
|
|
963
1046
|
out.push(`${d.label}:${plain}${d.text}${d.advice ? ` 建议:${d.advice}` : ''}${evidence}${know}`);
|
|
964
1047
|
}
|
|
965
1048
|
const secs = i.sections || [];
|
|
966
|
-
const longSecs =
|
|
1049
|
+
const longSecs = compact ? secs.filter((s) => String(s.text || '').length > LONG_SECTION) : [];
|
|
967
1050
|
for (const s of secs) {
|
|
968
|
-
if (
|
|
1051
|
+
if (compact && String(s.text || '').length > LONG_SECTION) continue;
|
|
969
1052
|
out.push(`${s.label}:${s.text}`);
|
|
970
1053
|
}
|
|
971
1054
|
if (longSecs.length) out.push(`(另有 ${longSecs.length} 节长论述未展开:${longSecs.map((s) => s.label).join('、')}。需要原文与出处时传 detail:"full")`);
|
|
972
1055
|
// 三张表:大运 / 流年 / 触发事件(纯文本对齐,工具输出也能直接读)
|
|
973
1056
|
for (const t of i.tables || []) {
|
|
974
1057
|
if (!Array.isArray(t.rows) || !t.rows.length) continue;
|
|
1058
|
+
let rows = t.rows;
|
|
975
1059
|
out.push(`【${t.title}】`);
|
|
976
|
-
|
|
1060
|
+
if (brief && rows.length > BRIEF_TABLE_ROWS) {
|
|
1061
|
+
rows = rows.slice(0, BRIEF_TABLE_ROWS);
|
|
1062
|
+
out.push(`(共 ${t.rows.length} 行,仅列前 ${BRIEF_TABLE_ROWS} 行;全表传 detail:"lean")`);
|
|
1063
|
+
}
|
|
1064
|
+
out.push([t.columns.join(' | '), ...rows.map((r) => r.join(' | '))].join('\n'));
|
|
977
1065
|
}
|
|
978
1066
|
if (Array.isArray(i.followups) && i.followups.length) {
|
|
979
1067
|
out.push('还可以这样追问:' + i.followups.map((q) => `「${q}」`).join(' '));
|
|
980
1068
|
}
|
|
981
|
-
if (Array.isArray(i.glossary) && i.glossary.length) {
|
|
1069
|
+
if (!brief && Array.isArray(i.glossary) && i.glossary.length) {
|
|
982
1070
|
out.push('名词解释:' + i.glossary.map((g) => `${g.term}=${g.plain}`).join(' '));
|
|
983
1071
|
}
|
|
984
1072
|
// 能力边界:这段是给调用方(模型)看的上下文,不是给用户直接念的。
|
|
985
1073
|
// 写全比只写标题有用——模型知道「为什么不答」,才不会自己补一个答案出来。
|
|
1074
|
+
let text = '';
|
|
986
1075
|
if (Array.isArray(i.limits) && i.limits.length) {
|
|
987
|
-
|
|
988
|
-
}
|
|
989
|
-
if (i.disclaimer)
|
|
990
|
-
|
|
1076
|
+
text += `【本插件不回答什么】${i.limits.map((l) => `${l.title}——${l.text}`).join('\n')}\n`;
|
|
1077
|
+
}
|
|
1078
|
+
if (i.disclaimer) text += i.disclaimer;
|
|
1079
|
+
// 不按体积截断:这里只把「边界与免责声明」放在末尾,
|
|
1080
|
+
// 正文一个字都不砍——brief 已经用「少给维度、表限行数」控过长度了,
|
|
1081
|
+
// 再按字节腰斩只会把结论、依据和必要交代一起截掉,反而让模型读到半句话。
|
|
1082
|
+
const head = out.join('\n');
|
|
1083
|
+
return text ? `${head}\n${text}` : head;
|
|
991
1084
|
}
|
|
992
1085
|
|
|
993
1086
|
/** 对照时不参与计算的参数:它们只影响解析/附加模块,不影响盘面 */
|
|
@@ -1107,12 +1200,12 @@ export function normalizeLongitudes(args) {
|
|
|
1107
1200
|
return out;
|
|
1108
1201
|
}
|
|
1109
1202
|
|
|
1110
|
-
/**
|
|
1111
|
-
*
|
|
1203
|
+
/** 输出档位:缺省 'full'(面板与直接调用保持历史全文);'lean' 去掉长论述与规则出处,
|
|
1204
|
+
* 'brief' 在 lean 上再限表行(不设体积上限)。工具层在 withInterpret 且未指定时传 brief。 */
|
|
1112
1205
|
function detailOf(value) {
|
|
1113
|
-
if (value === undefined || value === null || value === ''
|
|
1114
|
-
if (value === 'lean') return
|
|
1115
|
-
throw fail('E_INPUT', `detail 只能是 'full'
|
|
1206
|
+
if (value === undefined || value === null || value === '') return 'full';
|
|
1207
|
+
if (value === 'full' || value === 'lean' || value === 'brief') return value;
|
|
1208
|
+
throw fail('E_INPUT', `detail 只能是 'full' / 'lean' / 'brief':收到「${String(value).slice(0, 24)}」`);
|
|
1116
1209
|
}
|
|
1117
1210
|
|
|
1118
1211
|
/** asOf 只接受能解析成真实日期的字符串;非法值以前会静默回退到「此刻」 */
|
|
@@ -1123,6 +1216,11 @@ function checkAsOf(asOf) {
|
|
|
1123
1216
|
const parts = asOfParts(asOf);
|
|
1124
1217
|
if (!parts) throw fail('E_ASOF', `无法解析 asOf:「${asOf}」,请用 2026-10-22T16:00 这种格式`);
|
|
1125
1218
|
const { year, month, day, hour, minute } = parts;
|
|
1219
|
+
// 与 parseDateTime / assertCivil 同一把年份尺:asOf 只校验月日时,1500 / 2500 / 9999
|
|
1220
|
+
// 会一路排进紫微流月流日,农历表却只覆盖 1600–2400,出来的是一张外推盘。
|
|
1221
|
+
if (year < MIN_YEAR || year > MAX_YEAR) {
|
|
1222
|
+
throw fail('E_ASOF', `asOf 的年份超出可计算范围(${MIN_YEAR}–${MAX_YEAR}):${asOf}`);
|
|
1223
|
+
}
|
|
1126
1224
|
if (!(month >= 1 && month <= 12) || !(day >= 1 && day <= daysInMonth(year, month))) {
|
|
1127
1225
|
throw fail('E_ASOF', `asOf 指向的日期不存在:${asOf}`);
|
|
1128
1226
|
}
|
|
@@ -1152,6 +1250,17 @@ function computeRaw(args = {}) {
|
|
|
1152
1250
|
throw fail('E_INPUT', `${system} 的 mode 只能是 ${allowed.join(' / ')},收到「${String(args.mode).slice(0, 24)}」`);
|
|
1153
1251
|
}
|
|
1154
1252
|
}
|
|
1253
|
+
// gender / direction 的枚举写在 schema 里,引擎直调也得守同一份契约:
|
|
1254
|
+
// 六爻曾把「女」静默当 male 出用神,direction 拼错则静默按 general——
|
|
1255
|
+
// 换一个用神、换一套断法,盘面却看不出任何异常。
|
|
1256
|
+
if (args.gender !== undefined && args.gender !== null && args.gender !== ''
|
|
1257
|
+
&& args.gender !== 'male' && args.gender !== 'female') {
|
|
1258
|
+
throw fail('E_INPUT', `gender 只能是 male / female,收到「${String(args.gender).slice(0, 24)}」`);
|
|
1259
|
+
}
|
|
1260
|
+
if (args.direction !== undefined && args.direction !== null && args.direction !== ''
|
|
1261
|
+
&& !['general', 'wealth', 'career', 'love', 'health', 'study', 'travel'].includes(args.direction)) {
|
|
1262
|
+
throw fail('E_INPUT', `direction 只能是 general / wealth / career / love / health / study / travel,收到「${String(args.direction).slice(0, 24)}」`);
|
|
1263
|
+
}
|
|
1155
1264
|
// 风水 fortuneYear 以前只认整数:2026.5 被静默当成「没给」,整块年飞星消失。
|
|
1156
1265
|
if (args.fortuneYear !== undefined && args.fortuneYear !== null && args.fortuneYear !== '') {
|
|
1157
1266
|
const n = Number(args.fortuneYear);
|
|
@@ -1169,25 +1278,25 @@ function computeRaw(args = {}) {
|
|
|
1169
1278
|
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
1170
1279
|
address: typeof args.address === 'string' ? args.address : undefined,
|
|
1171
1280
|
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
1172
|
-
lateZi: args.lateZi
|
|
1173
|
-
trueSolar: args.trueSolar
|
|
1174
|
-
dst: args.dst
|
|
1281
|
+
lateZi: bool(args.lateZi, true),
|
|
1282
|
+
trueSolar: bool(args.trueSolar, true),
|
|
1283
|
+
dst: bool(args.dst, true),
|
|
1175
1284
|
// 流年表默认从「今年」起排;要复现历史结论就把基准年钉死
|
|
1176
1285
|
nowYear: asOf ? asOf.year : undefined,
|
|
1177
1286
|
// 时辰未知:三柱模式(时柱不排、精度降到日、摘要首行固定提示)
|
|
1178
|
-
hourUnknown: args.hourUnknown
|
|
1287
|
+
hourUnknown: bool(args.hourUnknown),
|
|
1179
1288
|
});
|
|
1180
1289
|
return { ...result, ...(asOf ? { asOf } : {}), precision: dt.precision, summary: baziText(result) };
|
|
1181
1290
|
}
|
|
1182
1291
|
if (system === 'ziwei') {
|
|
1183
1292
|
// 命宫由「生月 + 生时支」定:时支未知则整盘无处落脚,没有「大概的紫微盘」这种东西
|
|
1184
|
-
if (args.hourUnknown
|
|
1293
|
+
if (bool(args.hourUnknown)) {
|
|
1185
1294
|
throw fail('HOUR_UNKNOWN', '紫微必须有出生时刻:命宫由生月与生时支定,时支未知则整盘不成立。请补上出生时刻,或改用八字三柱模式只看年/月/日。');
|
|
1186
1295
|
}
|
|
1187
1296
|
// 与奇门同一个别名口径:lateZi 是八字/六壬的参数名,紫微的历史参数名是 lateZishi。
|
|
1188
1297
|
// 不归一的话 revise「晚子时算次日」会照常回执已应用,盘却按默认 current-day 排。
|
|
1189
1298
|
if (args.lateZi !== undefined && args.lateZishi === undefined) {
|
|
1190
|
-
args = { ...args, lateZishi: args.lateZi
|
|
1299
|
+
args = { ...args, lateZishi: bool(args.lateZi, true) ? 'next-day' : 'current-day' };
|
|
1191
1300
|
}
|
|
1192
1301
|
const dt = parseDateTime(args.datetime);
|
|
1193
1302
|
const asOf = checkAsOf(args.asOf);
|
|
@@ -1195,13 +1304,13 @@ function computeRaw(args = {}) {
|
|
|
1195
1304
|
...dt,
|
|
1196
1305
|
gender: requireGender(args.gender),
|
|
1197
1306
|
leapMonth: args.leapMonth,
|
|
1198
|
-
leapAsNext: args.leapAsNext
|
|
1307
|
+
leapAsNext: bool(args.leapAsNext),
|
|
1199
1308
|
...ziweiSchoolArgs(args),
|
|
1200
1309
|
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
1201
1310
|
address: typeof args.address === 'string' ? args.address : undefined,
|
|
1202
1311
|
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
1203
|
-
trueSolar: args.trueSolar,
|
|
1204
|
-
dst: args.dst
|
|
1312
|
+
trueSolar: bool(args.trueSolar, true),
|
|
1313
|
+
dst: bool(args.dst, true),
|
|
1205
1314
|
// 流月/流日/流时的基准时刻:默认本机此刻,可用 asOf 指定以便复现
|
|
1206
1315
|
asOf,
|
|
1207
1316
|
});
|
|
@@ -1221,7 +1330,7 @@ function computeRaw(args = {}) {
|
|
|
1221
1330
|
}
|
|
1222
1331
|
if (system === 'daliuren') {
|
|
1223
1332
|
const dt = parseDateTime(args.datetime);
|
|
1224
|
-
const lateZi = args.lateZi
|
|
1333
|
+
const lateZi = bool(args.lateZi, true);
|
|
1225
1334
|
const result = buildDaliuren({ ...dt, lateZi });
|
|
1226
1335
|
result.lateZi = lateZi;
|
|
1227
1336
|
return { ...result, precision: dt.precision, summary: daliurenText(result) };
|
|
@@ -1235,7 +1344,7 @@ function computeRaw(args = {}) {
|
|
|
1235
1344
|
// lateZiHourMode 是奇门的历史参数名,lateZi 是八字/六壬的;两者互为别名。
|
|
1236
1345
|
const mode = args.lateZiHourMode !== undefined
|
|
1237
1346
|
? args.lateZiHourMode
|
|
1238
|
-
: args.lateZi ===
|
|
1347
|
+
: args.lateZi === undefined || args.lateZi === null ? undefined : bool(args.lateZi) ? 'next-day' : 'same-day';
|
|
1239
1348
|
if (mode !== undefined && !LATE_ZI_MODES.includes(mode)) {
|
|
1240
1349
|
throw fail('E_INPUT', `lateZiHourMode 只能是 same-day / next-day,收到「${mode}」`);
|
|
1241
1350
|
}
|
|
@@ -1245,9 +1354,14 @@ function computeRaw(args = {}) {
|
|
|
1245
1354
|
return { ...result, summary: qimenText(result) };
|
|
1246
1355
|
}
|
|
1247
1356
|
if (system === 'hepan') {
|
|
1248
|
-
//
|
|
1249
|
-
|
|
1250
|
-
|
|
1357
|
+
// 双方(以及可选的第三方)夫妻宫、大运都依赖时支:任一方时辰未知就不出盘,
|
|
1358
|
+
// 免得「差不多」的合盘误导决定。第三方只在给了 partner2 时才参与校验,
|
|
1359
|
+
// 但一旦参与,口径必须和主方、对方一致——不能主方缺时柱就拦、第三方缺时柱却照排。
|
|
1360
|
+
const unknownSide = bool(args.hourUnknown)
|
|
1361
|
+
|| (args.partner && bool(args.partner.hourUnknown))
|
|
1362
|
+
|| (args.partner2 && bool(args.partner2.hourUnknown));
|
|
1363
|
+
if (unknownSide) {
|
|
1364
|
+
throw fail('HOUR_UNKNOWN', '合盘需要每一方都有出生时刻:夫妻宫与大运都依赖时支。请补齐时刻后再看,或用八字三柱模式各自看年/月/日。');
|
|
1251
1365
|
}
|
|
1252
1366
|
const partner = args.partner;
|
|
1253
1367
|
if (!partner || typeof partner !== 'object') {
|
|
@@ -1269,12 +1383,23 @@ function computeRaw(args = {}) {
|
|
|
1269
1383
|
// 性别决定双方大运/大限顺逆,两边都不能用默认值
|
|
1270
1384
|
const sideOf = (src, who) => {
|
|
1271
1385
|
const dt = parseDateTime(src.datetime);
|
|
1386
|
+
// 紫微读的是历史参数名 lateZishi,合盘这一层若不转写,`chart:'ziwei', lateZi:false`
|
|
1387
|
+
// 会排出与 true 完全相同的盘(八字合盘同一开关却生效)。
|
|
1388
|
+
const lateZishi = src.lateZishi !== undefined
|
|
1389
|
+
? src.lateZishi
|
|
1390
|
+
: src.lateZi === undefined || src.lateZi === null || src.lateZi === ''
|
|
1391
|
+
? undefined
|
|
1392
|
+
: bool(src.lateZi, true) ? 'next-day' : 'current-day';
|
|
1272
1393
|
return {
|
|
1273
1394
|
...dt,
|
|
1274
1395
|
gender: requireGender(src.gender, who),
|
|
1275
1396
|
trueSolar: src.trueSolar,
|
|
1276
1397
|
lateZi: src.lateZi,
|
|
1277
|
-
|
|
1398
|
+
...(lateZishi !== undefined ? { lateZishi } : {}),
|
|
1399
|
+
// 紫微的闰月口径同属「读盘时才算」的参数,漏传会让合盘与单盘在闰月出生时差一个月
|
|
1400
|
+
...(src.leapMonth !== undefined ? { leapMonth: src.leapMonth } : {}),
|
|
1401
|
+
...(src.leapAsNext !== undefined ? { leapAsNext: bool(src.leapAsNext) } : {}),
|
|
1402
|
+
dst: bool(src.dst, true),
|
|
1278
1403
|
address: typeof src.address === 'string' ? src.address : undefined,
|
|
1279
1404
|
longitude: typeof src.longitude === 'number' ? src.longitude : undefined,
|
|
1280
1405
|
geo: src.geo && typeof src.geo === 'object' ? src.geo : undefined,
|
|
@@ -1428,7 +1553,7 @@ function computeRaw(args = {}) {
|
|
|
1428
1553
|
lunarYear: Number(args.lunarYear),
|
|
1429
1554
|
lunarMonth: Number(args.lunarMonth),
|
|
1430
1555
|
lunarDay: Number(args.lunarDay),
|
|
1431
|
-
isLeapMonth: args.isLeapMonth
|
|
1556
|
+
isLeapMonth: bool(args.isLeapMonth),
|
|
1432
1557
|
});
|
|
1433
1558
|
} else {
|
|
1434
1559
|
const dt = parseDateTime(args.datetime);
|
|
@@ -1479,18 +1604,18 @@ function computeRaw(args = {}) {
|
|
|
1479
1604
|
nowYear: nowParts ? nowParts.year : undefined,
|
|
1480
1605
|
gender: requireGender(args.gender),
|
|
1481
1606
|
toAge: args.toAge,
|
|
1482
|
-
lateZi: args.lateZi
|
|
1483
|
-
trueSolar: args.trueSolar
|
|
1484
|
-
dst: args.dst
|
|
1607
|
+
lateZi: bool(args.lateZi, true),
|
|
1608
|
+
trueSolar: bool(args.trueSolar, true),
|
|
1609
|
+
dst: bool(args.dst, true),
|
|
1485
1610
|
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
1486
1611
|
address: typeof args.address === 'string' ? args.address : undefined,
|
|
1487
1612
|
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
1488
|
-
hourUnknown: args.hourUnknown
|
|
1613
|
+
hourUnknown: bool(args.hourUnknown),
|
|
1489
1614
|
});
|
|
1490
1615
|
// 趋势没有独立的 dayun 字段(大运在 buildTrend 内部用),所以精度标记挂在结果顶层
|
|
1491
1616
|
return {
|
|
1492
1617
|
...result,
|
|
1493
|
-
...(args.hourUnknown
|
|
1618
|
+
...(bool(args.hourUnknown) ? { hourUnknown: true, hourPrecision: 'day' } : {}),
|
|
1494
1619
|
precision: dt.precision,
|
|
1495
1620
|
summary: trendText(result),
|
|
1496
1621
|
};
|
|
@@ -1513,7 +1638,7 @@ function computeRaw(args = {}) {
|
|
|
1513
1638
|
...(typeof args.facingDegree === 'number' ? { facingDegree: args.facingDegree } : {}),
|
|
1514
1639
|
...(typeof args.fortuneDate === 'string' ? { fortuneDate: args.fortuneDate } : {}),
|
|
1515
1640
|
...(typeof args.fortuneTime === 'string' ? { fortuneTime: args.fortuneTime } : {}),
|
|
1516
|
-
dst: args.dst
|
|
1641
|
+
dst: bool(args.dst, true),
|
|
1517
1642
|
});
|
|
1518
1643
|
return { ...result, summary: fengshuiText(result) };
|
|
1519
1644
|
}
|
|
@@ -1524,7 +1649,7 @@ function computeRaw(args = {}) {
|
|
|
1524
1649
|
facing: args.facing,
|
|
1525
1650
|
...(args.datetime ? parseDateTime(args.datetime) : {}),
|
|
1526
1651
|
...(args.gender === 'male' || args.gender === 'female' ? { gender: args.gender } : {}),
|
|
1527
|
-
dst: args.dst
|
|
1652
|
+
dst: bool(args.dst, true),
|
|
1528
1653
|
});
|
|
1529
1654
|
return { ...result, summary: fengshuiText(result) };
|
|
1530
1655
|
}
|
|
@@ -1534,7 +1659,7 @@ function computeRaw(args = {}) {
|
|
|
1534
1659
|
year: dt.year, month: dt.month, day: dt.day,
|
|
1535
1660
|
gender: requireGender(args.gender),
|
|
1536
1661
|
fortuneYear: args.fortuneYear,
|
|
1537
|
-
dst: args.dst
|
|
1662
|
+
dst: bool(args.dst, true),
|
|
1538
1663
|
});
|
|
1539
1664
|
return { ...result, summary: fengshuiText(result) };
|
|
1540
1665
|
}
|
|
@@ -1563,9 +1688,9 @@ function computeRaw(args = {}) {
|
|
|
1563
1688
|
...birth,
|
|
1564
1689
|
...(args.gender === 'male' || args.gender === 'female' ? { gender: args.gender } : {}),
|
|
1565
1690
|
...(args.datetime ? {
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1691
|
+
lateZi: bool(args.lateZi, true),
|
|
1692
|
+
trueSolar: bool(args.trueSolar, true),
|
|
1693
|
+
dst: bool(args.dst, true),
|
|
1569
1694
|
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
1570
1695
|
address: typeof args.address === 'string' ? args.address : undefined,
|
|
1571
1696
|
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
@@ -1711,8 +1836,11 @@ function mbtiText(r) {
|
|
|
1711
1836
|
const stack = Array.isArray(p.stack) && p.stack.length
|
|
1712
1837
|
? p.stack.map((f, i) => `${i === 0 ? '主导 ' : ''}${f.code}`).join(' → ') + `\n ${p.stack.map((f) => `${f.code}:${f.say}`).join('\n ')}`
|
|
1713
1838
|
: '';
|
|
1839
|
+
const missing = Array.isArray(r.unanswered) && r.unanswered.length
|
|
1840
|
+
? ` 未答维度:${r.unanswered.join('、')}(该维按平手占位)`
|
|
1841
|
+
: '';
|
|
1714
1842
|
return [
|
|
1715
|
-
`【十六型人格】${r.type}(${p.title})· ${p.keywords} 已答 ${r.answered}/${r.total}
|
|
1843
|
+
`【十六型人格】${r.type}(${p.title})· ${p.keywords} 已答 ${r.answered}/${r.total} 题${missing}`,
|
|
1716
1844
|
`一句:${p.oneLine}`,
|
|
1717
1845
|
'【四维】',
|
|
1718
1846
|
...dims,
|
|
@@ -1886,6 +2014,8 @@ export function renderText(result) {
|
|
|
1886
2014
|
parts.push(`【推导链】${SYSTEM_LABEL[result.system] || result.system} · ${result.trace.length} 层\n`
|
|
1887
2015
|
+ result.trace.map((t, i) => `${i + 1}. ${t.field}|${t.result}\n 规则:${t.rule} 出处:${t.source}`).join('\n'));
|
|
1888
2016
|
}
|
|
2017
|
+
// 解析段按档位渲染。brief 只做结构性收敛(少给维度、表限行数),不压体积:
|
|
2018
|
+
// 盘面、句柄、解析全都不裁,这段有多长就多长。
|
|
1889
2019
|
if (result.interpret) {
|
|
1890
2020
|
const extra = interpretText(result.interpret, result.interpretTier);
|
|
1891
2021
|
if (extra) parts.push(extra);
|