guanwei 1.1.4 → 1.2.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/dist/index.html CHANGED
@@ -6,7 +6,7 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>观微 · 玄学问道 —— 九术参详,观微知著</title>
8
8
  <meta name="description" content="以纸墨留白为骨,融四柱八字、紫微斗数、奇门遁甲、梅花易数、六爻、大六壬、小六壬、星盘、塔罗九术于一体。观微知著,以术问道。" />
9
- <script type="module" crossorigin src="/assets/index-DmdGzBy9.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-CRUHghuF.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/assets/index-hLV3S-Cj.css">
11
11
  </head>
12
12
  <body>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "guanwei",
3
3
  "private": false,
4
- "version": "1.1.4",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -12285,6 +12285,132 @@
12285
12285
  }
12286
12286
  }
12287
12287
  ]
12288
+ },
12289
+ {
12290
+ "username": "联调6708",
12291
+ "passHash": "scrypt$3ac3d143a76487162227a90bba2845d2$86a8f1db4c6a641add09f23fcba637b6863df22d4abe783a1a4b2bd4daccd63e4863b6a9348f412586cce7022e60646115b4bf5651815a9fad8a108b9095fba7",
12292
+ "createdAt": 1787577786872,
12293
+ "profile": {
12294
+ "birthDate": "1996-09-09",
12295
+ "birthHourIndex": 5,
12296
+ "gender": "男",
12297
+ "location": null
12298
+ },
12299
+ "samples": [],
12300
+ "records": [
12301
+ {
12302
+ "artId": "bazi",
12303
+ "createdAt": 1,
12304
+ "result": {
12305
+ "dayGZ": "壬申"
12306
+ }
12307
+ }
12308
+ ]
12309
+ },
12310
+ {
12311
+ "username": "联调9224",
12312
+ "passHash": "scrypt$f479cd30f9b3135833b943094349a9ff$67a48c88afa7c9191641af8fd373b22176e1c4f5681d0c05d108ffc233080f7a233fc90ed818d7e9f7545f40a87cf6e362205830d5764d5efcf1c6566e11e8a4",
12313
+ "createdAt": 1787577979329,
12314
+ "profile": {
12315
+ "birthDate": "1996-09-09",
12316
+ "birthHourIndex": 5,
12317
+ "gender": "男",
12318
+ "location": null
12319
+ },
12320
+ "samples": [],
12321
+ "records": [
12322
+ {
12323
+ "artId": "bazi",
12324
+ "createdAt": 1,
12325
+ "result": {
12326
+ "dayGZ": "壬申"
12327
+ }
12328
+ }
12329
+ ]
12330
+ },
12331
+ {
12332
+ "username": "联调2066",
12333
+ "passHash": "scrypt$e7858b64e6b69ce6bd024ac2354b8e5a$e13f447b726c78211daff17882e69ae75316246677b4ddc78a98b37124561ea0a5b8b70a62b29ce79a962ce0c4514730bf8a13e5dc55f3cb00480c198fac364d",
12334
+ "createdAt": 1787578142164,
12335
+ "profile": {
12336
+ "birthDate": "1996-09-09",
12337
+ "birthHourIndex": 5,
12338
+ "gender": "男",
12339
+ "location": null
12340
+ },
12341
+ "samples": [],
12342
+ "records": [
12343
+ {
12344
+ "artId": "bazi",
12345
+ "createdAt": 1,
12346
+ "result": {
12347
+ "dayGZ": "壬申"
12348
+ }
12349
+ }
12350
+ ]
12351
+ },
12352
+ {
12353
+ "username": "联调6325",
12354
+ "passHash": "scrypt$3d3aa4df4a572fb9b10d909e026b0779$6e1f65b1d2f20bc42113595dafe9da46919ca4bfa0cd6f1f1c8aca6b9f8ccd165a236e9f621fa4811273e693b9995dc96e9c5fb21435513f99ca24c95dcb2e59",
12355
+ "createdAt": 1787578376461,
12356
+ "profile": {
12357
+ "birthDate": "1996-09-09",
12358
+ "birthHourIndex": 5,
12359
+ "gender": "男",
12360
+ "location": null
12361
+ },
12362
+ "samples": [],
12363
+ "records": [
12364
+ {
12365
+ "artId": "bazi",
12366
+ "createdAt": 1,
12367
+ "result": {
12368
+ "dayGZ": "壬申"
12369
+ }
12370
+ }
12371
+ ]
12372
+ },
12373
+ {
12374
+ "username": "联调9786",
12375
+ "passHash": "scrypt$1ee3931430e606b9f2b3dc0489d51ce4$aaa8317bc0a1da52fea2f5e7b6043908ecc64e4de1002357f429adb81cb872eecb72a4a3798844a1037f8b1899d7cb628f1c2da4ca381ad56a669f4efc921682",
12376
+ "createdAt": 1787578469976,
12377
+ "profile": {
12378
+ "birthDate": "1996-09-09",
12379
+ "birthHourIndex": 5,
12380
+ "gender": "男",
12381
+ "location": null
12382
+ },
12383
+ "samples": [],
12384
+ "records": [
12385
+ {
12386
+ "artId": "bazi",
12387
+ "createdAt": 1,
12388
+ "result": {
12389
+ "dayGZ": "壬申"
12390
+ }
12391
+ }
12392
+ ]
12393
+ },
12394
+ {
12395
+ "username": "联调7046",
12396
+ "passHash": "scrypt$33ba6f88579964590d68cbc509ec1556$1802a99fcf3568113592a0bbb8e2176bd4a6199490eea7cf382f8d1c74d5d3efdfc6da688889b9a03bd90e2ab7f835c7635102bf4abfe08ea696266107246d9a",
12397
+ "createdAt": 1787578527311,
12398
+ "profile": {
12399
+ "birthDate": "1996-09-09",
12400
+ "birthHourIndex": 5,
12401
+ "gender": "男",
12402
+ "location": null
12403
+ },
12404
+ "samples": [],
12405
+ "records": [
12406
+ {
12407
+ "artId": "bazi",
12408
+ "createdAt": 1,
12409
+ "result": {
12410
+ "dayGZ": "壬申"
12411
+ }
12412
+ }
12413
+ ]
12288
12414
  }
