dsh-metaphysics 0.19.0 → 0.21.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/bazi.js CHANGED
@@ -506,7 +506,8 @@ export function buildBazi(input) {
506
506
  list: dayun,
507
507
  },
508
508
  strength,
509
- frame,
509
+ frame,
510
+ climate: frame.yongshen.climate || frame.yongshen.tiaohou || null,
510
511
  liunian,
511
512
  liunianFrom: thisYear,
512
513
  };
package/engine/geju.js CHANGED
@@ -2,10 +2,13 @@
2
2
  //
3
3
  // 定位:在 bazi.js 算出四柱、五行、旺衰之后,给出
4
4
  // 1) 格局(月令格 + 特殊格 + 常见组合);
5
- // 2) 用神 / 喜忌(扶抑为主,调候作简化提示);
6
- // 3) 大运、流年的吉凶倾向评分。
5
+ // 2) 用神 / 喜忌(调候优先 + 扶抑校验;气候平和时仍以扶抑为主);
6
+ // 3) 大运、流年的吉凶倾向评分(含调候修正项)。
7
7
  // 取舍:格局派别差异极大,这里只给「规则可复核」的判定,evidence 写明依据;
8
- // 调候未做穷通宝鉴 120 条全表,只按寒暖燥湿给方向,并在 detail 里注明。
8
+ // 调候由 tiaohou.js 独立判定,与扶抑冲突时显式裁决并写明理由,不静默取用。
9
+
10
+ import { judgeClimate, arbitrate } from './tiaohou.js';
11
+ import { qtOf } from './qiongtong.js';
9
12
 
10
13
  const STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
11
14
  const BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
@@ -197,38 +200,39 @@ function specialPatterns(pillars, dayStem, monthBranch, strength, wuxing, gods)
197
200
 
198
201
  // ── 用神 / 喜忌 ──────────────────────────────────────────────
199
202
 
200
- const TIAOHOU = {
201
- 亥: ['火', '冬月寒水当令,最喜火暖局'],
202
- 子: ['火', '仲冬水旺,最喜火暖局'],
203
- 丑: ['火', '季冬湿寒,喜火解冻'],
204
- 巳: ['水', '初夏火旺,喜水制衡'],
205
- 午: ['水', '仲夏火炎,最喜水调候'],
206
- 未: ['水', '季夏燥土,喜水润局'],
207
- 寅: ['火', '初春尚寒,得火而发'],
208
- 卯: ['火', '仲春木旺,喜火吐秀'],
209
- 辰: ['金', '季春湿土,喜金水疏泄'],
210
- 申: ['火', '初秋金旺,喜火炼金'],
211
- 酉: ['火', '仲秋金锐,喜火制'],
212
- 戌: ['水', '季秋燥土,喜水润泽'],
213
- };
214
203
 
