dsh-metaphysics 0.20.0 → 0.22.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/engine/geju.js CHANGED
@@ -8,6 +8,7 @@
8
8
  // 调候由 tiaohou.js 独立判定,与扶抑冲突时显式裁决并写明理由,不静默取用。
9
9
 
10
10
  import { judgeClimate, arbitrate } from './tiaohou.js';
11
+ import { qtOf } from './qiongtong.js';
11
12
 
12
13
  const STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
13
14
  const BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
@@ -213,6 +214,8 @@ function yongshenOf(dayStem, monthBranch, strength, wuxing, branches = []) {
213
214
 
214
215
  // 先判气候,再决定要不要让调候压过扶抑
215
216
  const climate = judgeClimate({ monthBranch, dayStem, wuxing, branches });
217
+ // 《穷通宝鉴》120 格:同一「日主 + 月令」的取用顺序。只作展示与加权,不覆盖气候调候
218
+ const qt = qtOf(dayStem, monthBranch);
216
219
  const arb = arbitrate(climate, baseFavor, baseAvoid);
217
220
  if (climate.need && climate.priority === '调候优先') {
218
221
  const needEl = climate.need.element;
@@ -238,6 +241,7 @@ function yongshenOf(dayStem, monthBranch, strength, wuxing, branches = []) {
238
241
  ? `喜${roles.shi}(食伤)泄秀、${roles.cai}(财)耗、${roles.guan}(官杀)制;忌${me}(比劫)与${roles.inMe}(印)再助。`
239
242
  : '身中和,先取调候与通关,喜忌随大运流年动态变化。',
240
243
  `调候:${climate.label}(${(climate.evidence || []).join('、')})${climate.need ? `,取${climate.need.stem}${climate.need.element},忌${climate.avoidElement}` : ',气候平和'}。`,
244
+ qt ? `穷通宝鉴:${qt.month}${dayStem}${me},用神${qt.yong.join('、')}(首取${qt.primary})。` : '',
241
245
  arb.text,
242
246
  arb.clash ? '取舍:本盘按「调候优先、扶抑次之」取用;若你更认同扶抑派,可把调候当环境偏好,强弱判断不受影响。' : '',
243
247
  ].filter(Boolean).join('');
@@ -253,6 +257,7 @@ function yongshenOf(dayStem, monthBranch, strength, wuxing, branches = []) {
253
257
  avoidTenGods,
254
258
  tiaohou: { ...climate, ...arb, element: climate.need ? climate.need.element : null },
255
259
  climate,
260
+ qt,
256
261
  detail,
257
262
  };
258
263
  }
@@ -318,6 +323,18 @@ export function scoreLuck(ganzhi, frame) {
318
323
 
319
324
  const godStem = tenGod(frame.dayStem, stem);
320
325
  const bonus = ys.favorTenGods.includes(godStem) ? 0.3 : 0;
326
+ // 穷通宝鉴:天干正好是这一格的首取/次取时,再给一点「按书取用」的分
327
+ // 只在该干不属于忌神时加分——书里的取用与气候调候不同向时,以调候为先
328
+ const qt = ys.qt || null;
329
+ if (qt && qt.primary && !ys.avoid.includes(STEM_ELEMENT[qt.primary])) {
330
+ if (stem === qt.primary) {
331
+ score += 0.3;
332
+ reasons.push(`天干${stem}正是${qt.branch}月${qt.stem}日的首取(穷通宝鉴)`);
333
+ } else if (qt.yong.includes(stem) && !ys.avoid.includes(STEM_ELEMENT[stem])) {
334
+ score += 0.15;
335
+ reasons.push(`天干${stem}为穷通宝鉴次取`);
336
+ }
337
+ }
321
338
  let total = Math.round((score + bonus) * 10) / 10;
322
339
  total = Math.max(-4, Math.min(4, total));
323
340
 
package/engine/geo.js CHANGED
@@ -143,7 +143,7 @@ const DETAIL = /(区|县|镇|乡|村|小区|街道|路|号|巷|弄|组|队)/;
143
143
 
144
144
  /** OpenStreetMap Nominatim:免密钥、支持中文。失败返回 null。
145
145
  * UA 按 OSM 使用政策要求写明应用与联系方式(改版本时同步这里)。 */
146
- const USER_AGENT = 'dsh-metaphysics/0.20.0 (+https://github.com/xuanfu-web/dsh-metaphysics)';
146
+ const USER_AGENT = 'dsh-metaphysics/0.22.0 (+https://github.com/xuanfu-web/dsh-metaphysics)';
147
147
 
148
148
  async function onlineLookup(text, { timeoutMs = 2500 } = {}) {
149
149
  if (typeof fetch !== 'function') return null;
package/engine/index.js CHANGED
@@ -21,7 +21,7 @@ import { buildZeri } from './zeri.js';
21
21
  import { buildChenggu } from './chenggu.js';
22
22
  import { buildXiaoliuren } from './xiaoliuren.js';
23
23
  import { buildStar } from './star.js';
24
- import { buildNumerology } from './numerology.js';
24
+ import { buildNumerology } from './numerology.js';
25
25
  import { buildConvert } from './convert.js';
26
26
  import { buildTrend } from './trend.js';
27
27
  import { buildFengshui } from './fengshui.js';
@@ -110,7 +110,8 @@ function baziText(r) {
110
110
  `   ${r.frame.geju.map((g) => `${g.name}[${g.kind}]`).join(' ')}`,
111
111
  `用神:喜${r.frame.yongshen.favor.join('')}(${r.frame.yongshen.favorTenGods.join('、')}) 忌${r.frame.yongshen.avoid.join('')}(${r.frame.yongshen.avoidTenGods.join('、')}) ${r.frame.yongshen.method}`,
112
112
  `   ${r.frame.yongshen.detail}`,
113
- `寒暖:${r.climate ? `${r.climate.label}(${(r.climate.evidence || []).join('、')})${r.climate.need ? ` 调候取${r.climate.need.stem}${r.climate.need.element} 忌${r.climate.avoidElement}` : ''}` : '—'}`,
113
+ `寒暖:${r.climate ? `${r.climate.label}(${(r.climate.evidence || []).join('、')})${r.climate.need ? ` 调候取${r.climate.need.stem}${r.climate.need.element} 忌${r.climate.avoidElement}` : ''}` : '—'}` +
114
+ (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})` : ''),
114
115
  `十神权重:${Object.entries(r.frame.godStats.groups).map(([k, v]) => `${k}${v}`).join(' ')}`,
115
116
  ] : [];
116
117
  return [
@@ -702,12 +703,21 @@ function computeRaw(args = {}) {
702
703
  }
703
704
  if (system === 'name') {
704
705
  const action = args.action === 'generate' ? 'generate' : 'assess';
705
- const birth = args.datetime ? parseDateTime(args.datetime) : {};
706
+ const birth = args.datetime
707
+ ? parseDateTime(args.datetime)
708
+ : (Number.isInteger(args.year) && Number.isInteger(args.month) && Number.isInteger(args.day)
709
+ ? {
710
+ year: args.year, month: args.month, day: args.day,
711
+ ...(Number.isInteger(args.hour) ? { hour: args.hour } : {}),
712
+ ...(Number.isInteger(args.minute) ? { minute: args.minute } : {}),
713
+ }
714
+ : {});
706
715
  const result = buildName({
707
716
  action,
708
717
  surname: args.surname,
709
718
  givenName: args.givenName,
710
719
  limit: args.limit,
720
+ charElements: args.charElements && typeof args.charElements === 'object' ? args.charElements : undefined,
711
721
  ...birth,
712
722
  ...(args.gender === 'male' || args.gender === 'female' ? { gender: args.gender } : {}),
713
723
  ...(args.datetime ? {
@@ -885,9 +895,18 @@ function nameText(r) {
885
895
  ...r.notes.map((n) => `※ ${n}`),
886
896
  ].join('\n');
887
897
  }
898
+ const srcLabel = (c) => (c.source === 'lib' ? '字库' : c.source === 'user' ? '用户指定' : '未收录');
899
+ const charLine = (c) => {
900
+ const el = c.element === '未收录' ? '未收录(不猜五行)' : `${c.element}(${srcLabel(c)})`;
901
+ const fit = c.favorable === true ? '合喜用' : c.favorable === false ? '不合喜用' : '不判';
902
+ const sug = c.suggest && c.suggest.length
903
+ ? ` 可换:${c.suggest.map((x) => `${x.char}(${x.element})`).join('、')}`
904
+ : '';
905
+ return `· ${c.char}:${el}|${fit}|${c.meaning}${sug}`;
906
+ };
888
907
  return [
889
908
  `【名字评估】${r.fullName}`,
890
- ...r.chars.map((c) => `· ${c.char}:五行 ${c.element}${c.element !== '未收录' ? `(喜用${c.favorable ? '中' : '外'})` : ''},${c.meaning}`),
909
+ ...r.chars.map(charLine),
891
910
  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}` : '',
892
911
  `【结论】${r.verdict}`,
893
912
  ...r.notes.map((n) => `※ ${n}`),
@@ -374,6 +374,11 @@ function baziInterpret(r) {
374
374
  ? `调候取${cli.need.stem}${cli.need.element},忌${cli.avoidElement};${cli.clash ? '与扶抑冲突,本盘以调候为先' : '与扶抑同向'}`
375
375
  : '气候平和,调候不作主取'}。`,
376
376
  }] : []),
377
+ ...(ys.qt ? [{
378
+ key: 'qiongtong', label: '穷通宝鉴',
379
+ text: `${ys.qt.label}:用神 ${ys.qt.yong.join('、')}(首取 ${ys.qt.primary})。原文:「${ys.qt.quote}」` +
380
+ `(《穷通宝鉴》第 ${ys.qt.chapter} 章${ys.qt.note ? ';' + ys.qt.note : ''})`,
381
+ }] : []),
377
382
  {
378
383
  key: 'geju', label: '格局依据',
379
384
  text: (Array.isArray(frame.geju) ? frame.geju : []).map((g) => `${g.name}(${g.kind}):${g.evidence}`).join(';') || '未取到格局',
@@ -384,7 +389,7 @@ function baziInterpret(r) {
384
389
  },
385
390
  {
386
391
  key: 'limits', label: '未纳入',
387
- text: '本盘没有计算:流月与流日、神煞的加权轻重、地支藏干的动态引化(合冲后再论)。取用按「调候优先、扶抑校验」;调候由月令与全局寒暖燥湿判定,未逐条录入穷通宝鉴 120 条,各派取法仍有分歧;真太阳时未含历史时区与夏令时。把这些补上会改变"强弱"的边际判断,所以这里只给方向,不下断语。',
392
+ text: '本盘没有计算:流月与流日、神煞的加权轻重、地支藏干的动态引化(合冲后再论)。取用按「调候优先、扶抑校验」;调候由月令与全局寒暖燥湿判定,并按《穷通宝鉴》120 格给出用神顺序(整理表口径,原文摘句照录,异文处已标 note);各派取法仍有分歧;真太阳时未含历史时区与夏令时。把这些补上会改变"强弱"的边际判断,所以这里只给方向,不下断语。',
388
393
  },
389
394
  ];
390
395
 
package/engine/name.js CHANGED
@@ -116,6 +116,115 @@ export const CHAR_LIB = [
116
116
  ['月', '木', '明月', 'f'], ['言', '木', '言语,信', 'n'],
117
117
  ['和', '水', '和顺', 'n'], ['安', '土', '安宁', 'n'],
118
118
  ['宁', '火', '安宁', 'f'],
119
+ // ── 扩充:木 ──
120
+ ['林', '木', '林木成片,茂盛有荫', 'n'], ['森', '木', '众木成林,森然', 'n'],
121
+ ['枝', '木', '枝条,生发', 'n'], ['果', '木', '果实,有成', 'n'],
122
+ ['禾', '木', '禾苗,五谷初生', 'n'], ['秀', '木', '禾吐穗,秀美出众', 'n'],
123
+ ['穗', '木', '谷穗,丰实', 'n'], ['稼', '木', '庄稼,耕作有成', 'm'],
124
+ ['颖', '木', '禾颖,聪颖', 'n'], ['苗', '木', '苗裔,初生有生机', 'n'],
125
+ ['菁', '木', '菁华,草木茂盛', 'f'], ['菡', '木', '荷花含苞', 'f'],
126
+ ['蕴', '木', '蕴积,含而不露', 'f'], ['蕾', '木', '花蕾,待放', 'f'],
127
+ ['薇', '木', '蔷薇,细而韧', 'f'], ['薏', '木', '薏苡,清润', 'f'],
128
+ ['菀', '木', '草木茂盛', 'f'], ['荷', '木', '荷花,出淤泥', 'f'],
129
+ ['莲', '木', '莲花,清直', 'f'], ['菱', '木', '菱角,水生', 'f'],
130
+ ['苇', '木', '芦苇,柔韧', 'n'], ['荻', '木', '荻草,秋声', 'n'],
131
+ ['筠', '木', '竹皮,竹之青', 'n'], ['笛', '木', '竹笛,清音', 'n'],
132
+ ['杉', '木', '杉木,挺拔', 'n'], ['柠', '木', '柠檬,清新', 'f'],
133
+ ['桢', '木', '桢干,栋梁材', 'm'], ['樾', '木', '树荫,庇下', 'm'],
134
+ ['柘', '木', '柘木,坚韧', 'n'], ['权', '木', '权衡,执中', 'm'],
135
+ ['材', '木', '木材,成材', 'm'], ['杭', '木', '苏杭,舟行', 'n'],
136
+ ['枚', '木', '衔枚,静而进', 'n'], ['梧', '木', '梧桐,栖凤', 'n'],
137
+ ['竹', '木', '竹,有节而虚心', 'n'], ['简', '木', '简朴,简静', 'n'],
138
+ ['策', '木', '策谋,鞭策', 'm'], ['筹', '木', '筹算,谋划', 'm'],
139
+ ['芬', '木', '芬芳', 'f'], ['芳', '木', '芳华', 'f'],
140
+ ['花', '木', '花,明媚', 'f'], ['菊', '木', '菊花,晚节', 'f'],
141
+ ['兰', '木', '兰花,幽香', 'f'], ['芝', '木', '芝兰,瑞草', 'f'],
142
+ ['芹', '木', '芹献,谦诚', 'f'], ['荃', '木', '荃草,香草', 'f'],
143
+ ['萃', '木', '荟萃,聚合', 'n'], ['蓁', '木', '蓁蓁,草木盛', 'n'],
144
+ ['荞', '木', '荞麦,实而不华', 'f'], ['莞', '木', '莞尔,浅笑', 'f'],
145
+ ['萦', '木', '萦绕,绵长', 'f'], ['蔚', '木', '蔚然,文采盛', 'n'],
146
+ ['芃', '木', '芃芃,草木茂', 'n'], ['芩', '木', '黄芩,草本', 'f'],
147
+ ['苡', '木', '薏苡,清润', 'f'], ['莱', '木', '莱草,野而秀', 'n'],
148
+ ['萌', '木', '萌芽,生机', 'n'], ['葆', '木', '永葆,守而不失', 'n'],
149
+ ['萧', '木', '萧然,清疏', 'n'], ['樟', '木', '樟木,香而久', 'n'],
150
+ ['樱', '木', '樱花,明丽', 'f'], ['栎', '木', '栎树,坚实', 'n'],
151
+ ['棹', '木', '舟棹,行水', 'n'], ['楸', '木', '楸树,高直', 'n'],
152
+ ['槐', '木', '槐树,三公之位', 'n'], ['橙', '木', '橙,明暖', 'n'],
153
+ ['檀', '木', '檀香,沉静', 'n'],
154
+ // ── 扩充:火 ──
155
+ ['丰', '火', '丰盛、丰盈(繁体豐从豆,归火)', 'n'], ['日', '火', '太阳,光明', 'm'],
156
+ ['阳', '火', '阳光,向阳而生', 'm'], ['光', '火', '光明', 'n'],
157
+ ['亮', '火', '明亮,爽朗', 'm'], ['明', '火', '光明、明理', 'n'],
158
+ ['晶', '火', '晶莹,三光', 'f'], ['晴', '火', '晴朗,天开', 'f'],
159
+ ['夏', '火', '夏天,盛大', 'n'], ['智', '火', '智慧,明达', 'm'],
160
+ ['卓', '火', '卓越,卓立', 'm'], ['乐', '火', '和乐', 'n'],
161
+ ['恺', '火', '恺悌,和乐', 'm'], ['恬', '火', '恬静', 'f'],
162
+ ['甜', '火', '甜美', 'f'], ['亭', '火', '亭亭,端立', 'f'],
163
+ ['俊', '火', '俊朗,才俊', 'm'], ['丙', '火', '天干丙,阳火', 'm'],
164
+ ['丁', '火', '天干丁,阴火', 'n'], ['鼎', '火', '鼎立,厚重', 'm'],
165
+ ['陶', '火', '陶然,自得', 'n'], ['熹', '火', '熹微,晨光', 'n'],
166
+ ['炀', '火', '火旺,炽盛', 'm'], ['烜', '火', '烜赫,显明', 'm'],
167
+ ['暻', '火', '日光', 'n'], ['晖', '火', '日晖,光彩', 'n'],
168
+ ['晔', '火', '光晔,明盛', 'n'], ['旻', '火', '秋天,天高', 'm'],
169
+ ['晏', '火', '晏然,安和', 'n'], ['晓', '火', '拂晓,天将明', 'f'],
170
+ ['暄', '火', '温暖,日暖', 'f'], ['昆', '火', '昆山,同也', 'm'],
171
+ ['伶', '火', '伶俐,聪敏', 'f'],
172
+ // ── 扩充:土 ──
173
+ ['山', '土', '山,稳重', 'm'], ['岸', '土', '岸然,独立', 'm'],
174
+ ['岑', '土', '小山,高而静', 'n'], ['巍', '土', '巍峨,高大', 'm'],
175
+ ['嵩', '土', '嵩山,高峻', 'm'], ['岭', '土', '山岭,绵延', 'n'],
176
+ ['奥', '土', '深奥,蕴藉', 'm'], ['宜', '土', '宜然,合度', 'n'],
177
+ ['容', '土', '包容,容仪', 'n'], ['意', '土', '心意,诚意', 'f'],
178
+ ['依', '土', '依倚,亲近', 'f'], ['悠', '土', '悠然,从容', 'f'],
179
+ ['维', '土', '维系,纲维', 'n'], ['纬', '土', '经纬,有条', 'n'],
180
+ ['永', '土', '永恒,久长', 'n'], ['远', '土', '高远', 'm'],
181
+ ['辰', '土', '星辰、辰时', 'm'], ['壁', '土', '壁立,端正', 'm'],
182
+ ['田', '土', '田野,耕获', 'm'], ['由', '土', '由来,自得', 'n'],
183
+ ['画', '土', '图画,文采', 'f'], ['延', '土', '延展,绵长', 'm'],
184
+ ['建', '土', '建立,树也', 'm'], ['音', '土', '音声,和鸣', 'f'],
185
+ ['韵', '土', '韵致,余响', 'f'], ['玮', '土', '美玉,珍奇', 'm'],
186
+ ['雍', '土', '雍容,和穆', 'm'], ['懿', '土', '懿德,美善', 'f'],
187
+ // ── 扩充:金 ──
188
+ ['金', '金', '金,坚而贵', 'm'], ['秋', '金', '秋收、成熟(秋属金,肃而收)', 'n'],
189
+ ['书', '金', '书卷,文墨', 'n'], ['舒', '金', '舒展,从容', 'n'],
190
+ ['善', '金', '善良,善成', 'n'], ['信', '金', '诚信,符信', 'm'],
191
+ ['修', '金', '修远,修身', 'm'], ['素', '金', '素雅,本色', 'f'],
192
+ ['静', '金', '安静,沉静', 'f'], ['心', '金', '心志,中藏', 'f'],
193
+ ['新', '金', '新意,日新', 'n'], ['正', '金', '正直,端正', 'm'],
194
+ ['世', '金', '世代,绵延', 'm'], ['少', '金', '少年,少壮', 'm'],
195
+ ['西', '金', '西方,金位', 'n'], ['白', '金', '洁白,素净', 'n'],
196
+ ['辛', '金', '天干辛,阴金', 'n'], ['成', '金', '成器,成事', 'm'],
197
+ ['利', '金', '利落,锋利', 'm'], ['剑', '金', '剑,刚锐', 'm'],
198
+ ['刚', '金', '刚强,不屈', 'm'], ['则', '金', '法则,有度', 'm'],
199
+ ['初', '金', '初心,开端', 'n'], ['硕', '金', '硕大,学殖', 'm'],
200
+ ['尚', '金', '崇尚,尚贤', 'n'], ['臣', '金', '臣,佐事', 'm'],
201
+ ['川', '金', '川流,通达', 'n'], ['舟', '金', '舟行,济渡', 'n'],
202
+ ['州', '金', '州,聚落', 'n'], ['祖', '金', '祖德,源远', 'm'],
203
+ ['祥', '金', '吉祥,祥瑞', 'n'], ['禄', '金', '福禄,俸给', 'm'],
204
+ ['禧', '金', '禧庆,福', 'n'], ['珍', '金', '珍宝,珍重', 'f'],
205
+ ['宣', '金', '宣明,布达', 'n'], ['序', '金', '秩序,庠序', 'n'],
206
+ ['谦', '金', '谦逊,有终', 'm'], ['赞', '金', '赞美,佐成', 'm'],
207
+ ['超', '金', '超越,越过', 'm'], ['锴', '金', '精铁,坚刚', 'm'],
208
+ // ── 扩充:水 ──
209
+ ['水', '水', '水,润下', 'n'], ['冰', '水', '冰清,澄澈', 'f'],
210
+ ['洁', '水', '洁净,洁身', 'f'], ['浩', '水', '浩大,广远', 'm'],
211
+ ['瀚', '水', '浩瀚,无涯', 'm'], ['渊', '水', '渊深,有本', 'm'],
212
+ ['澜', '水', '波澜,壮阔', 'n'], ['潮', '水', '潮涌,有信', 'n'],
213
+ ['溪', '水', '溪流,清浅', 'f'], ['湘', '水', '湘水,清深', 'f'],
214
+ ['渝', '水', '渝水,守而不变', 'n'], ['深', '水', '深厚,深藏', 'm'],
215
+ ['湛', '水', '湛然,清深', 'm'], ['汇', '水', '汇聚,会归', 'n'],
216
+ ['洲', '水', '洲渚,水中地', 'n'], ['湾', '水', '水湾,曲处', 'f'],
217
+ ['云', '水', '云行,舒卷', 'n'], ['风', '水', '风雅,流动', 'n'],
218
+ ['北', '水', '北方,水位', 'n'], ['冬', '水', '冬藏,静敛', 'f'],
219
+ ['玄', '水', '玄远,幽深', 'n'], ['子', '水', '子,初生', 'm'],
220
+ ['平', '水', '平和,坦易', 'n'], ['方', '水', '方正,有隅', 'n'],
221
+ ['帆', '水', '风帆,顺行', 'n'], ['航', '水', '航行,济远', 'm'],
222
+ ['敏', '水', '敏思,捷悟', 'f'], ['慧', '水', '慧心,明彻', 'f'],
223
+ ['惠', '水', '惠泽,施与', 'f'], ['霈', '水', '霈雨,丰沛', 'm'],
224
+ ['灏', '水', '灏气,广大', 'm'], ['湄', '水', '水湄,岸边', 'f'],
225
+ ['滨', '水', '水滨,近水', 'n'], ['洛', '水', '洛水,清流', 'n'],
226
+ ['津', '水', '津渡,要处', 'n'], ['泊', '水', '淡泊,自守', 'm'],
227
+ ['沧', '水', '沧海,苍茫', 'm'], ['浦', '水', '浦,水滨', 'n'],
119
228
  ];
120
229
 
121
230
  const EL_INDEX = new Map();
@@ -123,6 +232,24 @@ for (const c of CHAR_LIB) {
123
232
  if (!EL_INDEX.has(c[0])) EL_INDEX.set(c[0], { c: c[0], el: c[1], meaning: c[2], g: c[3], order: EL_INDEX.size });
124
233
  }
125
234
 
235
+ /** 用户可自带某字的五行(字库未收时用,按你信的那派算;传了就算「用户指定」)。 */
236
+ function userElement(input, ch) {
237
+ const map = input && input.charElements;
238
+ if (!map || typeof map !== 'object') return null;
239
+ const v = map[ch];
240
+ return ALL.includes(v) ? v : null;
241
+ }
242
+
243
+ /** 喜用五行里挑几个可替换的常用字(同性别倾向优先),给未收录字一个落点。 */
244
+ function suggestFor(element, genderPref, exclude, count = 3) {
245
+ const pool = CHAR_LIB.filter((c) => c[1] === element && !exclude.has(c[0]));
246
+ pool.sort((a, b) => {
247
+ const fit = (x) => (x[3] === genderPref || x[3] === 'n' ? 1 : 0);
248
+ return fit(b) - fit(a);
249
+ });
250
+ return pool.slice(0, count).map((c) => ({ char: c[0], element: c[1], meaning: c[2] }));
251
+ }
252
+
126
253
  const KIND_LABEL = { m: '男', f: '女', n: '中' };
127
254
 
128
255
  /** 喜用五行(简化判定:旺衰 + 生克泄耗)。 */
@@ -178,23 +305,44 @@ export function assessName(input) {
178
305
  baziNote = { dayMaster: r.dayMaster, solarNote: r.solarTime ? r.solarTime.note : '' };
179
306
  }
180
307
 
308
+ const genderPref = input.gender === 'female' ? 'f' : 'm';
309
+ const taken = new Set([surname, ...given]);
310
+ const favPool = favorable.list.length ? favorable.list : ALL.slice();
181
311
  const chars = [...given].map((ch) => {
182
312
  const info = charInfo(ch);
183
- const inFav = info ? favorable.list.includes(info.el) : null;
313
+ const given = userElement(input, ch);
314
+ if (info) {
315
+ return {
316
+ char: ch, element: info.el, meaning: info.meaning,
317
+ genderHint: KIND_LABEL[info.g], favorable: hasBirth ? favorable.list.includes(info.el) : null,
318
+ source: 'lib', note: null,
319
+ };
320
+ }
321
+ if (given) {
322
+ return {
323
+ char: ch, element: given, meaning: '字库未收录;五行按你指定的「' + given + '」计',
324
+ genderHint: null, favorable: hasBirth ? favorable.list.includes(given) : null,
325
+ source: 'user', note: '此字五行由调用方指定,不是字库推断',
326
+ };
327
+ }
184
328
  return {
185
- char: ch,
186
- element: info ? info.el : '未收录',
187
- meaning: info ? info.meaning : '字库未收录,未推断五行',
188
- genderHint: info ? KIND_LABEL[info.g] : null,
189
- favorable: inFav,
329
+ char: ch, element: '未收录', meaning: '字库未收录,未推断五行(不猜)',
330
+ genderHint: null, favorable: null, source: 'none',
331
+ note: hasBirth ? '若想贴合命局,可换用下面的同喜用字' : '字库未收录:给出生辰后可以看同喜用的替换字',
332
+ suggest: hasBirth
333
+ ? favPool.flatMap((el) => suggestFor(el, genderPref, taken, 2))
334
+ : [],
190
335
  };
191
336
  });
192
- const known = chars.filter((c) => c.element !== '未收录').length;
337
+ const known = chars.filter((c) => c.source === 'lib').length;
338
+ const unknown = chars.filter((c) => c.source === 'none').length;
193
339
  const favCount = chars.filter((c) => c.favorable === true).length;
340
+ const judged = chars.length - unknown;
194
341
  let verdict;
195
342
  if (!hasBirth) verdict = '只做了字义与五行归类;给出生年月日才能判断用字是否合喜用。';
196
- else if (favCount === chars.length && chars.length > 0) verdict = `用字都落在喜用(${favorable.list.join('、')})上,与命局方向一致。`;
197
- else if (favCount > 0) verdict = `部分用字合喜用(${favCount}/${chars.length}),其余可再斟酌;喜用为 ${favorable.list.join('、')}。`;
343
+ else if (unknown === chars.length) verdict = `两字都不在字库里,没法判断合不合喜用(喜用为 ${favorable.list.join('、')})。下面给了可替换的常用字,或把你自己认定的五行传进来。`;
344
+ else if (favCount === judged && judged > 0) verdict = `用字都落在喜用(${favorable.list.join('、')})上,与命局方向一致。`;
345
+ else if (favCount > 0) verdict = `部分用字合喜用(${favCount}/${judged}),其余可再斟酌;喜用为 ${favorable.list.join('、')}。`;
198
346
  else verdict = `用字未落在喜用(${favorable.list.join('、')})上;不必然是坏名,但若想贴合命局可换字再看。`;
199
347
 
200
348
  return {
@@ -212,10 +360,10 @@ export function assessName(input) {
212
360
  },
213
361
  } : {}),
214
362
  chars,
215
- stats: { chars: chars.length, known, favorableCount: favCount },
363
+ stats: { chars: chars.length, known, unknown, favorableCount: favCount, libSize: CHAR_LIB.length },
216
364
  verdict,
217
365
  notes: [
218
- '字之五行按通行字义/偏旁归类,自建字库有限:未收录的字不猜五行,如实标注。',
366
+ `字之五行按通行字义/偏旁归类,自建字库共 ${CHAR_LIB.length} 字(常用名用字为主);未收录的字不猜五行,如实标注,「用户指定」是你传入的归类。`,
219
367
  '不做五格剖象(需康熙字典笔画),也不做音韵格律判断——谐音、拗口请自己读三遍或用方言读一遍。',
220
368
  '起名只做用字参考,不替父母做决定。',
221
369
  ],
@@ -267,19 +415,41 @@ export function generateNames(input) {
267
415
  elements: [a[1], b[1]],
268
416
  allFavorable: fav.list.includes(a[1]) && fav.list.includes(b[1]),
269
417
  genderScore: gScore,
270
- order: i * 1000 + j,
418
+ order: (i * 7 + j * 13) % 9973, // 打散字库顺序,避免候选清一色都是前几个字
271
419
  });
272
420
  }
273
421
  }
274
- scored.sort((x, y) => (y.allFavorable - x.allFavorable) || (y.genderScore - x.genderScore) || (x.order - y.order));
422
+ const mixed = (c) => (c.elements[0] !== c.elements[1] ? 1 : 0); // 两字异行,比同气堆叠更有层次
423
+ scored.sort((x, y) => (y.allFavorable - x.allFavorable) || (mixed(y) - mixed(x)) || (y.genderScore - x.genderScore) || (x.order - y.order));
424
+ // 去重 + 用字轮换:先收「两个字都没出现过」的候选,再放宽——避免 20 个候选里 13 个都用同一个「梓」
275
425
  const out = [];
276
- const used = new Set();
277
- for (const cand of scored) {
278
- if (out.length >= limit) break;
426
+ const usedNames = new Set();
427
+ const usedChars = new Set([surname]);
428
+ const usedPairs = new Set();
429
+ const take = (cand, strict, freshPair) => {
279
430
  const key = [...cand.name].sort().join('');
280
- if (used.has(key)) continue;
281
- used.add(key);
431
+ if (usedNames.has(key)) return false;
432
+ if (strict && cand.chars.some((x) => usedChars.has(x.char))) return false;
433
+ const pair = cand.elements.join('');
434
+ if (freshPair && usedPairs.has(pair)) return false;
435
+ usedNames.add(key);
436
+ usedPairs.add(pair);
437
+ cand.chars.forEach((x) => usedChars.add(x.char));
282
438
  out.push({ rank: out.length + 1, name: surname + cand.name, given: cand.name, chars: cand.chars });
439
+ return true;
440
+ };
441
+ // 三轮:①新字 + 新五行组合 ②新字 ③放开——先保证候选之间不撞字、不撞组合
442
+ for (const cand of scored) {
443
+ if (out.length >= limit) break;
444
+ take(cand, true, true);
445
+ }
446
+ for (const cand of scored) {
447
+ if (out.length >= limit) break;
448
+ take(cand, true, false);
449
+ }
450
+ for (const cand of scored) {
451
+ if (out.length >= limit) break;
452
+ take(cand, false, false);
283
453
  }
284
454
  return {
285
455
  system: 'name',
@@ -289,7 +459,7 @@ export function generateNames(input) {
289
459
  bazi: { dayMaster: r.dayMaster, strength, favorable: fav.list, basis: fav.basis },
290
460
  candidates: out,
291
461
  notes: [
292
- `字库共 ${CHAR_LIB.length} 字,只在喜用五行(${fav.list.join('、')})里选字;未覆盖的生僻好字可手工评估。`,
462
+ `字库共 ${CHAR_LIB.length} 字,只在喜用五行(${fav.list.join('、')})里选字;未覆盖的生僻好字可用 charElements 指定五行后评估。`,
293
463
  '候选按合喜用与性别倾向确定性排序,不是「吉名」判定;音韵与方言谐音请自行读念检查。',
294
464
  '不做五格剖象(需康熙字典笔画)。',
295
465
  ],