guanwei 1.1.1

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.
Files changed (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +202 -0
  3. package/README.md +247 -0
  4. package/dist/assets/index-CsOD5rSo.js +102 -0
  5. package/dist/assets/index-CsOD5rSo.js.map +1 -0
  6. package/dist/assets/index-DQx62TUr.css +1 -0
  7. package/dist/favicon.svg +12 -0
  8. package/dist/index.html +15 -0
  9. package/eslint.config.js +28 -0
  10. package/index.html +25 -0
  11. package/package.json +96 -0
  12. package/postcss.config.js +10 -0
  13. package/public/favicon.svg +12 -0
  14. package/scripts/guanwei +245 -0
  15. package/scripts/install.sh +79 -0
  16. package/scripts/release.sh +42 -0
  17. package/scripts/setup.bat +36 -0
  18. package/scripts/setup.sh +123 -0
  19. package/server/src/data/db.json +12059 -0
  20. package/server/src/data/guanwei.db +0 -0
  21. package/server/src/data/lifeContext.ts +423 -0
  22. package/server/src/data/spreads.ts +255 -0
  23. package/server/src/data/tarotCards.ts +2353 -0
  24. package/server/src/index.ts +71 -0
  25. package/server/src/routes/ai.ts +376 -0
  26. package/server/src/routes/divine.ts +140 -0
  27. package/server/src/routes/hour.ts +40 -0
  28. package/server/src/routes/tarot.ts +129 -0
  29. package/server/src/routes/users.ts +140 -0
  30. package/server/src/services/chartBrief.ts +131 -0
  31. package/server/src/services/divineStore.ts +188 -0
  32. package/server/src/services/hourInference.ts +150 -0
  33. package/server/src/services/llmProvider.ts +181 -0
  34. package/server/src/services/promptBuilder.ts +327 -0
  35. package/server/src/services/relativesCheck.ts +139 -0
  36. package/server/src/services/sixRelatives.ts +70 -0
  37. package/server/src/services/skills.ts +116 -0
  38. package/server/src/types/index.ts +104 -0
  39. package/server/src/utils/semanticAnalyzer.ts +555 -0
  40. package/server/src/utils/tarotEngine.ts +849 -0
  41. package/server/tsconfig.json +18 -0
  42. package/shared/core/data/classics.ts +276 -0
  43. package/shared/core/data/courses.ts +211 -0
  44. package/shared/core/data/duanyu.ts +148 -0
  45. package/shared/core/data/ganzhi.ts +53 -0
  46. package/shared/core/data/gua64.ts +93 -0
  47. package/shared/core/data/liuren.ts +18 -0
  48. package/shared/core/data/liuyao.ts +73 -0
  49. package/shared/core/data/qimen.ts +27 -0
  50. package/shared/core/data/region-full.ts +3 -0
  51. package/shared/core/data/region.ts +310 -0
  52. package/shared/core/data/tarotSpreads.ts +78 -0
  53. package/shared/core/data/xiaoliuren.ts +18 -0
  54. package/shared/core/data/ziwei.ts +97 -0
  55. package/shared/core/data/zodiac.ts +20 -0
  56. package/shared/core/engine/astrology.ts +169 -0
  57. package/shared/core/engine/bazi.ts +306 -0
  58. package/shared/core/engine/calendar.ts +127 -0
  59. package/shared/core/engine/daily.ts +133 -0
  60. package/shared/core/engine/liuren.ts +86 -0
  61. package/shared/core/engine/liuyao.ts +93 -0
  62. package/shared/core/engine/meihua.ts +87 -0
  63. package/shared/core/engine/qimen.ts +78 -0
  64. package/shared/core/engine/questionFit.ts +94 -0
  65. package/shared/core/engine/tarot.ts +69 -0
  66. package/shared/core/engine/trueSolarTime.ts +86 -0
  67. package/shared/core/engine/xiaoliuren.ts +19 -0
  68. package/shared/core/engine/ziwei.ts +217 -0
  69. package/shared/core/engine/ziweiInterpret.ts +161 -0
  70. package/shared/core/types.ts +206 -0
  71. package/src/App.tsx +69 -0
  72. package/src/arts/registry.ts +31 -0
  73. package/src/assets/react.svg +1 -0
  74. package/src/components/Backdrop.tsx +27 -0
  75. package/src/components/BambooArt.tsx +41 -0
  76. package/src/components/CalendarInput.tsx +131 -0
  77. package/src/components/DateInput.tsx +103 -0
  78. package/src/components/Disclaimer.tsx +12 -0
  79. package/src/components/Empty.tsx +8 -0
  80. package/src/components/ErrorBoundary.tsx +38 -0
  81. package/src/components/HomeSideAnchor.tsx +62 -0
  82. package/src/components/InkVein.tsx +10 -0
  83. package/src/components/LifeEventsInput.tsx +47 -0
  84. package/src/components/LocationPicker.tsx +117 -0
  85. package/src/components/Motes.tsx +65 -0
  86. package/src/components/Navigation.tsx +80 -0
  87. package/src/components/ProfileForm.tsx +40 -0
  88. package/src/components/ProfilePicker.tsx +40 -0
  89. package/src/components/QuestionFields.tsx +47 -0
  90. package/src/components/ReportView.tsx +211 -0
  91. package/src/components/ResultCard.tsx +40 -0
  92. package/src/components/SealButton.tsx +18 -0
  93. package/src/components/SiteFooter.tsx +17 -0
  94. package/src/components/SiteNav.tsx +103 -0
  95. package/src/components/SongDialog.tsx +64 -0
  96. package/src/components/SongSearchSelect.tsx +81 -0
  97. package/src/components/SongSelect.tsx +61 -0
  98. package/src/components/StarField.tsx +119 -0
  99. package/src/components/TarotCard.tsx +129 -0
  100. package/src/components/TimeShichenInput.tsx +29 -0
  101. package/src/components/arts/AstrologyArt.tsx +141 -0
  102. package/src/components/arts/BaziArt.tsx +269 -0
  103. package/src/components/arts/LiurenArt.tsx +87 -0
  104. package/src/components/arts/LiuyaoArt.tsx +111 -0
  105. package/src/components/arts/MeihuaArt.tsx +105 -0
  106. package/src/components/arts/QimenArt.tsx +86 -0
  107. package/src/components/arts/TarotArt.tsx +110 -0
  108. package/src/components/arts/XiaoliurenArt.tsx +93 -0
  109. package/src/components/arts/ZiweiArt.tsx +296 -0
  110. package/src/data/arts.ts +84 -0
  111. package/src/data/lifeContext.ts +423 -0
  112. package/src/data/sample-report.json +146 -0
  113. package/src/data/shichen.ts +21 -0
  114. package/src/data/spreads.ts +255 -0
  115. package/src/data/tarotCards.ts +2353 -0
  116. package/src/hooks/useAIInterpret.ts +76 -0
  117. package/src/hooks/useDivine.ts +39 -0
  118. package/src/hooks/useTheme.ts +29 -0
  119. package/src/index.css +190 -0
  120. package/src/lib/utils.ts +6 -0
  121. package/src/main.tsx +12 -0
  122. package/src/pages/AboutPage.tsx +25 -0
  123. package/src/pages/AcademyPage.tsx +128 -0
  124. package/src/pages/AstrologyPage.tsx +33 -0
  125. package/src/pages/AuthPage.tsx +263 -0
  126. package/src/pages/BaZiPage.tsx +33 -0
  127. package/src/pages/ClassicsPage.tsx +120 -0
  128. package/src/pages/DemoPage.tsx +161 -0
  129. package/src/pages/HistoryPage.tsx +133 -0
  130. package/src/pages/Home.tsx +179 -0
  131. package/src/pages/HomePage.tsx +174 -0
  132. package/src/pages/ModulePage.tsx +320 -0
  133. package/src/pages/SpreadEditor.tsx +230 -0
  134. package/src/pages/SpreadEditorPage.tsx +105 -0
  135. package/src/pages/SpreadLibrary.tsx +199 -0
  136. package/src/pages/SpreadLibraryPage.tsx +17 -0
  137. package/src/pages/TarotPage.tsx +738 -0
  138. package/src/pages/ZiWeiPage.tsx +33 -0
  139. package/src/services/api.ts +342 -0
  140. package/src/stores/useDivinationStore.ts +22 -0
  141. package/src/styles/song.css +1066 -0
  142. package/src/styles/tokens.css +60 -0
  143. package/src/types/index.ts +104 -0
  144. package/src/utils/astrologyCalc.ts +61 -0
  145. package/src/utils/comboEngine.ts +134 -0
  146. package/src/utils/dst.ts +24 -0
  147. package/src/utils/panTone.ts +206 -0
  148. package/src/utils/recordStore.ts +60 -0
  149. package/src/utils/semanticAnalyzer.ts +555 -0
  150. package/src/utils/spreadStorage.ts +32 -0
  151. package/src/utils/storage.ts +27 -0
  152. package/src/utils/tarotEngine.ts +849 -0
  153. package/src/utils/userStore.ts +234 -0
  154. package/src/vite-env.d.ts +1 -0
  155. package/tailwind.config.js +52 -0
  156. package/tsconfig.json +40 -0
  157. package/vite.config.ts +34 -0
@@ -0,0 +1,86 @@
1
+ // 真太阳时:经度修正 + 均时差 EoT + 中国夏令时回拨
2
+ import type { TrueSolarTime } from '../types';
3
+
4
+ // 中国夏令时区间(1986-1991,北京时间 02:00 起止)
5
+ const DST_PERIODS: [number, number, number, number, number, number][] = [
6
+ [1986, 5, 4, 1986, 9, 14],
7
+ [1987, 4, 12, 1987, 9, 13],
8
+ [1988, 4, 10, 1988, 9, 11],
9
+ [1989, 4, 16, 1989, 9, 17],
10
+ [1990, 4, 15, 1990, 9, 16],
11
+ [1991, 4, 14, 1991, 9, 15],
12
+ ];
13
+
14
+ export function isChinaDST(y: number, m: number, d: number): boolean {
15
+ for (const [sy, sm, sd, ey, em, ed] of DST_PERIODS) {
16
+ const start = new Date(sy, sm - 1, sd, 2, 0, 0).getTime();
17
+ const end = new Date(ey, em - 1, ed, 2, 0, 0).getTime();
18
+ const t = new Date(y, m - 1, d, 12, 0, 0).getTime();
19
+ if (t >= start && t <= end) return true;
20
+ }
21
+ return false;
22
+ }
23
+
24
+ // 一年中的第几天
25
+ function dayOfYear(y: number, m: number, d: number): number {
26
+ return Math.floor((new Date(y, m - 1, d).getTime() - new Date(y, 0, 1).getTime()) / 86400000) + 1;
27
+ }
28
+
29
+ /**
30
+ * 真太阳时计算
31
+ * @param y/m/d 公历日期(北京时间)
32
+ * @param hour 小时(0-23)
33
+ * @param min 分钟
34
+ * @param lng 出生地东经(如北京 116.4)
35
+ * @param applyDST 是否回拨夏令时(默认 true)
36
+ */
37
+ export function trueSolarTime(
38
+ y: number, m: number, d: number,
39
+ hour: number, min: number,
40
+ lng: number,
41
+ applyDST = true,
42
+ ): TrueSolarTime {
43
+ let beijingHours = hour + min / 60;
44
+ // 夏令时回拨 1 小时
45
+ if (applyDST && isChinaDST(y, m, d)) beijingHours -= 1;
46
+ // 平太阳时:东经 120° 基准,每 1° 差 4 分钟
47
+ const localMeanHours = beijingHours + (lng - 120) * 4 / 60;
48
+ // 均时差 EoT(分钟)
49
+ const N = dayOfYear(y, m, d);
50
+ const B = (2 * Math.PI * (N - 81)) / 364;
51
+ const eot = 9.87 * Math.sin(2 * B) - 7.53 * Math.cos(B) - 1.5 * Math.sin(B);
52
+ const trueSolarHours = localMeanHours + eot / 60;
53
+ // 归一化并定日期偏移
54
+ let hh = trueSolarHours;
55
+ let dateOffset = 0;
56
+ while (hh >= 24) { hh -= 24; dateOffset++; }
57
+ while (hh < 0) { hh += 24; dateOffset--; }
58
+ const hourIndex = Math.floor(((hh + 1) % 24) / 2);
59
+ return {
60
+ beijingHours,
61
+ localMeanHours,
62
+ trueSolarHours,
63
+ eotMinutes: eot,
64
+ hourIndex,
65
+ dateOffset,
66
+ };
67
+ }
68
+
69
+ /**
70
+ * 纯经度修正(用于无公历日期的场景,如紫微农历输入):
71
+ * 只做东经 120° 基准修正,忽略均时差(±15 分钟,不跨时辰段)
72
+ */
73
+ export function longitudeCorrectedHourIndex(hour: number, lng: number): number {
74
+ const localMeanHours = hour + (lng - 120) * 4 / 60;
75
+ let hh = localMeanHours;
76
+ while (hh >= 24) hh -= 24;
77
+ while (hh < 0) hh += 24;
78
+ return Math.floor(((hh + 1) % 24) / 2);
79
+ }
80
+
81
+ // 校正日期(应用 dateOffset)
82
+ export function shiftedDate(y: number, m: number, d: number, offset: number): [number, number, number] {
83
+ const dt = new Date(y, m - 1, d);
84
+ dt.setDate(dt.getDate() + offset);
85
+ return [dt.getFullYear(), dt.getMonth() + 1, dt.getDate()];
86
+ }
@@ -0,0 +1,19 @@
1
+ // 小六壬:掌诀推算(大安起月 → 月上起日 → 日上起时)
2
+ import { XLR, XLR_ORDER } from '../data/xiaoliuren';
3
+ import { mod } from '../data/ganzhi';
4
+ import type { XiaoliurenResult } from '../types';
5
+
6
+ export function xiaoliurenCalc(
7
+ mode: 'time' | 'num',
8
+ m: number, d: number, h: number,
9
+ n1?: number, n2?: number, n3?: number,
10
+ ): XiaoliurenResult {
11
+ let a: number, b: number, c: number;
12
+ if (mode === 'time') { a = m; b = d; c = h; }
13
+ else { a = n1 ?? 1; b = n2 ?? 1; c = n3 ?? 1; }
14
+ const p1 = mod(a - 1, 6);
15
+ const p2 = mod(p1 + (b - 1), 6);
16
+ const p3 = mod(p2 + (c - 1), 6);
17
+ const name = XLR_ORDER[p3];
18
+ return { a, b, c, idx: p3, name, detail: XLR[name] };
19
+ }
@@ -0,0 +1,217 @@
1
+ // 紫微斗数:简式星盘(命宫/五行局/紫微定位/十四主星)+ 补齐层(辅星/身宫/四化/庙旺/格局)
2
+ import { NAYIN, NAYIN_JU, ganZhiIndex, mod } from '../data/ganzhi';
3
+ import { trueSolarTime } from './trueSolarTime';
4
+ import type { ZiweiInput, ZiweiResult } from '../types';
5
+ import { GAN, ZHI } from '../data/ganzhi';
6
+ import { PALACE_NAMES, ZW_SIHUA, ZW_BRIGHTNESS, LU_CUN, KUI_YUE, TIAN_MA, HUO_START, LING_START, ZW_GEJU } from '../data/ziwei';
7
+
8
+ // 地支位:0=寅 起(与既有约定一致)
9
+ const DIZHI = ['寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥', '子', '丑'];
10
+ const zhiIdx = (z: string) => DIZHI.indexOf(z);
11
+
12
+ export function ziweiCalc(input: ZiweiInput): ZiweiResult {
13
+ let { month, day, hour } = input;
14
+ const gz = input.ganzhi;
15
+ const gan = gz[0];
16
+ const zhi = gz[1];
17
+ // 时辰真太阳时校正(经度 + 均时差;有公历日期用完整版)
18
+ let correctedHour = hour;
19
+ if (input.location) {
20
+ const [th, tm] = input.time ? input.time.split(':').map(Number) : [(hour * 2) % 24, 30];
21
+ if (input.solarDate) {
22
+ const ts = trueSolarTime(input.solarDate[0], input.solarDate[1], input.solarDate[2], th, tm, input.location.lng);
23
+ correctedHour = ts.hourIndex;
24
+ } else {
25
+ // 无公历日期:仅经度修正(均时差 ≤15 分钟,边界时辰有风险——建议传 solarDate)
26
+ const localMean = th + (tm || 0) / 60 + (input.location.lng - 120) * 4 / 60;
27
+ correctedHour = Math.floor((((localMean + 24) % 24) + 1) % 24 / 2);
28
+ }
29
+ }
30
+ /* 命宫:寅宫起正月顺数至生月,再从生月宫起子时逆数至生时 */
31
+ const ming = mod((month - 1) - correctedHour, 12);
32
+ /* 身宫:寅起正月顺数生月,再从生月宫起子时顺数生时 */
33
+ const shen = mod((month - 1) + correctedHour, 12);
34
+ const nayin = NAYIN[Math.floor(ganZhiIndex(gz) / 2) % 30]; // 年柱纳音(展示用)
35
+ /* ── 补齐修正(2026-08-20,以 iztro《紫微斗数全书》安星为准)──
36
+ 五行局:由「命宫干支」纳音取数定局(非年干支纳音) */
37
+ // 命宫天干:五虎遁起寅月,顺数至命宫位(TIGER_RULE:甲己丙、乙庚戊、丙辛庚、丁壬壬、戊癸甲)
38
+ const TIGER: Record<string, string> = { 甲: '丙', 乙: '戊', 丙: '庚', 丁: '壬', 戊: '甲', 己: '丙', 庚: '戊', 辛: '庚', 壬: '壬', 癸: '甲' };
39
+ const mingGan = GAN[mod(GAN.indexOf(TIGER[gan] as any) + ming, 10)];
40
+ const mingGZ = mingGan + DIZHI[ming];
41
+ // 纳音五行取数:天干 甲乙1 丙丁2 戊己3 庚辛4 壬癸5;地支 子丑午未1 寅卯申酉2 辰巳戌亥3;和 >5 减 5;1木 2金 3水 4火 5土
42
+ const ganNum = Math.floor(GAN.indexOf(mingGan as any) / 2) + 1;
43
+ const zhiNum = Math.floor(mod(ming + 2, 6) / 2) + 1; // 寅起序 +2 → 子起序(子丑午未1 寅卯申酉2 辰巳戌亥3)
44
+ let fiveIdx = ganNum + zhiNum;
45
+ while (fiveIdx > 5) fiveIdx -= 5;
46
+ const FIVE_CLASS = ['木三局', '金四局', '水二局', '火六局', '土五局'];
47
+ const juName = FIVE_CLASS[fiveIdx - 1];
48
+ const JU_NUM: Record<string, number> = { 二: 2, 三: 3, 四: 4, 五: 5, 六: 6 };
49
+ const juNum = JU_NUM[juName[1]] || 4;
50
+ /* 紫微定位(起紫微星诀):(生日+offset) 可被局数整除,商定宫位,offset 奇偶定顺逆 */
51
+ let offset = -1, quotient = 0, remainder = -1;
52
+ do {
53
+ offset++;
54
+ const divisor = day + offset;
55
+ quotient = Math.floor(divisor / juNum);
56
+ remainder = divisor % juNum;
57
+ } while (remainder !== 0);
58
+ quotient %= 12;
59
+ let ziweiIdx = quotient - 1;
60
+ if (offset % 2 === 0) ziweiIdx += offset;
61
+ else ziweiIdx -= offset;
62
+ const zwPos = mod(ziweiIdx, 12);
63
+ /* 天府与紫微相对(寅起 12 之补) */
64
+ const tianfu = mod(12 - zwPos, 12);
65
+ /* 十四主星 */
66
+ const zwStars: Record<string, number> = {};
67
+ const ziweiList = ['紫微', '天机', '太阳', '武曲', '天同', '廉贞'];
68
+ const ziweiOff = [0, -1, -3, -4, -5, -8];
69
+ ziweiList.forEach((s, i) => { zwStars[s] = mod(zwPos + ziweiOff[i], 12); });
70
+ const tianfuList = ['天府', '太阴', '贪狼', '巨门', '天相', '天梁', '七杀', '破军'];
71
+ const tianfuOff = [0, 1, 2, 3, 4, 5, 6, 10]; // 七杀空三宫后破军(iztro 校准)
72
+ tianfuList.forEach((s, i) => { zwStars[s] = mod(tianfu + tianfuOff[i], 12); });
73
+ /* 十二宫布列:命宫起逆布 */
74
+ const palaces: Record<number, number> = {};
75
+ for (let i = 0; i < 12; i++) palaces[i] = mod(ming - i, 12);
76
+
77
+ /* ===== 补齐层 ===== */
78
+
79
+ /* 1. 辅星安星 */
80
+ const fuStars: Record<string, number> = {};
81
+ // 左辅右弼:左辅辰起顺数至生月,右弼戌起逆数至生月(iztro 校准)
82
+ fuStars['左辅'] = mod(zhiIdx('辰') + (month - 1), 12);
83
+ fuStars['右弼'] = mod(zhiIdx('戌') - (month - 1), 12);
84
+ // 文昌文曲:文昌戌起逆数至生时,文曲辰起顺数至生时(iztro 校准)
85
+ fuStars['文昌'] = mod(zhiIdx('戌') - correctedHour, 12);
86
+ fuStars['文曲'] = mod(zhiIdx('辰') + correctedHour, 12);
87
+ // 天魁天钺(年干)
88
+ const ky = KUI_YUE[gan] || [];
89
+ if (ky[0]) fuStars['天魁'] = zhiIdx(ky[0]);
90
+ if (ky[1]) fuStars['天钺'] = zhiIdx(ky[1]);
91
+ // 禄存 + 擎羊陀罗(年干)
92
+ const lu = LU_CUN[gan];
93
+ if (lu) {
94
+ const luIdx = zhiIdx(lu);
95
+ fuStars['禄存'] = luIdx;
96
+ fuStars['擎羊'] = mod(luIdx + 1, 12);
97
+ fuStars['陀罗'] = mod(luIdx - 1, 12);
98
+ }
99
+ // 火星铃星(年支三合起宫,均顺数至生时——iztro 校准,铃星原误为逆行)
100
+ const hStart = HUO_START[zhi];
101
+ if (hStart) fuStars['火星'] = mod(zhiIdx(hStart) + correctedHour, 12);
102
+ const lStart = LING_START[zhi];
103
+ if (lStart) fuStars['铃星'] = mod(zhiIdx(lStart) + correctedHour, 12);
104
+ // 地空地劫:亥起,地空逆数、地劫顺数至生时
105
+ fuStars['地空'] = mod(zhiIdx('亥') - correctedHour, 12);
106
+ fuStars['地劫'] = mod(zhiIdx('亥') + correctedHour, 12);
107
+ // 天马(年支三合)
108
+ const tm = TIAN_MA[zhi];
109
+ if (tm) fuStars['天马'] = zhiIdx(tm);
110
+
111
+
112
+ /* 1.5 小星(年支起法,2026-08-20 补全) */
113
+ const yearZhiIdx = zhiIdx(zhi);
114
+ fuStars['红鸾'] = mod(11 - yearZhiIdx, 12); // 卯起子年逆数(寅起序验证:子→卯)
115
+ fuStars['天喜'] = mod(fuStars['红鸾'] + 6, 12);
116
+ const XIANCHI: Record<string, string> = { 申: '酉', 子: '酉', 辰: '酉', 寅: '卯', 午: '卯', 戌: '卯', 巳: '午', 酉: '午', 丑: '午', 亥: '子', 卯: '子', 未: '子' };
117
+ fuStars['咸池'] = zhiIdx(XIANCHI[zhi] || '酉');
118
+ fuStars['天姚'] = mod(yearZhiIdx + 2, 12); // 寅起子年顺行
119
+ fuStars['天刑'] = mod(yearZhiIdx + 9, 12); // 酉起子年顺行
120
+ const GU_GUA: Record<string, [string, string]> = { 亥: ['寅', '戌'], 子: ['寅', '戌'], 丑: ['寅', '戌'], 寅: ['巳', '丑'], 卯: ['巳', '丑'], 辰: ['巳', '丑'], 巳: ['申', '辰'], 午: ['申', '辰'], 未: ['申', '辰'], 申: ['亥', '未'], 酉: ['亥', '未'], 戌: ['亥', '未'] };
121
+ fuStars['孤辰'] = zhiIdx((GU_GUA[zhi] || ['寅', '戌'])[0]);
122
+ fuStars['寡宿'] = zhiIdx((GU_GUA[zhi] || ['寅', '戌'])[1]);
123
+ fuStars['天哭'] = mod(yearZhiIdx + 6, 12); // 午起子年顺行
124
+ fuStars['天虚'] = mod(fuStars['天哭'] + 6, 12);
125
+
126
+ /* 2. 生年四化 */
127
+ const sh = ZW_SIHUA[gan] || { lu: '', quan: '', ke: '', ji: '' };
128
+ const sihua = { lu: sh.lu, quan: sh.quan, ke: sh.ke, ji: sh.ji };
129
+ const sihuaPos: ZiweiResult['sihuaPos'] = {};
130
+ (['lu', 'quan', 'ke', 'ji'] as const).forEach(k => {
131
+ const star = sh[k];
132
+ if (star && zwStars[star] !== undefined) sihuaPos[k] = zwStars[star];
133
+ });
134
+
135
+ /* 3. 主星亮度(庙旺落陷) */
136
+ const brightness: Record<string, string> = {};
137
+ Object.keys(zwStars).forEach(s => {
138
+ const pos = zwStars[s];
139
+ brightness[s] = (ZW_BRIGHTNESS[s] || [])[pos] || '平';
140
+ });
141
+
142
+ /* 4. 格局识别 */
143
+ const geju: ZiweiResult['geju'] = [];
144
+ const pushGeju = (key: string, why: string) => {
145
+ const g = ZW_GEJU[key];
146
+ if (g) geju.push({ key, name: g.name, ji: g.ji, desc: g.desc, why });
147
+ };
148
+ const starsAt = (pos: number) => Object.keys(zwStars).filter(s => zwStars[s] === pos);
149
+ const mingStars = starsAt(ming);
150
+ // 三合宫位(命宫 ±4)
151
+ const sanhe = [ming, mod(ming + 4, 12), mod(ming - 4, 12)];
152
+ const sanheStars = sanhe.flatMap(starsAt);
153
+ const duiStars = starsAt(mod(ming + 6, 12));
154
+ // 杀破狼:命宫或三合会齐七杀破军贪狼(≥2 即记)
155
+ const sbp = sanheStars.filter(s => ['七杀', '破军', '贪狼'].includes(s));
156
+ if (sbp.length >= 2) pushGeju('杀破狼', '命宫三合会 ' + [...new Set(sbp)].join('、'));
157
+ // 紫府同宫
158
+ if (zwStars['紫微'] === zwStars['天府']) pushGeju('紫府同宫', '紫微天府同守 ' + DIZHI[zwStars['紫微']] + ' 宫');
159
+ // 紫微朝垣:紫微在午(位置 4)
160
+ if (zwStars['紫微'] === 4) pushGeju('紫微朝垣', '紫微独坐午宫');
161
+ // 机月同梁:命宫及三合会天机/太阴/天同/天梁 ≥2
162
+ const jytl = sanheStars.filter(s => ['天机', '太阴', '天同', '天梁'].includes(s));
163
+ if (jytl.length >= 2) pushGeju('机月同梁', '三合会 ' + [...new Set(jytl)].join('、'));
164
+ // 日月同宫
165
+ if (zwStars['太阳'] === zwStars['太阴']) pushGeju('日月同宫', '太阳太阴同守 ' + DIZHI[zwStars['太阳']] + ' 宫');
166
+ // 日月反背:太阳太阴皆陷
167
+ const sunB = brightness['太阳'], moonB = brightness['太阴'];
168
+ if (sunB === '陷' && moonB === '陷') pushGeju('日月反背', '太阳(' + DIZHI[zwStars['太阳']] + sunB + ')太阴(' + DIZHI[zwStars['太阴']] + moonB + ')皆落陷');
169
+ // 月朗天门:太阴在亥庙
170
+ if (zwStars['太阴'] === 9 && brightness['太阴'] === '庙') pushGeju('月朗天门', '太阴独坐亥宫庙旺');
171
+ // 火贪格 / 铃贪格
172
+ const tanPos = zwStars['贪狼'];
173
+ const huoPos = fuStars['火星'], lingPos = fuStars['铃星'];
174
+ const huoNear = huoPos !== undefined && (huoPos === tanPos || sanhe.includes(huoPos));
175
+ const lingNear = lingPos !== undefined && (lingPos === tanPos || sanhe.includes(lingPos));
176
+ if (huoNear) pushGeju('火贪格', '火星与贪狼同宫/三合');
177
+ if (lingNear) pushGeju('铃贪格', '铃星与贪狼同宫/三合');
178
+ // 石中隐玉:巨门在子午
179
+ if (zwStars['巨门'] === 10 || zwStars['巨门'] === 4) pushGeju('石中隐玉', '巨门坐' + DIZHI[zwStars['巨门']] + '宫' + (gan === '辛' ? ',辛年生人尤佳' : ''));
180
+ // 君臣庆会:紫微 + 左辅右弼/昌曲拱照
181
+ const zwPalace = zwStars['紫微'];
182
+ const junchen = (fuStars['左辅'] === zwPalace || fuStars['右弼'] === zwPalace || fuStars['左辅'] === mod(zwPalace + 6, 12) || fuStars['右弼'] === mod(zwPalace + 6, 12) || fuStars['文昌'] === zwPalace || fuStars['文曲'] === zwPalace);
183
+ if (junchen) pushGeju('君臣庆会', '紫微得辅弼/昌曲拱照');
184
+ // 禄马交驰:禄存天马同宫
185
+ if (fuStars['禄存'] !== undefined && fuStars['禄存'] === fuStars['天马']) pushGeju('禄马交驰', '禄存天马同守 ' + DIZHI[fuStars['禄存']] + ' 宫');
186
+ // 科权禄拱命:命宫三合方有化禄/权/科
187
+ const s3 = (['lu', 'quan', 'ke'] as const).filter(k => sihuaPos[k] !== undefined && sanhe.includes(sihuaPos[k]!));
188
+ if (s3.length >= 2) pushGeju('科权禄拱命', '命宫三合方会 ' + s3.map(k => sihua[k]).join('、'));
189
+ // 廉贞七杀同宫
190
+ if (zwStars['廉贞'] === zwStars['七杀']) pushGeju('廉贞七杀', '廉贞七杀同守 ' + DIZHI[zwStars['廉贞']] + ' 宫');
191
+ // 命无正曜
192
+ if (mingStars.length === 0) pushGeju('命无正曜', '命宫无十四主星,借对宫 ' + [...new Set(duiStars)].join('、') + ' 为用');
193
+
194
+ /* ===== 大限与流年(原逻辑保留) ===== */
195
+ const JU_AGE: Record<string, number> = { 二: 2, 三: 3, 四: 4, 五: 5, 六: 6 };
196
+ const startAge = JU_AGE[juName[1]] || 4;
197
+ const ganYang = GAN.indexOf(gz[0] as any) % 2 === 0;
198
+ const gender = input.gender || '男';
199
+ const forward = (gender === '男' && ganYang) || (gender === '女' && !ganYang);
200
+ const dayun: { palaceIdx: number; start: number; end: number }[] = [];
201
+ for (let i = 0; i < 12; i++) {
202
+ const palaceIdx = mod(ming + (forward ? i : -i), 12);
203
+ dayun.push({ palaceIdx, start: startAge + i * 10, end: startAge + i * 10 + 9 });
204
+ }
205
+ const birthYear = input.birthYear || 1993;
206
+ const nowYear = new Date().getFullYear();
207
+ const nominalAge = Math.max(nowYear - birthYear + 1, 1);
208
+ let curDayunIdx = 0;
209
+ for (let i = 0; i < dayun.length; i++) {
210
+ if (nominalAge >= dayun[i].start && nominalAge <= dayun[i].end) { curDayunIdx = i; break; }
211
+ }
212
+ const liunianIdx = mod(ming + (nominalAge - 1), 12);
213
+ const liunianPalaceName = PALACE_NAMES[Object.keys(palaces).find(k => palaces[Number(k)] === liunianIdx) ? Number(Object.keys(palaces).find(k => palaces[Number(k)] === liunianIdx)) : 0];
214
+ const liunianStars = Object.keys(zwStars).filter(s => zwStars[s] === liunianIdx);
215
+
216
+ return { ming, shen, zwPos, zwStars, fuStars, palaces, juName, mingGZ, nayin, correctedHour, dayun, curDayunIdx, nominalAge, liunianIdx, liunianPalaceName, liunianStars, startAge, forward, sihua, sihuaPos, brightness, geju };
217
+ }
@@ -0,0 +1,161 @@
1
+ // 紫微命盘详解:按盘主星曜落宫生成个性化解读(现代语言)
2
+ import { ZW_STARS, ZW_STAR_MEAN, PALACE_NAMES } from '../data/ziwei';
3
+ import type { ZiweiResult } from '../types';
4
+
5
+ const DIZHI = ['寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥', '子', '丑'];
6
+
7
+ export interface ZiweiInsight {
8
+ title: string;
9
+ content: string;
10
+ }
11
+
12
+ function mod(a: number, n: number) {
13
+ return ((a % n) + n) % n;
14
+ }
15
+
16
+ // 吉性主星(得地多顺)
17
+ const GOOD_STARS = ['紫微', '天同', '天府', '天相', '天梁', '太阴', '太阳', '武曲'];
18
+
19
+ function starBrief(s: string): string {
20
+ return s + '为' + ZW_STARS[s] + ',' + ZW_STAR_MEAN[s].split(',')[0];
21
+ }
22
+
23
+ function palaceTheme(name: string): string {
24
+ const themes: Record<string, string> = {
25
+ 命宫: '一生的根基与底色', 兄弟: '手足缘分与同侪之助', 夫妻: '姻缘之象与伴侣之缘',
26
+ 子女: '子息缘分与晚辈之应', 财帛: '求财之途与理财之能', 疾厄: '体魄之况与健康之需',
27
+ 迁移: '外出之运与环境之变', 仆役: '部属朋友之助', 官禄: '事业之途与名位之求',
28
+ 田宅: '家宅之基与置业之机', 福德: '福分之厚与心境之安', 父母: '父母荫庇与长辈之缘',
29
+ };
30
+ return themes[name] || '';
31
+ }
32
+
33
+ function palaceNameOf(r: ZiweiResult, pos: number): string {
34
+ const idx = Object.keys(r.palaces).find(k => r.palaces[Number(k)] === pos);
35
+ return idx !== undefined ? PALACE_NAMES[Number(idx)] : '';
36
+ }
37
+
38
+ function starsOf(r: ZiweiResult, pos: number): string[] {
39
+ return Object.keys(r.zwStars).filter(s => r.zwStars[s] === pos);
40
+ }
41
+
42
+
43
+ export function interpretZiwei(r: ZiweiResult): ZiweiInsight[] {
44
+ const out: ZiweiInsight[] = [];
45
+
46
+ // 1. 命宫详解
47
+ const mingStars = starsOf(r, r.ming);
48
+ if (mingStars.length > 0) {
49
+ const t1 = '命宫 · ' + DIZHI[r.ming];
50
+ const c1 = '命宫安于' + DIZHI[r.ming] + '位,主星为' + mingStars.join('、') + '。'
51
+ + mingStars.map(starBrief).join(';') + '。此组合以' + mingStars[0] + '为纲,一生行运以此为底色。'
52
+ out.push({ title: t1, content: c1 });
53
+ } else {
54
+ const t1 = '命宫 · ' + DIZHI[r.ming];
55
+ const c1 = '命宫安于' + DIZHI[r.ming] + '位,主星未临,为「命宫无主星」之局。此时当借对宫(' + DIZHI[mod(r.ming + 6, 12)] + ')主星为用,或以紫微落宫为纲领观之。命主性情内敛,行事多受环境牵动,宜借外力补足。'
56
+ out.push({ title: t1, content: c1 });
57
+ }
58
+
59
+ // 2. 紫微落宫
60
+ const zwPalace = palaceNameOf(r, r.zwPos);
61
+ let c2 = '帝星紫微落于' + (zwPalace ? zwPalace : '未明之宫') + '。';
62
+ if (zwPalace === '命宫') c2 += '紫微坐命,格局自高,一生贵气与担当并存,唯须防孤高自许。';
63
+ else if (zwPalace === '财帛') c2 += '紫微入财帛,掌财有方,格局开阔,宜谋大局之财。';
64
+ else if (zwPalace === '官禄') c2 += '紫微入官禄,事业有威,宜居领导之位,名位可期。';
65
+ else if (zwPalace === '田宅') c2 += '紫微入田宅,家业殷实,置业有成,祖荫可承。';
66
+ else c2 += '紫微入' + (zwPalace || '未明之宫') + ',帝星所临之处即一生重心所在,此宫之事易得显达与助力。';
67
+ out.push({ title: '紫微落宫 · ' + (zwPalace || '') + '(' + DIZHI[r.zwPos] + ')', content: c2 });
68
+
69
+ // 3. 十二宫要览
70
+ const overview: { name: string; stars: string[]; pos: number }[] = [];
71
+ for (let i = 0; i < 12; i++) {
72
+ const pos = r.palaces[i];
73
+ const stars = starsOf(r, pos);
74
+ if (stars.length === 0) continue;
75
+ const pname = PALACE_NAMES[i];
76
+ const brief = stars.map(starBrief).join(';');
77
+ const line = pname + '宫(' + DIZHI[pos] + ')见' + stars.join('、') + '——' + brief + '。此宫主' + palaceTheme(pname) + '。'
78
+ + stars[0] + '入' + pname + ',' + (GOOD_STARS.includes(stars[0]) ? '为得地之象,多主顺遂。' : '须留意分寸,化其棱角则吉。');
79
+ overview.push({ name: pname, stars, pos });
80
+ }
81
+ out.push({
82
+ title: '十二宫要览',
83
+ content: overview.map(o => o.name + '宫(' + DIZHI[o.pos] + ')见' + o.stars.join('、') + '——' + o.stars.map(starBrief).join(';') + '。此宫主' + palaceTheme(o.name) + '。' + o.stars[0] + '入' + o.name + ',' + (GOOD_STARS.includes(o.stars[0]) ? '为得地之象,多主顺遂。' : '须留意分寸,化其棱角则吉。')).join('\n\n'),
84
+ });
85
+
86
+ // 4. 当前大限
87
+ if (r.dayun && r.curDayunIdx !== undefined) {
88
+ const d = r.dayun[r.curDayunIdx];
89
+ const pname = palaceNameOf(r, d.palaceIdx);
90
+ const stars = starsOf(r, d.palaceIdx);
91
+ let c4 = '命主现处第' + (r.curDayunIdx + 1) + '大限,行至' + (pname || '') + '宫(' + DIZHI[d.palaceIdx] + ')。';
92
+ if (stars.length) {
93
+ const s0 = stars[0];
94
+ c4 += '此宫主星' + stars.join('、') + ',' + s0 + '入' + (pname || '') + ','
95
+ + (ZW_STAR_MEAN[s0].includes('财') ? '此十年财机为要,宜务实经营。'
96
+ : ZW_STAR_MEAN[s0].includes('贵') || ZW_STAR_MEAN[s0].includes('官') ? '此十年名位为重,宜向上进取。'
97
+ : '此十年' + (pname || '') + '之事务为重心,静心经营自有所成。');
98
+ } else {
99
+ c4 += '此宫主星未临,十年之事多借他宫之力,宜稳中求进。';
100
+ }
101
+ out.push({ title: '当前大限 · ' + d.start + '–' + d.end + '岁', content: c4 });
102
+ }
103
+
104
+ // 5. 流年
105
+ if (r.liunianIdx !== undefined) {
106
+ const stars = starsOf(r, r.liunianIdx);
107
+ let c5 = '今年虚岁' + (r.nominalAge ?? '') + ',流年命宫行至' + (r.liunianPalaceName || '') + '宫(' + DIZHI[r.liunianIdx] + ')。';
108
+ if (stars.length) {
109
+ const s0 = stars[0];
110
+ c5 += '流年主星' + stars.join('、') + '照临,' + s0 + '入此宫,'
111
+ + (ZW_STAR_MEAN[s0].includes('财') ? '今年财机可期,唯须防急进。'
112
+ : ZW_STAR_MEAN[s0].includes('贵') ? '今年名望有进,宜谦和持守。'
113
+ : ZW_STAR_MEAN[s0].includes('福') ? '今年福泽安稳,家和事顺。'
114
+ : '今年此宫所主之事多有动静,顺势而为即可。');
115
+ } else {
116
+ c5 += '流年宫主星未临,今年整体平稳,可按大限之势行事。';
117
+ }
118
+ out.push({ title: '流年之示', content: c5 });
119
+ }
120
+
121
+ // 5.5 格局 + 四化 + 庙旺
122
+ if (r.geju && r.geju.length > 0) {
123
+ const g = r.geju.map(x => x.name + (x.ji === '吉' ? '(吉格)' : x.ji === '凶' ? '(凶格)' : '(平格)')).join('、');
124
+ const gDetail = r.geju.map(x => x.name + ':' + x.desc.split('。')[0] + '。').join('');
125
+ out.push({
126
+ title: '格局定式',
127
+ content: '本命会 ' + g + '。' + gDetail + '成格之局趋向鲜明:' + (r.geju.some(x => x.ji === '吉') ? '吉格者顺势而取,是命盘中最可依托的优势方向。' : '') + (r.geju.some(x => x.ji === '凶') ? '凶格者(或煞忌冲破)须早作防备,化被动为主动。' : '') + '平格者动静皆宜,重在行运把握。',
128
+ });
129
+ }
130
+ if (r.sihua) {
131
+ const s = r.sihua;
132
+ const luPal = r.sihuaPos?.lu !== undefined ? palaceNameOf(r, r.sihuaPos.lu) : '';
133
+ const jiPal = r.sihuaPos?.ji !== undefined ? palaceNameOf(r, r.sihuaPos.ji) : '';
134
+ out.push({
135
+ title: '生年四化',
136
+ content: s.lu + '化禄(入' + (luPal || '某宫') + ')主财缘与福荫所钟;' + s.quan + '化权主魄力与开创所在;' + s.ke + '化科主名声与文华所显;' + s.ji + '化忌(入' + (jiPal || '某宫') + ')是命中最须留意之处——忌星所落之宫,其事务易生阻滞、消耗与执着,宜以' + s.ji + '之性反求克制(' + ZW_STAR_MEAN[s.ji]?.split(',')[0] + ')。四化合观,禄权科为吉化,忌为暗伤,一生取舍可由此参详。',
137
+ });
138
+ }
139
+ const brightNotes: string[] = [];
140
+ if (r.brightness) {
141
+ Object.keys(r.zwStars).forEach(s => {
142
+ const b = r.brightness[s];
143
+ if (b === '庙' && (r.zwStars[s] === r.ming || r.zwStars[s] === r.shen)) brightNotes.push(s + '坐' + (r.zwStars[s] === r.ming ? '命' : '身') + '宫庙旺');
144
+ });
145
+ if (brightNotes.length) {
146
+ out.push({
147
+ title: '庙旺点睛',
148
+ content: brightNotes.join(';') + '——星曜得地,力量全发,是盘中最有力的支撑点;行运至此宫之年,多有实质性进展。',
149
+ });
150
+ }
151
+ }
152
+
153
+ // 6. 总述
154
+ const keyStar = mingStars[0] || '紫微';
155
+ const c6 = '盘主以' + keyStar + '为命宫主星(' + (mingStars.length ? ZW_STARS[keyStar] : '借对宫之力') + '),紫微落' + (zwPalace || '') + '宫、五行属' + r.juName + '。'
156
+ + '纵观全局,' + (mingStars.length ? keyStar + '主' + (ZW_STAR_MEAN[keyStar].includes('贵') ? '格局与担当' : '其性之显') + ',一生之成在于' + (zwPalace || '未明之宫') + '之经营。' : '命宫虚而借力,一生之成在于择善而从。')
157
+ + '大限顺逆已定,行运起伏观其宫位主星之庙陷与四化之忌。' + (r.geju && r.geju.length ? '格局' + r.geju.map(x => x.name).join('、') + '定其一生趋向,' : '') + '此盘为观微所推(含辅曜四化),供修身养性、怡情遣兴之用。';
158
+ out.push({ title: '命盘总述', content: c6 });
159
+
160
+ return out;
161
+ }