215
- function yongshenOf(dayStem, monthBranch, strength, wuxing) {
204
+ function yongshenOf(dayStem, monthBranch, strength, wuxing, branches = []) {
216
205
  const roles = elementRoles(dayStem);
217
206
  const me = roles.me;
218
207
  const weak = strength.level === '身弱' || strength.level === '偏弱';
219
208
  const strong = strength.level === '身强' || strength.level === '偏强';
220
209
 
221
- const favor = weak ? [roles.inMe, me] : strong ? [roles.shi, roles.cai, roles.guan] : [roles.inMe, me, roles.shi];
222
- const avoid = weak ? [roles.shi, roles.cai, roles.guan] : strong ? [me, roles.inMe] : [roles.guan, roles.cai];
223
- const favorableTenGods = weak ? ['正印', '偏印', '比肩', '劫财'] : strong
224
- ? ['食神', '伤官', '正财', '偏财', '正官', '七杀']
225
- : ['正印', '偏印', '食神', '伤官'];
226
-
227
- const th = TIAOHOU[monthBranch];
228
- const tiaohou = th ? { element: th[0], note: th[1] } : null;
210
+ const baseFavor = weak ? [roles.inMe, me] : strong ? [roles.shi, roles.cai, roles.guan] : [roles.inMe, me, roles.shi];
211
+ const baseAvoid = weak ? [roles.shi, roles.cai, roles.guan] : strong ? [me, roles.inMe] : [roles.guan, roles.cai];
212
+ let favor = baseFavor.slice();
213
+ let avoid = baseAvoid.slice();
214
+
215
+ // 先判气候,再决定要不要让调候压过扶抑
216
+ const climate = judgeClimate({ monthBranch, dayStem, wuxing, branches });
217
+ // 《穷通宝鉴》120 格:同一「日主 + 月令」的取用顺序。只作展示与加权,不覆盖气候调候
218
+ const qt = qtOf(dayStem, monthBranch);
219
+ const arb = arbitrate(climate, baseFavor, baseAvoid);
220
+ if (climate.need && climate.priority === '调候优先') {
221
+ const needEl = climate.need.element;
222
+ if (!favor.includes(needEl)) favor.unshift(needEl);
223
+ avoid = avoid.filter((e) => e !== needEl);
224
+ if (climate.avoidElement && !avoid.includes(climate.avoidElement)) avoid.unshift(climate.avoidElement);
225
+ }
229
226
 
230
227
  const roleName = (el) => (el === me ? '比劫' : el === roles.inMe ? '印'
231
228
  : el === roles.shi ? '食伤' : el === roles.cai ? '财' : '官杀');
229
+ const expand = {
230
+ 比劫: ['比肩', '劫财'], 印: ['正印', '偏印'], 食伤: ['食神', '伤官'],
231
+ 财: ['正财', '偏财'], 官杀: ['正官', '七杀'],
232
+ };
233
+ const favorTenGods = Array.from(new Set(favor.flatMap((el) => expand[roleName(el)] || [])));
234
+ const avoidTenGods = Array.from(new Set(avoid.flatMap((el) => expand[roleName(el)] || [])));
235
+
232
236
  const detail = [
233
237
  `日主${dayStem}${me},${strength.level}(${strength.score} 分):`,
234
238
  weak
@@ -236,24 +240,24 @@ function yongshenOf(dayStem, monthBranch, strength, wuxing) {
236
240
  : strong
237
241
  ? `喜${roles.shi}(食伤)泄秀、${roles.cai}(财)耗、${roles.guan}(官杀)制;忌${me}(比劫)与${roles.inMe}(印)再助。`
238
242
  : '身中和,先取调候与通关,喜忌随大运流年动态变化。',
239
- tiaohou ? `调候(简化):${tiaohou.note},宜${tiaohou.element}。` : '',
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})。` : '',
245
+ arb.text,
246
+ arb.clash ? '取舍:本盘按「调候优先、扶抑次之」取用;若你更认同扶抑派,可把调候当环境偏好,强弱判断不受影响。' : '',
241
247
  ].filter(Boolean).join('');
242
248
 
243
249
  return {
244
- method: '扶抑为主 + 调候简化提示',
250
+ method: climate.priority === '调候优先' ? '调候优先 + 扶抑校验' : '扶抑为主 + 调候校验',
245
251
  level: strength.level,
246
252
  favor,
247
253
  avoid,
248
254
  favorRoles: favor.map(roleName),
249
255
  avoidRoles: avoid.map(roleName),
250
- favorTenGods: favorableTenGods,
251
- avoidTenGods: weak
252
- ? ['食神', '伤官', '正财', '偏财', '正官', '七杀']
253
- : strong
254
- ? ['比肩', '劫财', '正印', '偏印']
255
- : ['正官', '七杀', '正财', '偏财'],
256
- tiaohou,
256
+ favorTenGods,
257
+ avoidTenGods,
258
+ tiaohou: { ...climate, ...arb, element: climate.need ? climate.need.element : null },
259
+ climate,
260
+ qt,
257
261
  detail,
258
262
  };
259
263
  }
@@ -274,33 +278,67 @@ export function scoreLuck(ganzhi, frame) {
274
278
  const branch = ganzhi[1];
275
279
  const stemEl = STEM_ELEMENT[stem];
276
280
  const branchEl = BRANCH_ELEMENT[branch];
281
+ const th = ys.tiaohou || ys.climate || null;
277
282
  const scoreEl = (el) => {
278
283
  let s = 0;
279
284
  if (ys.favor.includes(el)) s += 1;
280
285
  if (ys.avoid.includes(el)) s -= 1;
281
- if (ys.tiaohou && el === ys.tiaohou.element) s += 0.4;
282
286
  return s;
283
287
  };
284
288
  let score = scoreEl(stemEl) * 1.0 + scoreEl(branchEl) * 0.8;
285
289
  for (const h of (HIDDEN[branch] || []).slice(1)) {
286
290
  score += scoreEl(STEM_ELEMENT[h]) * 0.2;
287
291
  }
288
- score = Math.round(score * 10) / 10;
289
292
 
290
- const godStem = tenGod(frame.dayStem, stem);
291
- const bonus = ys.favorTenGods.includes(godStem) ? 0.3 : 0;
292
- const total = Math.round((score + bonus) * 10) / 10;
293
-
294
- const tier = TIERS.find(([min]) => total >= min)[1];
295
293
  const reasons = [];
296
294
  if (ys.favor.includes(stemEl)) reasons.push(`天干${stem}${stemEl}为用神`);
297
295
  else if (ys.avoid.includes(stemEl)) reasons.push(`天干${stem}${stemEl}为忌神`);
298
296
  else reasons.push(`天干${stem}${stemEl}不喜不忌`);
299
297
  if (ys.favor.includes(branchEl)) reasons.push(`地支${branch}${branchEl}助用`);
300
298
  else if (ys.avoid.includes(branchEl)) reasons.push(`地支${branch}${branchEl}助忌`);
301
- if (ys.tiaohou && (stemEl === ys.tiaohou.element || branchEl === ys.tiaohou.element)) {
302
- reasons.push(`合调候(${ys.tiaohou.element})`);
299
+
300
+ // 调候修正:不是"有没有这个五行",而是"这一年是给命局添了一把火,还是泼了一盆水"
301
+ if (th && th.need && th.priority === '调候优先') {
302
+ const needEl = th.need.element;
303
+ const oppEl = th.avoidElement;
304
+ const cold = String(th.kind).includes('寒');
305
+ if (stemEl === needEl) {
306
+ score += 1.1;
307
+ reasons.push(`${th.label}之局得${stem}${stemEl},${cold ? '解冻' : '得润'}`);
308
+ } else if (stemEl === oppEl) {
309
+ score -= 0.9;
310
+ reasons.push(`${th.label}之局再逢${stem}${stemEl},${cold ? '冻木难生' : '燥上加燥'}`);
311
+ }
312
+ if (branchEl === needEl) {
313
+ score += 0.9;
314
+ reasons.push(`地支${branch}${branchEl}合调候`);
315
+ } else if (branchEl === oppEl) {
316
+ score -= 1.0;
317
+ reasons.push(`地支${branch}${branchEl}加重${th.label}`);
318
+ }
319
+ if (cold && (stemEl === '水' || branchEl === '水')) score -= 0.3;
320
+ } else if (th && th.need && (stemEl === th.need.element || branchEl === th.need.element)) {
321
+ reasons.push(`合调候(${th.need.element})`);
322
+ }
323
+
324
+ const godStem = tenGod(frame.dayStem, stem);
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
+ }
303
337
  }
338
+ let total = Math.round((score + bonus) * 10) / 10;
339
+ total = Math.max(-4, Math.min(4, total));
340
+
341
+ const tier = TIERS.find(([min]) => total >= min)[1];
304
342
  return { ganzhi, score: total, tier, stemTenGod: godStem, reasons, note: `${godStem}运,${tier}(${total})` };
305
343
  }
306
344
 
@@ -319,7 +357,7 @@ export function analyzeBaziFrame({ pillars, dayStem, monthBranch, strength, wuxi
319
357
  const month = monthPattern(pillars, dayStem, monthBranch);
320
358
  const special = specialPatterns(pillars, dayStem, monthBranch, strength, wuxing, gods);
321
359
  const geju = [month, ...special];
322
- const yongshen = yongshenOf(dayStem, monthBranch, strength, wuxing);
360
+ const yongshen = yongshenOf(dayStem, monthBranch, strength, wuxing, Object.values(pillars).map((p) => p.branch));
323
361
  return {
324
362
  geju,
325
363
  primary: month,
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.19.0 (+https://github.com/xuanfu-web/dsh-metaphysics)';
146
+ const USER_AGENT = 'dsh-metaphysics/0.21.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,6 +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}` : ''}` : '—'}` +
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})` : ''),
113
115
  `十神权重:${Object.entries(r.frame.godStats.groups).map(([k, v]) => `${k}${v}`).join(' ')}`,
114
116
  ] : [];
115
117
  return [