dsh-metaphysics 0.19.0 → 0.20.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
@@ -2,10 +2,12 @@
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';
9
11
 
10
12
  const STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
11
13
  const BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
@@ -197,38 +199,37 @@ function specialPatterns(pillars, dayStem, monthBranch, strength, wuxing, gods)
197
199
 
198
200
  // ── 用神 / 喜忌 ──────────────────────────────────────────────
199
201
 
200
- const TIAOHOU = {
201
- 亥: ['火', '冬月寒水当令,最喜火暖局'],
202
- 子: ['火', '仲冬水旺,最喜火暖局'],
203
- 丑: ['火', '季冬湿寒,喜火解冻'],
204
- 巳: ['水', '初夏火旺,喜水制衡'],
205
- 午: ['水', '仲夏火炎,最喜水调候'],
206
- 未: ['水', '季夏燥土,喜水润局'],
207
- 寅: ['火', '初春尚寒,得火而发'],
208
- 卯: ['火', '仲春木旺,喜火吐秀'],
209
- 辰: ['金', '季春湿土,喜金水疏泄'],
210
- 申: ['火', '初秋金旺,喜火炼金'],
211
- 酉: ['火', '仲秋金锐,喜火制'],
212
- 戌: ['水', '季秋燥土,喜水润泽'],
213
- };
214
202
 