12289
12415
  ]
12290
12416
  }
@@ -0,0 +1,67 @@
1
+ // 断语匹配:排盘因子 → 已校核断语(reviewed)→ 注入 AI prompt 作「古籍引证」
2
+ import { duanyuReviewed, type DuanyuEntry } from '../../../shared/core/data/duanyu';
3
+
4
+ // 盘面因子 → 断语 tags/因子 的关键词映射(每术可扩展)
5
+ // artId → 从排盘结果提取的候选关键词
6
+ function extractKeywords(artId: string, resultRaw: unknown): string[] {
7
+ const r = resultRaw as Record<string, any> | null;
8
+ if (!r) return [];
9
+ const kws: string[] = [];
10
+ switch (artId) {
11
+ case 'bazi':
12
+ // 月令/格局/用神/调候/旺衰
13
+ if (r.monthZhi) kws.push('月令');
14
+ if (r.geju) kws.push('格局', String(r.geju));
15
+ if (r.yongshen) kws.push('用神', String(r.yongshen));
16
+ if (r.strength) kws.push(r.strength === '身强' ? '旺衰' : '旺衰');
17
+ if (r.tiaohou) kws.push('调候');
18
+ break;
19
+ case 'liuyao':
20
+ if (r.yongshen) kws.push('用神', String(r.yongshen));
21
+ break;
22
+ case 'meihua':
23
+ if (r.tiyong) kws.push('体用');
24
+ break;
25
+ case 'qimen':
26
+ kws.push('阴阳遁', '三奇六仪', '起局');
27
+ break;
28
+ case 'liuren':
29
+ kws.push('四课', '三传');
30
+ break;
31
+ case 'ziwei':
32
+ if (r.brightness) kws.push('庙旺落陷', '星曜生克');
33
+ break;
34
+ case 'xiaoliuren':
35
+ kws.push('六神', '掌诀');
36
+ break;
37
+ default:
38
+ break;
39
+ }
40
+ return kws;
41
+ }
42
+
43
+ // 匹配:断语 tags 与候选关键词有交集即命中;按命中数排序取前 N
44
+ export function matchDuanyu(artId: string, resultRaw: unknown, limit = 3): DuanyuEntry[] {
45
+ const pool = duanyuReviewed().filter(d => d.art === artId);
46
+ if (pool.length === 0) return [];
47
+ const kws = extractKeywords(artId, resultRaw);
48
+ const scored = pool.map(d => {
49
+ const hit = d.tags.filter(t => kws.includes(t)).length + (d.factors || []).filter(f => kws.includes(f)).length;
50
+ return { d, hit };
51
+ }).sort((a, b) => b.hit - a.hit);
52
+ // 至少命中 1 个关键词才注入(避免无关引证);全部不命中则按术别取第一条最通用的
53
+ const matched = scored.filter(s => s.hit > 0);
54
+ const picked = matched.length > 0 ? matched : [scored[0]];
55
+ return picked.slice(0, limit).map(s => s.d);
56
+ }
57
+
58
+ // 生成 prompt 引证段(仅 reviewed 条目;未校核 seed 一律不注入)
59
+ export function duanyuPromptBlock(artId: string, resultRaw: unknown): string {
60
+ const matched = matchDuanyu(artId, resultRaw);
61
+ if (matched.length === 0) return '';
62
+ const lines = matched.map(d => {
63
+ const book = d.bookId ? '《' + d.bookId + '》' : '';
64
+ return '· ' + d.original + '(' + book + d.chapter + ')——断语:' + d.duanyu;
65
+ });
66
+ return '【古籍引证(断语库 · 已校核)】以下为与本盘/本问相关的古籍断语,可在行文自然处引用(须标注出处「《书·篇》」,且不得改写原文):\n' + lines.join('\n');
67
+ }
@@ -3,6 +3,7 @@ import type { ChatMessage } from './llmProvider';
3
3
  import { agentOf } from './skills';
4
4
  import { chartBrief } from './chartBrief';
5
5
  import { sixRelativesFacts } from './sixRelatives';
6
+ import { duanyuPromptBlock } from './duanyu';
6
7
 
7
8
  // ===== 报告 Schema 模板(命盘类:八字/紫微/星盘) =====
