dsh-metaphysics 0.18.4 → 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/client.js CHANGED
@@ -746,7 +746,7 @@ function resolveAddress(input) {
746
746
 
747
747
  const DETAIL = /(区|县|镇|乡|村|小区|街道|路|号|巷|弄|组|队)/;
748
748
 
749
- const USER_AGENT = 'dsh-metaphysics/0.18.4 (+https://github.com/xuanfu-web/dsh-metaphysics)';
749
+ const USER_AGENT = 'dsh-metaphysics/0.20.0 (+https://github.com/xuanfu-web/dsh-metaphysics)';
750
750
 
751
751
  async function onlineLookup(text, { timeoutMs = 2500 } = {}) {
752
752
  if (typeof fetch !== 'function') return null;
@@ -1280,8 +1280,216 @@ const RULE_TABLE = RULES.reduce((acc, r) => { acc[r.name] = { cat: r.cat, luck:
1280
1280
  return { PILLAR_KEYS, PILLAR_LABEL, CATEGORY_ORDER, computeBaziShensha, RULE_TABLE };
1281
1281
  })();
1282
1282
 
1283
+ __mods.tiaohou = (function () {
1284
+
1285
+ const STEM_ELEMENT = {
1286
+ 甲: '木', 乙: '木', 丙: '火', 丁: '火', 戊: '土',
1287
+ 己: '土', 庚: '金', 辛: '金', 壬: '水', 癸: '水',
1288
+ };
1289
+
1290
+ const SEASON_TEMP = {
1291
+ 亥: -1.8, 子: -2, 丑: -1.5,
1292
+ 寅: -0.4, 卯: 0.2, 辰: 0.5,
1293
+ 巳: 1.3, 午: 2, 未: 1.5,
1294
+ 申: 0.6, 酉: 0.2, 戌: -0.2,
1295
+ };
1296
+
1297
+ const SEASON_NAME = {
1298
+ 亥: '初冬', 子: '仲冬', 丑: '季冬',
1299
+ 寅: '初春', 卯: '仲春', 辰: '季春',
1300
+ 巳: '初夏', 午: '仲夏', 未: '季夏',
1301
+ 申: '初秋', 酉: '仲秋', 戌: '季秋',
1302
+ };
1303
+
1304
+ const WET_BRANCH = { 丑: 1, 辰: 1, 子: 0.6, 亥: 0.5 };
1305
+ const DRY_BRANCH = { 未: 1, 戌: 1, 午: 0.4, 巳: 0.4 };
1306
+
1307
+ const NEED = {
1308
+ 寒湿: { element: '火', stem: '丙', avoid: '水', why: '冻土要太阳来解,水再多只会更冷' },
1309
+ 寒燥: { element: '火', stem: '丙', avoid: '水', why: '天寒地干,先要一点阳和之气' },
1310
+ 寒: { element: '火', stem: '丙', avoid: '水', why: '冬令气寒,非火不暖' },
1311
+ 温热: { element: '水', stem: '壬', avoid: '火', why: '火气初旺,得水润泽才好用' },
1312
+ 燥热: { element: '水', stem: '壬', avoid: '火', why: '火炎土燥,非水不润' },
1313
+ 热: { element: '水', stem: '壬', avoid: '火', why: '夏令炎威,先求一泓清水' },
1314
+ 湿滞: { element: '火', stem: '丙', avoid: '水', why: '湿气郁结,要火来蒸腾' },
1315
+ 燥: { element: '水', stem: '壬', avoid: '火', why: '气燥不润,得水则舒' },
1316
+ 平和: { element: null, stem: null, avoid: null, why: '寒暖相济,气候不是这一盘的主要矛盾' },
1317
+ };
1318
+
1319
+ const KIND_NAME = {
1320
+ 寒湿: '寒湿', 寒燥: '寒燥', 寒: '寒',
1321
+ 温热: '温热', 燥热: '燥热', 热: '热',
1322
+ 湿滞: '湿滞', 燥: '燥', 平和: '平和',
1323
+ };
1324
+
1325
+ const DAY_FEEL = {
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
+ const round2 = (n) => Math.round(n * 100) / 100;
1392
+
1393
+ function judgeClimate({ monthBranch, dayStem, wuxing = {}, branches = [] }) {
1394
+ const fire = Number(wuxing['火'] || 0);
1395
+ const water = Number(wuxing['水'] || 0);
1396
+ let wet = 0;
1397
+ let dry = 0;
1398
+ let wetCount = 0;
1399
+ let dryCount = 0;
1400
+ for (const b of branches) {
1401
+ if (WET_BRANCH[b]) { wet += WET_BRANCH[b]; if (b === '丑' || b === '辰') wetCount += 1; }
1402
+ if (DRY_BRANCH[b]) { dry += DRY_BRANCH[b]; if (b === '未' || b === '戌') dryCount += 1; }
1403
+ }
1404
+ const seasonT = SEASON_TEMP[monthBranch] ?? 0;
1405
+ const temp = round2(seasonT + 0.45 * fire - 0.4 * water);
1406
+ const damp = round2(wet - dry + 0.25 * water - 0.25 * fire);
1407
+
1408
+ const cold = temp <= -0.8;
1409
+ const hot = temp >= 0.9;
1410
+ const wetSide = damp >= 0.8;
1411
+ const drySide = damp <= -0.8;
1412
+
1413
+ let kind = '平和';
1414
+ if (cold && wetSide) kind = '寒湿';
1415
+ else if (cold && drySide) kind = '寒燥';
1416
+ else if (cold) kind = '寒';
1417
+ else if (hot && drySide) kind = '燥热';
1418
+ else if (hot) kind = '温热';
1419
+ else if (hot && damp >= 0.8) kind = '湿滞';
1420
+ else if (drySide) kind = '燥';
1421
+ else if (wetSide) kind = '湿滞';
1422
+
1423
+ const need = NEED[kind] || NEED.平和;
1424
+ const dayElement = STEM_ELEMENT[dayStem] || '土';
1425
+ const evidence = [
1426
+ `${SEASON_NAME[monthBranch] || monthBranch}${monthBranch}月`,
1427
+ `火${fire}`,
1428
+ `水${water}`,
1429
+ wetCount ? `湿土${wetCount}处` : '',
1430
+ dryCount ? `燥土${dryCount}处` : '',
1431
+ ].filter(Boolean);
1432
+
1433
+ const priority = Math.abs(temp) >= 1.2 || Math.abs(damp) >= 1.2 ? '调候优先' : '扶抑为主';
1434
+ const narration = buildNarration({ kind, monthBranch, dayElement, need, temp, damp, fire, water });
1435
+
1436
+ return {
1437
+ kind,
1438
+ label: KIND_NAME[kind] || kind,
1439
+ temp,
1440
+ damp,
1441
+ cold,
1442
+ hot,
1443
+ wet: wetSide,
1444
+ dry: drySide,
1445
+ need: need.element ? { element: need.element, stem: need.stem, why: need.why } : null,
1446
+ avoidElement: need.avoid,
1447
+ dayElement,
1448
+ priority,
1449
+ evidence,
1450
+ narration,
1451
+ };
1452
+ }
1453
+
1454
+ function buildNarration({ kind, monthBranch, dayElement, need, fire, water }) {
1455
+ const feel = (DAY_FEEL[kind] || DAY_FEEL.平和)[dayElement] || '';
1456
+ const season = SEASON_NAME[monthBranch] || '';
1457
+ const head = `${season ? season : ''}${monthBranch}月出生,全局火${fire}、水${water},这一盘的气候是「${KIND_NAME[kind] || kind}」`;
1458
+ if (!need.element) {
1459
+ return `${head}。${feel}。调候不是这一盘的主要矛盾,取用仍以扶抑为主,寒暖只作微调。`;
1460
+ }
1461
+ return `${head}。${feel}。所以这一局要的不是更多的${kind.includes('寒') ? '水' : '火'},而是${need.stem}${need.element}——${need.why}。`;
1462
+ }
1463
+
1464
+ function arbitrate(climate, favors = [], avoids = []) {
1465
+ if (!climate || !climate.need) return { clash: false, text: '气候平和,按扶抑取用即可。' };
1466
+ const needEl = climate.need.element;
1467
+ const inFavor = favors.includes(needEl);
1468
+ const inAvoid = avoids.includes(needEl);
1469
+ const same = inFavor && !inAvoid;
1470
+ if (same) return { clash: false, text: `调候要${climate.need.stem}${needEl},扶抑也以${needEl}为喜——两套取法同向,不必取舍。` };
1471
+ return {
1472
+ clash: true,
1473
+ text: `调候要${climate.need.stem}${needEl},扶抑却把${needEl}列为${inAvoid ? '忌' : '闲'};两者不一致,本盘按调候优先取${needEl},扶抑次之——因为寒暖失衡时,再补强弱也难受。`,
1474
+ };
1475
+ }
1476
+
1477
+ function climateText(climate) {
1478
+ if (!climate) return '';
1479
+ const ev = (climate.evidence || []).join('、');
1480
+ const arb = climate.need
1481
+ ? `调候取${climate.need.stem}${climate.need.element}(${climate.need.why}),忌${climate.avoidElement}。`
1482
+ : '气候平和,调候不作主取。';
1483
+ return `${climate.label}(${ev})。${climate.narration}${arb}${climate.priority === '调候优先' ? ' 本盘调候优先于扶抑。' : ''}`;
1484
+ }
1485
+
1486
+ return { judgeClimate, arbitrate, climateText };
1487
+ })();
1488
+
1283
1489
  __mods.geju = (function () {
1284
1490
 
1491
+ const { judgeClimate, arbitrate } = __mods.tiaohou;
1492
+
1285
1493
  const STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
1286
1494
  const BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
1287
1495
 
@@ -1459,38 +1667,35 @@ function specialPatterns(pillars, dayStem, monthBranch, strength, wuxing, gods)
1459
1667
  return out;
1460
1668
  }
1461
1669
 
1462
- const TIAOHOU = {
1463
- 亥: ['火', '冬月寒水当令,最喜火暖局'],
1464
- 子: ['火', '仲冬水旺,最喜火暖局'],
1465
- 丑: ['火', '季冬湿寒,喜火解冻'],
1466
- 巳: ['水', '初夏火旺,喜水制衡'],
1467
- 午: ['水', '仲夏火炎,最喜水调候'],
1468
- 未: ['水', '季夏燥土,喜水润局'],
1469
- 寅: ['火', '初春尚寒,得火而发'],
1470
- 卯: ['火', '仲春木旺,喜火吐秀'],
1471
- 辰: ['金', '季春湿土,喜金水疏泄'],
1472
- 申: ['火', '初秋金旺,喜火炼金'],
1473
- 酉: ['火', '仲秋金锐,喜火制'],
1474
- 戌: ['水', '季秋燥土,喜水润泽'],
1475
- };
1476
-
1477
- function yongshenOf(dayStem, monthBranch, strength, wuxing) {
1670
+ function yongshenOf(dayStem, monthBranch, strength, wuxing, branches = []) {
1478
1671
  const roles = elementRoles(dayStem);
1479
1672
  const me = roles.me;
1480
1673
  const weak = strength.level === '身弱' || strength.level === '偏弱';
1481
1674
  const strong = strength.level === '身强' || strength.level === '偏强';
1482
1675
 
1483
- const favor = weak ? [roles.inMe, me] : strong ? [roles.shi, roles.cai, roles.guan] : [roles.inMe, me, roles.shi];
1484
- const avoid = weak ? [roles.shi, roles.cai, roles.guan] : strong ? [me, roles.inMe] : [roles.guan, roles.cai];
1485
- const favorableTenGods = weak ? ['正印', '偏印', '比肩', '劫财'] : strong
1486
- ? ['食神', '伤官', '正财', '偏财', '正官', '七杀']
1487
- : ['正印', '偏印', '食神', '伤官'];
1676
+ const baseFavor = weak ? [roles.inMe, me] : strong ? [roles.shi, roles.cai, roles.guan] : [roles.inMe, me, roles.shi];
1677
+ const baseAvoid = weak ? [roles.shi, roles.cai, roles.guan] : strong ? [me, roles.inMe] : [roles.guan, roles.cai];
1678
+ let favor = baseFavor.slice();
1679
+ let avoid = baseAvoid.slice();
1488
1680
 
1489
- const th = TIAOHOU[monthBranch];
1490
- const tiaohou = th ? { element: th[0], note: th[1] } : null;
1681
+ const climate = judgeClimate({ monthBranch, dayStem, wuxing, branches });
1682
+ const arb = arbitrate(climate, baseFavor, baseAvoid);
1683
+ if (climate.need && climate.priority === '调候优先') {
1684
+ const needEl = climate.need.element;
1685
+ if (!favor.includes(needEl)) favor.unshift(needEl);
1686
+ avoid = avoid.filter((e) => e !== needEl);
1687
+ if (climate.avoidElement && !avoid.includes(climate.avoidElement)) avoid.unshift(climate.avoidElement);
1688
+ }
1491
1689
 
1492
1690
  const roleName = (el) => (el === me ? '比劫' : el === roles.inMe ? '印'
1493
1691
  : el === roles.shi ? '食伤' : el === roles.cai ? '财' : '官杀');
1692
+ const expand = {
1693
+ 比劫: ['比肩', '劫财'], 印: ['正印', '偏印'], 食伤: ['食神', '伤官'],
1694
+ 财: ['正财', '偏财'], 官杀: ['正官', '七杀'],
1695
+ };
1696
+ const favorTenGods = Array.from(new Set(favor.flatMap((el) => expand[roleName(el)] || [])));
1697
+ const avoidTenGods = Array.from(new Set(avoid.flatMap((el) => expand[roleName(el)] || [])));
1698
+
1494
1699
  const detail = [
1495
1700
  `日主${dayStem}${me},${strength.level}(${strength.score} 分):`,
1496
1701
  weak
@@ -1498,24 +1703,22 @@ function yongshenOf(dayStem, monthBranch, strength, wuxing) {
1498
1703
  : strong
1499
1704
  ? `喜${roles.shi}(食伤)泄秀、${roles.cai}(财)耗、${roles.guan}(官杀)制;忌${me}(比劫)与${roles.inMe}(印)再助。`
1500
1705
  : '身中和,先取调候与通关,喜忌随大运流年动态变化。',
1501
- tiaohou ? `调候(简化):${tiaohou.note},宜${tiaohou.element}。` : '',
1502
- '扶抑法为主;调候未用穷通宝鉴全表,各派取用不同,仅作方向提示。',
1706
+ `调候:${climate.label}(${(climate.evidence || []).join('、')})${climate.need ? `,取${climate.need.stem}${climate.need.element},忌${climate.avoidElement}` : ',气候平和'}。`,
1707
+ arb.text,
1708
+ arb.clash ? '取舍:本盘按「调候优先、扶抑次之」取用;若你更认同扶抑派,可把调候当环境偏好,强弱判断不受影响。' : '',
1503
1709
  ].filter(Boolean).join('');
1504
1710
 
1505
1711
  return {
1506
- method: '扶抑为主 + 调候简化提示',
1712
+ method: climate.priority === '调候优先' ? '调候优先 + 扶抑校验' : '扶抑为主 + 调候校验',
1507
1713
  level: strength.level,
1508
1714
  favor,
1509
1715
  avoid,
1510
1716
  favorRoles: favor.map(roleName),
1511
1717
  avoidRoles: avoid.map(roleName),
1512
- favorTenGods: favorableTenGods,
1513
- avoidTenGods: weak
1514
- ? ['食神', '伤官', '正财', '偏财', '正官', '七杀']
1515
- : strong
1516
- ? ['比肩', '劫财', '正印', '偏印']
1517
- : ['正官', '七杀', '正财', '偏财'],
1518
- tiaohou,
1718
+ favorTenGods,
1719
+ avoidTenGods,
1720
+ tiaohou: { ...climate, ...arb, element: climate.need ? climate.need.element : null },
1721
+ climate,
1519
1722
  detail,
1520
1723
  };
1521
1724
  }
@@ -1534,33 +1737,54 @@ function scoreLuck(ganzhi, frame) {
1534
1737
  const branch = ganzhi[1];
1535
1738
  const stemEl = STEM_ELEMENT[stem];
1536
1739
  const branchEl = BRANCH_ELEMENT[branch];
1740
+ const th = ys.tiaohou || ys.climate || null;
1537
1741
  const scoreEl = (el) => {
1538
1742
  let s = 0;
1539
1743
  if (ys.favor.includes(el)) s += 1;
1540
1744
  if (ys.avoid.includes(el)) s -= 1;
1541
- if (ys.tiaohou && el === ys.tiaohou.element) s += 0.4;
1542
1745
  return s;
1543
1746
  };
1544
1747
  let score = scoreEl(stemEl) * 1.0 + scoreEl(branchEl) * 0.8;
1545
1748
  for (const h of (HIDDEN[branch] || []).slice(1)) {
1546
1749
  score += scoreEl(STEM_ELEMENT[h]) * 0.2;
1547
1750
  }
1548
- score = Math.round(score * 10) / 10;
1549
1751
 
1550
- const godStem = tenGod(frame.dayStem, stem);
1551
- const bonus = ys.favorTenGods.includes(godStem) ? 0.3 : 0;
1552
- const total = Math.round((score + bonus) * 10) / 10;
1553
-
1554
- const tier = TIERS.find(([min]) => total >= min)[1];
1555
1752
  const reasons = [];
1556
1753
  if (ys.favor.includes(stemEl)) reasons.push(`天干${stem}${stemEl}为用神`);
1557
1754
  else if (ys.avoid.includes(stemEl)) reasons.push(`天干${stem}${stemEl}为忌神`);
1558
1755
  else reasons.push(`天干${stem}${stemEl}不喜不忌`);
1559
1756
  if (ys.favor.includes(branchEl)) reasons.push(`地支${branch}${branchEl}助用`);
1560
1757
  else if (ys.avoid.includes(branchEl)) reasons.push(`地支${branch}${branchEl}助忌`);
1561
- if (ys.tiaohou && (stemEl === ys.tiaohou.element || branchEl === ys.tiaohou.element)) {
1562
- reasons.push(`合调候(${ys.tiaohou.element})`);
1758
+
1759
+ if (th && th.need && th.priority === '调候优先') {
1760
+ const needEl = th.need.element;
1761
+ const oppEl = th.avoidElement;
1762
+ const cold = String(th.kind).includes('寒');
1763
+ if (stemEl === needEl) {
1764
+ score += 1.1;
1765
+ reasons.push(`${th.label}之局得${stem}${stemEl},${cold ? '解冻' : '得润'}`);
1766
+ } else if (stemEl === oppEl) {
1767
+ score -= 0.9;
1768
+ reasons.push(`${th.label}之局再逢${stem}${stemEl},${cold ? '冻木难生' : '燥上加燥'}`);
1769
+ }
1770
+ if (branchEl === needEl) {
1771
+ score += 0.9;
1772
+ reasons.push(`地支${branch}${branchEl}合调候`);
1773
+ } else if (branchEl === oppEl) {
1774
+ score -= 1.0;
1775
+ reasons.push(`地支${branch}${branchEl}加重${th.label}`);
1776
+ }
1777
+ if (cold && (stemEl === '水' || branchEl === '水')) score -= 0.3;
1778
+ } else if (th && th.need && (stemEl === th.need.element || branchEl === th.need.element)) {
1779
+ reasons.push(`合调候(${th.need.element})`);
1563
1780
  }
1781
+
1782
+ const godStem = tenGod(frame.dayStem, stem);
1783
+ const bonus = ys.favorTenGods.includes(godStem) ? 0.3 : 0;
1784
+ let total = Math.round((score + bonus) * 10) / 10;
1785
+ total = Math.max(-4, Math.min(4, total));
1786
+
1787
+ const tier = TIERS.find(([min]) => total >= min)[1];
1564
1788
  return { ganzhi, score: total, tier, stemTenGod: godStem, reasons, note: `${godStem}运,${tier}(${total})` };
1565
1789
  }
1566
1790
 
@@ -1569,7 +1793,7 @@ function analyzeBaziFrame({ pillars, dayStem, monthBranch, strength, wuxing }) {
1569
1793
  const month = monthPattern(pillars, dayStem, monthBranch);
1570
1794
  const special = specialPatterns(pillars, dayStem, monthBranch, strength, wuxing, gods);
1571
1795
  const geju = [month, ...special];
1572
- const yongshen = yongshenOf(dayStem, monthBranch, strength, wuxing);
1796
+ const yongshen = yongshenOf(dayStem, monthBranch, strength, wuxing, Object.values(pillars).map((p) => p.branch));
1573
1797
  return {
1574
1798
  geju,
1575
1799
  primary: month,
@@ -2042,6 +2266,7 @@ function buildBazi(input) {
2042
2266
  },
2043
2267
  strength,
2044
2268
  frame,
2269
+ climate: frame.yongshen.climate || frame.yongshen.tiaohou || null,
2045
2270
  liunian,
2046
2271
  liunianFrom: thisYear,
2047
2272
  };
@@ -5732,6 +5957,7 @@ return { TRIGRAM, YAO_POSITION, MOVING_NATURE, HEX64, HEX_BY_CORE, ZIWEI_STARS,
5732
5957
 
5733
5958
  __mods.almanac = (function () {
5734
5959
  const { solarToLunar } = __mods.lunar;
5960
+ const { nayinOf } = __mods.nayin;
5735
5961
  const { toJDN, fromJDN, dayGanzhiIndex, ganzhiName, termWindow, termInstantJd, partsToJd, STEMS, BRANCHES, TERM_NAMES } = __mods.calendar;
5736
5962
  const { HEX64 } = __mods['interpret-data'];
5737
5963
  const { assertCivil, daysInMonth } = __mods.validate;
@@ -5803,6 +6029,80 @@ function lunarMonthText(month, isLeap = false) {
5803
6029
  return `${isLeap ? '闰' : ''}${MONTH_TEXT[month - 1] || month}月`;
5804
6030
  }
5805
6031
 
6032
+ const TIAN_SHEN = ['青龙', '明堂', '天刑', '朱雀', '金匮', '天德', '白虎', '玉堂', '天牢', '玄武', '司命', '勾陈'];
6033
+ const TIAN_SHEN_TYPE = {
6034
+ 青龙: '黄道', 明堂: '黄道', 金匮: '黄道', 天德: '黄道', 玉堂: '黄道', 司命: '黄道',
6035
+ 天刑: '黑道', 朱雀: '黑道', 白虎: '黑道', 天牢: '黑道', 玄武: '黑道', 勾陈: '黑道',
6036
+ };
6037
+ const TIAN_SHEN_OFFSET = { 子: 4, 丑: 2, 寅: 0, 卯: 10, 辰: 8, 巳: 6, 午: 4, 未: 2, 申: 0, 酉: 10, 戌: 8, 亥: 6 };
6038
+
6039
+ function tianShenOf(branch, anchorBranch) {
6040
+ const b = BRANCHES.indexOf(branch);
6041
+ const off = TIAN_SHEN_OFFSET[anchorBranch];
6042
+ if (b < 0 || off == null) return null;
6043
+ const name = TIAN_SHEN[(b + off) % 12];
6044
+ const type = TIAN_SHEN_TYPE[name];
6045
+ return { name, type, luck: type === '黄道' ? '吉' : '凶' };
6046
+ }
6047
+
6048
+ const MANSIONS = {
6049
+ 角: { luck: '吉', zheng: '木', animal: '蛟', gong: '东' }, 亢: { luck: '凶', zheng: '金', animal: '龙', gong: '东' },
6050
+ 氐: { luck: '凶', zheng: '土', animal: '貉', gong: '东' }, 房: { luck: '吉', zheng: '日', animal: '兔', gong: '东' },
6051
+ 心: { luck: '凶', zheng: '月', animal: '狐', gong: '东' }, 尾: { luck: '吉', zheng: '火', animal: '虎', gong: '东' },
6052
+ 箕: { luck: '吉', zheng: '水', animal: '豹', gong: '东' }, 斗: { luck: '吉', zheng: '木', animal: '獬', gong: '北' },
6053
+ 牛: { luck: '凶', zheng: '金', animal: '牛', gong: '北' }, 女: { luck: '凶', zheng: '土', animal: '蝠', gong: '北' },
6054
+ 虚: { luck: '凶', zheng: '日', animal: '鼠', gong: '北' }, 危: { luck: '凶', zheng: '月', animal: '燕', gong: '北' },
6055
+ 室: { luck: '吉', zheng: '火', animal: '猪', gong: '北' }, 壁: { luck: '吉', zheng: '水', animal: '獝', gong: '北' },
6056
+ 奎: { luck: '凶', zheng: '木', animal: '狼', gong: '西' }, 娄: { luck: '吉', zheng: '金', animal: '狗', gong: '西' },
6057
+ 胃: { luck: '吉', zheng: '土', animal: '彘', gong: '西' }, 昴: { luck: '凶', zheng: '日', animal: '鸡', gong: '西' },
6058
+ 毕: { luck: '吉', zheng: '月', animal: '乌', gong: '西' }, 觜: { luck: '凶', zheng: '火', animal: '猴', gong: '西' },
6059
+ 参: { luck: '吉', zheng: '水', animal: '猿', gong: '西' }, 井: { luck: '吉', zheng: '木', animal: '犴', gong: '南' },
6060
+ 鬼: { luck: '凶', zheng: '金', animal: '羊', gong: '南' }, 柳: { luck: '凶', zheng: '土', animal: '獐', gong: '南' },
6061
+ 星: { luck: '凶', zheng: '日', animal: '马', gong: '南' }, 张: { luck: '吉', zheng: '月', animal: '鹿', gong: '南' },
6062
+ 翼: { luck: '凶', zheng: '火', animal: '蛇', gong: '南' }, 轸: { luck: '吉', zheng: '水', animal: '蚓', gong: '南' },
6063
+ };
6064
+ const XIU_GROUPS = {
6065
+ A: ['虚', '毕', '翼', '箕', '奎', '鬼', '氐'],
6066
+ B: ['房', '危', '觜', '轸', '斗', '娄', '柳'],
6067
+ C: ['星', '心', '室', '参', '角', '牛', '胃'],
6068
+ D: ['昴', '张', '尾', '壁', '井', '亢', '女'],
6069
+ };
6070
+ const XIU_GROUP_OF = { 子: 'A', 辰: 'A', 申: 'A', 丑: 'B', 巳: 'B', 酉: 'B', 寅: 'C', 午: 'C', 戌: 'C', 卯: 'D', 未: 'D', 亥: 'D' };
6071
+
6072
+ function xiuOf(dayBranch, weekdayIndex) {
6073
+ const g = XIU_GROUP_OF[dayBranch];
6074
+ if (!g) return null;
6075
+ const name = XIU_GROUPS[g][weekdayIndex];
6076
+ const info = MANSIONS[name];
6077
+ return info ? { name, ...info } : null;
6078
+ }
6079
+
6080
+ const DIR_OF = { 坎: '正北', 艮: '东北', 震: '正东', 巽: '东南', 离: '正南', 坤: '西南', 兑: '正西', 乾: '西北' };
6081
+ const POS_XI = { 甲: '艮', 乙: '乾', 丙: '坤', 丁: '离', 戊: '巽', 己: '艮', 庚: '乾', 辛: '坤', 壬: '离', 癸: '巽' };
6082
+ const POS_CAI = { 甲: '艮', 乙: '艮', 丙: '坤', 丁: '坤', 戊: '坎', 己: '坎', 庚: '震', 辛: '震', 壬: '离', 癸: '离' };
6083
+ const POS_FU = { 甲: '坎', 乙: '坤', 丙: '乾', 丁: '巽', 戊: '艮', 己: '坎', 庚: '坤', 辛: '乾', 壬: '巽', 癸: '艮' };
6084
+ const POS_YANG_GUI = { 甲: '坤', 乙: '坤', 丙: '兑', 丁: '乾', 戊: '艮', 己: '坎', 庚: '离', 辛: '艮', 壬: '震', 癸: '巽' };
6085
+ const POS_YIN_GUI = { 甲: '艮', 乙: '坎', 丙: '乾', 丁: '兑', 戊: '坤', 己: '坤', 庚: '艮', 辛: '离', 壬: '巽', 癸: '震' };
6086
+ const posOf = (map, stem) => (map[stem] ? { gua: map[stem], dir: DIR_OF[map[stem]] } : null);
6087
+
6088
+ const TAI_SHEN = {
6089
+ 甲子: '占门碓 外东南', 乙丑: '碓磨厕 外东南', 丙寅: '厨灶炉 外正南', 丁卯: '仓库门 外正南', 戊辰: '房床栖 外正南',
6090
+ 己巳: '占门床 外正南', 庚午: '占碓磨 外正南', 辛未: '厨灶厕 外西南', 壬申: '仓库炉 外西南', 癸酉: '房床门 外西南',
6091
+ 甲戌: '占门栖 外西南', 乙亥: '碓磨床 外西南', 丙子: '厨灶碓 外西南', 丁丑: '仓库厕 外正西', 戊寅: '房床炉 外正西',
6092
+ 己卯: '占大门 外正西', 庚辰: '碓磨栖 外正西', 辛巳: '厨灶床 外正西', 壬午: '仓库碓 外西北', 癸未: '房床厕 外西北',
6093
+ 甲申: '占门炉 外西北', 乙酉: '碓磨门 外西北', 丙戌: '厨灶栖 外西北', 丁亥: '仓库床 外西北', 戊子: '房床碓 外正北',
6094
+ 己丑: '占门厕 外正北', 庚寅: '碓磨炉 外正北', 辛卯: '厨灶门 外正北', 壬辰: '仓库栖 外正北', 癸巳: '占房床 房内北',
6095
+ 甲午: '占门碓 房内北', 乙未: '碓磨厕 房内北', 丙申: '厨灶炉 房内北', 丁酉: '仓库门 房内北', 戊戌: '房床栖 房内中',
6096
+ 己亥: '占门床 房内中', 庚子: '占碓磨 房内南', 辛丑: '厨灶厕 房内南', 壬寅: '仓库炉 房内南', 癸卯: '房床门 房内西',
6097
+ 甲辰: '占门栖 房内东', 乙巳: '碓磨床 房内东', 丙午: '厨灶碓 房内东', 丁未: '仓库厕 房内东', 戊申: '房床炉 房内中',
6098
+ 己酉: '占大门 外东北', 庚戌: '碓磨栖 外东北', 辛亥: '厨灶床 外东北', 壬子: '仓库碓 外东北', 癸丑: '房床厕 外东北',
6099
+ 甲寅: '占门炉 外东北', 乙卯: '碓磨门 外正东', 丙辰: '厨灶栖 外正东', 丁巳: '仓库床 外正东', 戊午: '房床碓 外正东',
6100
+ 己未: '占门厕 外正东', 庚申: '碓磨炉 外东南', 辛酉: '厨灶门 外东南', 壬戌: '仓库栖 外东南', 癸亥: '占房床 外东南',
6101
+ };
6102
+
6103
+ const LIUHE = { 子: '丑', 丑: '子', 寅: '亥', 亥: '寅', 卯: '戌', 戌: '卯', 辰: '酉', 酉: '辰', 巳: '申', 申: '巳', 午: '未', 未: '午' };
6104
+ const SANHE = { 申: '申子辰', 子: '申子辰', 辰: '申子辰', 亥: '亥卯未', 卯: '亥卯未', 未: '亥卯未', 寅: '寅午戌', 午: '寅午戌', 戌: '寅午戌', 巳: '巳酉丑', 酉: '巳酉丑', 丑: '巳酉丑' };
6105
+
5806
6106
  function dailyHexagram(year, month, day) {
5807
6107
  const names = Object.keys(HEX64);
5808
6108
  if (!names.length) return null;
@@ -5858,7 +6158,41 @@ function buildAlmanac(input) {
5858
6158
  return `${p.year}-${String(p.month).padStart(2, '0')}-${String(p.day).padStart(2, '0')}`;
5859
6159
  };
5860
6160
 
5861
- const weekday = ['日', '一', '二', '三', '四', '五', '六'][(jdn + 1) % 7];
6161
+ const weekdayIdx = (jdn + 1) % 7;
6162
+ const weekday = ['日', '一', '二', '三', '四', '五', '六'][weekdayIdx];
6163
+ const dayGod = tianShenOf(dayBranch, BRANCHES[monthBranchIdx]);
6164
+ const xiu = xiuOf(dayBranch, weekdayIdx);
6165
+ const taiShen = TAI_SHEN[dayGz.name] || null;
6166
+ const hourBase = (STEMS.indexOf(dayGz.stem) % 5) * 2;
6167
+ const hours = BRANCHES.map((zhi, i) => {
6168
+ const gan = STEMS[(hourBase + i) % 10];
6169
+ const gz = ganzhiName(hourComposite((hourBase + i) % 10, i));
6170
+ const shen = tianShenOf(zhi, dayBranch);
6171
+ return {
6172
+ zhi,
6173
+ name: `${zhi}时`,
6174
+ ganZhi: gz.name,
6175
+ gan,
6176
+ shen: shen ? shen.name : null,
6177
+ type: shen ? shen.type : null,
6178
+ luck: shen ? shen.luck : null,
6179
+ };
6180
+ });
6181
+ const sanheGroup = SANHE[dayBranch] || '';
6182
+ const liuheBranch = LIUHE[dayBranch] || null;
6183
+ const lucky = {
6184
+ liuhe: liuheBranch ? SHENGXIAO[BRANCHES.indexOf(liuheBranch)] : null,
6185
+ sanhe: [...sanheGroup].filter((b) => b !== dayBranch).map((b) => SHENGXIAO[BRANCHES.indexOf(b)]),
6186
+ };
6187
+ const positions = {
6188
+ caiShen: CAI_SHEN[dayGz.stem] || null,
6189
+ xiShen: XI_SHEN[dayGz.stem] || null,
6190
+ fuShen: posOf(POS_FU, dayGz.stem),
6191
+ yangGui: posOf(POS_YANG_GUI, dayGz.stem),
6192
+ yinGui: posOf(POS_YIN_GUI, dayGz.stem),
6193
+ xiShenFull: posOf(POS_XI, dayGz.stem),
6194
+ caiShenFull: posOf(POS_CAI, dayGz.stem),
6195
+ };
5862
6196
  const lunarText = `农历${springGz.name}年${lunarMonthText(lunar.month, lunar.isLeap)}${dayText(lunar.day)}`;
5863
6197
  const boundaryNote = yearGz.name !== springGz.name
5864
6198
  ? `年界:本条按立春界作${yearGz.name}年·属${SHENGXIAO[BRANCHES.indexOf(yearGz.branch)]}(命理排盘用);春节界为${springGz.name}年·属${SHENGXIAO[BRANCHES.indexOf(springGz.branch)]},两种都对,取用场合不同`
@@ -5870,7 +6204,7 @@ function buildAlmanac(input) {
5870
6204
  date: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
5871
6205
  weekday: `星期${weekday}`,
5872
6206
  ...(boundaryNote ? { boundaryNote } : {}),
5873
- scope: '通行简表口径:宜忌只按建除十二神;逐日神煞/二十八宿/彭祖百忌/胎神等全量黄历数据未收录',
6207
+ scope: '通行口径:宜忌按建除十二神简表;已收录值日十二神(黄道/黑道)、时辰吉凶、二十八宿、胎神、吉神方位、纳音、六合三合吉配;逐日全量吉神凶煞与彭祖百忌未收录',
5874
6208
  },
5875
6209
  lunar: {
5876
6210
  year: springYear,
@@ -5891,7 +6225,13 @@ function buildAlmanac(input) {
5891
6225
  prev: prevTerm ? { name: termName(prevTerm), date: fmtJdDate(prevTerm.jd) } : null,
5892
6226
  next: nextTerm ? { name: termName(nextTerm), date: fmtJdDate(nextTerm.jd) } : null,
5893
6227
  },
5894
- positions: { caiShen: CAI_SHEN[dayGz.stem] || null, xiShen: XI_SHEN[dayGz.stem] || null },
6228
+ positions,
6229
+ dayGod,
6230
+ xiu,
6231
+ taiShen,
6232
+ nayin: { day: nayinOf(dayGanzhiIndex(jdn)) },
6233
+ lucky,
6234
+ hours,
5895
6235
  zhiXing,
5896
6236
  yi: ZHIXING_YI[zhiXing].slice(),
5897
6237
  ji: ZHIXING_JI[zhiXing].slice(),
@@ -5904,6 +6244,7 @@ function buildAlmanac(input) {
5904
6244
  sources: [
5905
6245
  '农历与节气:本地天文历法(定朔 + 节气黄经)',
5906
6246
  '建除十二神与冲煞:传统通行口径',
6247
+ '值日/时家十二神(青龙…勾陈)、二十八宿、胎神、吉神方位:通行表',
5907
6248
  '宜忌:建除十二神通行简表(非逐日全量黄历)',
5908
6249
  '每日一卦:按公历日期确定性取卦(同日同卦)',
5909
6250
  ],
@@ -5918,7 +6259,14 @@ function monthComposite(stemIdx, branchIdx) {
5918
6259
  return 0;
5919
6260
  }
5920
6261
 
5921
- return { ZHIXING, ZHIXING_YI, ZHIXING_JI, SHENGXIAO, hourToBranch, zhixingOf, lunarMonthText, dailyHexagram, buildAlmanac };
6262
+ function hourComposite(stemIdx, branchIdx) {
6263
+ for (let i = 0; i < 60; i++) {
6264
+ if (i % 10 === stemIdx && i % 12 === branchIdx) return i;
6265
+ }
6266
+ return 0;
6267
+ }
6268
+
6269
+ return { ZHIXING, ZHIXING_YI, ZHIXING_JI, SHENGXIAO, hourToBranch, zhixingOf, lunarMonthText, TIAN_SHEN, TIAN_SHEN_TYPE, tianShenOf, MANSIONS, xiuOf, dailyHexagram, buildAlmanac };
5922
6270
  })();
5923
6271
 
5924
6272
  __mods.star = (function () {
@@ -6304,26 +6652,52 @@ const { buildAlmanac, SHENGXIAO } = __mods.almanac;
6304
6652
  const TASKS = {
6305
6653
  嫁娶: { yi: ['嫁娶', '结亲', '订盟', '纳采'], related: ['会亲友', '裁衣'], category: 'marriage' },
6306
6654
  订婚: { yi: ['订盟', '纳采'], related: ['嫁娶', '会亲友'], category: 'marriage' },
6655
+ 领证: { yi: ['嫁娶', '纳采'], related: ['订盟', '会亲友'], category: 'marriage' },
6656
+ 提亲: { yi: ['纳采', '订盟'], related: ['会亲友'], category: 'marriage' },
6657
+ 收送聘金: { yi: ['纳采', '订盟'], related: ['嫁娶'], category: 'marriage' },
6658
+ 满月酒: { yi: ['祭祀', '祈福'], related: ['会亲友'], category: 'celebration' },
6307
6659
  开业: { yi: ['开市', '立券', '交易'], related: ['纳财', '挂匾'], category: 'business' },
6660
+ 交易: { yi: ['交易', '立券'], related: ['开市', '纳财'], category: 'business' },
6661
+ 投资: { yi: ['纳财', '交易'], related: ['立券'], category: 'business' },
6662
+ 签订合同: { yi: ['立券', '交易'], related: ['纳财', '开市'], category: 'contract' },
6663
+ 签约: { yi: ['立券', '交易'], related: ['纳财', '开市'], category: 'contract' },
6664
+ 纳财: { yi: ['纳财', '收账'], related: ['开市', '立券'], category: 'contract' },
6308
6665
  入宅: { yi: ['入宅', '移徙'], related: ['安床', '修造', '扫舍'], category: 'move' },
6309
6666
  搬家: { yi: ['移徙', '入宅'], related: ['安床', '修造'], category: 'move' },
6667
+ 搬家入宅: { yi: ['入宅', '移徙'], related: ['安床', '扫舍'], category: 'move' },
6310
6668
  动土: { yi: ['动土', '修造'], related: ['竖柱', '上梁'], category: 'construction' },
6311
6669
  装修: { yi: ['修造', '修饰垣墙'], related: ['动土'], category: 'construction' },
6670
+ 装修建造: { yi: ['修造', '动土'], related: ['竖柱', '上梁'], category: 'construction' },
6671
+ 试车买车: { yi: ['交易', '纳财'], related: ['出行'], category: 'purchase' },
6312
6672
  安葬: { yi: ['安葬', '破土'], related: ['立碑', '埋穴'], category: 'burial' },
6313
6673
  出行: { yi: ['出行'], related: ['上任', '赴任'], category: 'travel' },
6674
+ 旅行: { yi: ['出行'], related: ['上任', '赴任'], category: 'travel' },
6675
+ 赴任: { yi: ['上任', '赴任'], related: ['出行'], category: 'travel' },
6676
+ 访亲友: { yi: ['会亲友'], related: ['出行'], category: 'visit' },
6677
+ 入学: { yi: ['入学'], related: ['开市', '修造'], category: 'study' },
6314
6678
  祭祀: { yi: ['祭祀'], related: ['祈福', '求嗣'], category: 'worship' },
6315
- 签约: { yi: ['立券', '交易'], related: ['纳财', '开市'], category: 'contract' },
6316
6679
  求医: { yi: ['求医', '疗病'], related: ['针灸'], category: 'medical' },
6317
- 纳财: { yi: ['纳财', '收账'], related: ['开市', '立券'], category: 'contract' },
6680
+ };
6681
+
6682
+ const TASK_GROUPS = {
6683
+ 常用: ['嫁娶', '搬家入宅', '出行', '开业', '签订合同', '祭祀'],
6684
+ 婚嫁: ['嫁娶', '订婚', '领证', '提亲', '收送聘金', '满月酒'],
6685
+ 生活: ['搬家', '入宅', '旅行', '访亲友', '入学', '求医', '祭祀'],
6686
+ 建造: ['动土', '装修', '装修建造', '安葬', '试车买车'],
6687
+ 工商: ['开业', '交易', '投资', '签订合同', '签约', '纳财', '赴任'],
6318
6688
  };
6319
6689
 
6320
6690
  const ZHIXING_FAVOR = {
6321
6691
  marriage: ['满', '定', '成', '开'],
6692
+ celebration: ['满', '成', '开', '平'],
6322
6693
  business: ['开', '成', '满', '收'],
6323
6694
  move: ['定', '成', '开', '除'],
6324
6695
  construction: ['建', '除', '成', '开'],
6696
+ purchase: ['成', '开', '满'],
6325
6697
  burial: ['除', '定', '闭', '破'],
6326
6698
  travel: ['建', '成', '开', '除'],
6699
+ visit: ['满', '平', '定', '成'],
6700
+ study: ['成', '开', '建'],
6327
6701
  worship: ['除', '满', '平', '定', '执', '成', '开'],
6328
6702
  contract: ['定', '成', '收', '开'],
6329
6703
  medical: ['除', '开'],
@@ -6417,7 +6791,133 @@ function buildZeri(input) {
6417
6791
  };
6418
6792
  }
6419
6793
 
6420
- return { TASKS, buildZeri };
6794
+ return { TASKS, TASK_GROUPS, buildZeri };
6795
+ })();
6796
+
6797
+ __mods.convert = (function () {
6798
+ const { solarToLunar } = __mods.lunar;
6799
+ const { fromJDN, toJDN } = __mods.calendar;
6800
+ const { buildAlmanac } = __mods.almanac;
6801
+ const { assertCivil, daysInMonth } = __mods.validate;
6802
+
6803
+ const WEEK = ['日', '一', '二', '三', '四', '五', '六'];
6804
+ const MONTH_TEXT = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊'];
6805
+ const DAY_TENS = ['初', '十', '廿'];
6806
+ const DAY_UNITS = ['十', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
6807
+
6808
+ function dayText(day) {
6809
+ if (day === 10) return '初十';
6810
+ if (day === 20) return '二十';
6811
+ if (day === 30) return '三十';
6812
+ return `${DAY_TENS[Math.floor(day / 10)]}${DAY_UNITS[day % 10]}`;
6813
+ }
6814
+
6815
+ function lunarText(year, month, day, isLeap) {
6816
+ return `农历${year}年${isLeap ? '闰' : ''}${MONTH_TEXT[month - 1] || month}月${dayText(day)}`;
6817
+ }
6818
+
6819
+ function lunarNewYearJdn(lunarYear) {
6820
+ const from = toJDN(lunarYear, 1, 20);
6821
+ const to = toJDN(lunarYear, 2, 25);
6822
+ for (let jdn = from; jdn <= to; jdn++) {
6823
+ const p = fromJDN(jdn);
6824
+ const l = solarToLunar(p.year, p.month, p.day);
6825
+ if (l.lunarYear === lunarYear && l.month === 1 && l.day === 1) return jdn;
6826
+ }
6827
+ return null;
6828
+ }
6829
+
6830
+ function lunarMonths(lunarYear) {
6831
+ const first = lunarNewYearJdn(lunarYear);
6832
+ if (first == null) return [];
6833
+ const out = [];
6834
+ let cur = first;
6835
+ for (let i = 0; i < 14; i++) {
6836
+ const p = fromJDN(cur);
6837
+ const l = solarToLunar(p.year, p.month, p.day);
6838
+ if (i > 0 && l.month === 1 && !l.isLeap) break;
6839
+ let next = null;
6840
+ for (let k = 29; k <= 31; k++) {
6841
+ const d = fromJDN(cur + k);
6842
+ const l2 = solarToLunar(d.year, d.month, d.day);
6843
+ if (l2.day === 1) { next = cur + k; break; }
6844
+ }
6845
+ if (next == null) break;
6846
+ out.push({ month: l.month, isLeap: l.isLeap, startJdn: cur, length: next - cur });
6847
+ cur = next;
6848
+ }
6849
+ return out;
6850
+ }
6851
+
6852
+ function buildConvert(input) {
6853
+ const mode = input.mode === 'lunar2solar' ? 'lunar2solar' : 'solar2lunar';
6854
+ let solar;
6855
+ let lunar;
6856
+ let note = null;
6857
+ if (mode === 'solar2lunar') {
6858
+ solar = { year: input.year, month: input.month, day: input.day };
6859
+ assertCivil(solar, '公农历转换');
6860
+ if (solar.day > daysInMonth(solar.year, solar.month)) {
6861
+ throw new Error(`非法日期:${solar.year} 年 ${solar.month} 月没有 ${solar.day} 日`);
6862
+ }
6863
+ lunar = solarToLunar(solar.year, solar.month, solar.day);
6864
+ } else {
6865
+ const y = Number(input.lunarYear);
6866
+ const m = Number(input.lunarMonth);
6867
+ const d = Number(input.lunarDay);
6868
+ if (![y, m, d].every(Number.isInteger)) throw new Error('农历转换需要 lunarYear / lunarMonth / lunarDay');
6869
+ if (m < 1 || m > 12) throw new Error('农历月份须在 1–12');
6870
+ const months = lunarMonths(y);
6871
+ if (!months.length) throw new Error(`农历 ${y} 年超出本地历法覆盖范围(1900–2100)`);
6872
+ const isLeap = input.isLeapMonth === true;
6873
+ const hit = months.find((x) => x.month === m && x.isLeap === isLeap);
6874
+ if (!hit) {
6875
+ const leapMonths = months.filter((x) => x.isLeap).map((x) => `闰${MONTH_TEXT[x.month - 1]}月`);
6876
+ throw new Error(`农历 ${y} 年没有${leapMonths.length ? `该月(该年闰月为 ${leapMonths.join('、')})` : '闰月'}:${isLeap ? '请确认闰月标记' : '请确认月份'}`);
6877
+ }
6878
+ if (d < 1 || d > hit.length) throw new Error(`农历 ${y} 年${isLeap ? '闰' : ''}${MONTH_TEXT[m - 1]}月只有 ${hit.length} 天,没有 ${d} 日`);
6879
+ const p = fromJDN(hit.startJdn + d - 1);
6880
+ solar = { year: p.year, month: p.month, day: p.day };
6881
+ const check = solarToLunar(p.year, p.month, p.day);
6882
+ if (check.month !== m || check.day !== d || check.isLeap !== isLeap) {
6883
+ note = '反查结果与目标农历日不完全一致,已按本地历法实际月首回推';
6884
+ }
6885
+ lunar = check;
6886
+ }
6887
+
6888
+ const a = buildAlmanac(solar);
6889
+ const jdn = toJDN(solar.year, solar.month, solar.day);
6890
+ const solarText = `${solar.year}-${String(solar.month).padStart(2, '0')}-${String(solar.day).padStart(2, '0')}`;
6891
+ return {
6892
+ system: 'convert',
6893
+ meta: {
6894
+ mode,
6895
+ solarDate: solarText,
6896
+ weekday: `星期${WEEK[(jdn + 1) % 7]}`,
6897
+ ...(note ? { note } : {}),
6898
+ scope: '公农历互转按本地定朔历(1900–2100);农历闰月须显式指定,不自动猜同名月',
6899
+ },
6900
+ solar: { year: solar.year, month: solar.month, day: solar.day, text: solarText },
6901
+ lunar: {
6902
+ year: lunar.lunarYear,
6903
+ month: lunar.month,
6904
+ day: lunar.day,
6905
+ isLeap: lunar.isLeap,
6906
+ text: lunarText(lunar.lunarYear, lunar.month, lunar.day, lunar.isLeap),
6907
+ },
6908
+ ganzhi: {
6909
+ year: a.ganzhi.year,
6910
+ month: a.ganzhi.month,
6911
+ day: a.ganzhi.day,
6912
+ shengXiao: a.ganzhi.shengXiao,
6913
+ springYear: a.ganzhi.springYear,
6914
+ },
6915
+ sources: ['农历:本地定朔 + 节气黄经历法(engine/lunar.js)', '干支与生肖:与黄历同口径(年按立春界,另列春节界)'],
6916
+ disclaimer: '历法换算为确定性计算;民俗历法各版本在极少数日期(交节当日、闰月边界)存在日界差异,已注明口径。',
6917
+ };
6918
+ }
6919
+
6920
+ return { buildConvert };
6421
6921
  })();
6422
6922
 
6423
6923
  __mods['interpret-plain'] = (function () {
@@ -7001,6 +7501,7 @@ function baziInterpret(r) {
7001
7501
  const st = isObj(r.strength) ? r.strength : {};
7002
7502
  const frame = isObj(r.frame) ? r.frame : {};
7003
7503
  const ys = isObj(frame.yongshen) ? frame.yongshen : {};
7504
+ const cli = r.climate || (isObj(ys.climate) ? ys.climate : (isObj(ys.tiaohou) ? ys.tiaohou : null));
7004
7505
  const wx = isObj(r.wuxing) ? r.wuxing : {};
7005
7506
  const level = pick(st, 'level', '中和');
7006
7507
  const favor = Array.isArray(ys.favor) ? ys.favor : [];
@@ -7184,10 +7685,12 @@ function baziInterpret(r) {
7184
7685
  else if (!good.includes(curTier) && !good.includes(lnTier)) out.push('大运与流年都偏逆:以守住基本盘为主,减少新增承诺。');
7185
7686
  else out.push('大运与流年同向偏顺:可以把想做但一直拖着的事排上日程。');
7186
7687
  }
7187
- const tiao = ys.tiaohou && ys.tiaohou.element;
7188
- if (tiao) out.push(favor.includes(tiao)
7189
- ? `调候喜${tiao},与扶抑用神同向:取用上不打架,${tiao}相关的行业与环境优先。`
7190
- : `调候喜${tiao},不在扶抑的喜用里:两套取法有分歧,这里以扶抑为主,${tiao}只作环境上的小幅偏好。`);
7688
+ const th = ys.tiaohou && ys.tiaohou.need ? ys.tiaohou : null;
7689
+ if (th) {
7690
+ out.push(th.clash
7691
+ ? `调候要${th.need.stem}${th.need.element},扶抑不在喜用里:两套取法有分歧,本盘按调候优先——寒暖失衡时,再补强弱也难受。`
7692
+ : `调候要${th.need.stem}${th.need.element},与扶抑用神同向:取用上不打架,${th.need.element}相关的行业与环境优先。`);
7693
+ }
7191
7694
  return out.join(' ');
7192
7695
  })(),
7193
7696
  },
@@ -7196,6 +7699,12 @@ function baziInterpret(r) {
7196
7699
  text: `喜用:${join(favor)}(对应十神:${join(ys.favorTenGods || ys.favorRoles || [])});忌:${join(avoid)}(对应十神:${join(ys.avoidTenGods || ys.avoidRoles || [])})。` +
7197
7700
  `${pick(ys, 'detail', '')}`,
7198
7701
  },
7702
+ ...(cli ? [{
7703
+ key: 'climate', label: '寒暖燥湿',
7704
+ text: `${cli.narration}${cli.need
7705
+ ? `调候取${cli.need.stem}${cli.need.element},忌${cli.avoidElement};${cli.clash ? '与扶抑冲突,本盘以调候为先' : '与扶抑同向'}`
7706
+ : '气候平和,调候不作主取'}。`,
7707
+ }] : []),
7199
7708
  {
7200
7709
  key: 'geju', label: '格局依据',
7201
7710
  text: (Array.isArray(frame.geju) ? frame.geju : []).map((g) => `${g.name}(${g.kind}):${g.evidence}`).join(';') || '未取到格局',
@@ -7206,7 +7715,7 @@ function baziInterpret(r) {
7206
7715
  },
7207
7716
  {
7208
7717
  key: 'limits', label: '未纳入',
7209
- text: '本盘没有计算:流月与流日、神煞的加权轻重、地支藏干的动态引化(合冲后再论)。取用以扶抑为主、调候只做简化提示,各派取法不同;真太阳时未含历史时区与夏令时。把这些补上会改变"强弱"的边际判断,所以这里只给方向,不下断语。',
7718
+ text: '本盘没有计算:流月与流日、神煞的加权轻重、地支藏干的动态引化(合冲后再论)。取用按「调候优先、扶抑校验」;调候由月令与全局寒暖燥湿判定,未逐条录入穷通宝鉴 120 条,各派取法仍有分歧;真太阳时未含历史时区与夏令时。把这些补上会改变"强弱"的边际判断,所以这里只给方向,不下断语。',
7210
7719
  },
7211
7720
  ];
7212
7721
 
@@ -8590,6 +9099,7 @@ const { buildChenggu } = __mods.chenggu;
8590
9099
  const { buildXiaoliuren } = __mods.xiaoliuren;
8591
9100
  const { buildStar } = __mods.star;
8592
9101
  const { buildNumerology } = __mods.numerology;
9102
+ const { buildConvert } = __mods.convert;
8593
9103
  const { buildTrend } = __mods.trend;
8594
9104
  const { buildFengshui } = __mods.fengshui;
8595
9105
  const { buildName } = __mods.name;
@@ -8667,6 +9177,7 @@ function baziText(r) {
8667
9177
  `   ${r.frame.geju.map((g) => `${g.name}[${g.kind}]`).join(' ')}`,
8668
9178
  `用神:喜${r.frame.yongshen.favor.join('')}(${r.frame.yongshen.favorTenGods.join('、')}) 忌${r.frame.yongshen.avoid.join('')}(${r.frame.yongshen.avoidTenGods.join('、')}) ${r.frame.yongshen.method}`,
8669
9179
  `   ${r.frame.yongshen.detail}`,
9180
+ `寒暖:${r.climate ? `${r.climate.label}(${(r.climate.evidence || []).join('、')})${r.climate.need ? ` 调候取${r.climate.need.stem}${r.climate.need.element} 忌${r.climate.avoidElement}` : ''}` : '—'}`,
8670
9181
  `十神权重:${Object.entries(r.frame.godStats.groups).map(([k, v]) => `${k}${v}`).join(' ')}`,
8671
9182
  ] : [];
8672
9183
  return [
@@ -9144,6 +9655,26 @@ function computeRaw(args = {}) {
9144
9655
  const result = buildChenggu({ year: dt.year, month: dt.month, day: dt.day, hour, minute: hasTime ? dt.minute : 0 });
9145
9656
  return { ...result, summary: chengguText(result) };
9146
9657
  }
9658
+ if (system === 'convert') {
9659
+ const mode = args.mode === 'lunar2solar' ? 'lunar2solar' : 'solar2lunar';
9660
+ let result;
9661
+ if (mode === 'lunar2solar') {
9662
+ if (args.lunarYear == null || args.lunarMonth == null || args.lunarDay == null) {
9663
+ throw fail('E_INPUT', '农历转公历需要 lunarYear / lunarMonth / lunarDay(闰月另加 isLeapMonth: true)');
9664
+ }
9665
+ result = buildConvert({
9666
+ mode,
9667
+ lunarYear: Number(args.lunarYear),
9668
+ lunarMonth: Number(args.lunarMonth),
9669
+ lunarDay: Number(args.lunarDay),
9670
+ isLeapMonth: args.isLeapMonth === true,
9671
+ });
9672
+ } else {
9673
+ const dt = parseDateTime(args.datetime);
9674
+ result = buildConvert({ mode, year: dt.year, month: dt.month, day: dt.day });
9675
+ }
9676
+ return { ...result, summary: convertText(result) };
9677
+ }
9147
9678
  if (system === 'xiaoliuren') {
9148
9679
  const dt = args.datetime ? parseDateTime(args.datetime) : null;
9149
9680
  const result = buildXiaoliuren({
@@ -9227,21 +9758,34 @@ function computeRaw(args = {}) {
9227
9758
  return { ...result, summary: nameText(result) };
9228
9759
  }
9229
9760
  if (!system) {
9230
- throw fail('E_INPUT', '缺少 system,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)。');
9761
+ throw fail('E_INPUT', '缺少 system,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)、convert(公农历互转)。');
9231
9762
  }
9232
- throw fail('E_SYSTEM', `未知体系「${system}」,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)。`);
9763
+ throw fail('E_SYSTEM', `未知体系「${system}」,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)、convert(公农历互转)。`);
9233
9764
  }
9234
9765
 
9235
9766
  function almanacText(r) {
9236
9767
  const g = r.ganzhi;
9237
9768
  const jq = r.jieQi && r.jieQi.next ? ` 下一节:${r.jieQi.next.name} ${r.jieQi.next.date}` : '';
9769
+ const god = r.dayGod ? `值日:${r.dayGod.name}(${r.dayGod.type}·${r.dayGod.luck})` : '';
9770
+ const xiu = r.xiu ? `二十八宿:${r.xiu.name}宿(${r.xiu.luck}·${r.xiu.zheng}·${r.xiu.animal})` : '';
9771
+ const tai = r.taiShen ? `胎神:${r.taiShen}` : '';
9772
+ const pos = r.positions
9773
+ ? `吉神方位:喜神${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 : '—'}`
9774
+ : '';
9775
+ const lucky = r.lucky ? `吉配生肖:六合${r.lucky.liuhe || '—'} 三合${(r.lucky.sanhe || []).join('、') || '—'}` : '';
9776
+ const nayin = r.nayin && r.nayin.day ? `日柱纳音:${r.nayin.day.name}` : '';
9777
+ const hours = (r.hours || []).map((h) => `${h.zhi}${h.ganZhi.slice(0, 1)}·${h.shen}${h.luck === '吉' ? '吉' : '凶'}`).join(' ');
9238
9778
  return [
9239
9779
  `【黄历】${r.meta.date} ${r.meta.weekday} ${r.lunar.text}`,
9240
9780
  `干支:${g.year}年 ${g.month}月 ${g.day}日 属${g.shengXiao}`,
9241
9781
  ...(r.meta.boundaryNote ? [` ${r.meta.boundaryNote}`] : []),
9242
- `建除:${r.zhiXing}日 冲:${r.chong.text}${r.chong.sha ? `(${r.chong.sha})` : ''} 财神:${r.positions.caiShen || '—'} 喜神:${r.positions.xiShen || '—'}`,
9782
+ `建除:${r.zhiXing}日 冲:${r.chong.text}${r.chong.sha ? `(${r.chong.sha})` : ''} ${god}`,
9783
+ [xiu, nayin, tai].filter(Boolean).join(' '),
9243
9784
  `宜:${r.yi.join('、')}`,
9244
9785
  `忌:${r.ji.join('、')}`,
9786
+ pos,
9787
+ lucky,
9788
+ hours ? `时辰吉凶:${hours}` : '',
9245
9789
  `节气:上一节 ${r.jieQi.prev ? `${r.jieQi.prev.name} ${r.jieQi.prev.date}` : '—'}${jq}`,
9246
9790
  r.dailyHexagram ? `每日一卦:${r.dailyHexagram.name} ${r.dailyHexagram.meaning || ''}` : '',
9247
9791
  `口径:${r.meta.scope}`,
@@ -9297,6 +9841,17 @@ function starText(r) {
9297
9841
  ].join('\n');
9298
9842
  }
9299
9843
 
9844
+ function convertText(r) {
9845
+ const g = r.ganzhi;
9846
+ const dir = r.meta.mode === 'lunar2solar' ? '农历 → 公历' : '公历 → 农历';
9847
+ return [
9848
+ `【公农历转换·${dir}】${r.lunar.text} ⇄ ${r.solar.text} ${r.meta.weekday}`,
9849
+ `干支:${g.year}年 ${g.month}月 ${g.day}日 属${g.shengXiao}${g.springYear && g.springYear !== g.year ? `(春节界为${g.springYear}年)` : ''}`,
9850
+ ...(r.meta.note ? [`注:${r.meta.note}`] : []),
9851
+ `口径:${r.meta.scope}`,
9852
+ ].join('\n');
9853
+ }
9854
+
9300
9855
  function numerologyText(r) {
9301
9856
  return [
9302
9857
  `【生命数字】${r.meta.date}(西方数字学,与八字/紫微不可互证)`,
@@ -9391,7 +9946,7 @@ function renderText(result) {
9391
9946
  return parts.filter(Boolean).join('\n\n');
9392
9947
  }
9393
9948
 
9394
- return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAddress, resolveAddress, buildBazi, buildLiuyao, tossLines, followup, pastSignals, verifyFit, rectifyBirth, crossCheck, crossText, buildAlmanac, buildZeri, buildChenggu, buildXiaoliuren, buildStar, buildNumerology, buildTrend, buildFengshui, buildName, parseDateTime, compute, interpretText, renderText };
9949
+ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAddress, resolveAddress, buildBazi, buildLiuyao, tossLines, followup, pastSignals, verifyFit, rectifyBirth, crossCheck, crossText, buildAlmanac, buildZeri, buildChenggu, buildXiaoliuren, buildStar, buildNumerology, buildConvert, buildTrend, buildFengshui, buildName, parseDateTime, compute, interpretText, renderText };
9395
9950
  })();
9396
9951
 
9397
9952
  const Engine = __mods.index;
@@ -9921,7 +10476,52 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
9921
10476
  @media print{.mp-cg{break-inside:avoid}}
9922
10477
  @media (max-width:640px){.mp-cg-w{grid-template-columns:repeat(2,minmax(0,1fr))}}
9923
10478
  .mp-quick-h{font-size:13px;color:var(--lu);margin-bottom:6px;font-family:system-ui,sans-serif}
10479
+ .mp-cli{margin:10px 0;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--card)}
10480
+ .mp-cli-h{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--ink)}
10481
+ .mp-cli-h b{font-size:13px}
10482
+ .mp-cli-k{font-weight:700;color:var(--lu)}
10483
+ .mp-cli-p{margin-left:auto;font-size:11.5px;color:var(--quan);background:var(--bg);border:1px solid var(--line);border-radius:999px;padding:1px 8px}
10484
+ .mp-cli-bar{position:relative;margin:9px 0 4px;height:8px;border-radius:999px;background:linear-gradient(90deg,var(--quan) 0%,var(--line) 50%,var(--ji) 100%)}
10485
+ .mp-cli-bar i{position:absolute;top:-3px;width:14px;height:14px;margin-left:-7px;border-radius:50%;background:var(--card);border:2px solid var(--ink);box-shadow:0 0 0 2px var(--card)}
10486
+ .mp-cli-axis{display:flex;justify-content:space-between;font-size:11px;color:var(--ink3);margin-bottom:8px}
10487
+ .mp-cli-row{display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center;font-size:12.5px;color:var(--ink2)}
10488
+ .mp-cli-row em{font-style:normal;color:var(--ink3)}
10489
+ .mp-cli-row b{color:var(--ink)}
10490
+ .mp-cli-narr{margin-top:8px;font-size:13px;line-height:1.85;color:var(--ink2);border-left:2px solid var(--quan);padding-left:9px}
10491
+ @media print{.mp-cli{break-inside:avoid}}
9924
10492
  .mp-quick-pre{margin:0;white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;line-height:1.9;color:var(--ink2)}
10493
+ .mp-al{margin:12px 16px;border:1px solid var(--line);border-radius:8px;background:var(--card);padding:12px 14px;font-family:system-ui,sans-serif}
10494
+ .mp-al-h{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;border-bottom:1px solid var(--hair);padding-bottom:8px;margin-bottom:9px}
10495
+ .mp-al-h b{font-size:18px;color:var(--ink);font-weight:700;font-variant-numeric:tabular-nums}
10496
+ .mp-al-h i{font-style:normal;font-size:12px;color:var(--muted)}
10497
+ .mp-al-gz{font-size:12.5px;color:var(--ink2);margin-bottom:9px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
10498
+ .mp-al-badge{font-size:11px;border:1px solid var(--line);border-radius:999px;padding:1px 8px}
10499
+ .mp-al-badge.good{color:var(--lu);border-color:var(--lu)}
10500
+ .mp-al-badge.bad{color:var(--ji);border-color:var(--ji)}
10501
+ .mp-al-yiji{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:6px 0 9px}
10502
+ .mp-al-col em,.mp-al-row>em{font-style:normal;font-size:10.5px;color:var(--muted);display:block;margin-bottom:4px}
10503
+ .mp-al-chips{display:flex;flex-wrap:wrap;gap:4px}
10504
+ .mp-al-chip{font-size:11.5px;padding:1px 7px;border:1px solid var(--line);border-radius:999px;color:var(--ink2);white-space:nowrap}
10505
+ .mp-al-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1px;background:var(--line);border:1px solid var(--line);border-radius:6px;overflow:hidden;margin:8px 0}
10506
+ .mp-al-cell{background:var(--card);padding:6px 8px;min-width:0}
10507
+ .mp-al-cell em{display:block;font-style:normal;font-size:10.5px;color:var(--muted);margin-bottom:2px}
10508
+ .mp-al-cell b{font-size:12.5px;color:var(--ink);font-weight:600;line-height:1.45;display:block}
10509
+ .mp-al-row{margin:9px 0}
10510
+ .mp-al-hours{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:5px}
10511
+ .mp-al-hour{display:flex;align-items:baseline;gap:3px;border:1px solid var(--hair);border-radius:5px;padding:3px 6px;font-size:11px;color:var(--ink2);background:var(--band)}
10512
+ .mp-al-hour i{font-style:normal;font-weight:600;color:var(--ink)}
10513
+ .mp-al-hour em{font-style:normal;color:var(--muted)}
10514
+ .mp-al-hour b{font-weight:500}
10515
+ .mp-al-hour u{text-decoration:none;margin-left:auto;font-size:10.5px}
10516
+ .mp-al-hour.good{border-color:var(--lu);color:var(--lu)}
10517
+ .mp-al-hour.good i{color:var(--lu)}
10518
+ .mp-al-hour.bad{border-color:var(--hair)}
10519
+ .mp-al-hour.bad u{color:var(--ji)}
10520
+ .mp-al-hex{margin-top:8px;font-size:12px;color:var(--ink2)}
10521
+ .mp-al-hex b{color:var(--quan)}
10522
+ .mp-al-note{margin-top:9px;padding-top:8px;border-top:1px dashed var(--line);font-size:11px;line-height:1.7;color:var(--muted)}
10523
+ @media print{.mp-al{break-inside:avoid}}
10524
+ @media (max-width:640px){.mp-al{margin:10px;padding:10px}.mp-al-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.mp-al-yiji{grid-template-columns:1fr}}.mp-quick-pre{margin:0;white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;line-height:1.9;color:var(--ink2)}
9925
10525
  .mp-it-dis{padding:11px 16px;border-top:1px solid var(--line);background:var(--band);font-size:12px;line-height:1.75;color:var(--muted);font-family:system-ui,sans-serif}
9926
10526
 
9927
10527
  @media (max-width:640px){
@@ -10715,7 +11315,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
10715
11315
  const num = (v) => (typeof v === 'number' && isFinite(v) ? v : 0);
10716
11316
  const dyScore = (d) => (d.luck ? num(d.luck.score) : 0);
10717
11317
 
10718
- const VMAX = 3.2;
11318
+ const VMAX = 4.2;
10719
11319
  const VMIN = -2.2;
10720
11320
  const startAge = num(r.dayun.startAge && r.dayun.startAge.years);
10721
11321
  const dyEnd = dyList.length
@@ -10731,7 +11331,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
10731
11331
  const pw = W - M.l - M.r;
10732
11332
  const ph = H - M.t - M.b;
10733
11333
  const X = (age) => M.l + ((age - a0) / span) * pw;
10734
- const Y = (v) => M.t + ph - ((v - VMIN) / (VMAX - VMIN)) * ph;
11334
+ const Y = (v) => M.t + ph - ((Math.max(VMIN, Math.min(VMAX, v)) - VMIN) / (VMAX - VMIN)) * ph;
10735
11335
 
10736
11336
  const BANDS = [
10737
11337
  { from: 1.2, to: VMAX, cls: 'b-good' },
@@ -11056,6 +11656,21 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
11056
11656
  <div class="pp-tl">喜 ${fr.yongshen.favor.map(el).join('')} 忌 ${fr.yongshen.avoid.map(el).join('')} <span class="pp-dim">${esc(fr.yongshen.method)}</span></div>
11057
11657
  <div class="pp-chips">${fr.yongshen.favorTenGods.map((t) => `<span class="pp-chip ok">${esc(t)}</span>`).join('')}${fr.yongshen.avoidTenGods ? fr.yongshen.avoidTenGods.map((t) => `<span class="pp-chip warn">${esc(t)}</span>`).join('') : ''}</div>
11058
11658
  <div class="pp-tl pp-dim">${esc(fr.yongshen.detail)}</div>` : '';
11659
+ const cli = r.climate || (fr.yongshen && fr.yongshen.climate) || null;
11660
+ const cliCard = cli ? (() => {
11661
+ const pct = Math.max(2, Math.min(98, Math.round(((Number(cli.temp) || 0) + 3) / 6 * 100)));
11662
+ const need = cli.need ? `<em>调候取</em><b>${esc(cli.need.stem + cli.need.element)}</b>` : '<em>气候平和</em>';
11663
+ const avoid = cli.need ? `<em>忌</em><b>${esc(cli.avoidElement)}</b>` : '';
11664
+ const ev = (cli.evidence || []).length ? `<em>依据</em><span>${esc(cli.evidence.join('、'))}</span>` : '';
11665
+ const pri = cli.priority === '调候优先' ? '<span class="mp-cli-p">调候优先</span>' : '<span class="mp-cli-p">扶抑为主</span>';
11666
+ return `<section class="mp-cli" aria-label="寒暖燥湿">
11667
+ <div class="mp-cli-h"><b>寒暖燥湿</b><span class="mp-cli-k">${esc(cli.label || '')}</span>${pri}</div>
11668
+ <div class="mp-cli-bar" title="寒 ← → 热"><i style="left:${pct}%"></i></div>
11669
+ <div class="mp-cli-axis"><span>寒</span><span>平</span><span>热</span></div>
11670
+ <div class="mp-cli-row">${need}${avoid}${ev}</div>
11671
+ <div class="mp-cli-narr">${esc(cli.narration || '')}</div>
11672
+ </section>`;
11673
+ })() : '';
11059
11674
  const gods = fr.godStats ? `<div class="pp-chips">${Object.entries(fr.godStats.groups).map(([k, v]) => `<span class="pp-chip">${k} ${v}</span>`).join('')}</div>` : '';
11060
11675
 
11061
11676
  const dy = r.dayun.list.map((d) => {
@@ -11081,6 +11696,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
11081
11696
  ${str}
11082
11697
  ${sect('格局', gj)}
11083
11698
  ${sect('用神', ys)}
11699
+ ${cliCard}
11084
11700
  ${sect('五行与十神', `<div class="pp-chips">${wx}</div>${gods}`)}
11085
11701
  ${sect('大运', `${pp.curveHTML(r)}<div class="pp-chips">${dy}</div>`)}
11086
11702
  ${ln ? sect('流年', ln) : ''}
@@ -11183,11 +11799,43 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
11183
11799
  return (html || '') + pp.interpretHTML(it, opt);
11184
11800
  };
11185
11801
 
11802
+ pp.almanacHTML = function (r) {
11803
+ if (!r || !r.meta) return pp.quickHTML(r);
11804
+ const g = r.ganzhi || {};
11805
+ const god = r.dayGod || null;
11806
+ const chips = (list, cls) => (list || []).map((x) => `<span class="mp-al-chip${cls ? ` ${cls}` : ''}">${esc(x)}</span>`).join('');
11807
+ const cell = (k, v) => (v ? `<div class="mp-al-cell"><em>${esc(k)}</em><b>${esc(v)}</b></div>` : '');
11808
+ const xiu = r.xiu ? `${r.xiu.name}宿(${r.xiu.luck}·${r.xiu.zheng}·${r.xiu.animal})` : '';
11809
+ const nayin = r.nayin && r.nayin.day ? r.nayin.day.name : '';
11810
+ const chong = r.chong ? `${r.chong.text}${r.chong.sha ? ` ${r.chong.sha}` : ''}` : '';
11811
+ const hours = (r.hours || []).map((h) => `<span class="mp-al-hour ${h.luck === '吉' ? 'good' : 'bad'}">`
11812
+ + `<i>${esc(h.zhi)}</i><em>${esc((h.ganZhi || '').slice(0, 1))}</em><b>${esc(h.shen || '')}</b>`
11813
+ + `<u>${esc(h.luck || '')}</u></span>`).join('');
11814
+ const pos = r.positions || {};
11815
+ const posRow = (k, v) => (v ? `<span class="mp-al-chip">${esc(k)}·${esc(typeof v === 'string' ? v : v.dir || '')}</span>` : '');
11816
+ const lucky = r.lucky || {};
11817
+ const godBadge = god ? `<span class="mp-al-badge ${god.luck === '吉' ? 'good' : 'bad'}">${esc(god.name)}·${esc(god.type)}·${esc(god.luck)}</span>` : '';
11818
+ return `<section class="mp-al" aria-label="黄历">
11819
+ <div class="mp-al-h"><b>${esc(r.meta.date || '')}</b><i>${esc(r.meta.weekday || '')} ${esc((r.lunar || {}).text || '')}</i></div>
11820
+ <div class="mp-al-gz">${esc(g.year || '')}年 ${esc(g.month || '')}月 ${esc(g.day || '')}日 属${esc(g.shengXiao || '')} 建除 ${esc(r.zhiXing || '')}日 ${godBadge}</div>
11821
+ <div class="mp-al-yiji">
11822
+ <div class="mp-al-col"><em>宜</em><div class="mp-al-chips">${chips(r.yi)}</div></div>
11823
+ <div class="mp-al-col"><em>忌</em><div class="mp-al-chips">${chips(r.ji)}</div></div>
11824
+ </div>
11825
+ <div class="mp-al-grid">${cell('二十八宿', xiu)}${cell('日柱纳音', nayin)}${cell('胎神', r.taiShen)}${cell('冲煞', chong)}</div>
11826
+ <div class="mp-al-row"><em>吉神方位</em><div class="mp-al-chips">${posRow('喜神', pos.xiShen)}${posRow('财神', pos.caiShen)}${posRow('福神', pos.fuShen)}${posRow('阳贵', pos.yangGui)}${posRow('阴贵', pos.yinGui)}</div></div>
11827
+ <div class="mp-al-row"><em>时刻吉凶</em><div class="mp-al-hours">${hours}</div></div>
11828
+ <div class="mp-al-row"><em>吉配生肖</em><div class="mp-al-chips">${lucky.liuhe ? `<span class="mp-al-chip">六合·${esc(lucky.liuhe)}</span>` : ''}${chips(lucky.sanhe || [])}</div></div>
11829
+ ${r.dailyHexagram ? `<div class="mp-al-hex">每日一卦:<b>${esc(r.dailyHexagram.name)}</b> ${esc(r.dailyHexagram.meaning || '')}</div>` : ''}
11830
+ <div class="mp-al-note">${esc((r.meta && r.meta.scope) || '')}</div>
11831
+ </section>`;
11832
+ };
11833
+
11186
11834
  pp.quickHTML = function (r) {
11187
11835
  if (!r) return '';
11188
11836
  const LABEL = {
11189
11837
  almanac: '黄历', zeri: '择日', chenggu: '称骨', xiaoliuren: '小六壬',
11190
- star: '星座', numerology: '生命数字', trend: '人生 K 线', fengshui: '风水', name: '起名',
11838
+ star: '星座', numerology: '生命数字', trend: '人生 K 线', fengshui: '风水', name: '起名', convert: '公农历转换',
11191
11839
  };
11192
11840
  const title = LABEL[r.system] || '速查';
11193
11841
  const text = r.summary || '';
@@ -11457,7 +12105,8 @@ const TABS = [
11457
12105
  { k: 'qimen', label: '奇门', timeLabel: '起局时刻', hint: '奇门时家转盘:用起局时刻定节气、三元与局数;换局规则决定符头与节气如何对齐。' },
11458
12106
  { k: 'hepan', label: '合盘', timeLabel: '你的出生时刻', hint: '合盘按「日主 + 夫妻宫 + 五行互补」看两个人的相处与互补,需要双方出生信息。' },
11459
12107
  { k: 'tarot', label: '塔罗', timeLabel: '', hint: '塔罗不看时间,只记录抽牌时刻与你的提问。' },
11460
- { k: 'almanac', label: '黄历', timeLabel: '', hint: '黄历按公历日期查农历、干支、建除、宜忌与冲煞;只做本地计算。' },
12108
+ { k: 'almanac', label: '黄历', timeLabel: '', hint: '黄历按公历日期查农历、干支、宜忌、时辰吉凶、二十八宿与吉神方位;只做本地计算。' },
12109
+ { k: 'convert', label: '农历', timeLabel: '', hint: '公农历互转:公历生日可反查农历;只知道农历生日时,可转成公历再排盘。' },
11461
12110
  { k: 'zeri', label: '择日', timeLabel: '', hint: '择日把区间内每天的建除宜忌与事项适配打分排序,并按属相排除冲煞日。' },
11462
12111
  { k: 'chenggu', label: '称骨', timeLabel: '出生时刻', hint: '称骨按农历年、月、日、时的骨重求和;时辰必填。' },
11463
12112
  { k: 'xiaoliuren', label: '小六壬', timeLabel: '起课时刻', hint: '小六壬用农历月/日/时掐指,也可用三个随机数起课。' },
@@ -11479,7 +12128,7 @@ const TIME_KIND = {
11479
12128
  ziwei: 'birth', bazi: 'birth', hepan: 'birth',
11480
12129
  liuyao: 'cast', meihua: 'cast', daliuren: 'cast', qimen: 'cast',
11481
12130
  tarot: 'none',
11482
- almanac: 'date', zeri: 'date', star: 'date', numerology: 'date',
12131
+ almanac: 'date', zeri: 'date', star: 'date', numerology: 'date', convert: 'date',
11483
12132
  chenggu: 'birth', trend: 'birth', fengshui: 'birth', name: 'birth',
11484
12133
  xiaoliuren: 'cast',
11485
12134
  };
@@ -11514,6 +12163,9 @@ function App() {
11514
12163
  const [task, setTask] = useState('开业');
11515
12164
  const [days, setDays] = useState('30');
11516
12165
  const [shengXiao, setShengXiao] = useState('');
12166
+ const [convMode, setConvMode] = useState('solar2lunar');
12167
+ const [lunarDate, setLunarDate] = useState('');
12168
+ const [leapMonth, setLeapMonth] = useState(false);
11517
12169
  const [xlrMode, setXlrMode] = useState('time');
11518
12170
  const [num1, setNum1] = useState(''); const [num2, setNum2] = useState(''); const [num3, setNum3] = useState('');
11519
12171
  const [toAge, setToAge] = useState('60');
@@ -11619,6 +12271,24 @@ function App() {
11619
12271
  delete args.datetime;
11620
12272
  }
11621
12273
  }
12274
+ if (k === 'convert') {
12275
+ args.system = 'convert';
12276
+ if (convMode === 'lunar2solar') {
12277
+ const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(String(lunarDate).trim());
12278
+ if (!m) {
12279
+ setError('农历转公历请按「农历年-月-日」填写,例如 2026-9-17;闰月单独勾选。');
12280
+ return;
12281
+ }
12282
+ args.mode = 'lunar2solar';
12283
+ args.lunarYear = Number(m[1]);
12284
+ args.lunarMonth = Number(m[2]);
12285
+ args.lunarDay = Number(m[3]);
12286
+ args.isLeapMonth = leapMonth;
12287
+ delete args.datetime;
12288
+ } else {
12289
+ args.mode = 'solar2lunar';
12290
+ }
12291
+ }
11622
12292
  if (k === 'tarot') args.count = Number(count);
11623
12293
  if (k === 'hepan') {
11624
12294
  args.chart = hpChart;
@@ -11987,8 +12657,24 @@ function App() {
11987
12657
  })) : null,
11988
12658
  tab === 'zeri' ? field('所办事项', h('select', {
11989
12659
  className: 'mp-input', value: task, onChange: (e) => setTask(e.target.value),
11990
- }, ['嫁娶', '订婚', '开业', '入宅', '搬家', '动土', '装修', '安葬', '出行', '祭祀', '签约', '求医', '纳财']
11991
- .map((t) => h('option', { key: t, value: t }, t)))) : null,
12660
+ }, [
12661
+ ['常用', ['嫁娶', '搬家入宅', '出行', '开业', '签订合同', '祭祀']],
12662
+ ['婚嫁', ['嫁娶', '订婚', '领证', '提亲', '收送聘金', '满月酒']],
12663
+ ['生活', ['搬家', '入宅', '旅行', '访亲友', '入学', '求医', '祭祀']],
12664
+ ['建造', ['动土', '装修', '装修建造', '安葬', '试车买车']],
12665
+ ['工商', ['开业', '交易', '投资', '签订合同', '签约', '纳财', '赴任']],
12666
+ ].map(([g, items]) => h('optgroup', { key: g, label: g },
12667
+ items.map((t) => h('option', { key: `${g}-${t}`, value: t }, t)))))) : null,
12668
+ tab === 'convert' ? field('转换方向', h('div', { className: 'mp-seg' },
12669
+ h('button', { className: convMode === 'solar2lunar' ? 'on' : '', onClick: () => setConvMode('solar2lunar') }, '公历 → 农历'),
12670
+ h('button', { className: convMode === 'lunar2solar' ? 'on' : '', onClick: () => setConvMode('lunar2solar') }, '农历 → 公历'))) : null,
12671
+ (tab === 'convert' && convMode === 'lunar2solar') ? field('农历日期', h('input', {
12672
+ className: 'mp-input', value: lunarDate, placeholder: '如 2026-9-17',
12673
+ onChange: (e) => setLunarDate(e.target.value),
12674
+ })) : null,
12675
+ (tab === 'convert' && convMode === 'lunar2solar') ? h('label', {
12676
+ className: 'mp-check',
12677
+ }, h('input', { type: 'checkbox', checked: leapMonth, onChange: (e) => setLeapMonth(e.target.checked) }), ' 闰月') : null,
11992
12678
  tab === 'zeri' ? field('看多少天', h('input', {
11993
12679
  className: 'mp-input', type: 'number', min: 1, max: 60, value: days,
11994
12680
  onChange: (e) => setDays(e.target.value),
@@ -12114,6 +12800,7 @@ function App() {
12114
12800
  : result.kind === 'tarot' ? PP.tarotHTML(result.r)
12115
12801
  : result.kind === 'liuyao' ? PP.liuyaoHTML(result.r)
12116
12802
  : result.kind === 'chenggu' ? PP.chengguHTML(result.r)
12803
+ : result.kind === 'almanac' ? PP.almanacHTML(result.r)
12117
12804
  : result.kind === 'trend' ? PP.trendHTML(result.r)
12118
12805
  : PP.quickHTML(result.r),
12119
12806
  result.it,