215
- function yongshenOf(dayStem, monthBranch, strength, wuxing) {
203
+ function yongshenOf(dayStem, monthBranch, strength, wuxing, branches = []) {
216
204
  const roles = elementRoles(dayStem);
217
205
  const me = roles.me;
218
206
  const weak = strength.level === '身弱' || strength.level === '偏弱';
219
207
  const strong = strength.level === '身强' || strength.level === '偏强';
220
208
 
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;
209
+ const baseFavor = weak ? [roles.inMe, me] : strong ? [roles.shi, roles.cai, roles.guan] : [roles.inMe, me, roles.shi];
210
+ const baseAvoid = weak ? [roles.shi, roles.cai, roles.guan] : strong ? [me, roles.inMe] : [roles.guan, roles.cai];
211
+ let favor = baseFavor.slice();
212
+ let avoid = baseAvoid.slice();
213
+
214
+ // 先判气候,再决定要不要让调候压过扶抑
215
+ const climate = judgeClimate({ monthBranch, dayStem, wuxing, branches });
216
+ const arb = arbitrate(climate, baseFavor, baseAvoid);
217
+ if (climate.need && climate.priority === '调候优先') {
218
+ const needEl = climate.need.element;
219
+ if (!favor.includes(needEl)) favor.unshift(needEl);
220
+ avoid = avoid.filter((e) => e !== needEl);
221
+ if (climate.avoidElement && !avoid.includes(climate.avoidElement)) avoid.unshift(climate.avoidElement);
222
+ }
229
223
 
230
224
  const roleName = (el) => (el === me ? '比劫' : el === roles.inMe ? '印'
231
225
  : el === roles.shi ? '食伤' : el === roles.cai ? '财' : '官杀');
226
+ const expand = {
227
+ 比劫: ['比肩', '劫财'], 印: ['正印', '偏印'], 食伤: ['食神', '伤官'],
228
+ 财: ['正财', '偏财'], 官杀: ['正官', '七杀'],
229
+ };
230
+ const favorTenGods = Array.from(new Set(favor.flatMap((el) => expand[roleName(el)] || [])));
231
+ const avoidTenGods = Array.from(new Set(avoid.flatMap((el) => expand[roleName(el)] || [])));
232
+
232
233
  const detail = [
233
234
  `日主${dayStem}${me},${strength.level}(${strength.score} 分):`,
234
235
  weak
@@ -236,24 +237,22 @@ function yongshenOf(dayStem, monthBranch, strength, wuxing) {
236
237
  : strong
237
238
  ? `喜${roles.shi}(食伤)泄秀、${roles.cai}(财)耗、${roles.guan}(官杀)制;忌${me}(比劫)与${roles.inMe}(印)再助。`
238
239
  : '身中和,先取调候与通关,喜忌随大运流年动态变化。',
239
- tiaohou ? `调候(简化):${tiaohou.note},宜${tiaohou.element}。` : '',
240
- '扶抑法为主;调候未用穷通宝鉴全表,各派取用不同,仅作方向提示。',
240
+ `调候:${climate.label}(${(climate.evidence || []).join('、')})${climate.need ? `,取${climate.need.stem}${climate.need.element},忌${climate.avoidElement}` : ',气候平和'}。`,
241
+ arb.text,
242
+ arb.clash ? '取舍:本盘按「调候优先、扶抑次之」取用;若你更认同扶抑派,可把调候当环境偏好,强弱判断不受影响。' : '',
241
243
  ].filter(Boolean).join('');
242
244
 
243
245
  return {
244
- method: '扶抑为主 + 调候简化提示',
246
+ method: climate.priority === '调候优先' ? '调候优先 + 扶抑校验' : '扶抑为主 + 调候校验',
245
247
  level: strength.level,
246
248
  favor,
247
249
  avoid,
248
250
  favorRoles: favor.map(roleName),
249
251
  avoidRoles: avoid.map(roleName),
250
- favorTenGods: favorableTenGods,
251
- avoidTenGods: weak
252
- ? ['食神', '伤官', '正财', '偏财', '正官', '七杀']
253
- : strong
254
- ? ['比肩', '劫财', '正印', '偏印']
255
- : ['正官', '七杀', '正财', '偏财'],
256
- tiaohou,
252
+ favorTenGods,
253
+ avoidTenGods,
254
+ tiaohou: { ...climate, ...arb, element: climate.need ? climate.need.element : null },
255
+ climate,
257
256
  detail,
258
257
  };
259
258
  }
@@ -274,33 +273,55 @@ export function scoreLuck(ganzhi, frame) {
274
273
  const branch = ganzhi[1];
275
274
  const stemEl = STEM_ELEMENT[stem];
276
275
  const branchEl = BRANCH_ELEMENT[branch];
276
+ const th = ys.tiaohou || ys.climate || null;
277
277
  const scoreEl = (el) => {
278
278
  let s = 0;
279
279
  if (ys.favor.includes(el)) s += 1;
280
280
  if (ys.avoid.includes(el)) s -= 1;
281
- if (ys.tiaohou && el === ys.tiaohou.element) s += 0.4;
282
281
  return s;
283
282
  };
284
283
  let score = scoreEl(stemEl) * 1.0 + scoreEl(branchEl) * 0.8;
285
284
  for (const h of (HIDDEN[branch] || []).slice(1)) {
286
285
  score += scoreEl(STEM_ELEMENT[h]) * 0.2;
287
286
  }
288
- score = Math.round(score * 10) / 10;
289
-
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
287
 
294
- const tier = TIERS.find(([min]) => total >= min)[1];
295
288
  const reasons = [];
296
289
  if (ys.favor.includes(stemEl)) reasons.push(`天干${stem}${stemEl}为用神`);
297
290
  else if (ys.avoid.includes(stemEl)) reasons.push(`天干${stem}${stemEl}为忌神`);
298
291
  else reasons.push(`天干${stem}${stemEl}不喜不忌`);
299
292
  if (ys.favor.includes(branchEl)) reasons.push(`地支${branch}${branchEl}助用`);
300
293
  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})`);
294
+
295
+ // 调候修正:不是"有没有这个五行",而是"这一年是给命局添了一把火,还是泼了一盆水"
296
+ if (th && th.need && th.priority === '调候优先') {
297
+ const needEl = th.need.element;
298
+ const oppEl = th.avoidElement;
299
+ const cold = String(th.kind).includes('寒');
300
+ if (stemEl === needEl) {
301
+ score += 1.1;
302
+ reasons.push(`${th.label}之局得${stem}${stemEl},${cold ? '解冻' : '得润'}`);
303
+ } else if (stemEl === oppEl) {
304
+ score -= 0.9;
305
+ reasons.push(`${th.label}之局再逢${stem}${stemEl},${cold ? '冻木难生' : '燥上加燥'}`);
306
+ }
307
+ if (branchEl === needEl) {
308
+ score += 0.9;
309
+ reasons.push(`地支${branch}${branchEl}合调候`);
310
+ } else if (branchEl === oppEl) {
311
+ score -= 1.0;
312
+ reasons.push(`地支${branch}${branchEl}加重${th.label}`);
313
+ }
314
+ if (cold && (stemEl === '水' || branchEl === '水')) score -= 0.3;
315
+ } else if (th && th.need && (stemEl === th.need.element || branchEl === th.need.element)) {
316
+ reasons.push(`合调候(${th.need.element})`);
303
317
  }
318
+
319
+ const godStem = tenGod(frame.dayStem, stem);
320
+ const bonus = ys.favorTenGods.includes(godStem) ? 0.3 : 0;
321
+ let total = Math.round((score + bonus) * 10) / 10;
322
+ total = Math.max(-4, Math.min(4, total));
323
+
324
+ const tier = TIERS.find(([min]) => total >= min)[1];
304
325
  return { ganzhi, score: total, tier, stemTenGod: godStem, reasons, note: `${godStem}运,${tier}(${total})` };
305
326
  }
306
327
 
@@ -319,7 +340,7 @@ export function analyzeBaziFrame({ pillars, dayStem, monthBranch, strength, wuxi
319
340
  const month = monthPattern(pillars, dayStem, monthBranch);
320
341
  const special = specialPatterns(pillars, dayStem, monthBranch, strength, wuxing, gods);
321
342
  const geju = [month, ...special];
322
- const yongshen = yongshenOf(dayStem, monthBranch, strength, wuxing);
343
+ const yongshen = yongshenOf(dayStem, monthBranch, strength, wuxing, Object.values(pillars).map((p) => p.branch));
323
344
  return {
324
345
  geju,
325
346
  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.20.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
@@ -110,6 +110,7 @@ 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
114
  `十神权重:${Object.entries(r.frame.godStats.groups).map(([k, v]) => `${k}${v}`).join(' ')}`,
114
115
  ] : [];
115
116
  return [