8
9
  const MINGPAN_SAMPLE = {
@@ -139,6 +140,8 @@ export function buildReportMessages(
139
140
  '',
140
141
  hourUnknownNote(profile),
141
142
  '',
143
+ duanyuPromptBlock(artId, resultRaw),
144
+ '',
142
145
  '【Skills 编排 · 请依序调用以下技能,逐章成文】',
143
146
  skillLines,
144
147
  '',
@@ -253,6 +256,8 @@ export function buildStep2Messages(
253
256
  '3. 全篇论断须自洽:性格/家庭/事业/感情各区块相互呼应,不得前后矛盾(同一维度两次出现必须口径一致)。',
254
257
  relativesFacts,
255
258
  '',
259
+ duanyuPromptBlock(artId, resultRaw),
260
+ '',
256
261
  '【技能编排】依序运用以下技能完成报告(每一步的结论都要落进对应章节):',
257
262
  skillLines,
258
263
  '',
@@ -1,8 +1,9 @@
1
- // 断语库 v1(种子版):结构化「原文 → 断语要点 → 适用因子」条目
1
+ // 断语库 v2:结构化「原文 → 断语要点 → 适用因子」条目
2
2
  // 定位:AI 解读的引证源(排盘 → 断语匹配 → 解读引用「《书·篇》:原文」),未来接考时校准
3
3
  // 收录规范 / 版权核查 / 处理清单:见 docs/古籍参考库收录规范.md
4
- // ⚠️ 全部条目 status: 'seed'(待逐字校核),未 reviewed 前不得注入 AI prompt
5
- // 来源 URL 2026-08-20 检索核实的公有领域在线底本
4
+ // 已校核 11 条(status: 'reviewed',来源为 ctext/维基文库/时点古籍 权威底本)→ 已接入 AI prompt 引证
5
+ // ⚠️ 剩余 5 status: 'seed'(待逐字校核/待补底本),未 reviewed 前不得注入 AI prompt
6
+ // 来源 URL 为 2026-08-20/23 检索核实的公有领域在线底本
6
7
 
7
8
  export type DuanyuStatus = 'seed' | 'reviewed';
8
9
 
@@ -27,14 +28,14 @@ export const DUANYU: DuanyuEntry[] = [
27
28
  original: '人禀天地,命属阴阳。生居覆载之内,尽在五行之中。欲知贵贱,先观月令乃提纲。',
28
29
  duanyu: '论命首重月令:先以月支定旺衰提纲,再论格局贵贱。月令为子平断命第一步。',
29
30
  factors: ['月令', '提纲'], tags: ['格局', '月令', '旺衰'],
30
- sources: ['https://ctext.org/wiki.pl?if=gb&chapter=524726'], status: 'seed',
31
+ sources: ['https://ctext.org/wiki.pl?if=gb&chapter=524726'], status: 'reviewed',
31
32
  },
32
33
  {
33
34
  id: 'dts-tiandao-01', bookId: 'ditian-sui', art: 'bazi', chapter: '天道',
34
35
  original: '欲识三元万法宗,先观帝载与神功。',
35
36
  duanyu: '断命先察三元(天干/地支/人元藏干)之体用与造化之枢机,体用立而后吉凶可论。',
36
37
  factors: ['三元', '体用'], tags: ['体用', '三元'],
37
- sources: ['https://zh.wikisource.org/zh-hant/%E6%BB%B4%E5%A4%A9%E9%AB%93%E9%97%A1%E5%BE%AE'], status: 'seed',
38
+ sources: ['https://zh.wikisource.org/zh-hant/%E6%BB%B4%E5%A4%A9%E9%AB%93%E9%97%A1%E5%BE%AE'], status: 'reviewed',
38
39
  },
39
40
  {
40
41
  id: 'dts-tiangan-01', bookId: 'ditian-sui', art: 'bazi', chapter: '天干(甲木)',
@@ -43,7 +44,7 @@ export const DUANYU: DuanyuEntry[] = [
43
44
  factors: ['调候', '生克制化'], tags: ['天干', '调候'],
44
45
  sources: ['https://zh.wikisource.org/zh-hans/%E6%BB%B4%E5%A4%A9%E9%AB%93/02'],
45
46
  note: '此诀《穷通宝鉴》亦引(转引);classics.ts 已修正:滴天髓收本源段,穷通宝鉴段注明转引(处理清单 #2 ✅)。',
46
- status: 'seed',
47
+ status: 'reviewed',
47
48
  },
48
49
  {
49
50
  id: 'zpzq-yongshen-01', bookId: 'ziping-zhenquan', art: 'bazi', chapter: '论用神',
@@ -59,14 +60,14 @@ export const DUANYU: DuanyuEntry[] = [
59
60
  original: '用神者,卦之体也。凡占必先寻用神,用神不现,须寻伏神。',
60
61
  duanyu: '六爻断卦第一步取用神:按所问之事定六亲取用;用神不上卦则寻伏神。',
61
62
  factors: ['用神', '伏神'], tags: ['用神', '六亲'],
62
- sources: ['https://www.shidianguji.com/zh/book/XYXZSBY/chapter/1lzxsn553hcfm'], status: 'seed',
63
+ sources: ['https://www.shidianguji.com/zh/book/XYXZSBY/chapter/1lzxsn553hcfm'], status: 'reviewed',
63
64
  },
64
65
  {
65
66
  id: 'bszz-yongshen-01', bookId: 'bushe-zhengzong', art: 'liuyao', chapter: '启蒙节要',
66
67
  original: '凡占必以用神为主,有根有气则吉,无根无气则凶。',
67
68
  duanyu: '用神有根(得月日生扶)有气(不遭刑冲克害)为吉,反之则凶。',
68
69
  factors: ['旺衰', '生扶'], tags: ['用神', '旺衰'],
69
- sources: ['https://ctext.org/wiki.pl?if=gb&chapter=801184'], status: 'seed',
70
+ sources: ['https://ctext.org/wiki.pl?if=gb&chapter=801184'], status: 'reviewed',
70
71
  },
71
72
  // ─── 梅花易数 meihua ───
72
73
  {
@@ -82,7 +83,7 @@ export const DUANYU: DuanyuEntry[] = [
82
83
  original: '不动不占,不因事不占。',
83
84
  duanyu: '起占原则:无异常变动不起卦、无事由不起卦,心动则应机而占。',
84
85
  factors: ['起卦原则'], tags: ['起卦', '原则'],
85
- sources: ['https://ctext.org/wiki.pl?if=gb&chapter=475043'], status: 'seed',
86
+ sources: ['https://ctext.org/wiki.pl?if=gb&chapter=475043'], status: 'reviewed',
86
87
  },
87
88
  // ─── 奇门遁甲 qimen ───
88
89
  {
@@ -90,7 +91,7 @@ export const DUANYU: DuanyuEntry[] = [
90
91
  original: '阴阳顺逆妙难穷,二至还乡一九宫。若能了达阴阳理,天地都来一掌中。',
91
92
  duanyu: '奇门总纲:冬至后阳遁顺布(一宫起)、夏至后阴遁逆布(九宫起),阴阳二遁为起局之枢。',
92
93
  factors: ['阴阳遁', '起局'], tags: ['阴阳遁', '九宫'],
93
- sources: ['https://zh.wikisource.org/wiki/%E7%85%99%E6%B3%A2%E9%87%A3%E5%8F%9F%E6%AD%8C'], status: 'seed',
94
+ sources: ['https://zh.wikisource.org/wiki/%E7%85%99%E6%B3%A2%E9%87%A3%E5%8F%9F%E6%AD%8C'], status: 'reviewed',
94
95
  },
95
96
  {
96
97
  id: 'ypdsw-sanqi-01', bookId: 'yanpo-diaosou', art: 'qimen', chapter: '三奇六仪',
@@ -106,14 +107,14 @@ export const DUANYU: DuanyuEntry[] = [
106
107
  original: '前后引从升迁吉,首尾相见始终宜。',
107
108
  duanyu: '毕法之一:三传中初末传夹拱中传(引从)主升迁之喜;首尾同气则事有始终。',
108
109
  factors: ['三传', '引从'], tags: ['毕法赋', '三传'],
109
- sources: ['https://www.shidianguji.com/zh/book/SK1599/chapter/1k1lql7vm29te'], status: 'seed',
110
+ sources: ['https://www.shidianguji.com/zh/book/SK1599/chapter/1k1lql7vm29te'], status: 'reviewed',
110
111
  },
111
112
  {
112
113
  id: 'lrdq-kezhuan-01', bookId: 'liuren-daquan', art: 'liuren', chapter: '起课',
113
114
  original: '课传既定,先视四课之生克,次观三传之发用。天地盘加临,日月星为纬。',
114
115
  duanyu: '断课次序:四课定生克、三传察发用,天地盘加临为纲。',
115
116
  factors: ['四课', '三传'], tags: ['四课', '三传', '断课'],
116
- sources: ['https://www.shidianguji.com/zh/book/SK1599/chapter/1k1lql7vm29te'], status: 'seed',
117
+ sources: ['https://www.shidianguji.com/zh/book/SK1599/chapter/1k1lql7vm29te'], status: 'reviewed',
117
118
  },
118
119
  // ─── 紫微斗数 ziwei ───
119
120
  {
@@ -122,7 +123,7 @@ export const DUANYU: DuanyuEntry[] = [
122
123
  duanyu: '斗数断命以庙旺落陷与生克制化为枢:星入庙为奇、失度为虚;星临庙旺之位再观生克之机,命坐强宫细察制化之理。',
123
124
  factors: ['庙旺落陷', '星曜生克'], tags: ['太微赋', '庙陷', '生克'],
124
125
  sources: ['https://www.shidianguji.com/mid-page/7356346219641356314', 'https://m.gushiwen.cn/guwen/bookv_46653FD803893E4F4F9BB8FD2D9C9B7F.aspx'],
125
- note: '2026-08-20 已按《紫微斗数全书》卷一·太微赋第一原文替换意译段(classics.ts 同步修正);断句待与刻本终校。', status: 'seed',
126
+ note: '2026-08-20 已按《紫微斗数全书》卷一·太微赋第一原文替换意译段(classics.ts 同步修正);断句待与刻本终校。', status: 'reviewed',
126
127
  },
127
128
  // ─── 小六壬 xiaoliuren(民间口诀,无成书古籍)───
128
129
  {
@@ -139,7 +140,7 @@ export const DUANYU: DuanyuEntry[] = [
139
140
  original: '易与天地准,故能弥纶天地之道。仰以观于天文,俯以察于地理,是故知幽明之故。',
140
141
  duanyu: '易学认识论总纲:卦象法天地,观天文察地理而知幽明——一切卦术「观物取象」的理据。',
141
142
  factors: ['观物取象'], tags: ['系辞', '取象'],
142
- sources: ['https://ctext.org/book-of-changes/zh'], status: 'seed',
143
+ sources: ['https://ctext.org/book-of-changes/zh'], status: 'reviewed',
143
144
  },
144
145
  ];
145
146
 
@@ -62,6 +62,8 @@ export function baziCalc(input: BaziInput): BaziResult {
62
62
  const min = 0;
63
63
 
64
64
  // 2. 年柱(立春为界)
65
+ // 注意:getJieQiTableExact(yy) 返回的是「农历年」节气表,冬至后的立春可能落在公历次年
66
+ // (如 2023 表中含 2024-02-04 立春)——取立春发生的公历年份定年柱,不能用表格年份 yy
65
67
  const t = new Date(y, m - 1, d, hour, min).getTime();
66
68
  let lichunBest = -Infinity;
67
69
  let lichunYear = y - 1;
@@ -69,7 +71,7 @@ export function baziCalc(input: BaziInput): BaziResult {
69
71
  for (const jq of getJieQiTableExact(yy)) {
70
72
  if (jq.name === '立春') {
71
73
  const tt = jq.time.getTime();
72
- if (tt <= t && tt > lichunBest) { lichunBest = tt; lichunYear = yy; }
74
+ if (tt <= t && tt > lichunBest) { lichunBest = tt; lichunYear = jq.time.getFullYear(); }
73
75
  }
74
76
  }
75
77
  }
@@ -27,14 +27,23 @@ export function getJieqiApproxName(_y: number, m: number, d: number): string {
27
27
  }
28
28
 
29
29
  // 精确节气时刻表(某年全年 24 节气)
30
+ // 注:lunar-typescript 的 getJieQiTable key 混用中英文(当年冬至→次年惊蛰段为英文拼音),此处统一映射为中文
30
31
  export interface JieQiTime { name: string; time: Date }
32
+ const JQ_KEY_CN: Record<string, string> = {
33
+ LI_CHUN: '立春', YU_SHUI: '雨水', JING_ZHE: '惊蛰', CHUN_FEN: '春分',
34
+ QING_MING: '清明', GU_YU: '谷雨', LI_XIA: '立夏', XIAO_MAN: '小满',
35
+ MANG_ZHONG: '芒种', XIA_ZHI: '夏至', XIAO_SHU: '小暑', DA_SHU: '大暑',
36
+ LI_QIU: '立秋', CHU_SHU: '处暑', BAI_LU: '白露', QIU_FEN: '秋分',
37
+ HAN_LU: '寒露', SHUANG_JIANG: '霜降', LI_DONG: '立冬', XIAO_XUE: '小雪',
38
+ DA_XUE: '大雪', DONG_ZHI: '冬至', XIAO_HAN: '小寒', DA_HAN: '大寒',
39
+ };
31
40
  export function getJieQiTableExact(year: number): JieQiTime[] {
32
41
  const solar = Solar.fromYmdHms(year, 6, 1, 12, 0, 0);
33
42
  const table = solar.getLunar().getJieQiTable();
34
43
  const out: JieQiTime[] = [];
35
44
  for (const key of Object.keys(table)) {
36
45
  const s = table[key];
37
- out.push({ name: key, time: new Date(s.getYear(), s.getMonth() - 1, s.getDay(), s.getHour(), s.getMinute(), s.getSecond()) });
46
+ out.push({ name: JQ_KEY_CN[key] || key, time: new Date(s.getYear(), s.getMonth() - 1, s.getDay(), s.getHour(), s.getMinute(), s.getSecond()) });
38
47
  }
39
48
  return out;
40
49
  }
@@ -1,9 +1,220 @@
1
- // 大六壬:月将加时(中气定将)· 四课三传(简式)
2
- import { GAN, ZHI, mod } from '../data/ganzhi';
1
+ // 大六壬:月将加时(中气定将)· 四课三传(九宗门真实起法)
2
+ // 九宗门:贼克(元首/始入/重审/知一)→ 比用 涉害 遥克(蒿矢/弹射)→ 昴星(虎视/冬蛇掩目)→ 别责 → 八专 → 返吟(取驿马)→ 伏吟(取刑)
3
+ import { GAN, ZHI, WUXING, mod } from '../data/ganzhi';
3
4
  import { LR_JIANGS, LR_JIANG_SUN, LR_GANJI } from '../data/liuren';
4
5
  import { daysSince, currentJieqiNameExact } from './calendar';
5
6
  import type { LiurenResult } from '../types';
6
7
 
8
+ // ─── 九宗门辅助表 ───
9
+ // 五行生克:克我者(谁克我)——五行相克序:木→土→水→火→金→木
10
+ const WX_KE: Record<string, string> = { 木: '土', 土: '水', 水: '火', 火: '金', 金: '木' };
11
+
12
+ function ke(wx1: string, wx2: string): boolean {
13
+ // wx1 克 wx2?
14
+ return WX_KE[wx1] === wx2;
15
+ }
16
+
17
+ // 地支阴阳:子(0)阳 丑(1)阴 寅(2)阳 … 亥(11)阴
18
+ const zhiYang = (z: string) => ZHI.indexOf(z as any) % 2 === 0;
19
+
20
+ // 干阴阳:甲丙戊庚壬 阳;乙丁己辛癸 阴
21
+ const ganYang = (g: string) => GAN.indexOf(g as any) % 2 === 0;
22
+
23
+ // 刑:子刑卯、卯刑子;寅刑巳、巳刑申、申刑寅;丑刑戌、戌刑未、未刑丑;辰午酉亥自刑
24
+ const XING: Record<string, string> = { 子: '卯', 卯: '子', 寅: '巳', 巳: '申', 申: '寅', 丑: '戌', 戌: '未', 未: '丑' };
25
+ const ZIXING = ['辰', '午', '酉', '亥'];
26
+
27
+ // 冲:子午、丑未、寅申、卯酉、辰戌、巳亥
28
+ const CHONG: Record<string, string> = { 子: '午', 午: '子', 丑: '未', 未: '丑', 寅: '申', 申: '寅', 卯: '酉', 酉: '卯', 辰: '戌', 戌: '辰', 巳: '亥', 亥: '巳' };
29
+
30
+ // 驿马(三合局冲):申子辰→寅、寅午戌→申、巳酉丑→亥、亥卯未→巳
31
+ function yimaOf(zhi: string): string {
32
+ const maMap: Record<string, string> = { 申: '寅', 子: '寅', 辰: '寅', 寅: '申', 午: '申', 戌: '申', 巳: '亥', 酉: '亥', 丑: '亥', 亥: '巳', 卯: '巳', 未: '巳' };
33
+ return maMap[zhi] || '寅';
34
+ }
35
+
36
+ // 干五合(别责阳日用):甲己合、乙庚合、丙辛合、丁壬合、戊癸合
37
+ const GAN_HE: Record<string, string> = { 甲: '己', 己: '甲', 乙: '庚', 庚: '乙', 丙: '辛', 辛: '丙', 丁: '壬', 壬: '丁', 戊: '癸', 癸: '戊' };
38
+
39
+ interface Ke {
40
+ shang: string; // 上神(天盘所加之神)
41
+ xia: string; // 下神(地盘本支)
42
+ shangKe: boolean; // 上克下
43
+ xiaKe: boolean; // 下克上(贼)
44
+ }
45
+
46
+ function buildKe(tianpan: Record<number, string>, ganJi: string, dayZhi: string): Ke[] {
47
+ const zi = (z: string) => ZHI.indexOf(z as any);
48
+ const ke1 = tianpan[zi(ganJi)];
49
+ const ke2 = tianpan[zi(ke1)];
50
+ const ke3 = tianpan[zi(dayZhi)];
51
+ const ke4 = tianpan[zi(ke3)];
52
+ const rows: Ke[] = [
53
+ { shang: ke1, xia: ganJi, shangKe: false, xiaKe: false },
54
+ { shang: ke2, xia: ke1, shangKe: false, xiaKe: false },
55
+ { shang: ke3, xia: dayZhi, shangKe: false, xiaKe: false },
56
+ { shang: ke4, xia: ke3, shangKe: false, xiaKe: false },
57
+ ];
58
+ for (const r of rows) {
59
+ const sw = WUXING[r.shang], xw = WUXING[r.xia];
60
+ r.shangKe = ke(sw, xw);
61
+ r.xiaKe = ke(xw, sw);
62
+ }
63
+ return rows;
64
+ }
65
+
66
+ interface Chuan { method: string; note: string; chuan1: string; chuan2: string; chuan3: string; }
67
+
68
+ // 由初传推中传、末传:中传 = 初传上神(天盘加临初传者),末传 = 中传上神
69
+ function chuanFrom(tianpan: Record<number, string>, chuan1: string): { chuan2: string; chuan3: string } {
70
+ const zi = (z: string) => ZHI.indexOf(z as any);
71
+ const chuan2 = tianpan[zi(chuan1)];
72
+ const chuan3 = tianpan[zi(chuan2)];
73
+ return { chuan2, chuan3 };
74
+ }
75
+
76
+ /**
77
+ * 九宗门起三传(《六壬大全》标准规则)
78
+ * 顺序:有克→贼克(唯一取之;多克→比用→涉害);无克→遥克;无遥克→别责/八专/昴星;
79
+ * 返吟无克取驿马;伏吟无克取刑
80
+ */
81
+ export function jiuzongmen(tianpan: Record<number, string>, ganJi: string, dayZhi: string, dayGan: string, hourIndex: number, jiang: string): Chuan {
82
+ const zi = (z: string) => ZHI.indexOf(z as any);
83
+ const kes = buildKe(tianpan, ganJi, dayZhi);
84
+ const shangKeRows = kes.filter(k => k.shangKe);
85
+ const xiaKeRows = kes.filter(k => k.xiaKe);
86
+ const jiangIdx = zi(jiang);
87
+ const isFuyin = jiangIdx === hourIndex; // 天地盘同位
88
+ const isFanyin = mod(jiangIdx - hourIndex, 12) === 6; // 天地盘对冲
89
+
90
+ // 涉害:多克且比用不出时,取受克最深者(涉害深者发用);同深取先见
91
+ const shehai = (candidates: Ke[]): Ke => {
92
+ let best = candidates[0], bestDepth = -1;
93
+ for (const c of candidates) {
94
+ let depth = 0;
95
+ for (const z of ZHI) {
96
+ const wx = WUXING[c.shang];
97
+ if (ke(WUXING[z], wx)) depth++; // 地盘支克上神
98
+ }
99
+ if (depth > bestDepth) { best = c; bestDepth = depth; }
100
+ }
101
+ return best;
102
+ };
103
+
104
+ // ── 1. 贼克法 ──
105
+ if (shangKeRows.length > 0 || xiaKeRows.length > 0) {
106
+ // 有上克下优先取上克下;无上克下取贼
107
+ const pool = shangKeRows.length > 0 ? shangKeRows : xiaKeRows;
108
+ if (pool.length === 1) {
109
+ // 元首课(一上克下)/ 始入课(一下贼上)
110
+ const c1 = pool[0].shang;
111
+ const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
112
+ return {
113
+ method: pool[0].shangKe ? (shangKeRows.length === 1 && xiaKeRows.length === 0 ? '元首课' : '重审课') : '始入课',
114
+ note: pool[0].shangKe ? (shangKeRows.length === 1 && xiaKeRows.length === 0 ? '一上克下,事起于外,宜主动决断。' : '课多上克,主事多制肘,须审时度势。') : '一下贼上,事起于内,防人算计。',
115
+ chuan1: c1, chuan2, chuan3,
116
+ };
117
+ }
118
+ // 多克 → 比用(取与日干阴阳相同之上神)
119
+ const dayGanYang = ganYang(dayGan);
120
+ const bi = pool.filter(k => zhiYang(k.shang) === dayGanYang);
121
+ if (bi.length === 1) {
122
+ const c1 = bi[0].shang;
123
+ const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
124
+ return { method: '知一课', note: '取与日干比和者发用,事有取舍,宜择同类而谋。', chuan1: c1, chuan2, chuan3 };
125
+ }
126
+ // 比用不出 → 涉害
127
+ const pick = shehai(pool);
128
+ const c1 = pick.shang;
129
+ const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
130
+ return { method: '涉害课', note: '诸克不比,取受克最深者发用,事机隐晦,宜深察利害。', chuan1: c1, chuan2, chuan3 };
131
+ }
132
+
133
+ // ── 返吟无克:取驿马 ──
134
+ if (isFanyin) {
135
+ const ma = yimaOf(dayZhi);
136
+ const c1 = tianpan[zi(ma)]; // 马星所加之神
137
+ const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
138
+ return { method: '返吟课', note: '天地盘相冲,事有反复翻覆;无克取驿马发用,主变动奔波,宜动不宜静。', chuan1: c1, chuan2, chuan3 };
139
+ }
140
+
141
+ // ── 伏吟无克:取刑 ──
142
+ if (isFuyin) {
143
+ const ganYangFlag = ganYang(dayGan);
144
+ const c1 = ganYangFlag ? ganJi : dayZhi; // 阳日取干上(寄宫本支),阴日取支上
145
+ const xing1 = XING[c1] || (ZIXING.includes(c1) ? CHONG[c1] : c1);
146
+ const xing2 = xing1 && xing1 !== c1 ? (XING[xing1] || (ZIXING.includes(xing1) ? CHONG[xing1] : xing1)) : c1;
147
+ const chuan2 = tianpan[zi(xing1)] || c1;
148
+ const chuan3 = tianpan[zi(xing2)] || chuan2;
149
+ return { method: '伏吟课', note: '天地盘同位,诸事迟滞反复;无克取刑发用,主静中藏动,宜守待变。', chuan1: tianpan[zi(c1)], chuan2, chuan3 };
150
+ }
151
+
152
+ // ── 2. 遥克法(无上下克):日干遥克上神(蒿矢)/ 上神遥克日干(弹射) ──
153
+ const ganWx = WUXING[dayGan];
154
+ const yaoKe: Ke[] = [];
155
+ const yaoBei: Ke[] = [];
156
+ for (const k of kes) {
157
+ if (ke(ganWx, WUXING[k.shang])) yaoKe.push(k); // 日干克上神
158
+ if (ke(WUXING[k.shang], ganWx)) yaoBei.push(k); // 上神克日干
159
+ }
160
+ if (yaoKe.length > 0 || yaoBei.length > 0) {
161
+ const pool = yaoKe.length > 0 ? yaoKe : yaoBei;
162
+ const dayGanYang = ganYang(dayGan);
163
+ const bi = pool.filter(k => zhiYang(k.shang) === dayGanYang);
164
+ const pick = bi.length > 0 ? bi[0] : pool[0];
165
+ const c1 = pick.shang;
166
+ const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
167
+ return {
168
+ method: yaoKe.length > 0 ? '蒿矢课' : '弹射课',
169
+ note: yaoKe.length > 0 ? '日遥克神,事由己发,主主动求取,渐有所成。' : '神遥克日,事出意外,防暗箭来袭,宜谨慎自守。',
170
+ chuan1: c1, chuan2, chuan3,
171
+ };
172
+ }
173
+
174
+ // ── 3. 无克无遥克:别责 / 八专 / 昴星 ──
175
+ const ganJiZhi = ganJi;
176
+ // 八专:日干寄宫与日支同位(四课止两课)
177
+ if (ganJiZhi === dayZhi) {
178
+ const ganYangFlag = ganYang(dayGan);
179
+ const c1 = ganYangFlag ? kes[0].shang : kes[2].shang; // 阳日取干上神,阴日取支上神
180
+ const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
181
+ return { method: '八专课', note: '干支同位,四课不完;主事机专一,惟阴阳未分,宜专一其心。', chuan1: c1, chuan2, chuan3 };
182
+ }
183
+ // 别责:干上神与支上神相同(四课不备)
184
+ if (kes[0].shang === kes[2].shang) {
185
+ const ganYangFlag = ganYang(dayGan);
186
+ // 阳日取干五合之干寄宫上神;阴日取支上神
187
+ if (ganYangFlag) {
188
+ const heGan = GAN_HE[dayGan];
189
+ const heJi = LR_GANJI[heGan];
190
+ const c1 = tianpan[zi(heJi)];
191
+ const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
192
+ return { method: '别责课', note: '四课不备,取干合发用;主事不归一,须借力他人,另辟蹊径。', chuan1: c1, chuan2, chuan3 };
193
+ }
194
+ const c1 = kes[2].shang;
195
+ const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
196
+ return { method: '别责课', note: '四课不备,取支上神发用;主事有隐衷,宜旁敲侧击,徐图其成。', chuan1: c1, chuan2, chuan3 };
197
+ }
198
+ // 昴星:无克无遥克且四课全备——以酉(昴星)为用
199
+ {
200
+ const ganYangFlag = ganYang(dayGan);
201
+ const youIdx = zi('酉');
202
+ if (ganYangFlag) {
203
+ // 阳日:取天盘加临地盘酉之神(酉上神)为初传(虎视课);中传取支上神,末传取干上神
204
+ const c1 = tianpan[youIdx];
205
+ const c2 = tianpan[zi(dayZhi)]; // 支上神
206
+ const c3 = tianpan[zi(ganJi)]; // 干上神
207
+ return { method: '昴星课', note: '无克无遥,取昴星(酉)发用(虎视格);主事机隐晦,宜静观其变,防惊变于暗处。', chuan1: c1, chuan2: c2, chuan3: c3 };
208
+ }
209
+ // 阴日:取天盘酉加临之地盘支(酉下神)为初传(冬蛇掩目);中传取干上神,末传取支上神
210
+ let c1 = '';
211
+ for (let i = 0; i < 12; i++) if (tianpan[i] === '酉') { c1 = ZHI[i]; break; }
212
+ const c2 = tianpan[zi(ganJi)];
213
+ const c3 = tianpan[zi(dayZhi)];
214
+ return { method: '昴星课', note: '无克无遥,取昴星(酉)发用(冬蛇掩目格);主事蒙昧,宜守正待时,防晦气暗生。', chuan1: c1, chuan2: c2, chuan3: c3 };
215
+ }
216
+ }
217
+
7
218
  export function liurenCalc(dt: string | Date): LiurenResult {
8
219
  const d = dt instanceof Date ? dt : new Date(dt);
9
220
  const y = d.getFullYear(), m = d.getMonth() + 1, day = d.getDate();
@@ -36,8 +247,9 @@ export function liurenCalc(dt: string | Date): LiurenResult {
36
247
  const ke2 = tianpan[ZHI.indexOf(ke1 as any)];
37
248
  const ke3 = tianpan[ZHI.indexOf(dayGZ[1] as any)];
38
249
  const ke4 = tianpan[ZHI.indexOf(ke3 as any)];
39
- /* 三传(简式取传:依次取四课初传、中传、末传上神) */
40
- const chuan1 = ke1, chuan2 = ke2, chuan3 = ke3;
250
+ /* 三传:九宗门真实起法(替代旧简式取传) */
251
+ const chuan = jiuzongmen(tianpan, ganJi, dayGZ[1], dayGZ[0], hourIndex, jiang);
252
+ const { chuan1, chuan2, chuan3, method, note } = chuan;
41
253
 
42
254
  /* ── 补齐层:贵人 + 十二天将 ── */
43
255
  // 贵人起法(日干):甲戊庚牛羊 乙己鼠猴乡 丙丁猪鸡位 壬癸兔蛇藏 六辛逢马虎
@@ -77,10 +289,10 @@ export function liurenCalc(dt: string | Date): LiurenResult {
77
289
  : keti === '反吟课' ? '天地盘相冲,事有反复翻覆,来去无常,宜缓不宜急。'
78
290
  : '四课三传乘常气,事机明朗,顺其自然即可。';
79
291
 
80
- return { dayGZ, hourGZ, jiang, jqName, tianpan, ganJi, ke1, ke2, ke3, ke4, chuan1, chuan2, chuan3, guiRen, isDay, tianJiang, chuanJiang, keti, ketiNote };
292
+ return { dayGZ, hourGZ, jiang, jqName, tianpan, ganJi, ke1, ke2, ke3, ke4, chuan1, chuan2, chuan3, guiRen, isDay, tianJiang, chuanJiang, keti, ketiNote, chuanMethod: method, chuanNote: note };
81
293
  }
82
294
 
83
295
  export const jiangName = (zhi: string): string => {
84
296
  const row = LR_JIANGS.find(j => j[1] === zhi);
85
297
  return row ? row[0] : zhi;
86
- }
298
+ }