commit d2d1f358ffbca9d4d33fe401395c8c20a726f3a0 Author: wehub-resource-sync Date: Mon Jul 13 12:33:51 2026 +0800 chore: import upstream snapshot with attribution diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2189390 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# dependencies +node_modules/ +calculator/node_modules/ + +# build / runtime artifacts +*.log +.DS_Store +Thumbs.db + +# 排盘运行产物(用户生成的命盘 / 分析 / 报告,含个人生辰,勿入库) +*.tmp +chart.json +chart.txt +analysis.json +smoke.json +report.html +*-report.html +*-zonghe.html + +# 但仓库自带的合成示例必须入库(白名单放行) +!examples/sample-chart.json +!examples/sample-analysis.json +!examples/sample-report.html + +# editor +.vscode/ +.idea/ +*.swp diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dc7bd3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 dzcmemory-web + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..a94ef76 --- /dev/null +++ b/NOTICE @@ -0,0 +1,33 @@ +bazi-ziwei-skill +Copyright (c) 2026 dzcmemory-web + +================================================================================ +本项目包含以下第三方开源组件 +This product includes third-party open-source components. +================================================================================ + +1. Yiqi-BaZi-ZiWei (易奇八字紫微排盘系统 / BZZW) + ------------------------------------------------------------ + 上游 / Upstream: https://github.com/fdxuyq/Yiqi-BaZi-ZiWei + 位置 / Location: calculator/yiqi-core/ + 说明 / Description: + 紫微斗数与八字的核心排盘算法(四柱、十神、星运、纳音、大运、流年、 + 十二宫、生年四化、大限等)来自该上游项目,经少量改造后内置(vendored) + 于本仓库 calculator/yiqi-core/ 目录。 + The core charting algorithms (four pillars, ten gods, na-yin, luck cycles, + twelve palaces, birth-year transformations, major limits, etc.) are + derived from this upstream project and vendored, with minor edits, into + calculator/yiqi-core/. + 许可 / License: MIT (per upstream project README) + +2. lunar-typescript + ------------------------------------------------------------ + 使用方式 / Usage: npm dependency (calculator/package.json) + 说明 / Description: 农历 / 节气 / 干支换算底层库。 + 许可 / License: MIT + +================================================================================ +本项目自有部分(calculator/bazi-enrich/、prompts/、templates/、render.ts、 +dump-text.ts、run-chart.ts 等)以 MIT 协议发布,见 LICENSE 文件。 +The original parts of this project are released under the MIT License (see LICENSE). +================================================================================ diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..c5a28e4 --- /dev/null +++ b/README.en.md @@ -0,0 +1,184 @@ +
+ +# bazi-ziwei-skill + +**An AI Skill for BaZi (Four Pillars) + Zi Wei Dou Shu charting & cross-validation** + +Deterministic charting (not LLM guesswork) · 3 analysis modes · one-click ink-style HTML chart poster + +[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE) +[![SKILL.md](https://img.shields.io/badge/SKILL.md-compatible-c1432f.svg)](#-installation) + +[简体中文](./README.md) | English + +
+ +Cross-validation poster sample + +Cross-validation poster sample (synthetic subject, for display only) + +
+ +--- + +## What is this + +A Chinese metaphysics analysis Skill following the [SKILL.md open standard](https://code.claude.com/docs/en/skills). It plugs into any compatible AI agent — Claude Code / Claude Desktop / Codex / Cursor / Hermes / OpenClaw, etc. + +It does three things an LLM alone does poorly: + +1. **Accurate charting**: the Four Pillars (BaZi), the twelve palaces of Zi Wei Dou Shu, and the luck/year cycles are all computed by a bundled algorithm library — **the LLM never charts on its own**. Pure-LLM charting routinely gets the day pillar, day master, or chart structure wrong, and one wrong step corrupts everything downstream. +2. **Structural enrichment**: on top of the raw chart, an extra layer computes "chart structure / strength / climatic adjustment / clashes-combinations-harms / capped-pillars", feeding the LLM grounded inputs. +3. **Cross-validation**: it reconciles the conclusions of two independent systems — BaZi and Zi Wei — checking whether their main axes agree, whether life windows line up, and which one to trust on conflict. This is the core value that "any LLM + any charting tool" cannot replicate. + +--- + +## ✨ Features + +- 🎯 **Accurate algorithm**: charting core derived from the open-source project Yiqi (MIT), verified against its UI; the enrichment layer passes multi-dimensional regression on 7 test cases +- 🧭 **3 analysis modes**: BaZi only / Zi Wei only / BaZi + Zi Wei cross-validation +- 📜 **2 output formats**: in-depth Markdown long-form + 🎴 single-file HTML poster (cross-validation only) +- 🎴 **Ink-style chart poster**: modern minimal × Chinese ink, with the Zi Wei 12-palace chart + BaZi four-pillar chart + six-dimension cross-check, ready to screenshot and share +- 🔌 **Cross-agent**: one SKILL.md, works across major agents +- 🔒 **Privacy-first**: all charting runs locally, no network needed; runtime artifacts are gitignored by default + +--- + +## 🚀 Installation + +### 1. Clone + +```bash +git clone https://github.com/dzcmemory-web/bazi-ziwei-skill.git +``` + +### 2. Install algorithm dependencies + +```bash +cd bazi-ziwei-skill/calculator +npm install +``` +> Requires Node.js >= 18. Only one runtime dependency: `lunar-typescript` (MIT). + +### 3. Register with your agent + +Drop the whole `bazi-ziwei-skill/` folder into your agent's skills directory: + +| Agent | skills directory | +|---|---| +| Claude Code / Claude Desktop | `~/.claude/skills/bazi-ziwei/` | +| Codex | `~/.codex/skills/bazi-ziwei/` or reference via project AGENTS.md | +| Cursor | reference from project `.cursor/` rules | +| Hermes Agent | `~/.hermes/skills/bazi-ziwei/` | +| OpenClaw | its skills directory / local ClawHub install | + +The agent reads `SKILL.md` automatically and invokes it on demand. + +--- + +## 📖 Usage + +Once installed, just tell the agent a birth time: + +``` +I'm a male born at noon (12:00) on Jan 1, 2000. Read my chart. +``` + +The agent will: +1. Ask which analysis you want (BaZi / Zi Wei / cross-validation) +2. For cross-validation, ask long-form vs. HTML poster +3. Call the algorithm layer → load the matching prompt → output analysis or render the poster + +See [`SKILL.md`](./SKILL.md) for the full flow and [`TEST-GUIDE.md`](./TEST-GUIDE.md) for testing. + +### Charting directly from the CLI (no agent) + +```bash +cd calculator +# chart -> JSON +node dist/run-chart.js --year=2000 --month=1 --day=1 --hour=12 --minute=0 --gender=male > chart.json +# JSON -> readable text chart +node dist/dump-text.js --input=chart.json --output=chart.txt +# JSON + analysis JSON + template -> HTML poster +node dist/render.js --chart=chart.json --analysis=analysis.json \ + --template=../templates/report-zonghe-poster.html --output=report.html --currentYear=2026 +``` + +A synthetic sample is bundled (male, 2000-01-01, not a real person): +- `examples/sample-chart.json` — algorithm chart output +- `examples/sample-chart.txt` — text chart +- `examples/sample-analysis.json` — cross-validation analysis (sample) +- `examples/sample-report.html` — **finished poster; open in a browser to preview** + +--- + +## 📁 Layout + +``` +bazi-ziwei-skill/ +├── SKILL.md Skill entry point (the agent reads this) +├── TEST-GUIDE.md Testing guide (5 user paths) +├── calculator/ +│ ├── run-chart.ts charting entry: birth time -> JSON +│ ├── dump-text.ts JSON -> text chart +│ ├── render.ts JSON + analysis + template -> single-file HTML +│ ├── yiqi-core/ charting core (vendored from Yiqi, MIT) +│ └── bazi-enrich/ structure/strength/climate/clash enrichment +├── prompts/ +│ ├── bazi-prompt.md BaZi only (long-form) +│ ├── ziwei-prompt.md Zi Wei only (long-form) +│ ├── zonghe-yinzheng-prompt.md cross-validation (long-form) +│ └── zonghe-poster.md cross-validation (poster JSON output) +├── templates/ +│ └── report-zonghe-poster.html cross-validation poster template (placeholders) +└── examples/ + ├── sample-chart.json synthetic sample chart + └── sample-chart.txt synthetic text chart +``` + +--- + +## 🏗️ How it works + +``` +birth time ──> run-chart.ts ──> chart.json ──> dump-text.ts ──> chart.txt + (deterministic charting) (LLM-friendly text) + │ + ┌───────────────────────────────────┤ + ▼ ▼ + long-form prompt poster prompt + (Markdown prose) (strict JSON output) + │ + render.ts + template + ▼ + single-file HTML poster +``` + +**Key design**: the LLM only does *analysis* — never charting or HTML. Charting is handled by deterministic algorithms, the HTML visual by a fixed template, and the LLM's structured output fills template slots. Three concerns, cleanly separated. + +--- + +## 🙏 Acknowledgements + +- Charting core derived from the [Yiqi BaZi/Zi Wei system](https://github.com/fdxuyq/Yiqi-BaZi-ZiWei) (MIT); see [`NOTICE`](./NOTICE) +- Lunar conversion via [lunar-typescript](https://github.com/6tail/lunar-typescript) (MIT) + +--- + +## 📬 Contact + +Feedback, collaboration, or questions: **dzcmemory@gmail.com** + +If this project helps you, a ⭐ Star is appreciated. + +--- + +## ⚠️ Disclaimer + +This project is based on traditional BaZi and Zi Wei Dou Shu theory and is **for cultural research and entertainment only**. It does not constitute medical, financial, marital, legal, or any other decision-making basis. Your life is shaped by your own choices and circumstances. + +--- + +## 📄 License + +[MIT](./LICENSE) © 2026 dzcmemory-web diff --git a/README.md b/README.md new file mode 100644 index 0000000..11cd3d1 --- /dev/null +++ b/README.md @@ -0,0 +1,184 @@ +
+ +# bazi-ziwei-skill + +**AI 八字 + 紫微斗数排盘与综合印证 Skill** + +精准排盘(不靠 LLM 猜)· 三种分析模式 · 一键生成水墨风 HTML 命盘海报 + +[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE) +[![SKILL.md](https://img.shields.io/badge/SKILL.md-compatible-c1432f.svg)](#-安装) + +简体中文 | [English](./README.en.md) + +
+ +综合印证海报示例 + +综合印证海报示例(合成命主,仅供展示) + +
+ +--- + +## 这是什么 + +一个遵循 [SKILL.md 开放标准](https://code.claude.com/docs/en/skills) 的命理分析 Skill,可装入 Claude Code / Claude Desktop / Codex / Cursor / Hermes / OpenClaw 等支持该标准的 AI Agent。 + +它做三件大模型单独做不好的事: + +1. **精准排盘**:八字四柱、紫微十二宫、大运流年由内置算法库计算,**不让 LLM 自己排**——纯 LLM 排盘常把日柱、日主、格局算错,一步错则全盘失真。 +2. **格局补层**:在排盘之上补一层"格局 / 旺衰 / 调候 / 刑冲合害 / 盖头截脚"算法,喂给 LLM 做有依据的分析。 +3. **综合印证**:把八字与紫微两套独立体系的结论做交叉对账——主轴是否一致、人生窗口是否对齐、冲突时听谁。这是本 Skill 区别于"任意 LLM + 任意排盘工具"的核心增量。 + +--- + +## ✨ 特性 + +- 🎯 **算法精准**:排盘核心源自开源项目 Yiqi(MIT),经实测对齐;补层算法经 7 组案例多维度回归验证 +- 🧭 **三种分析模式**:八字独立 / 紫微独立 / 八字+紫微综合印证 +- 📜 **两种呈现形态**:Markdown 长文深度版 + 🎴 单文件 HTML 海报版(综合印证专属) +- 🎴 **水墨风命盘海报**:现代极简 × 中式水墨,含紫微 12 宫盘 + 八字四柱盘 + 六维交叉对账,可截图分享 +- 🔌 **跨 Agent**:一份 SKILL.md,多个主流 Agent 通用 +- 🔒 **隐私优先**:所有排盘在本地完成,无需联网;运行产物默认 gitignore + +--- + +## 🚀 安装 + +### 1. 克隆仓库 + +```bash +git clone https://github.com/dzcmemory-web/bazi-ziwei-skill.git +``` + +### 2. 安装算法层依赖 + +```bash +cd bazi-ziwei-skill/calculator +npm install +``` +> 需要 Node.js >= 18。算法层依赖仅一个:`lunar-typescript`(MIT)。 + +### 3. 注册到你的 Agent + +把整个 `bazi-ziwei-skill/` 文件夹放进 Agent 的 skills 目录: + +| Agent | skills 目录 | +|---|---| +| Claude Code / Claude Desktop | `~/.claude/skills/bazi-ziwei/` | +| Codex | `~/.codex/skills/bazi-ziwei/` 或项目内 AGENTS.md 引用 | +| Cursor | 项目 `.cursor/` 规则引用 | +| Hermes Agent | `~/.hermes/skills/bazi-ziwei/` | +| OpenClaw | 其 skills 目录 / ClawHub 本地安装 | + +Agent 会自动读取 `SKILL.md` 并按需调用。 + +--- + +## 📖 使用 + +装好后,直接对 Agent 说出生时间即可: + +``` +我是 2000 年 1 月 1 日中午 12 点出生的男生,帮我看下命盘。 +``` + +Agent 会: +1. 问你要哪种分析(八字 / 紫微 / 综合印证) +2. 综合印证时再问要长文还是 HTML 海报 +3. 调用算法层排盘 → 加载对应提示词 → 输出分析或渲染海报 + +详细流程见 [`SKILL.md`](./SKILL.md),测试方法见 [`TEST-GUIDE.md`](./TEST-GUIDE.md)。 + +### 命令行直接排盘(不经 Agent) + +```bash +cd calculator +# 排盘 → JSON +node dist/run-chart.js --year=2000 --month=1 --day=1 --hour=12 --minute=0 --gender=male > chart.json +# JSON → 可读文本盘 +node dist/dump-text.js --input=chart.json --output=chart.txt +# JSON + 分析 JSON + 模板 → HTML 海报 +node dist/render.js --chart=chart.json --analysis=analysis.json \ + --template=../templates/report-zonghe-poster.html --output=report.html --currentYear=2026 +``` + +仓库自带一份合成示例(2000-01-01 男,非真人): +- `examples/sample-chart.json` — 算法层排盘输出 +- `examples/sample-chart.txt` — 文墨天机风文本盘 +- `examples/sample-analysis.json` — 综合印证分析(示例) +- `examples/sample-report.html` — **综合印证海报成品,下载后用浏览器打开即可预览效果** + +--- + +## 📁 目录结构 + +``` +bazi-ziwei-skill/ +├── SKILL.md Skill 主入口(Agent 读这个) +├── TEST-GUIDE.md 测试指南(5 条用户路径) +├── calculator/ +│ ├── run-chart.ts 排盘入口:生辰 → JSON +│ ├── dump-text.ts JSON → 文墨天机风文本盘 +│ ├── render.ts JSON + 分析 + 模板 → 单文件 HTML +│ ├── yiqi-core/ 排盘核心算法(vendored from Yiqi, MIT) +│ └── bazi-enrich/ 格局/旺衰/调候/刑冲合害 补层算法 +├── prompts/ +│ ├── bazi-prompt.md 八字独立分析(长文) +│ ├── ziwei-prompt.md 紫微独立分析(长文) +│ ├── zonghe-yinzheng-prompt.md 综合印证(长文) +│ └── zonghe-poster.md 综合印证(海报 JSON 输出) +├── templates/ +│ └── report-zonghe-poster.html 综合印证海报模板(占位符) +└── examples/ + ├── sample-chart.json 合成示例排盘 + └── sample-chart.txt 合成示例文本盘 +``` + +--- + +## 🏗️ 工作原理 + +``` +生辰 ──> run-chart.ts ──> chart.json ──> dump-text.ts ──> chart.txt + (算法层精准排盘) (LLM 友好文本) + │ + ┌───────────────────────────┤ + ▼ ▼ + 长文提示词 海报提示词 + (Markdown 散文) (严格 JSON 输出) + │ + render.ts + 模板 + ▼ + 单文件 HTML 海报 +``` + +**关键设计**:LLM 只负责"分析",不负责"排盘"和"画 HTML"。排盘交给确定性算法,HTML 视觉交给固定模板,LLM 产出的结构化内容填进模板槽位——三者各司其职,互不污染。 + +--- + +## 🙏 致谢 + +- 排盘核心算法源自 [Yiqi 八字紫微排盘系统](https://github.com/fdxuyq/Yiqi-BaZi-ZiWei)(MIT 协议),详见 [`NOTICE`](./NOTICE) +- 农历换算依赖 [lunar-typescript](https://github.com/6tail/lunar-typescript)(MIT) + +--- + +## 📬 联系 / Contact + +问题反馈、合作或交流,欢迎邮件:**dzcmemory@gmail.com** + +如果这个项目对你有帮助,欢迎点一个 ⭐ Star 支持。 + +--- + +## ⚠️ 免责声明 + +本项目基于传统八字与紫微斗数理论框架,**仅供文化研究与娱乐参考**,不构成医疗、投资、婚姻、法律等任何决策依据。命运由个人选择与客观环境共同塑造。 + +--- + +## 📄 License + +[MIT](./LICENSE) © 2026 dzcmemory-web diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..baa1489 --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,7 @@ +# WeHub 来源说明 + +- 原始项目:`dzcmemory-web/bazi-ziwei-skill` +- 原始仓库:https://github.com/dzcmemory-web/bazi-ziwei-skill +- 导入方式:上游默认分支的最新快照 +- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准 +- 本文件仅用于记录来源,不代表 WeHub 是原项目作者 diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..8398c7b --- /dev/null +++ b/SKILL.md @@ -0,0 +1,209 @@ +--- +name: bazi-ziwei +description: 八字 + 紫微斗数 AI 排盘与综合分析。当用户提供生辰(阳历/农历日期、时辰、性别)询问八字、紫微、命理、命盘、流年大运相关问题时使用。基于 Yiqi 算法库 + enrichBazi 补全层精准排盘(杜绝 LLM 自行排盘出错),支持按需独立分析八字 / 独立分析紫微 / 两盘交叉印证。 +--- + +# 八字 + 紫微斗数综合分析 Skill + +## 何时触发 + +用户场景: +- 提供出生时间("我是 2000-01-01 中午 12:00 男")并希望分析 +- 询问"帮我看八字 / 看紫微 / 算命 / 看命盘 / 看流年大运" +- 提供命盘文本(文墨天机、紫微斗数排盘软件导出格式)希望深度解读 +- 询问特定大运/流年的吉凶 + +**不触发**:单纯的星座 / 塔罗 / 周公解梦 / 风水 / 姓名学。 + +## 必需输入 + +``` +公历或农历日期 (YYYY-MM-DD) +出生时刻 (HH:MM, 24 小时制) +性别 (男/女) +``` + +若用户没给时辰,**必须主动询问**,不要默认子时。时辰对四柱日柱和紫微命宫影响极大。 + +> 不需要出生地。排盘直接使用钟表时间,不做真太阳时经度校正。 + +--- + +## 执行流程 + +### Step 0 — 决策门(开场必做) ⭐ + +**问题 1:要看哪种命理?** +> "我可以做三种分析: +> 1. **八字独立分析**(事业 / 财运 / 婚恋 / 子女 / 六亲 / 健康,按八字格局推演 — 长文输出) +> 2. **紫微独立分析**(十二宫 + 大限 + 生年四化 + 流年 — 长文输出) +> 3. **八字 + 紫微综合印证**(两盘交叉对账 — 提供长文 / 可视化海报 / 两种都要)" + +**如果用户选 3,再追问问题 2:呈现形态** +> "综合印证可以这样输出: +> A. **📜 长文深度版**(Markdown 散文,沉浸阅读) +> B. **🎴 结构化海报版**(单文件 HTML,可截图分享) +> C. **两种都要**" + +**根据选择加载相应提示词和模板**: + +| 用户选 | 加载提示词 | 模板 | 输出 | +|---|---|---|---| +| 1 | `prompts/bazi-prompt.md` | — | Markdown 对话回复 | +| 2 | `prompts/ziwei-prompt.md` | — | Markdown 对话回复 | +| 3 + A | `prompts/zonghe-yinzheng-prompt.md` | — | Markdown 对话回复 | +| 3 + B | `prompts/zonghe-poster.md` | `templates/report-zonghe-poster.html` | `-zonghe.html` | +| 3 + C | 上述两份都跑 | 同上 | Markdown + HTML 两份 | + +> **海报模板仅综合印证一种**。八字独立 / 紫微独立只有长文输出(用户深度阅读用)。这是经过设计的——海报是综合印证独占的杀手锏,承担社交分享 + 用户惊艳的角色。 + +--- + +### Step 1 — 排盘(算法层,产出 JSON) + +**绝对不要让 AI 自己排八字或紫微**。必须调用算法层脚本: + +```bash +cd calculator +node dist/run-chart.js --year=YYYY --month=MM --day=DD --hour=HH --minute=MM --gender=male > chart.json +``` + +**注意**:`run-chart.ts` 的 stdout 是纯 JSON,stderr 是 debug 信息。**重定向时只取 stdout**(`> chart.json`),不要 `2>&1`。 + +脚本输出 JSON: +- `bazi`:四柱 / 十神 / 星运 / 自坐 / 纳音 / 藏干 / 大运(含 startAge/endAge/startYear/endYear) +- `bazi.enrichment`:格局 / 旺衰 / 调候 / 五行旺相 / 五行统计 / 天干关系 / 地支关系 / 整柱判定 +- `ziwei`:十二宫 / 生年四化 / 大限 / 阴阳 / 五行局 / 命主身主 + +> **关键约束**:纯 LLM 排盘会错排日柱 → 日主 → 格局 → 用神,全链失真。Case B 测试证明 DeepSeek/Gemini 自行排盘均出错。算法层不可绕过。 + +--- + +### Step 2 — 文本盘转换(算法层,产出可读文本) + +LLM 读 JSON 不如读结构化文本。把 Step 1 的 JSON 转成文墨天机风格的树状文本: + +```bash +node dist/dump-text.js --input=chart.json --output=chart.txt +``` + +文本盘包含: +- 紫微部分:基本信息 + 生年四化 + 十二宫(含主星 / 辅星 / 大限 / 流年) +- 八字部分:四柱(含藏干十神 / 星运 / 自坐 / 纳音)+ 大运 + 算法补层(格局 / 旺衰 / 调候 / 关系 / 整柱) + +将 `chart.txt` 内容连同对应提示词一起送给 LLM 做分析。 + +--- + +### Step 3 — 分析(按 Step 0 用户选择执行对应分支) + +#### Step 3 — 长文版(用户选 1 / 2 / 3+A / 3+C) +读取对应长文提示词(`bazi-prompt.md` / `ziwei-prompt.md` / `zonghe-yinzheng-prompt.md`),喂入 `chart.txt`,输出 Markdown 长文。 + +> 综合印证(3+A)的前置条件:先跑八字 + 紫微独立分析拿到中间报告,再喂给 `zonghe-yinzheng-prompt.md`。 +> 如输出被截断,分段输出。 + +#### Step 3 — 海报版(仅用户选 3+B 或 3+C) +1. 读取 `prompts/zonghe-poster.md`,喂入 `chart.txt` +2. LLM **必须输出严格 JSON**(不是 Markdown)——提示词末尾会要求"直接以 `{` 开头",照办即可 +3. 把 LLM 输出的 JSON 保存为 `analysis.json` +4. 调用渲染脚本: + ```bash + cd calculator + node dist/render.js \ + --chart=chart.json \ + --analysis=analysis.json \ + --template=../templates/report-zonghe-poster.html \ + --output=-.html \ + --currentYear= + ``` +5. 把生成的 HTML 文件路径告诉用户,让其用浏览器打开 + +> **重要**:海报版的视觉由 HTML 模板决定,**LLM 只产数据不产 HTML**。如果 LLM 输出含 markdown 包装(如 \`\`\`json … \`\`\`),渲染前需剥掉。 + +--- + +## 安装后行为(重要) + +**装好 Skill 后不要主动跑任何验证 / 自检命令。** 不要试 Smoke Test、不要排示例盘、不要分析示例命主。装好就是装好,等用户来给生辰再开始工作。 + +> 自检命令在 `TEST-GUIDE.md` 中由人工按需运行,不在 Agent 的职责范围内。Agent 主动跑会浪费 token + 触发上下文压缩。 + +依赖检查(仅在用户首次提供生辰、Agent 准备跑 run-chart.ts 报错时): +```bash +cd /calculator +ls node_modules >/dev/null 2>&1 || npm install +``` +也就是说,依赖问题**报错时再修**,不要装好就主动检查。 + +--- + +## 关键约束 + +1. **装好不自检**:见上节"安装后行为"。不要主动跑示例排盘 / 自检 / smoke test +2. **决策门必做**:永远先问用户要哪种分析,避免无意义的 token 消耗 + 输出截断 +3. **排盘必须走算法层**:不要徒手算四柱、紫微宫位、大限。错一步全盘垮 +4. **不引入命盘外变量**:风水、姓名、阳宅、紫白飞星不在本 Skill 范围 +5. **冲突要说出来**:八字与紫微出现矛盾信号时按综合印证提示词的 4 条规则判定,不和稀泥 +6. **置信度自评**:边界情况(旺衰临界、格局模糊、两盘对立)必须标"置信度:低" +7. **不替用户做决策**:投资、择偶、医疗、堕胎等决策类问题,给信号不给指令 +8. **敏感问题拒答**:下蛊、断人财路、害人命运等违禁内容直接拒绝 +9. **免责声明**:分析末尾必带"仅供文化研究与娱乐参考" + +--- + +## 文件清单 + +``` + +├── SKILL.md ← 本文件 +├── calculator/ +│ ├── run-chart.ts ← 入口:生辰 → JSON(stdout 纯 JSON / stderr debug) +│ ├── dump-text.ts ← JSON → 文墨天机风文本 +│ ├── render.ts ← 渲染脚本:chart.json + analysis.json + 模板 → HTML +│ ├── package.json ← 算法层依赖声明 +│ ├── yiqi-core/ ← Yiqi 算法(已 vendored 入库,无外部依赖) +│ └── bazi-enrich/ ← enrichBazi 补层(格局/旺衰/调候/关系/整柱) +├── prompts/ +│ ├── bazi-prompt.md ← 八字独立分析(长文) +│ ├── ziwei-prompt.md ← 紫微独立分析(长文) +│ ├── zonghe-yinzheng-prompt.md ← ⭐ 综合印证(长文) +│ └── zonghe-poster.md ← ⭐ 综合印证(海报版 JSON 输出) +├── templates/ +│ └── report-zonghe-poster.html ← 综合印证海报模板(占位符) +└── fixtures/ ← 7 个验证案例(Case A-G) +``` + +**注**:HTML 渲染目前**仅支持综合印证模式**。如用户选了八字独立 / 紫微独立又问"能不能出 HTML 报告",告知"目前 HTML 海报仅对综合印证开放,建议选综合印证(含八字+紫微)以拿到海报"。 + +--- + +## 工作示例 + +**用户**:我是 2000 年 1 月 1 日 12:00 出生的男生,帮我看下命盘。 + +**Skill 应该走**: +1. 信息确认(日期/时辰/性别 ✅) +2. **决策门**:"想要八字分析 / 紫微分析 / 综合印证?" +3. 用户回 "八字": + - Step 1:跑 `run-chart.ts` 产出 `chart.json` + - Step 2:跑 `dump-text.ts` 产出 `chart.txt` + - Step 3a:加载 `prompts/bazi-prompt.md` + 喂入 `chart.txt` → 输出八字分析 +4. 提醒"若要紫微 / 综合印证可随时追问" + +--- + +## 失败模式与处理 + +| 现象 | 原因 | 处理 | +|---|---|---| +| 排盘脚本报错 | 日期超 1900-2100 / 时辰格式错 | 询问用户校正 | +| AI 想"凭记忆排盘" | 偷懒走捷径 | **拒绝**。算法层是不可绕过的硬约束 | +| 输出被截断 | 三段一锅出超 token 上限 | 回到决策门,拆分输出 | +| 算法层和用户其他软件结果不一致 | 命名流派差异(建禄格 vs 比肩格) | 按算法层 `notes` 解释,不偷换说法 | +| Windows + 中文路径 + PowerShell 编码错乱 | 平台特性 | 在 cmd / git bash / WSL 下运行,避免 PowerShell | + +--- + +## 免责声明(每次输出末必带) +> 本分析基于传统八字与紫微斗数理论框架,仅供文化研究与娱乐参考,不构成医疗、投资、婚姻、法律等任何决策依据。命运由个人选择与客观环境共同塑造。 diff --git a/TEST-GUIDE.md b/TEST-GUIDE.md new file mode 100644 index 0000000..4127057 --- /dev/null +++ b/TEST-GUIDE.md @@ -0,0 +1,179 @@ +# 测试指南 + +> 在 Claude Code / Claude Desktop / Codex / Cursor / Hermes / OpenClaw 等支持 SKILL.md 的 Agent 中测试本 Skill。 +> 本指南使用合成示例生辰 **2000-01-01 12:00 男**(非真人),其完整排盘见 `examples/sample-chart.json` 与 `examples/sample-chart.txt`。 + +--- + +## 0. 装好之后先跑 Smoke Test(人工,可选) + +确认算法层和依赖正常。在仓库根目录: + +```bash +cd calculator +npm install # 首次使用需要 +node dist/run-chart.js --year=2000 --month=1 --day=1 --hour=12 --minute=0 --gender=male > smoke.json +``` + +**预期**: +- 命令秒级返回 +- `smoke.json` 首字符是 `{`(纯 JSON) +- stderr 会输出 `🔍 八字计算结果 - ...` 两行 debug,这是正常的 + +检查关键字段: +```bash +node -e "const j=require('./smoke.json'); console.log('日柱:', j.bazi.siZhu.day.gan+j.bazi.siZhu.day.zhi, '/ 格局:', j.bazi.enrichment.格局.primary)" +``` +**预期输出**:`日柱: 戊午 / 格局: 正财格` + +跑通即装配正确。`smoke.json` 内容应与 `examples/sample-chart.json` 完全一致。 + +> 注意:Agent 装好 Skill 后**不会主动**跑此自检(SKILL.md 明确要求装好不自检,避免浪费 token)。Smoke Test 由你手动执行。 + +--- + +## 5 种用户路径速查 + +| 编号 | 用户选择 | 输出 | +|---|---|---| +| 路径 1 | 八字独立分析 | Markdown 长文 | +| 路径 2 | 紫微独立分析 | Markdown 长文 | +| 路径 3 | 综合印证 + 长文(A) | Markdown 长文 | +| 路径 4 | 综合印证 + 海报(B)⭐ | 单文件 HTML 海报 | +| 路径 5 | 综合印证 + 两种(C) | Markdown + HTML | + +--- + +## 路径 1:八字独立分析(长文) + +**测试输入**(发给 Agent): +``` +我是 2000 年 1 月 1 日中午 12 点出生的男生,帮我看下八字。 +``` + +**期望 Agent 做的事**: +1. 触发 Skill,进入决策门,问你要哪种分析(你回"八字") +2. 跑 `run-chart.ts` → chart.json +3. 跑 `dump-text.ts` → chart.txt +4. 加载 `prompts/bazi-prompt.md`,喂入 chart.txt,输出 Markdown + +**验证点**: +- ✅ 标明 "日主:戊土" +- ✅ 提到 "正财格" +- ✅ 提到 "调候用神:丙、甲" +- ✅ 提到 "子午相冲" 或地支关系 +- ✅ 末尾有 "仅供文化研究与娱乐参考" 免责声明 + +**❌ 失败信号**: +- 出现非 "戊" 的日主 → AI 自己排盘了,没用算法层 +- 完全没有 "戊 / 正财 / 调候" 字样 → 没读到 chart.txt + +--- + +## 路径 2:紫微独立分析(长文) + +**测试输入**: +``` +我是 2000 年 1 月 1 日中午 12 点出生的男生,帮我看下紫微。 +``` + +**验证点**: +- ✅ 提到 "命宫在午" 或 "命宫 紫微" +- ✅ 提到生年四化(武曲化禄 / 贪狼化权 / 天梁化科 / 文曲化忌)至少 1-2 个 +- ✅ 提到 "土五局" +- ✅ 末尾免责声明 + +--- + +## 路径 3:综合印证 + 长文(A) + +**测试输入**: +``` +我是 2000 年 1 月 1 日中午 12 点出生的男生,请做八字+紫微的综合分析(长文)。 +``` + +**期望**:先跑八字独立 + 紫微独立拿中间报告,再喂 `zonghe-yinzheng-prompt.md`。 + +**验证点**: +- ✅ 有"主轴印证"段,明说"同向 / 互补 / 矛盾" +- ✅ 至少 1-2 处八字与紫微互相印证或冲突的具体点 +- ✅ 给出关键时间窗口 +- ✅ 末尾免责声明 + +**注意**:综合印证输出最长,可能被 Agent 自动截断。如截断,让 Agent 分段输出。 + +--- + +## 路径 4:综合印证 + 海报 HTML ⭐ 杀手锏 + +**测试输入**: +``` +我是 2000 年 1 月 1 日中午 12 点出生的男生,请生成综合印证的 HTML 海报。 +``` + +**期望 Agent 做的事**: +1. 跑算法层(run-chart + dump-text) +2. 加载 `prompts/zonghe-poster.md`,喂入 chart.txt +3. LLM 输出**严格 JSON**(不是 markdown 散文) +4. JSON 存为 `analysis.json` +5. 跑渲染脚本: + ```bash + cd calculator + node dist/render.js \ + --chart=chart.json \ + --analysis=analysis.json \ + --template=../templates/report-zonghe-poster.html \ + --output=report.html \ + --currentYear=2026 + ``` +6. 告诉你 HTML 路径,你用浏览器打开 + +**验证点**: +- ✅ HTML 生成(约 45-50 KB) +- ✅ 浏览器打开能看到完整海报:Hero + 紫微盘 + 八字盘 + 命盘核心数据 + 六维交叉对账 + 综合定论 +- ✅ 无可见 `{{占位符}}` 残留 +- ✅ 紫微 12 宫每宫有星曜,八字四柱含藏干/星运/自坐/纳音 +- ✅ 02 阶段印证时间轴有数据(不空白) + +**❌ 失败信号**: +- LLM 输出 markdown 包装的 JSON → 渲染前需剥掉,否则 HTML 全是占位符 +- HTML 里大量 `-`(兜底字符)→ LLM 的 JSON schema 不完整 + +**快速验证 LLM 输出 JSON 是否合格**: +```bash +node -e "const j=require('./analysis.json'); console.log('strengths:', j.strengths?.length, '/dim:', Object.keys(j.dim||{}).length, '/nodes:', j.final?.nodes?.length, '(应为 3 / 6 / 5)')" +``` + +--- + +## 路径 5:综合印证 + 两种(C) + +**测试输入**: +``` +我是 2000 年 1 月 1 日中午 12 点出生的男生,请同时给我综合印证的长文版和 HTML 海报。 +``` + +**期望**:路径 3 + 路径 4 都跑。如上下文紧张,先出 HTML 海报(更核心)。 + +--- + +## 常见问题排查 + +| 现象 | 可能原因 | 处理 | +|---|---|---| +| Agent 找不到 Skill | SKILL.md 不在 Agent 扫描路径 | 把整个仓库放进 Agent 的 skills 目录(如 `~/.claude/skills/bazi-ziwei/`) | +| 排盘脚本报错 | 时辰格式错 / 日期超 1900-2100 | 校正输入 | +| `node dist/...` 报找不到模块 | 没装依赖 | `cd calculator && npm install` | +| 改了 .ts 源码后没生效 | dist 是预编译产物 | 重新编译 `npm run build` | +| 渲染 HTML 后全是占位符 | analysis.json 解析失败 | 看 analysis.json 第一字符是不是 `{`,不是就剥掉 markdown 包装 | +| 海报里命主信息错 | LLM 自行排盘没用算法层 | 重跑,强调"必须读 chart.txt 不要自己排" | +| 当前大限算错 | `--currentYear` 没传或传错 | 渲染时加 `--currentYear=<当前年份>` | +| Windows 中文路径/PowerShell 编码错乱 | 平台特性 | 用 git bash / WSL 替代 PowerShell | + +--- + +## 多 Agent 兼容性 + +本 Skill 遵循 SKILL.md 开放标准,理论兼容:Claude Code / Claude Desktop / Codex CLI / Codex Desktop / Cursor / Gemini CLI / GitHub Copilot / Hermes Agent / OpenClaw 等。 + +测完一个 Agent 把结果记下来,方便对比。 diff --git a/calculator/bazi-enrich/enrich.ts b/calculator/bazi-enrich/enrich.ts new file mode 100644 index 0000000..dee7049 --- /dev/null +++ b/calculator/bazi-enrich/enrich.ts @@ -0,0 +1,51 @@ +// 八字增强分析主入口 — 给定四柱, 输出格局/旺衰/刑冲合害/调候/自坐 等所有 Yiqi 未算的字段 + +import { Tiangan, Dizhi, getChangSheng } from './tables'; +import { detectZhiRelations } from './zhi-relations'; +import { detectGanRelations, judgePillars } from './gan-relations'; +import { countWuXing, wuXingMonthStatus } from './wu-xing'; +import { judgeWangShuai } from './wang-shuai'; +import { judgeGeJu } from './ge-ju'; +import { getTiaoHou } from './tiao-hou'; + +type Pillar = '年'|'月'|'日'|'时'; +type GanZhi = {gan: Tiangan, zhi: Dizhi}; + +export type BaziEnrichment = { + 自坐: Record; // 每柱 干 vs 自身支 的长生 + 五行旺相: Record; + 五行统计: ReturnType; + 调候用神: string[]; + 格局: ReturnType; + 旺衰: ReturnType; + 天干关系: ReturnType; + 地支关系: ReturnType; + 整柱: ReturnType; +}; + +export function enrichBazi(siZhu: Record): BaziEnrichment { + const dm = siZhu.日.gan; + const monthZhi = siZhu.月.zhi; + + // 自坐 — 每柱干在自身支的长生位 + const ziZuo: Record = {} as any; + for (const p of ['年','月','日','时'] as Pillar[]) { + ziZuo[p] = getChangSheng(siZhu[p].gan, siZhu[p].zhi); + } + + return { + 自坐: ziZuo, + 五行旺相: wuXingMonthStatus(monthZhi), + 五行统计: countWuXing(siZhu, dm), + 调候用神: getTiaoHou(dm, monthZhi), + 格局: judgeGeJu(siZhu), + 旺衰: judgeWangShuai(siZhu), + 天干关系: detectGanRelations({ + 年: siZhu.年.gan, 月: siZhu.月.gan, 日: siZhu.日.gan, 时: siZhu.时.gan + }), + 地支关系: detectZhiRelations({ + 年: siZhu.年.zhi, 月: siZhu.月.zhi, 日: siZhu.日.zhi, 时: siZhu.时.zhi + }), + 整柱: judgePillars(siZhu) + }; +} diff --git a/calculator/bazi-enrich/gan-relations.ts b/calculator/bazi-enrich/gan-relations.ts new file mode 100644 index 0000000..bfc4021 --- /dev/null +++ b/calculator/bazi-enrich/gan-relations.ts @@ -0,0 +1,65 @@ +// 天干关系 + 整柱盖头/截脚 + +import { Tiangan, GAN_WUXING, ZHI_WUXING, Dizhi, shengKe } from './tables'; + +export type GanRelation = { + type: '天干合'|'天干相克'|'天干同'; + gans: Tiangan[]; + pillars: string[]; +}; + +const GAN_HE_PAIRS: Array<[Tiangan, Tiangan, string]> = [ + ['甲','己','土'],['乙','庚','金'],['丙','辛','水'],['丁','壬','木'],['戊','癸','火'] +]; + +type Pillar = '年'|'月'|'日'|'时'; + +export function detectGanRelations(gans: Record): GanRelation[] { + const out: GanRelation[] = []; + const pillars: Pillar[] = ['年','月','日','时']; + const list = pillars.map(p => ({pillar: p, gan: gans[p]})); + for (let i = 0; i < list.length; i++) { + for (let j = i+1; j < list.length; j++) { + const a = list[i], b = list[j]; + // 五合 + const he = GAN_HE_PAIRS.find(([x,y]) => (a.gan===x && b.gan===y) || (a.gan===y && b.gan===x)); + if (he) { + out.push({type:'天干合', gans:[a.gan, b.gan], pillars:[a.pillar, b.pillar]}); + continue; + } + // 相克 (剔除合化情形) + const rel = shengKe(GAN_WUXING[a.gan], GAN_WUXING[b.gan]); + if (rel === '克' || rel === '被克') { + out.push({type:'天干相克', gans:[a.gan, b.gan], pillars:[a.pillar, b.pillar]}); + } + } + } + return out; +} + +// 整柱盖头/截脚 +// 盖头: 天干克地支(以五行论) +// 截脚: 地支克天干 +export type PillarVerdict = { + pillar: Pillar; + gan: Tiangan; + zhi: Dizhi; + verdict: '盖头'|'截脚'|'天干生地支'|'地支生天干'|'天地同气'|'天地异气无生克'; +}; + +export function judgePillars(siZhu: Record): PillarVerdict[] { + const pillars: Pillar[] = ['年','月','日','时']; + return pillars.map(p => { + const {gan, zhi} = siZhu[p]; + const ganWx = GAN_WUXING[gan]; + const zhiWx = ZHI_WUXING[zhi]; + const rel = shengKe(ganWx, zhiWx); + let verdict: PillarVerdict['verdict']; + if (rel === '克') verdict = '盖头'; + else if (rel === '被克') verdict = '截脚'; + else if (rel === '生') verdict = '天干生地支'; + else if (rel === '被生') verdict = '地支生天干'; + else verdict = '天地同气'; + return {pillar: p, gan, zhi, verdict}; + }); +} diff --git a/calculator/bazi-enrich/ge-ju.ts b/calculator/bazi-enrich/ge-ju.ts new file mode 100644 index 0000000..551aa7c --- /dev/null +++ b/calculator/bazi-enrich/ge-ju.ts @@ -0,0 +1,108 @@ +// 格局判定 — 普通八格 + 建禄/月刃 + 特殊格局提示 + +import { Tiangan, Dizhi, GAN_YINYANG, ZHI_CANG_GAN, getShiShen, getChangSheng, ShiShen } from './tables'; + +type Pillar = '年'|'月'|'日'|'时'; + +export type GeJuResult = { + primary: string; // 主格局 + basis: string; // 立格依据 + 透干: Tiangan[]; // 月令藏干中透出天干的列表 + confidence: '高'|'中'|'低'; + notes: string[]; // 特殊提示 (如从格可能、格不纯等) +}; + +const SHI_SHEN_TO_GE: Record = { + 比肩: '比肩格', + 劫财: '劫财格', + 食神: '食神格', + 伤官: '伤官格', + 偏财: '偏财格', + 正财: '正财格', + 七杀: '七杀格', + 正官: '正官格', + 偏印: '偏印格', + 正印: '正印格' +}; + +export function judgeGeJu(siZhu: Record): GeJuResult { + const dm = siZhu.日.gan; + const monthZhi = siZhu.月.zhi; + const cangGan = ZHI_CANG_GAN[monthZhi]; + const benqi = cangGan[0].gan; + const benqiSS = getShiShen(dm, benqi); + + const otherGans = [siZhu.年.gan, siZhu.月.gan, siZhu.时.gan]; + const tougan: Tiangan[] = []; + for (const cg of cangGan) { + if (otherGans.includes(cg.gan)) tougan.push(cg.gan); + } + + const notes: string[] = []; + + // 1. 月支为子/午/卯/酉 — 纯气月支, 直接立格 + // 2. 月支为四生(寅申巳亥) / 四库(辰戌丑未) — 先看透干 + const isPure = ['子','午','卯','酉'].includes(monthZhi); + + let primary: string; + let basis: string; + let confidence: '高'|'中'|'低' = '高'; + + if (isPure) { + primary = SHI_SHEN_TO_GE[benqiSS]; + basis = `月支${monthZhi}本气${benqi}(${benqiSS}) — 纯气月支直接立格`; + } else { + // 看月令藏干中是否有透出 + const touSS = tougan + .map(g => ({gan: g, ss: getShiShen(dm, g)})) + .filter(x => x.ss !== '比肩' && x.ss !== '劫财'); // 比劫一般不优先以透立格 + if (touSS.length > 0) { + // 优先取本气透出的;若无,取中气/余气透出的 + const benqiTou = touSS.find(x => x.gan === benqi); + if (benqiTou) { + primary = SHI_SHEN_TO_GE[benqiTou.ss]; + basis = `月支${monthZhi}本气${benqi}透干 (${benqiTou.ss})`; + } else { + const first = touSS[0]; + primary = SHI_SHEN_TO_GE[first.ss]; + basis = `月支${monthZhi}藏干${first.gan}透干 (${first.ss})`; + confidence = '中'; + notes.push(`月支本气${benqi}未透,取藏干${first.gan}立格(${first.ss}格)`); + } + } else { + // 无非比劫透出 — 以本气立格 + primary = SHI_SHEN_TO_GE[benqiSS]; + basis = `月支${monthZhi}本气${benqi}(${benqiSS}) — 本气未透,以本气论格`; + if (benqiSS !== '比肩' && benqiSS !== '劫财') confidence = '中'; + } + } + + // 比肩/劫财: 默认按现代命名, 同时按传统派看日干在月支的长生位补 notes + if (benqiSS === '比肩') { + primary = '比肩格'; + basis = `月支本气${benqi}=日主同行(比肩) — 比肩格`; + confidence = '高'; + notes.length = 0; + const cs = getChangSheng(dm, monthZhi); + if (cs === '临官') { + notes.push(`日干${dm}在月支${monthZhi}为临官 — 传统称"建禄格"`); + } else { + notes.push(`日干${dm}在月支${monthZhi}为${cs}(非临官) — 传统子平派不立建禄, 现代按比肩格论`); + } + } else if (benqiSS === '劫财') { + const yy = GAN_YINYANG[dm]; + notes.length = 0; + primary = '劫财格'; + basis = `月支本气${benqi}=日主劫财 — 劫财格`; + confidence = '高'; + const cs = getChangSheng(dm, monthZhi); + if (cs === '帝旺') { + const trad = yy === '阳' ? '羊刃格' : '月刃格(阴干月刃有争议)'; + notes.push(`日干${dm}在月支${monthZhi}为帝旺 — 传统称"${trad}"`); + } else { + notes.push(`日干${dm}在月支${monthZhi}为${cs}(非帝旺) — 传统不立月刃, 现代按劫财格论`); + } + } + + return {primary, basis, 透干: tougan, confidence, notes}; +} diff --git a/calculator/bazi-enrich/tables.ts b/calculator/bazi-enrich/tables.ts new file mode 100644 index 0000000..5f827cd --- /dev/null +++ b/calculator/bazi-enrich/tables.ts @@ -0,0 +1,184 @@ +// 八字基础查表 — 所有五行/十神/长生/调候底层数据 + +export type Tiangan = '甲'|'乙'|'丙'|'丁'|'戊'|'己'|'庚'|'辛'|'壬'|'癸'; +export type Dizhi = '子'|'丑'|'寅'|'卯'|'辰'|'巳'|'午'|'未'|'申'|'酉'|'戌'|'亥'; +export type WuXing = '木'|'火'|'土'|'金'|'水'; +export type YinYang = '阳'|'阴'; +export type ShiShen = '比肩'|'劫财'|'食神'|'伤官'|'偏财'|'正财'|'七杀'|'正官'|'偏印'|'正印'; + +export const TIANGAN: Tiangan[] = ['甲','乙','丙','丁','戊','己','庚','辛','壬','癸']; +export const DIZHI: Dizhi[] = ['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥']; + +export const GAN_WUXING: Record = { + 甲:'木',乙:'木',丙:'火',丁:'火',戊:'土',己:'土',庚:'金',辛:'金',壬:'水',癸:'水' +}; + +export const GAN_YINYANG: Record = { + 甲:'阳',乙:'阴',丙:'阳',丁:'阴',戊:'阳',己:'阴',庚:'阳',辛:'阴',壬:'阳',癸:'阴' +}; + +export const ZHI_WUXING: Record = { + 子:'水',丑:'土',寅:'木',卯:'木',辰:'土',巳:'火',午:'火',未:'土',申:'金',酉:'金',戌:'土',亥:'水' +}; + +// 地支藏干 — 本气、中气、余气 +// [{gan, role}] 顺序:本气、中气、余气 +export const ZHI_CANG_GAN: Record> = { + 子: [{gan:'癸',role:'本气'}], + 丑: [{gan:'己',role:'本气'},{gan:'癸',role:'中气'},{gan:'辛',role:'余气'}], + 寅: [{gan:'甲',role:'本气'},{gan:'丙',role:'中气'},{gan:'戊',role:'余气'}], + 卯: [{gan:'乙',role:'本气'}], + 辰: [{gan:'戊',role:'本气'},{gan:'乙',role:'中气'},{gan:'癸',role:'余气'}], + 巳: [{gan:'丙',role:'本气'},{gan:'庚',role:'中气'},{gan:'戊',role:'余气'}], + 午: [{gan:'丁',role:'本气'},{gan:'己',role:'中气'}], + 未: [{gan:'己',role:'本气'},{gan:'丁',role:'中气'},{gan:'乙',role:'余气'}], + 申: [{gan:'庚',role:'本气'},{gan:'壬',role:'中气'},{gan:'戊',role:'余气'}], + 酉: [{gan:'辛',role:'本气'}], + 戌: [{gan:'戊',role:'本气'},{gan:'辛',role:'中气'},{gan:'丁',role:'余气'}], + 亥: [{gan:'壬',role:'本气'},{gan:'甲',role:'中气'}] +}; + +// 五行生克 +export function shengKe(a: WuXing, b: WuXing): '生'|'克'|'同'|'被生'|'被克' { + if (a === b) return '同'; + const sheng: Record = {木:'火',火:'土',土:'金',金:'水',水:'木'}; + const ke: Record = {木:'土',火:'金',土:'水',金:'木',水:'火'}; + if (sheng[a] === b) return '生'; + if (ke[a] === b) return '克'; + if (sheng[b] === a) return '被生'; + if (ke[b] === a) return '被克'; + return '同'; // unreachable +} + +// 十神 — 以日干为基准对其他天干 +export function getShiShen(dayMaster: Tiangan, target: Tiangan): ShiShen { + const dmWx = GAN_WUXING[dayMaster]; + const dmYy = GAN_YINYANG[dayMaster]; + const tWx = GAN_WUXING[target]; + const tYy = GAN_YINYANG[target]; + const sameYy = dmYy === tYy; + const rel = shengKe(dmWx, tWx); + switch (rel) { + case '同': return sameYy ? '比肩' : '劫财'; + case '生': return sameYy ? '食神' : '伤官'; // 日主生他, 同性食神 + case '克': return sameYy ? '偏财' : '正财'; // 日主克他 + case '被克': return sameYy ? '七杀' : '正官'; // 他克日主 + case '被生': return sameYy ? '偏印' : '正印'; // 他生日主 + } +} + +// 十二长生 — 阳干顺行、阴干逆行 +// 长生位起点 +const CHANG_SHENG_START: Record = { + 甲:'亥',丙:'寅',戊:'寅',庚:'巳',壬:'申', + 乙:'午',丁:'酉',己:'酉',辛:'子',癸:'卯' +}; +const CHANG_SHENG_ORDER = ['长生','沐浴','冠带','临官','帝旺','衰','病','死','墓','绝','胎','养'] as const; +export type ChangSheng = typeof CHANG_SHENG_ORDER[number]; + +export function getChangSheng(gan: Tiangan, zhi: Dizhi): ChangSheng { + const start = CHANG_SHENG_START[gan]; + const startIdx = DIZHI.indexOf(start); + const zhiIdx = DIZHI.indexOf(zhi); + const forward = GAN_YINYANG[gan] === '阳'; + let step; + if (forward) { + step = (zhiIdx - startIdx + 12) % 12; + } else { + step = (startIdx - zhiIdx + 12) % 12; + } + return CHANG_SHENG_ORDER[step]; +} + +// 调候用神 — 穷通宝鉴 120 格 (日干 × 月支) +// 每格 1-3 字, 主用神在前 +// 注: 这是子平派主流取用法, 不同流派有微调 +export const TIAO_HOU: Record> = { + // 甲木 + 甲: { + 子:['丁','庚','丙'], 丑:['丁','庚','丙'], 寅:['丙','癸'], + 卯:['庚','丙','丁','戊','己'], 辰:['庚','丁','壬'], 巳:['癸','丁','庚'], + 午:['癸','丁','庚'], 未:['癸','丁','庚'], 申:['庚','丁','壬'], + 酉:['庚','丙','丁'], 戌:['庚','甲','壬','癸'], 亥:['庚','丁','戊','丙'] + }, + // 乙木 + 乙: { + 子:['丙','戊'], 丑:['丙'], 寅:['丙','癸'], + 卯:['丙','癸'], 辰:['癸','丙','戊'], 巳:['癸'], + 午:['癸','丙'], 未:['癸','丙'], 申:['丙','癸','己'], + 酉:['癸','丁','丙'], 戌:['癸','辛'], 亥:['丙','戊'] + }, + // 丙火 + 丙: { + 子:['壬','戊','己'], 丑:['壬','甲'], 寅:['壬','庚'], + 卯:['壬','己'], 辰:['壬','甲'], 巳:['壬','庚','癸'], + 午:['壬','庚'], 未:['壬','庚'], 申:['壬','戊'], + 酉:['壬','癸'], 戌:['甲','壬'], 亥:['甲','戊','庚','壬'] + }, + // 丁火 + 丁: { + 子:['甲','庚'], 丑:['甲','庚'], 寅:['庚','壬'], + 卯:['庚','甲'], 辰:['甲','庚'], 巳:['甲','庚'], + 午:['壬','庚','癸'], 未:['甲','壬','庚'], 申:['甲','庚','丙','戊'], + 酉:['甲','庚','丙','戊'], 戌:['甲','庚','戊'], 亥:['甲','庚'] + }, + // 戊土 + 戊: { + 子:['丙','甲'], 丑:['丙','甲'], 寅:['丙','甲','癸'], + 卯:['丙','甲','癸'], 辰:['甲','丙','癸'], 巳:['甲','丙','癸'], + 午:['壬','甲','丙'], 未:['癸','丙','甲'], 申:['丙','癸','甲'], + 酉:['丙','癸'], 戌:['甲','丙','癸'], 亥:['甲','丙'] + }, + // 己土 + 己: { + 子:['丙','甲','戊'], 丑:['丙','甲','戊'], 寅:['丙','庚','甲'], + 卯:['甲','癸','丙'], 辰:['丙','癸','甲'], 巳:['癸','丙'], + 午:['癸','丙'], 未:['癸','丙'], 申:['丙','癸'], + 酉:['丙','癸'], 戌:['甲','丙','癸'], 亥:['丙','甲','戊'] + }, + // 庚金 + 庚: { + 子:['丁','甲','丙'], 丑:['丙','丁','甲'], 寅:['丙','甲','壬'], + 卯:['丁','甲','庚','丙'], 辰:['甲','丁','壬','癸'], 巳:['壬','戊','丙','丁'], + 午:['壬','癸'], 未:['丁','甲'], 申:['丁','甲'], + 酉:['丁','甲','丙'], 戌:['甲','壬'], 亥:['丁','丙'] + }, + // 辛金 + 辛: { + 子:['丙','戊','壬','甲'], 丑:['丙','壬','戊','己'], 寅:['己','壬','庚'], + 卯:['壬','甲'], 辰:['壬','甲'], 巳:['壬','甲','癸'], + 午:['壬','己','癸'], 未:['壬','庚','甲'], 申:['壬','甲','戊'], + 酉:['壬','甲'], 戌:['壬','甲'], 亥:['壬','丙'] + }, + // 壬水 + 壬: { + 子:['戊','丙'], 丑:['丙','丁','甲'], 寅:['庚','丙','戊'], + 卯:['戊','辛','庚'], 辰:['甲','庚'], 巳:['壬','辛','庚','癸'], + 午:['癸','庚','辛'], 未:['辛','甲'], 申:['戊','丁'], + 酉:['甲','庚'], 戌:['甲','丙'], 亥:['戊','丙','庚'] + }, + // 癸水 + 癸: { + 子:['丙','辛'], 丑:['丙','丁'], 寅:['辛','丙'], + 卯:['庚','辛'], 辰:['丙','辛','甲'], 巳:['辛'], + 午:['庚','辛','壬','癸'], 未:['庚','辛','壬','癸'], 申:['丁'], + 酉:['辛','丙'], 戌:['辛','甲','壬','癸'], 亥:['庚','辛','戊','丁'] + } +}; + +// 月令旺相休囚死 — 五行随月令的状态 +export function getWuXingMonthStatus(monthZhi: Dizhi): Record { + const monthWx = ZHI_WUXING[monthZhi]; + // 当令=旺; 当令所生=相; 生当令者=休; 克当令者=囚; 当令所克=死 + const result: Record = {} as any; + const allWx: WuXing[] = ['木','火','土','金','水']; + for (const wx of allWx) { + const rel = shengKe(monthWx, wx); + if (rel === '同') result[wx] = '旺'; + else if (rel === '生') result[wx] = '相'; + else if (rel === '被生') result[wx] = '休'; + else if (rel === '被克') result[wx] = '囚'; + else if (rel === '克') result[wx] = '死'; + } + return result; +} diff --git a/calculator/bazi-enrich/tiao-hou.ts b/calculator/bazi-enrich/tiao-hou.ts new file mode 100644 index 0000000..a872fac --- /dev/null +++ b/calculator/bazi-enrich/tiao-hou.ts @@ -0,0 +1,7 @@ +// 调候用神 — 穷通宝鉴查表 wrapper + +import { Tiangan, Dizhi, TIAO_HOU } from './tables'; + +export function getTiaoHou(dayMaster: Tiangan, monthZhi: Dizhi): string[] { + return TIAO_HOU[dayMaster][monthZhi]; +} diff --git a/calculator/bazi-enrich/wang-shuai.ts b/calculator/bazi-enrich/wang-shuai.ts new file mode 100644 index 0000000..bc5fe4a --- /dev/null +++ b/calculator/bazi-enrich/wang-shuai.ts @@ -0,0 +1,156 @@ +// 旺衰判定 — 得令(月令) + 长生修正 + 得地(余三支) + 得势(余三干) + +import { Tiangan, Dizhi, GAN_WUXING, ZHI_CANG_GAN, getShiShen, getChangSheng, ShiShen } from './tables'; + +type Pillar = '年'|'月'|'日'|'时'; + +export type WangShuaiVerdict = + | '极旺(可能从强)' + | '偏旺' + | '中和' + | '偏弱' + | '极弱(可能从弱)'; + +export type WangShuaiResult = { + score: number; + verdict: WangShuaiVerdict; + confidence: '高'|'中'|'低'; + breakdown: { + 得令: number; + 长生: number; + 得地: number; + 得势: number; + details: string[]; + }; +}; + +// 月令(月支本气)对日干的关系打分 +function scoreMonthOrder(dayMaster: Tiangan, monthZhi: Dizhi): {score: number, desc: string} { + const cangGan = ZHI_CANG_GAN[monthZhi]; + const benqi = cangGan[0].gan; + const ss = getShiShen(dayMaster, benqi); + // 余气是否含同行 / 印 + const yuqi = cangGan.slice(1); + let extra = 0; + let extraDesc: string[] = []; + for (const cg of yuqi) { + const ssY = getShiShen(dayMaster, cg.gan); + if (ssY === '比肩' || ssY === '劫财') { extra += 1; extraDesc.push(`月余气${cg.gan}比劫+1`); } + else if (ssY === '正印' || ssY === '偏印') { extra += 0.7; extraDesc.push(`月余气${cg.gan}印+0.7`); } + } + let base = 0; + let baseDesc = ''; + switch (ss) { + case '比肩': case '劫财': + base = 5; baseDesc = `月支本气${benqi}=${ss}(建禄/月刃) +5`; break; + case '正印': case '偏印': + base = 3; baseDesc = `月支本气${benqi}=${ss} +3`; break; + case '食神': case '伤官': + base = -3; baseDesc = `月支本气${benqi}=${ss} -3`; break; + case '正官': case '七杀': + base = -4; baseDesc = `月支本气${benqi}=${ss} -4`; break; + case '偏财': case '正财': + base = -5; baseDesc = `月支本气${benqi}=${ss} -5`; break; + } + return { + score: base + extra, + desc: [baseDesc, ...extraDesc].join('; ') + }; +} + +// 日干在月支的长生位修正 +function scoreChangSheng(dayMaster: Tiangan, monthZhi: Dizhi): {score: number, desc: string} { + const cs = getChangSheng(dayMaster, monthZhi); + let s = 0; + if (cs === '长生' || cs === '帝旺') s = 2; + else if (cs === '临官' || cs === '冠带') s = 1; + else if (cs === '沐浴' || cs === '衰') s = 0; + else if (cs === '病' || cs === '死') s = -1; + else s = -3; // 墓/绝/胎/养 + return {score: s, desc: `日主${dayMaster}在月支${monthZhi}为${cs} (${s >= 0 ? '+' : ''}${s})`}; +} + +// 得地: 年/日/时三支查同行/印的根 +function scoreGround(dayMaster: Tiangan, siZhu: Record): {score: number, desc: string[]} { + const desc: string[] = []; + let total = 0; + for (const p of ['年','日','时'] as Pillar[]) { + const zhi = siZhu[p].zhi; + const cangGan = ZHI_CANG_GAN[zhi]; + for (const cg of cangGan) { + const ss = getShiShen(dayMaster, cg.gan); + if (ss === '比肩' || ss === '劫财') { + const v = cg.role === '本气' ? 2 : cg.role === '中气' ? 0.8 : 0.5; + total += v; + desc.push(`${p}支${zhi}藏${cg.gan}(${ss}, ${cg.role}) +${v}`); + } else if (ss === '正印' || ss === '偏印') { + const v = cg.role === '本气' ? 1 : cg.role === '中气' ? 0.5 : 0.3; + total += v; + desc.push(`${p}支${zhi}藏${cg.gan}(${ss}, ${cg.role}) +${v}`); + } + } + } + return {score: total, desc}; +} + +// 得势: 年/月/时干 +function scoreStems(dayMaster: Tiangan, siZhu: Record): {score: number, desc: string[]} { + const desc: string[] = []; + let total = 0; + for (const p of ['年','月','时'] as Pillar[]) { + const gan = siZhu[p].gan; + const ss = getShiShen(dayMaster, gan); + let v = 0; + if (ss === '比肩' || ss === '劫财') v = 1; + else if (ss === '正印' || ss === '偏印') v = 0.7; + else if (ss === '食神' || ss === '伤官') v = -0.5; + else if (ss === '正财' || ss === '偏财') v = -1; + else if (ss === '正官' || ss === '七杀') v = -1.5; + total += v; + desc.push(`${p}干${gan}(${ss}) ${v >= 0 ? '+' : ''}${v}`); + } + return {score: total, desc}; +} + +export function judgeWangShuai(siZhu: Record): WangShuaiResult { + const dm = siZhu.日.gan; + const monthZhi = siZhu.月.zhi; + const month = scoreMonthOrder(dm, monthZhi); + const cs = scoreChangSheng(dm, monthZhi); + const ground = scoreGround(dm, siZhu); + const stems = scoreStems(dm, siZhu); + const score = +(month.score + cs.score + ground.score + stems.score).toFixed(2); + + // 阈值不对称: 月令对负向影响更直接,偏弱区门槛略宽 + let verdict: WangShuaiVerdict; + if (score >= 8) verdict = '极旺(可能从强)'; + else if (score >= 3) verdict = '偏旺'; + else if (score > -2.5) verdict = '中和'; + else if (score > -8) verdict = '偏弱'; + else verdict = '极弱(可能从弱)'; + + // 置信度: 越靠近阈值越低 + const dist = Math.min( + Math.abs(score - 3), + Math.abs(score - (-2.5)), + Math.abs(score - 8), + Math.abs(score - (-8)) + ); + let confidence: '高'|'中'|'低'; + if (dist > 2) confidence = '高'; + else if (dist > 0.8) confidence = '中'; + else confidence = '低'; + + return { + score, + verdict, + confidence, + breakdown: { + 得令: +month.score.toFixed(2), + 长生: cs.score, + 得地: +ground.score.toFixed(2), + 得势: +stems.score.toFixed(2), + details: [month.desc, cs.desc, ...ground.desc, ...stems.desc] + } + }; +} diff --git a/calculator/bazi-enrich/wu-xing.ts b/calculator/bazi-enrich/wu-xing.ts new file mode 100644 index 0000000..152faaf --- /dev/null +++ b/calculator/bazi-enrich/wu-xing.ts @@ -0,0 +1,62 @@ +// 五行统计 + 旺相休囚死 + +import { Tiangan, Dizhi, WuXing, GAN_WUXING, ZHI_WUXING, ZHI_CANG_GAN, getWuXingMonthStatus, getShiShen, ShiShen } from './tables'; + +type Pillar = '年'|'月'|'日'|'时'; + +export type WuXingCount = { + // 不算藏干 (只数天干+地支本气? — 按提示词"不算藏干"理解为干支字面五行) + surface: Record; + // 算藏干 (本气1分,中气0.5,余气0.3) + withCangGan: Record; + // 哪些五行缺失 (surface 计法) + missing: WuXing[]; + // 最旺五行 (surface 计法) + strongest: WuXing[]; + // 转十神: 日干视角下五行对应的十神类别 + shiShenGroups: Record; +}; + +export function countWuXing( + siZhu: Record, + dayMaster: Tiangan +): WuXingCount { + const allWx: WuXing[] = ['木','火','土','金','水']; + const surface: Record = {木:0,火:0,土:0,金:0,水:0}; + const withCangGan: Record = {木:0,火:0,土:0,金:0,水:0}; + const pillars: Pillar[] = ['年','月','日','时']; + for (const p of pillars) { + const {gan, zhi} = siZhu[p]; + surface[GAN_WUXING[gan]] += 1; + surface[ZHI_WUXING[zhi]] += 1; + withCangGan[GAN_WUXING[gan]] += 1; + for (const cg of ZHI_CANG_GAN[zhi]) { + const weight = cg.role === '本气' ? 1 : cg.role === '中气' ? 0.5 : 0.3; + withCangGan[GAN_WUXING[cg.gan]] += weight; + } + } + const missing = allWx.filter(w => surface[w] === 0); + const maxCount = Math.max(...allWx.map(w => surface[w])); + const strongest = allWx.filter(w => surface[w] === maxCount); + + // 每个五行映射到日干视角的十神类 + const shiShenGroups: Record = {} as any; + const repGanByWx: Record = {木:'甲',火:'丙',土:'戊',金:'庚',水:'壬'}; + for (const wx of allWx) { + const ss = getShiShen(dayMaster, repGanByWx[wx]); + // 归类: 比劫/食伤/财/官杀/印 + let group: string; + if (ss === '比肩' || ss === '劫财') group = '比劫'; + else if (ss === '食神' || ss === '伤官') group = '食伤'; + else if (ss === '偏财' || ss === '正财') group = '财'; + else if (ss === '七杀' || ss === '正官') group = '官杀'; + else group = '印'; + shiShenGroups[wx] = {十神类: group, 实例数: surface[wx]}; + } + + return {surface, withCangGan, missing, strongest, shiShenGroups}; +} + +export function wuXingMonthStatus(monthZhi: Dizhi) { + return getWuXingMonthStatus(monthZhi); +} diff --git a/calculator/bazi-enrich/zhi-relations.ts b/calculator/bazi-enrich/zhi-relations.ts new file mode 100644 index 0000000..34ca1c8 --- /dev/null +++ b/calculator/bazi-enrich/zhi-relations.ts @@ -0,0 +1,176 @@ +// 地支关系检测 — 刑冲合害、三合三会、暗合、拱合拱会 + +import { Dizhi, DIZHI, ZHI_CANG_GAN, Tiangan } from './tables'; + +export type ZhiRelation = { + type: '六冲'|'六合'|'三合'|'三会'|'相刑'|'自刑'|'六害'|'暗合'|'拱合'|'拱会'; + zhi: Dizhi[]; // 参与的地支 + pillars: string[]; // 参与的柱名(年/月/日/时) + detail?: string; // 备注,如拱合的目标支 +}; + +// 六冲 +const LIU_CHONG: [Dizhi, Dizhi][] = [ + ['子','午'],['丑','未'],['寅','申'],['卯','酉'],['辰','戌'],['巳','亥'] +]; + +// 六合 +const LIU_HE: [Dizhi, Dizhi][] = [ + ['子','丑'],['寅','亥'],['卯','戌'],['辰','酉'],['巳','申'],['午','未'] +]; + +// 三合局 (申子辰水 / 亥卯未木 / 寅午戌火 / 巳酉丑金) +const SAN_HE: Array<{zhi: [Dizhi, Dizhi, Dizhi], wuxing: string}> = [ + {zhi:['申','子','辰'], wuxing:'水'}, + {zhi:['亥','卯','未'], wuxing:'木'}, + {zhi:['寅','午','戌'], wuxing:'火'}, + {zhi:['巳','酉','丑'], wuxing:'金'} +]; + +// 三会方 (寅卯辰东方木 / 巳午未南方火 / 申酉戌西方金 / 亥子丑北方水) +const SAN_HUI: Array<{zhi: [Dizhi, Dizhi, Dizhi], wuxing: string}> = [ + {zhi:['寅','卯','辰'], wuxing:'木'}, + {zhi:['巳','午','未'], wuxing:'火'}, + {zhi:['申','酉','戌'], wuxing:'金'}, + {zhi:['亥','子','丑'], wuxing:'水'} +]; + +// 相刑 (无礼之刑 子卯; 恃势之刑 寅巳申; 无恩之刑 丑戌未) +const XIANG_XING: Array<{zhi: Dizhi[], name: string}> = [ + {zhi:['子','卯'], name:'无礼之刑'}, + {zhi:['寅','巳','申'], name:'恃势之刑'}, + {zhi:['丑','戌','未'], name:'无恩之刑'} +]; +const ZI_XING: Dizhi[] = ['辰','午','酉','亥']; // 自刑 + +// 六害 +const LIU_HAI: [Dizhi, Dizhi][] = [ + ['子','未'],['丑','午'],['寅','巳'],['卯','辰'],['申','亥'],['酉','戌'] +]; + +// 暗合 — 地支藏干之间相合 +// 天干五合: 甲己 乙庚 丙辛 丁壬 戊癸 +const GAN_HE: Record = { + 甲:'己',己:'甲', 乙:'庚',庚:'乙', 丙:'辛',辛:'丙', 丁:'壬',壬:'丁', 戊:'癸',癸:'戊' +}; + +type Pillar = '年'|'月'|'日'|'时'; + +export function detectZhiRelations(zhis: Record): ZhiRelation[] { + const out: ZhiRelation[] = []; + const pillars: Pillar[] = ['年','月','日','时']; + const list = pillars.map(p => ({pillar: p, zhi: zhis[p]})); + + // 两两组合 + const pairs: Array<[typeof list[0], typeof list[0]]> = []; + for (let i = 0; i < list.length; i++) { + for (let j = i+1; j < list.length; j++) { + pairs.push([list[i], list[j]]); + } + } + + // 六冲 + for (const [a, b] of pairs) { + for (const [x, y] of LIU_CHONG) { + if ((a.zhi === x && b.zhi === y) || (a.zhi === y && b.zhi === x)) { + out.push({type:'六冲', zhi:[a.zhi, b.zhi], pillars:[a.pillar, b.pillar]}); + } + } + } + + // 六合 + for (const [a, b] of pairs) { + for (const [x, y] of LIU_HE) { + if ((a.zhi === x && b.zhi === y) || (a.zhi === y && b.zhi === x)) { + out.push({type:'六合', zhi:[a.zhi, b.zhi], pillars:[a.pillar, b.pillar]}); + } + } + } + + // 六害 + for (const [a, b] of pairs) { + for (const [x, y] of LIU_HAI) { + if ((a.zhi === x && b.zhi === y) || (a.zhi === y && b.zhi === x)) { + out.push({type:'六害', zhi:[a.zhi, b.zhi], pillars:[a.pillar, b.pillar]}); + } + } + } + + // 相刑 + for (const xing of XIANG_XING) { + const matches = list.filter(p => xing.zhi.includes(p.zhi)); + if (xing.zhi.length === 2) { + // 子卯刑:要两个都在 + const distinct = new Set(matches.map(m => m.zhi)); + if (distinct.size === 2) { + out.push({type:'相刑', zhi:[...distinct] as Dizhi[], pillars: matches.map(m=>m.pillar), detail: xing.name}); + } + } else { + // 三刑:两个就构成半刑(实际三刑要全) + const distinct = new Set(matches.map(m => m.zhi)); + if (distinct.size >= 2) { + out.push({type:'相刑', zhi:[...distinct] as Dizhi[], pillars: matches.map(m=>m.pillar), detail: xing.name + (distinct.size === 3 ? '(全)' : '(半)')}); + } + } + } + + // 自刑 + for (const zx of ZI_XING) { + const matches = list.filter(p => p.zhi === zx); + if (matches.length >= 2) { + out.push({type:'自刑', zhi:[zx, zx], pillars: matches.map(m=>m.pillar)}); + } + } + + // 三合 / 三会 / 半合 / 拱合 / 拱会 + for (const sh of SAN_HE) { + const present = list.filter(p => sh.zhi.includes(p.zhi)); + const distinctZhi = [...new Set(present.map(p => p.zhi))]; + if (distinctZhi.length === 3) { + out.push({type:'三合', zhi: distinctZhi as Dizhi[], pillars: present.map(p=>p.pillar), detail: `三合${sh.wuxing}局`}); + } else if (distinctZhi.length === 2) { + // 缺一支 → 拱合 + const missing = sh.zhi.find(z => !distinctZhi.includes(z))!; + out.push({type:'拱合', zhi: distinctZhi as Dizhi[], pillars: present.map(p=>p.pillar), detail: `拱合${missing}(${sh.wuxing}局)`}); + } + } + for (const sh of SAN_HUI) { + const present = list.filter(p => sh.zhi.includes(p.zhi)); + const distinctZhi = [...new Set(present.map(p => p.zhi))]; + if (distinctZhi.length === 3) { + out.push({type:'三会', zhi: distinctZhi as Dizhi[], pillars: present.map(p=>p.pillar), detail: `三会${sh.wuxing}方`}); + } else if (distinctZhi.length === 2) { + const missing = sh.zhi.find(z => !distinctZhi.includes(z))!; + out.push({type:'拱会', zhi: distinctZhi as Dizhi[], pillars: present.map(p=>p.pillar), detail: `拱会${missing}(${sh.wuxing}方)`}); + } + } + + // 暗合 — 地支藏干相合 + // 规则: 仅在该地支对没有其他显式关系(六冲/六合/六害/三合/三会/拱合/拱会/相刑)时才报暗合 + const explicitTypes: ZhiRelation['type'][] = ['六冲','六合','六害','三合','三会','拱合','拱会','相刑','自刑']; + function hasExplicit(za: Dizhi, zb: Dizhi): boolean { + return out.some(r => + explicitTypes.includes(r.type) && + r.zhi.includes(za) && r.zhi.includes(zb) + ); + } + for (const [a, b] of pairs) { + if (a.zhi === b.zhi) continue; + if (hasExplicit(a.zhi, b.zhi)) continue; + const aGans = ZHI_CANG_GAN[a.zhi].map(c => c.gan); + const bGans = ZHI_CANG_GAN[b.zhi].map(c => c.gan); + const hits: string[] = []; + for (const ag of aGans) { + for (const bg of bGans) { + if (GAN_HE[ag] === bg) { + hits.push(`${ag}${bg}合`); + } + } + } + if (hits.length > 0) { + out.push({type:'暗合', zhi:[a.zhi, b.zhi], pillars:[a.pillar, b.pillar], detail: hits.join('、')}); + } + } + + return out; +} diff --git a/calculator/dist/bazi-enrich/enrich.js b/calculator/dist/bazi-enrich/enrich.js new file mode 100644 index 0000000..68e989c --- /dev/null +++ b/calculator/dist/bazi-enrich/enrich.js @@ -0,0 +1,35 @@ +"use strict"; +// 八字增强分析主入口 — 给定四柱, 输出格局/旺衰/刑冲合害/调候/自坐 等所有 Yiqi 未算的字段 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.enrichBazi = enrichBazi; +const tables_1 = require("./tables"); +const zhi_relations_1 = require("./zhi-relations"); +const gan_relations_1 = require("./gan-relations"); +const wu_xing_1 = require("./wu-xing"); +const wang_shuai_1 = require("./wang-shuai"); +const ge_ju_1 = require("./ge-ju"); +const tiao_hou_1 = require("./tiao-hou"); +function enrichBazi(siZhu) { + const dm = siZhu.日.gan; + const monthZhi = siZhu.月.zhi; + // 自坐 — 每柱干在自身支的长生位 + const ziZuo = {}; + for (const p of ['年', '月', '日', '时']) { + ziZuo[p] = (0, tables_1.getChangSheng)(siZhu[p].gan, siZhu[p].zhi); + } + return { + 自坐: ziZuo, + 五行旺相: (0, wu_xing_1.wuXingMonthStatus)(monthZhi), + 五行统计: (0, wu_xing_1.countWuXing)(siZhu, dm), + 调候用神: (0, tiao_hou_1.getTiaoHou)(dm, monthZhi), + 格局: (0, ge_ju_1.judgeGeJu)(siZhu), + 旺衰: (0, wang_shuai_1.judgeWangShuai)(siZhu), + 天干关系: (0, gan_relations_1.detectGanRelations)({ + 年: siZhu.年.gan, 月: siZhu.月.gan, 日: siZhu.日.gan, 时: siZhu.时.gan + }), + 地支关系: (0, zhi_relations_1.detectZhiRelations)({ + 年: siZhu.年.zhi, 月: siZhu.月.zhi, 日: siZhu.日.zhi, 时: siZhu.时.zhi + }), + 整柱: (0, gan_relations_1.judgePillars)(siZhu) + }; +} diff --git a/calculator/dist/bazi-enrich/gan-relations.js b/calculator/dist/bazi-enrich/gan-relations.js new file mode 100644 index 0000000..07339c1 --- /dev/null +++ b/calculator/dist/bazi-enrich/gan-relations.js @@ -0,0 +1,52 @@ +"use strict"; +// 天干关系 + 整柱盖头/截脚 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.detectGanRelations = detectGanRelations; +exports.judgePillars = judgePillars; +const tables_1 = require("./tables"); +const GAN_HE_PAIRS = [ + ['甲', '己', '土'], ['乙', '庚', '金'], ['丙', '辛', '水'], ['丁', '壬', '木'], ['戊', '癸', '火'] +]; +function detectGanRelations(gans) { + const out = []; + const pillars = ['年', '月', '日', '时']; + const list = pillars.map(p => ({ pillar: p, gan: gans[p] })); + for (let i = 0; i < list.length; i++) { + for (let j = i + 1; j < list.length; j++) { + const a = list[i], b = list[j]; + // 五合 + const he = GAN_HE_PAIRS.find(([x, y]) => (a.gan === x && b.gan === y) || (a.gan === y && b.gan === x)); + if (he) { + out.push({ type: '天干合', gans: [a.gan, b.gan], pillars: [a.pillar, b.pillar] }); + continue; + } + // 相克 (剔除合化情形) + const rel = (0, tables_1.shengKe)(tables_1.GAN_WUXING[a.gan], tables_1.GAN_WUXING[b.gan]); + if (rel === '克' || rel === '被克') { + out.push({ type: '天干相克', gans: [a.gan, b.gan], pillars: [a.pillar, b.pillar] }); + } + } + } + return out; +} +function judgePillars(siZhu) { + const pillars = ['年', '月', '日', '时']; + return pillars.map(p => { + const { gan, zhi } = siZhu[p]; + const ganWx = tables_1.GAN_WUXING[gan]; + const zhiWx = tables_1.ZHI_WUXING[zhi]; + const rel = (0, tables_1.shengKe)(ganWx, zhiWx); + let verdict; + if (rel === '克') + verdict = '盖头'; + else if (rel === '被克') + verdict = '截脚'; + else if (rel === '生') + verdict = '天干生地支'; + else if (rel === '被生') + verdict = '地支生天干'; + else + verdict = '天地同气'; + return { pillar: p, gan, zhi, verdict }; + }); +} diff --git a/calculator/dist/bazi-enrich/ge-ju.js b/calculator/dist/bazi-enrich/ge-ju.js new file mode 100644 index 0000000..0272f29 --- /dev/null +++ b/calculator/dist/bazi-enrich/ge-ju.js @@ -0,0 +1,99 @@ +"use strict"; +// 格局判定 — 普通八格 + 建禄/月刃 + 特殊格局提示 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.judgeGeJu = judgeGeJu; +const tables_1 = require("./tables"); +const SHI_SHEN_TO_GE = { + 比肩: '比肩格', + 劫财: '劫财格', + 食神: '食神格', + 伤官: '伤官格', + 偏财: '偏财格', + 正财: '正财格', + 七杀: '七杀格', + 正官: '正官格', + 偏印: '偏印格', + 正印: '正印格' +}; +function judgeGeJu(siZhu) { + const dm = siZhu.日.gan; + const monthZhi = siZhu.月.zhi; + const cangGan = tables_1.ZHI_CANG_GAN[monthZhi]; + const benqi = cangGan[0].gan; + const benqiSS = (0, tables_1.getShiShen)(dm, benqi); + const otherGans = [siZhu.年.gan, siZhu.月.gan, siZhu.时.gan]; + const tougan = []; + for (const cg of cangGan) { + if (otherGans.includes(cg.gan)) + tougan.push(cg.gan); + } + const notes = []; + // 1. 月支为子/午/卯/酉 — 纯气月支, 直接立格 + // 2. 月支为四生(寅申巳亥) / 四库(辰戌丑未) — 先看透干 + const isPure = ['子', '午', '卯', '酉'].includes(monthZhi); + let primary; + let basis; + let confidence = '高'; + if (isPure) { + primary = SHI_SHEN_TO_GE[benqiSS]; + basis = `月支${monthZhi}本气${benqi}(${benqiSS}) — 纯气月支直接立格`; + } + else { + // 看月令藏干中是否有透出 + const touSS = tougan + .map(g => ({ gan: g, ss: (0, tables_1.getShiShen)(dm, g) })) + .filter(x => x.ss !== '比肩' && x.ss !== '劫财'); // 比劫一般不优先以透立格 + if (touSS.length > 0) { + // 优先取本气透出的;若无,取中气/余气透出的 + const benqiTou = touSS.find(x => x.gan === benqi); + if (benqiTou) { + primary = SHI_SHEN_TO_GE[benqiTou.ss]; + basis = `月支${monthZhi}本气${benqi}透干 (${benqiTou.ss})`; + } + else { + const first = touSS[0]; + primary = SHI_SHEN_TO_GE[first.ss]; + basis = `月支${monthZhi}藏干${first.gan}透干 (${first.ss})`; + confidence = '中'; + notes.push(`月支本气${benqi}未透,取藏干${first.gan}立格(${first.ss}格)`); + } + } + else { + // 无非比劫透出 — 以本气立格 + primary = SHI_SHEN_TO_GE[benqiSS]; + basis = `月支${monthZhi}本气${benqi}(${benqiSS}) — 本气未透,以本气论格`; + if (benqiSS !== '比肩' && benqiSS !== '劫财') + confidence = '中'; + } + } + // 比肩/劫财: 默认按现代命名, 同时按传统派看日干在月支的长生位补 notes + if (benqiSS === '比肩') { + primary = '比肩格'; + basis = `月支本气${benqi}=日主同行(比肩) — 比肩格`; + confidence = '高'; + notes.length = 0; + const cs = (0, tables_1.getChangSheng)(dm, monthZhi); + if (cs === '临官') { + notes.push(`日干${dm}在月支${monthZhi}为临官 — 传统称"建禄格"`); + } + else { + notes.push(`日干${dm}在月支${monthZhi}为${cs}(非临官) — 传统子平派不立建禄, 现代按比肩格论`); + } + } + else if (benqiSS === '劫财') { + const yy = tables_1.GAN_YINYANG[dm]; + notes.length = 0; + primary = '劫财格'; + basis = `月支本气${benqi}=日主劫财 — 劫财格`; + confidence = '高'; + const cs = (0, tables_1.getChangSheng)(dm, monthZhi); + if (cs === '帝旺') { + const trad = yy === '阳' ? '羊刃格' : '月刃格(阴干月刃有争议)'; + notes.push(`日干${dm}在月支${monthZhi}为帝旺 — 传统称"${trad}"`); + } + else { + notes.push(`日干${dm}在月支${monthZhi}为${cs}(非帝旺) — 传统不立月刃, 现代按劫财格论`); + } + } + return { primary, basis, 透干: tougan, confidence, notes }; +} diff --git a/calculator/dist/bazi-enrich/tables.js b/calculator/dist/bazi-enrich/tables.js new file mode 100644 index 0000000..bb75651 --- /dev/null +++ b/calculator/dist/bazi-enrich/tables.js @@ -0,0 +1,184 @@ +"use strict"; +// 八字基础查表 — 所有五行/十神/长生/调候底层数据 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TIAO_HOU = exports.ZHI_CANG_GAN = exports.ZHI_WUXING = exports.GAN_YINYANG = exports.GAN_WUXING = exports.DIZHI = exports.TIANGAN = void 0; +exports.shengKe = shengKe; +exports.getShiShen = getShiShen; +exports.getChangSheng = getChangSheng; +exports.getWuXingMonthStatus = getWuXingMonthStatus; +exports.TIANGAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; +exports.DIZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; +exports.GAN_WUXING = { + 甲: '木', 乙: '木', 丙: '火', 丁: '火', 戊: '土', 己: '土', 庚: '金', 辛: '金', 壬: '水', 癸: '水' +}; +exports.GAN_YINYANG = { + 甲: '阳', 乙: '阴', 丙: '阳', 丁: '阴', 戊: '阳', 己: '阴', 庚: '阳', 辛: '阴', 壬: '阳', 癸: '阴' +}; +exports.ZHI_WUXING = { + 子: '水', 丑: '土', 寅: '木', 卯: '木', 辰: '土', 巳: '火', 午: '火', 未: '土', 申: '金', 酉: '金', 戌: '土', 亥: '水' +}; +// 地支藏干 — 本气、中气、余气 +// [{gan, role}] 顺序:本气、中气、余气 +exports.ZHI_CANG_GAN = { + 子: [{ gan: '癸', role: '本气' }], + 丑: [{ gan: '己', role: '本气' }, { gan: '癸', role: '中气' }, { gan: '辛', role: '余气' }], + 寅: [{ gan: '甲', role: '本气' }, { gan: '丙', role: '中气' }, { gan: '戊', role: '余气' }], + 卯: [{ gan: '乙', role: '本气' }], + 辰: [{ gan: '戊', role: '本气' }, { gan: '乙', role: '中气' }, { gan: '癸', role: '余气' }], + 巳: [{ gan: '丙', role: '本气' }, { gan: '庚', role: '中气' }, { gan: '戊', role: '余气' }], + 午: [{ gan: '丁', role: '本气' }, { gan: '己', role: '中气' }], + 未: [{ gan: '己', role: '本气' }, { gan: '丁', role: '中气' }, { gan: '乙', role: '余气' }], + 申: [{ gan: '庚', role: '本气' }, { gan: '壬', role: '中气' }, { gan: '戊', role: '余气' }], + 酉: [{ gan: '辛', role: '本气' }], + 戌: [{ gan: '戊', role: '本气' }, { gan: '辛', role: '中气' }, { gan: '丁', role: '余气' }], + 亥: [{ gan: '壬', role: '本气' }, { gan: '甲', role: '中气' }] +}; +// 五行生克 +function shengKe(a, b) { + if (a === b) + return '同'; + const sheng = { 木: '火', 火: '土', 土: '金', 金: '水', 水: '木' }; + const ke = { 木: '土', 火: '金', 土: '水', 金: '木', 水: '火' }; + if (sheng[a] === b) + return '生'; + if (ke[a] === b) + return '克'; + if (sheng[b] === a) + return '被生'; + if (ke[b] === a) + return '被克'; + return '同'; // unreachable +} +// 十神 — 以日干为基准对其他天干 +function getShiShen(dayMaster, target) { + const dmWx = exports.GAN_WUXING[dayMaster]; + const dmYy = exports.GAN_YINYANG[dayMaster]; + const tWx = exports.GAN_WUXING[target]; + const tYy = exports.GAN_YINYANG[target]; + const sameYy = dmYy === tYy; + const rel = shengKe(dmWx, tWx); + switch (rel) { + case '同': return sameYy ? '比肩' : '劫财'; + case '生': return sameYy ? '食神' : '伤官'; // 日主生他, 同性食神 + case '克': return sameYy ? '偏财' : '正财'; // 日主克他 + case '被克': return sameYy ? '七杀' : '正官'; // 他克日主 + case '被生': return sameYy ? '偏印' : '正印'; // 他生日主 + } +} +// 十二长生 — 阳干顺行、阴干逆行 +// 长生位起点 +const CHANG_SHENG_START = { + 甲: '亥', 丙: '寅', 戊: '寅', 庚: '巳', 壬: '申', + 乙: '午', 丁: '酉', 己: '酉', 辛: '子', 癸: '卯' +}; +const CHANG_SHENG_ORDER = ['长生', '沐浴', '冠带', '临官', '帝旺', '衰', '病', '死', '墓', '绝', '胎', '养']; +function getChangSheng(gan, zhi) { + const start = CHANG_SHENG_START[gan]; + const startIdx = exports.DIZHI.indexOf(start); + const zhiIdx = exports.DIZHI.indexOf(zhi); + const forward = exports.GAN_YINYANG[gan] === '阳'; + let step; + if (forward) { + step = (zhiIdx - startIdx + 12) % 12; + } + else { + step = (startIdx - zhiIdx + 12) % 12; + } + return CHANG_SHENG_ORDER[step]; +} +// 调候用神 — 穷通宝鉴 120 格 (日干 × 月支) +// 每格 1-3 字, 主用神在前 +// 注: 这是子平派主流取用法, 不同流派有微调 +exports.TIAO_HOU = { + // 甲木 + 甲: { + 子: ['丁', '庚', '丙'], 丑: ['丁', '庚', '丙'], 寅: ['丙', '癸'], + 卯: ['庚', '丙', '丁', '戊', '己'], 辰: ['庚', '丁', '壬'], 巳: ['癸', '丁', '庚'], + 午: ['癸', '丁', '庚'], 未: ['癸', '丁', '庚'], 申: ['庚', '丁', '壬'], + 酉: ['庚', '丙', '丁'], 戌: ['庚', '甲', '壬', '癸'], 亥: ['庚', '丁', '戊', '丙'] + }, + // 乙木 + 乙: { + 子: ['丙', '戊'], 丑: ['丙'], 寅: ['丙', '癸'], + 卯: ['丙', '癸'], 辰: ['癸', '丙', '戊'], 巳: ['癸'], + 午: ['癸', '丙'], 未: ['癸', '丙'], 申: ['丙', '癸', '己'], + 酉: ['癸', '丁', '丙'], 戌: ['癸', '辛'], 亥: ['丙', '戊'] + }, + // 丙火 + 丙: { + 子: ['壬', '戊', '己'], 丑: ['壬', '甲'], 寅: ['壬', '庚'], + 卯: ['壬', '己'], 辰: ['壬', '甲'], 巳: ['壬', '庚', '癸'], + 午: ['壬', '庚'], 未: ['壬', '庚'], 申: ['壬', '戊'], + 酉: ['壬', '癸'], 戌: ['甲', '壬'], 亥: ['甲', '戊', '庚', '壬'] + }, + // 丁火 + 丁: { + 子: ['甲', '庚'], 丑: ['甲', '庚'], 寅: ['庚', '壬'], + 卯: ['庚', '甲'], 辰: ['甲', '庚'], 巳: ['甲', '庚'], + 午: ['壬', '庚', '癸'], 未: ['甲', '壬', '庚'], 申: ['甲', '庚', '丙', '戊'], + 酉: ['甲', '庚', '丙', '戊'], 戌: ['甲', '庚', '戊'], 亥: ['甲', '庚'] + }, + // 戊土 + 戊: { + 子: ['丙', '甲'], 丑: ['丙', '甲'], 寅: ['丙', '甲', '癸'], + 卯: ['丙', '甲', '癸'], 辰: ['甲', '丙', '癸'], 巳: ['甲', '丙', '癸'], + 午: ['壬', '甲', '丙'], 未: ['癸', '丙', '甲'], 申: ['丙', '癸', '甲'], + 酉: ['丙', '癸'], 戌: ['甲', '丙', '癸'], 亥: ['甲', '丙'] + }, + // 己土 + 己: { + 子: ['丙', '甲', '戊'], 丑: ['丙', '甲', '戊'], 寅: ['丙', '庚', '甲'], + 卯: ['甲', '癸', '丙'], 辰: ['丙', '癸', '甲'], 巳: ['癸', '丙'], + 午: ['癸', '丙'], 未: ['癸', '丙'], 申: ['丙', '癸'], + 酉: ['丙', '癸'], 戌: ['甲', '丙', '癸'], 亥: ['丙', '甲', '戊'] + }, + // 庚金 + 庚: { + 子: ['丁', '甲', '丙'], 丑: ['丙', '丁', '甲'], 寅: ['丙', '甲', '壬'], + 卯: ['丁', '甲', '庚', '丙'], 辰: ['甲', '丁', '壬', '癸'], 巳: ['壬', '戊', '丙', '丁'], + 午: ['壬', '癸'], 未: ['丁', '甲'], 申: ['丁', '甲'], + 酉: ['丁', '甲', '丙'], 戌: ['甲', '壬'], 亥: ['丁', '丙'] + }, + // 辛金 + 辛: { + 子: ['丙', '戊', '壬', '甲'], 丑: ['丙', '壬', '戊', '己'], 寅: ['己', '壬', '庚'], + 卯: ['壬', '甲'], 辰: ['壬', '甲'], 巳: ['壬', '甲', '癸'], + 午: ['壬', '己', '癸'], 未: ['壬', '庚', '甲'], 申: ['壬', '甲', '戊'], + 酉: ['壬', '甲'], 戌: ['壬', '甲'], 亥: ['壬', '丙'] + }, + // 壬水 + 壬: { + 子: ['戊', '丙'], 丑: ['丙', '丁', '甲'], 寅: ['庚', '丙', '戊'], + 卯: ['戊', '辛', '庚'], 辰: ['甲', '庚'], 巳: ['壬', '辛', '庚', '癸'], + 午: ['癸', '庚', '辛'], 未: ['辛', '甲'], 申: ['戊', '丁'], + 酉: ['甲', '庚'], 戌: ['甲', '丙'], 亥: ['戊', '丙', '庚'] + }, + // 癸水 + 癸: { + 子: ['丙', '辛'], 丑: ['丙', '丁'], 寅: ['辛', '丙'], + 卯: ['庚', '辛'], 辰: ['丙', '辛', '甲'], 巳: ['辛'], + 午: ['庚', '辛', '壬', '癸'], 未: ['庚', '辛', '壬', '癸'], 申: ['丁'], + 酉: ['辛', '丙'], 戌: ['辛', '甲', '壬', '癸'], 亥: ['庚', '辛', '戊', '丁'] + } +}; +// 月令旺相休囚死 — 五行随月令的状态 +function getWuXingMonthStatus(monthZhi) { + const monthWx = exports.ZHI_WUXING[monthZhi]; + // 当令=旺; 当令所生=相; 生当令者=休; 克当令者=囚; 当令所克=死 + const result = {}; + const allWx = ['木', '火', '土', '金', '水']; + for (const wx of allWx) { + const rel = shengKe(monthWx, wx); + if (rel === '同') + result[wx] = '旺'; + else if (rel === '生') + result[wx] = '相'; + else if (rel === '被生') + result[wx] = '休'; + else if (rel === '被克') + result[wx] = '囚'; + else if (rel === '克') + result[wx] = '死'; + } + return result; +} diff --git a/calculator/dist/bazi-enrich/tiao-hou.js b/calculator/dist/bazi-enrich/tiao-hou.js new file mode 100644 index 0000000..0275bf5 --- /dev/null +++ b/calculator/dist/bazi-enrich/tiao-hou.js @@ -0,0 +1,8 @@ +"use strict"; +// 调候用神 — 穷通宝鉴查表 wrapper +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getTiaoHou = getTiaoHou; +const tables_1 = require("./tables"); +function getTiaoHou(dayMaster, monthZhi) { + return tables_1.TIAO_HOU[dayMaster][monthZhi]; +} diff --git a/calculator/dist/bazi-enrich/wang-shuai.js b/calculator/dist/bazi-enrich/wang-shuai.js new file mode 100644 index 0000000..1a4bc6b --- /dev/null +++ b/calculator/dist/bazi-enrich/wang-shuai.js @@ -0,0 +1,163 @@ +"use strict"; +// 旺衰判定 — 得令(月令) + 长生修正 + 得地(余三支) + 得势(余三干) +Object.defineProperty(exports, "__esModule", { value: true }); +exports.judgeWangShuai = judgeWangShuai; +const tables_1 = require("./tables"); +// 月令(月支本气)对日干的关系打分 +function scoreMonthOrder(dayMaster, monthZhi) { + const cangGan = tables_1.ZHI_CANG_GAN[monthZhi]; + const benqi = cangGan[0].gan; + const ss = (0, tables_1.getShiShen)(dayMaster, benqi); + // 余气是否含同行 / 印 + const yuqi = cangGan.slice(1); + let extra = 0; + let extraDesc = []; + for (const cg of yuqi) { + const ssY = (0, tables_1.getShiShen)(dayMaster, cg.gan); + if (ssY === '比肩' || ssY === '劫财') { + extra += 1; + extraDesc.push(`月余气${cg.gan}比劫+1`); + } + else if (ssY === '正印' || ssY === '偏印') { + extra += 0.7; + extraDesc.push(`月余气${cg.gan}印+0.7`); + } + } + let base = 0; + let baseDesc = ''; + switch (ss) { + case '比肩': + case '劫财': + base = 5; + baseDesc = `月支本气${benqi}=${ss}(建禄/月刃) +5`; + break; + case '正印': + case '偏印': + base = 3; + baseDesc = `月支本气${benqi}=${ss} +3`; + break; + case '食神': + case '伤官': + base = -3; + baseDesc = `月支本气${benqi}=${ss} -3`; + break; + case '正官': + case '七杀': + base = -4; + baseDesc = `月支本气${benqi}=${ss} -4`; + break; + case '偏财': + case '正财': + base = -5; + baseDesc = `月支本气${benqi}=${ss} -5`; + break; + } + return { + score: base + extra, + desc: [baseDesc, ...extraDesc].join('; ') + }; +} +// 日干在月支的长生位修正 +function scoreChangSheng(dayMaster, monthZhi) { + const cs = (0, tables_1.getChangSheng)(dayMaster, monthZhi); + let s = 0; + if (cs === '长生' || cs === '帝旺') + s = 2; + else if (cs === '临官' || cs === '冠带') + s = 1; + else if (cs === '沐浴' || cs === '衰') + s = 0; + else if (cs === '病' || cs === '死') + s = -1; + else + s = -3; // 墓/绝/胎/养 + return { score: s, desc: `日主${dayMaster}在月支${monthZhi}为${cs} (${s >= 0 ? '+' : ''}${s})` }; +} +// 得地: 年/日/时三支查同行/印的根 +function scoreGround(dayMaster, siZhu) { + const desc = []; + let total = 0; + for (const p of ['年', '日', '时']) { + const zhi = siZhu[p].zhi; + const cangGan = tables_1.ZHI_CANG_GAN[zhi]; + for (const cg of cangGan) { + const ss = (0, tables_1.getShiShen)(dayMaster, cg.gan); + if (ss === '比肩' || ss === '劫财') { + const v = cg.role === '本气' ? 2 : cg.role === '中气' ? 0.8 : 0.5; + total += v; + desc.push(`${p}支${zhi}藏${cg.gan}(${ss}, ${cg.role}) +${v}`); + } + else if (ss === '正印' || ss === '偏印') { + const v = cg.role === '本气' ? 1 : cg.role === '中气' ? 0.5 : 0.3; + total += v; + desc.push(`${p}支${zhi}藏${cg.gan}(${ss}, ${cg.role}) +${v}`); + } + } + } + return { score: total, desc }; +} +// 得势: 年/月/时干 +function scoreStems(dayMaster, siZhu) { + const desc = []; + let total = 0; + for (const p of ['年', '月', '时']) { + const gan = siZhu[p].gan; + const ss = (0, tables_1.getShiShen)(dayMaster, gan); + let v = 0; + if (ss === '比肩' || ss === '劫财') + v = 1; + else if (ss === '正印' || ss === '偏印') + v = 0.7; + else if (ss === '食神' || ss === '伤官') + v = -0.5; + else if (ss === '正财' || ss === '偏财') + v = -1; + else if (ss === '正官' || ss === '七杀') + v = -1.5; + total += v; + desc.push(`${p}干${gan}(${ss}) ${v >= 0 ? '+' : ''}${v}`); + } + return { score: total, desc }; +} +function judgeWangShuai(siZhu) { + const dm = siZhu.日.gan; + const monthZhi = siZhu.月.zhi; + const month = scoreMonthOrder(dm, monthZhi); + const cs = scoreChangSheng(dm, monthZhi); + const ground = scoreGround(dm, siZhu); + const stems = scoreStems(dm, siZhu); + const score = +(month.score + cs.score + ground.score + stems.score).toFixed(2); + // 阈值不对称: 月令对负向影响更直接,偏弱区门槛略宽 + let verdict; + if (score >= 8) + verdict = '极旺(可能从强)'; + else if (score >= 3) + verdict = '偏旺'; + else if (score > -2.5) + verdict = '中和'; + else if (score > -8) + verdict = '偏弱'; + else + verdict = '极弱(可能从弱)'; + // 置信度: 越靠近阈值越低 + const dist = Math.min(Math.abs(score - 3), Math.abs(score - (-2.5)), Math.abs(score - 8), Math.abs(score - (-8))); + let confidence; + if (dist > 2) + confidence = '高'; + else if (dist > 0.8) + confidence = '中'; + else + confidence = '低'; + return { + score, + verdict, + confidence, + breakdown: { + 得令: +month.score.toFixed(2), + 长生: cs.score, + 得地: +ground.score.toFixed(2), + 得势: +stems.score.toFixed(2), + details: [month.desc, cs.desc, ...ground.desc, ...stems.desc] + } + }; +} diff --git a/calculator/dist/bazi-enrich/wu-xing.js b/calculator/dist/bazi-enrich/wu-xing.js new file mode 100644 index 0000000..e1c6c22 --- /dev/null +++ b/calculator/dist/bazi-enrich/wu-xing.js @@ -0,0 +1,48 @@ +"use strict"; +// 五行统计 + 旺相休囚死 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.countWuXing = countWuXing; +exports.wuXingMonthStatus = wuXingMonthStatus; +const tables_1 = require("./tables"); +function countWuXing(siZhu, dayMaster) { + const allWx = ['木', '火', '土', '金', '水']; + const surface = { 木: 0, 火: 0, 土: 0, 金: 0, 水: 0 }; + const withCangGan = { 木: 0, 火: 0, 土: 0, 金: 0, 水: 0 }; + const pillars = ['年', '月', '日', '时']; + for (const p of pillars) { + const { gan, zhi } = siZhu[p]; + surface[tables_1.GAN_WUXING[gan]] += 1; + surface[tables_1.ZHI_WUXING[zhi]] += 1; + withCangGan[tables_1.GAN_WUXING[gan]] += 1; + for (const cg of tables_1.ZHI_CANG_GAN[zhi]) { + const weight = cg.role === '本气' ? 1 : cg.role === '中气' ? 0.5 : 0.3; + withCangGan[tables_1.GAN_WUXING[cg.gan]] += weight; + } + } + const missing = allWx.filter(w => surface[w] === 0); + const maxCount = Math.max(...allWx.map(w => surface[w])); + const strongest = allWx.filter(w => surface[w] === maxCount); + // 每个五行映射到日干视角的十神类 + const shiShenGroups = {}; + const repGanByWx = { 木: '甲', 火: '丙', 土: '戊', 金: '庚', 水: '壬' }; + for (const wx of allWx) { + const ss = (0, tables_1.getShiShen)(dayMaster, repGanByWx[wx]); + // 归类: 比劫/食伤/财/官杀/印 + let group; + if (ss === '比肩' || ss === '劫财') + group = '比劫'; + else if (ss === '食神' || ss === '伤官') + group = '食伤'; + else if (ss === '偏财' || ss === '正财') + group = '财'; + else if (ss === '七杀' || ss === '正官') + group = '官杀'; + else + group = '印'; + shiShenGroups[wx] = { 十神类: group, 实例数: surface[wx] }; + } + return { surface, withCangGan, missing, strongest, shiShenGroups }; +} +function wuXingMonthStatus(monthZhi) { + return (0, tables_1.getWuXingMonthStatus)(monthZhi); +} diff --git a/calculator/dist/bazi-enrich/zhi-relations.js b/calculator/dist/bazi-enrich/zhi-relations.js new file mode 100644 index 0000000..5e24a5c --- /dev/null +++ b/calculator/dist/bazi-enrich/zhi-relations.js @@ -0,0 +1,155 @@ +"use strict"; +// 地支关系检测 — 刑冲合害、三合三会、暗合、拱合拱会 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.detectZhiRelations = detectZhiRelations; +const tables_1 = require("./tables"); +// 六冲 +const LIU_CHONG = [ + ['子', '午'], ['丑', '未'], ['寅', '申'], ['卯', '酉'], ['辰', '戌'], ['巳', '亥'] +]; +// 六合 +const LIU_HE = [ + ['子', '丑'], ['寅', '亥'], ['卯', '戌'], ['辰', '酉'], ['巳', '申'], ['午', '未'] +]; +// 三合局 (申子辰水 / 亥卯未木 / 寅午戌火 / 巳酉丑金) +const SAN_HE = [ + { zhi: ['申', '子', '辰'], wuxing: '水' }, + { zhi: ['亥', '卯', '未'], wuxing: '木' }, + { zhi: ['寅', '午', '戌'], wuxing: '火' }, + { zhi: ['巳', '酉', '丑'], wuxing: '金' } +]; +// 三会方 (寅卯辰东方木 / 巳午未南方火 / 申酉戌西方金 / 亥子丑北方水) +const SAN_HUI = [ + { zhi: ['寅', '卯', '辰'], wuxing: '木' }, + { zhi: ['巳', '午', '未'], wuxing: '火' }, + { zhi: ['申', '酉', '戌'], wuxing: '金' }, + { zhi: ['亥', '子', '丑'], wuxing: '水' } +]; +// 相刑 (无礼之刑 子卯; 恃势之刑 寅巳申; 无恩之刑 丑戌未) +const XIANG_XING = [ + { zhi: ['子', '卯'], name: '无礼之刑' }, + { zhi: ['寅', '巳', '申'], name: '恃势之刑' }, + { zhi: ['丑', '戌', '未'], name: '无恩之刑' } +]; +const ZI_XING = ['辰', '午', '酉', '亥']; // 自刑 +// 六害 +const LIU_HAI = [ + ['子', '未'], ['丑', '午'], ['寅', '巳'], ['卯', '辰'], ['申', '亥'], ['酉', '戌'] +]; +// 暗合 — 地支藏干之间相合 +// 天干五合: 甲己 乙庚 丙辛 丁壬 戊癸 +const GAN_HE = { + 甲: '己', 己: '甲', 乙: '庚', 庚: '乙', 丙: '辛', 辛: '丙', 丁: '壬', 壬: '丁', 戊: '癸', 癸: '戊' +}; +function detectZhiRelations(zhis) { + const out = []; + const pillars = ['年', '月', '日', '时']; + const list = pillars.map(p => ({ pillar: p, zhi: zhis[p] })); + // 两两组合 + const pairs = []; + for (let i = 0; i < list.length; i++) { + for (let j = i + 1; j < list.length; j++) { + pairs.push([list[i], list[j]]); + } + } + // 六冲 + for (const [a, b] of pairs) { + for (const [x, y] of LIU_CHONG) { + if ((a.zhi === x && b.zhi === y) || (a.zhi === y && b.zhi === x)) { + out.push({ type: '六冲', zhi: [a.zhi, b.zhi], pillars: [a.pillar, b.pillar] }); + } + } + } + // 六合 + for (const [a, b] of pairs) { + for (const [x, y] of LIU_HE) { + if ((a.zhi === x && b.zhi === y) || (a.zhi === y && b.zhi === x)) { + out.push({ type: '六合', zhi: [a.zhi, b.zhi], pillars: [a.pillar, b.pillar] }); + } + } + } + // 六害 + for (const [a, b] of pairs) { + for (const [x, y] of LIU_HAI) { + if ((a.zhi === x && b.zhi === y) || (a.zhi === y && b.zhi === x)) { + out.push({ type: '六害', zhi: [a.zhi, b.zhi], pillars: [a.pillar, b.pillar] }); + } + } + } + // 相刑 + for (const xing of XIANG_XING) { + const matches = list.filter(p => xing.zhi.includes(p.zhi)); + if (xing.zhi.length === 2) { + // 子卯刑:要两个都在 + const distinct = new Set(matches.map(m => m.zhi)); + if (distinct.size === 2) { + out.push({ type: '相刑', zhi: [...distinct], pillars: matches.map(m => m.pillar), detail: xing.name }); + } + } + else { + // 三刑:两个就构成半刑(实际三刑要全) + const distinct = new Set(matches.map(m => m.zhi)); + if (distinct.size >= 2) { + out.push({ type: '相刑', zhi: [...distinct], pillars: matches.map(m => m.pillar), detail: xing.name + (distinct.size === 3 ? '(全)' : '(半)') }); + } + } + } + // 自刑 + for (const zx of ZI_XING) { + const matches = list.filter(p => p.zhi === zx); + if (matches.length >= 2) { + out.push({ type: '自刑', zhi: [zx, zx], pillars: matches.map(m => m.pillar) }); + } + } + // 三合 / 三会 / 半合 / 拱合 / 拱会 + for (const sh of SAN_HE) { + const present = list.filter(p => sh.zhi.includes(p.zhi)); + const distinctZhi = [...new Set(present.map(p => p.zhi))]; + if (distinctZhi.length === 3) { + out.push({ type: '三合', zhi: distinctZhi, pillars: present.map(p => p.pillar), detail: `三合${sh.wuxing}局` }); + } + else if (distinctZhi.length === 2) { + // 缺一支 → 拱合 + const missing = sh.zhi.find(z => !distinctZhi.includes(z)); + out.push({ type: '拱合', zhi: distinctZhi, pillars: present.map(p => p.pillar), detail: `拱合${missing}(${sh.wuxing}局)` }); + } + } + for (const sh of SAN_HUI) { + const present = list.filter(p => sh.zhi.includes(p.zhi)); + const distinctZhi = [...new Set(present.map(p => p.zhi))]; + if (distinctZhi.length === 3) { + out.push({ type: '三会', zhi: distinctZhi, pillars: present.map(p => p.pillar), detail: `三会${sh.wuxing}方` }); + } + else if (distinctZhi.length === 2) { + const missing = sh.zhi.find(z => !distinctZhi.includes(z)); + out.push({ type: '拱会', zhi: distinctZhi, pillars: present.map(p => p.pillar), detail: `拱会${missing}(${sh.wuxing}方)` }); + } + } + // 暗合 — 地支藏干相合 + // 规则: 仅在该地支对没有其他显式关系(六冲/六合/六害/三合/三会/拱合/拱会/相刑)时才报暗合 + const explicitTypes = ['六冲', '六合', '六害', '三合', '三会', '拱合', '拱会', '相刑', '自刑']; + function hasExplicit(za, zb) { + return out.some(r => explicitTypes.includes(r.type) && + r.zhi.includes(za) && r.zhi.includes(zb)); + } + for (const [a, b] of pairs) { + if (a.zhi === b.zhi) + continue; + if (hasExplicit(a.zhi, b.zhi)) + continue; + const aGans = tables_1.ZHI_CANG_GAN[a.zhi].map(c => c.gan); + const bGans = tables_1.ZHI_CANG_GAN[b.zhi].map(c => c.gan); + const hits = []; + for (const ag of aGans) { + for (const bg of bGans) { + if (GAN_HE[ag] === bg) { + hits.push(`${ag}${bg}合`); + } + } + } + if (hits.length > 0) { + out.push({ type: '暗合', zhi: [a.zhi, b.zhi], pillars: [a.pillar, b.pillar], detail: hits.join('、') }); + } + } + return out; +} diff --git a/calculator/dist/dump-text.js b/calculator/dist/dump-text.js new file mode 100644 index 0000000..7c12d8b --- /dev/null +++ b/calculator/dist/dump-text.js @@ -0,0 +1,256 @@ +"use strict"; +// 把 run-chart.ts 的 JSON 输出转成文墨天机风格树状文本 +// 用法: +// npx tsx dump-text.ts --input=chart.json [--output=chart.txt] +// 不指定 --output 则打印到 stdout +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __importStar(require("fs")); +function parseArgs() { + const args = {}; + for (const a of process.argv.slice(2)) { + const m = a.match(/^--([^=]+)=(.*)$/); + if (m) + args[m[1]] = m[2]; + } + return args; +} +function padRight(s, n) { + // 中文字符按宽度 2 计算 + let w = 0; + for (const ch of s) + w += /[一-龥＀-￿]/.test(ch) ? 2 : 1; + return s + ' '.repeat(Math.max(0, n - w)); +} +function dumpZiwei(z, bi) { + const lines = []; + lines.push('紫微斗数命盘'); + lines.push('│'); + lines.push('├基本信息'); + lines.push(`│ ├性别 : ${bi.gender === 'male' ? '男' : '女'}`); + lines.push(`│ ├阳历 : ${bi.year}-${String(bi.month).padStart(2, '0')}-${String(bi.day).padStart(2, '0')} ${String(bi.hour).padStart(2, '0')}:${String(bi.minute).padStart(2, '0')}`); + if (z.lunarDate) { + lines.push(`│ ├农历 : ${z.lunarDate.year}年${z.lunarDate.monthCn}月${z.lunarDate.dayCn}`); + } + if (z.siZhu) { + const sz = z.siZhu; + lines.push(`│ ├节气四柱 : ${sz.year.gan}${sz.year.zhi} ${sz.month.gan}${sz.month.zhi} ${sz.day.gan}${sz.day.zhi} ${sz.hour.gan}${sz.hour.zhi}`); + } + lines.push(`│ ├阴阳 : ${z.yinYang || ''}`); + lines.push(`│ ├五行局 : ${z.wuXingJu?.name || ''}`); + const DIZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; + const mingDizhi = z.gongs[0]?.dizhi; + const shenDizhi = DIZHI[z.shenGongIndex]; + lines.push(`│ └命宫=${mingDizhi} 身宫=${shenDizhi}`); + lines.push('│'); + // 生年四化汇总 + const allSihua = []; + for (const g of z.gongs) { + for (const s of g.sihua || []) { + allSihua.push(`${s.star}${s.hua}`); + } + } + if (allSihua.length > 0) { + lines.push('├生年四化'); + lines.push(`│ └${allSihua.join(' · ')}`); + lines.push('│'); + } + // 十二宫 + lines.push('├命盘十二宫'); + z.gongs.forEach((g, idx) => { + const isLast = idx === z.gongs.length - 1; + const prefix = isLast ? '│ └' : '│ ├'; + const childPrefix = isLast ? '│ ' : '│ │ '; + const isMing = g.gong === '命宫'; + const isShen = g.dizhi === shenDizhi; + const marks = []; + if (isMing) + marks.push('[命]'); + if (isShen && !isMing) + marks.push('[身]'); + const gongName = g.gong.endsWith('宫') ? g.gong : g.gong + '宫'; + lines.push(`${prefix}${gongName}[${g.tiangan}${g.dizhi}]${marks.join('')}`); + const main = g.mainStars && g.mainStars.length > 0 ? g.mainStars.join('·') : '无主星'; + lines.push(`${childPrefix}├主星 : ${main}`); + const aux = g.auxStars && g.auxStars.length > 0 ? g.auxStars.join('·') : '无'; + lines.push(`${childPrefix}├辅星 : ${aux}`); + if (g.sihua && g.sihua.length > 0) { + lines.push(`${childPrefix}├生年四化 : ${g.sihua.map((s) => s.star + s.hua).join('·')}`); + } + if (g.daXian) { + const dxMark = g.daXian.isCurrent ? '★当前' : ''; + lines.push(`${childPrefix}├大限 : ${g.daXian.startAge}-${g.daXian.endAge}虚岁 ${dxMark}`); + } + if (g.liuNian && g.liuNian.length > 0) { + lines.push(`${childPrefix}└流年 : ${g.liuNian.join('·')}虚岁`); + } + if (!isLast) + lines.push('│ │'); + }); + return lines; +} +function dumpBazi(b, bi) { + const lines = []; + lines.push(''); + lines.push('八字命盘'); + lines.push('│'); + // 四柱表 + const sz = b.siZhu; + const ss = b.shiShen; + const zs = b.zhangSheng || {}; + const zz = b.enrichment?.自坐 || {}; + const ny = b.naYin || {}; + const cg = b.cangGan || {}; + lines.push('├四柱'); + const cols = ['年', '月', '日', '时']; + const pillarKeys = ['year', 'month', 'day', 'hour']; + const cangGanFmt = (pk) => { + const arr = cg[pk]; + if (!Array.isArray(arr)) + return ''; + return arr.map((x) => `${x.gan}(${x.shiShen || ''})`).join(' '); + }; + for (let i = 0; i < 4; i++) { + const isLast = i === 3; + const pre = isLast ? '│ └' : '│ ├'; + const subPre = isLast ? '│ ' : '│ │ '; + const pk = pillarKeys[i]; + const sx = ss[pk] || ''; + const isDay = pk === 'day'; + const tag = isDay ? `[日主]` : `[${sx}]`; + lines.push(`${pre}${cols[i]}柱 : ${sz[pk].gan}${sz[pk].zhi} ${tag}`); + if (cg[pk]) + lines.push(`${subPre}├藏干 : ${cangGanFmt(pk)}`); + lines.push(`${subPre}├星运 : ${zs[pk] || '-'}`); + lines.push(`${subPre}├自坐 : ${zz[cols[i]] || zz[pk] || '-'}`); + lines.push(`${subPre}└纳音 : ${ny[pk] || '-'}`); + if (!isLast) + lines.push('│ │'); + } + lines.push('│'); + // 大运 + if (b.dayun && b.dayun.length > 0) { + lines.push(`├大运 (起运 ${b.dayunStart}岁)`); + b.dayun.slice(0, 10).forEach((d, i) => { + const isLast = i === Math.min(9, b.dayun.length - 1); + const pre = isLast ? '│ └' : '│ ├'; + const dxTag = `${d.ganShiShen || ''}/${d.zhiShiShen || ''}`; + lines.push(`${pre}${d.startYear}-${d.endYear} ${d.ganZhi.gan}${d.ganZhi.zhi} (${dxTag})`); + }); + lines.push('│'); + } + // enrichBazi 补层 + const en = b.enrichment; + if (en) { + lines.push('├算法补层'); + lines.push(`│ ├格局 : ${en.格局?.primary || '-'} (置信度: ${en.格局?.confidence || '-'})`); + if (en.格局?.basis) + lines.push(`│ │ └依据 : ${en.格局.basis}`); + if (en.格局?.notes && en.格局.notes.length) { + for (const note of en.格局.notes) + lines.push(`│ │ └备注 : ${note}`); + } + const ws = en.旺衰; + if (ws) { + const lvl = ws.verdict || ws.level || '-'; + const score = ws.score !== undefined ? `score=${ws.score}` : ''; + lines.push(`│ ├旺衰 : ${lvl} (${score}, 置信度: ${ws.confidence || '-'})`); + if (ws.breakdown) { + const b = ws.breakdown; + lines.push(`│ │ └四维 : 得令${b.得令} 长生${b.长生} 得地${b.得地} 得势${b.得势}`); + } + } + if (en.调候用神) + lines.push(`│ ├调候用神 : ${en.调候用神.join('、')}`); + if (en.五行旺相) { + const ws5 = en.五行旺相; + lines.push(`│ ├五行旺相 : 木${ws5.木} 火${ws5.火} 土${ws5.土} 金${ws5.金} 水${ws5.水}`); + } + if (en.五行统计) { + const s = en.五行统计.surface || en.五行统计; + const w = en.五行统计.withCangGan; + if (s) + lines.push(`│ ├五行统计(surface) : 木${s.木 || 0} 火${s.火 || 0} 土${s.土 || 0} 金${s.金 || 0} 水${s.水 || 0}`); + if (w) + lines.push(`│ ├五行统计(含藏干) : 木${w.木 || 0} 火${w.火 || 0} 土${w.土 || 0} 金${w.金 || 0} 水${w.水 || 0}`); + } + // 天干关系 + const gr = en.天干关系; + if (gr && Array.isArray(gr) && gr.length > 0) { + lines.push('│ ├天干关系'); + gr.forEach((r, i) => { + const last = i === gr.length - 1; + const pair = (r.gans || []).join(''); + const pillars = (r.pillars || []).join('-'); + lines.push(`│ │ ${last ? '└' : '├'}${r.type} : ${pair} (${pillars}柱)`); + }); + } + // 地支关系 + const zr = en.地支关系; + if (zr && Array.isArray(zr) && zr.length > 0) { + lines.push('│ ├地支关系'); + zr.forEach((r, i) => { + const last = i === zr.length - 1; + const pair = (r.zhi || []).join(''); + const pillars = (r.pillars || []).join('-'); + const extra = r.detail ? ` ${r.detail}` : ''; + lines.push(`│ │ ${last ? '└' : '├'}${r.type} : ${pair} (${pillars}柱)${extra}`); + }); + } + // 整柱 + const zp = en.整柱; + if (zp && Array.isArray(zp) && zp.length > 0) { + lines.push('│ └整柱判定'); + zp.forEach((p, i) => { + const last = i === zp.length - 1; + lines.push(`│ ${last ? '└' : '├'}${p.pillar}柱 ${p.gan}${p.zhi} : ${p.verdict}`); + }); + } + } + lines.push(''); + lines.push('└[备注: 本盘由 bazi-ziwei skill 算法层生成 — Yiqi core + enrichBazi 补层]'); + return lines; +} +function main() { + const args = parseArgs(); + if (!args.input) { + console.error('Usage: npx tsx dump-text.ts --input=chart.json [--output=chart.txt]'); + process.exit(1); + } + const chart = JSON.parse(fs.readFileSync(args.input, 'utf-8')); + const bi = chart.bazi.birthInfo || chart.ziwei.birthInfo; + const lines = []; + lines.push(...dumpZiwei(chart.ziwei, bi)); + lines.push(...dumpBazi(chart.bazi, bi)); + const text = lines.join('\n'); + if (args.output) { + fs.writeFileSync(args.output, text, 'utf-8'); + console.error(`Text dump written to ${args.output}`); + } + else { + process.stdout.write(text); + } +} +main(); diff --git a/calculator/dist/render.js b/calculator/dist/render.js new file mode 100644 index 0000000..8ea45ea --- /dev/null +++ b/calculator/dist/render.js @@ -0,0 +1,351 @@ +"use strict"; +// 渲染脚本: 算法 JSON + 分析 JSON + 模板 → 单文件 HTML +// +// 用法: +// npx tsx render.ts \ +// --chart=path/to/chart.json \ +// --analysis=path/to/analysis.json \ +// --template=../templates/report-zonghe-poster.html \ +// --output=path/to/output.html +// +// chart.json: run-chart.ts 的输出 (算法层) +// analysis.json: LLM 按 zonghe-poster.md schema 输出的 JSON +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __importStar(require("fs")); +function parseArgs() { + const args = {}; + for (const a of process.argv.slice(2)) { + const m = a.match(/^--([^=]+)=(.*)$/); + if (m) + args[m[1]] = m[2]; + } + return args; +} +const DIZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; +function calcVirtualAge(birthYear, currentYear) { + return currentYear - birthYear + 1; +} +function chartToFlat(chart, currentYear) { + const out = {}; + const bi = chart.bazi.birthInfo; + const bz = chart.bazi; + const zw = chart.ziwei; + currentYear = currentYear || new Date().getFullYear(); + const virtualAge = calcVirtualAge(bi.year, currentYear); + // ============ META ============ + out['meta.solar_date'] = `${bi.year}-${String(bi.month).padStart(2, '0')}-${String(bi.day).padStart(2, '0')} ${String(bi.hour).padStart(2, '0')}:${String(bi.minute).padStart(2, '0')}`; + if (zw.lunarDate) { + out['meta.lunar_date'] = `${zw.lunarDate.year}年 ${zw.lunarDate.monthCn}月${zw.lunarDate.dayCn} ${zw.lunarDate.hourCn || ''}`.trim(); + } + else { + out['meta.lunar_date'] = '-'; + } + out['meta.gender_full'] = bi.gender === 'male' ? '男(' + (zw.yinYang || '') + ')' : '女(' + (zw.yinYang || '') + ')'; + out['meta.age_virtual'] = virtualAge.toString(); + out['meta.current_year'] = currentYear.toString(); + const now = new Date(); + out['meta.gen_time'] = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; + out['meta.yinyang'] = zw.yinYang || '-'; + // ============ ZIWEI META ============ + // Yiqi 没明确输出 命主/身主/子年斗君 — 从十二宫推导 / 留空 + // 简化: 默认根据命宫地支查命主, 身宫地支查身主 + const MING_ZHU = { '子': '贪狼', '丑': '巨门', '寅': '禄存', '卯': '文曲', '辰': '廉贞', '巳': '武曲', '午': '破军', '未': '武曲', '申': '廉贞', '酉': '文曲', '戌': '禄存', '亥': '巨门' }; + const SHEN_ZHU = { '子': '火星', '丑': '天相', '寅': '天梁', '卯': '天同', '辰': '文昌', '巳': '天机', '午': '火星', '未': '天相', '申': '天梁', '酉': '天同', '戌': '文昌', '亥': '天机' }; + const mingDizhi = zw.gongs[0].dizhi; + const shenDizhi = DIZHI[zw.shenGongIndex]; + out['ziwei.ming_zhu'] = MING_ZHU[mingDizhi] || '-'; + out['ziwei.shen_zhu'] = SHEN_ZHU[shenDizhi] || '-'; + // 子年斗君: 简化处理, 按生月+生时推算复杂, 暂用身宫前后位作占位 + out['ziwei.zi_dou_jun'] = zw.ziDouJun || '-'; + out['ziwei.wuxing_ju'] = zw.wuXingJu?.name || '-'; + // ============ CORE DATA ============ + const en = bz.enrichment; + out['core.geju'] = en?.格局?.primary || '-'; + out['core.geju_confidence'] = en?.格局?.confidence || '-'; + out['core.wangshuai_verdict'] = en?.旺衰?.verdict || '-'; + out['core.wangshuai_score'] = en?.旺衰?.score?.toString() || '-'; + // 把 score 映射到 0-100% (假设 score -10 ~ +10) + const ws = en?.旺衰?.score ?? 0; + out['core.wangshuai_pos_pct'] = Math.max(0, Math.min(100, Math.round((ws + 10) * 5))).toString(); + const tc = en?.调候用神 || []; + out['core.tiaohou.0'] = tc[0] || '-'; + out['core.tiaohou.1'] = tc[1] || '-'; + out['core.tiaohou_confidence'] = '高'; + const yl = en?.五行旺相 || {}; + for (const k of ['木', '火', '土', '金', '水']) { + out[`core.yueling.${k}`] = yl[k] || '-'; + } + const wx = en?.五行统计?.withCangGan || en?.五行统计 || { 木: 0, 火: 0, 土: 0, 金: 0, 水: 0 }; + for (const k of ['木', '火', '土', '金', '水']) + out[`core.wuxing.${k}`] = wx[k] ?? '-'; + const wxMax = Math.max(...['木', '火', '土', '金', '水'].map(k => +wx[k] || 0)) || 1; + for (const k of ['木', '火', '土', '金', '水']) + out[`core.wuxing_pct.${k}`] = Math.round(((+wx[k] || 0) / wxMax) * 100); + // ============ ZIWEI 12 GONGS ============ + const sihuaCharMap = { 化禄: '禄', 化权: '权', 化科: '科', 化忌: '忌' }; + for (const g of zw.gongs) { + const mainStarsHtml = (g.mainStars && g.mainStars.length > 0) + ? g.mainStars.map((s) => { + const sh = (g.sihua || []).find((x) => x.star === s); + if (sh) { + const huaChar = sihuaCharMap[sh.hua] || sh.hua.slice(-1); + return `${s}${huaChar}`; + } + return s; + }).join('·') + : '无主星'; + // 辅星同样要处理四化(右弼化科 / 文昌化忌 / 文曲化科 等常落辅星) + const auxStarsHtml = (g.auxStars && g.auxStars.length > 0) + ? g.auxStars.map((s) => { + const sh = (g.sihua || []).find((x) => x.star === s); + if (sh) { + const huaChar = sihuaCharMap[sh.hua] || sh.hua.slice(-1); + return `${s}${huaChar}`; + } + return s; + }).join('·') + : '—'; + out[`gongs.${g.dizhi}.name`] = g.gong.endsWith('宫') ? g.gong : g.gong + '宫'; + out[`gongs.${g.dizhi}.ganzhi`] = g.tiangan + g.dizhi; + out[`gongs.${g.dizhi}.mainStarsHtml`] = mainStarsHtml; + out[`gongs.${g.dizhi}.auxStars`] = auxStarsHtml; + out[`gongs.${g.dizhi}.smallStars`] = ''; + out[`gongs.${g.dizhi}.daxian_range`] = g.daXian ? `${g.daXian.startAge}-${g.daXian.endAge}` : '-'; + // 命宫红框 / 身宫徽标 / 当前大限高亮 — 数据驱动, 不硬编码到模板 + const flags = []; + if (g.dizhi === mingDizhi) + flags.push('ming'); + if (g.dizhi === shenDizhi) + flags.push('shen'); + if (g.daXian && g.daXian.startAge <= virtualAge && virtualAge <= g.daXian.endAge) + flags.push('current-daxian'); + out[`gongs.${g.dizhi}.flag`] = flags.join(' '); + out[`gongs.${g.dizhi}.shenBadge`] = (g.dizhi === shenDizhi) ? '' : ''; + } + // ============ BAZI 4 PILLARS ============ + const cangGanFmt = (arr) => (arr || []).map((x) => `${x.gan}(${x.shiShen})`).join(' '); + const pillarKeyToCn = { year: '年', month: '月', day: '日', hour: '时' }; + for (const k of ['year', 'month', 'day', 'hour']) { + out[`bazi.${k}.shiShen`] = bz.shiShen?.[k] || '-'; + out[`bazi.${k}.gan`] = bz.siZhu[k].gan; + out[`bazi.${k}.zhi`] = bz.siZhu[k].zhi; + out[`bazi.${k}.cangGanHtml`] = cangGanFmt(bz.cangGan?.[k] || []); + out[`bazi.${k}.zhangSheng`] = bz.zhangSheng?.[k] || '-'; + out[`bazi.${k}.ziZuo`] = en?.自坐?.[pillarKeyToCn[k]] || en?.自坐?.[k] || '-'; + out[`bazi.${k}.naYin`] = bz.naYin?.[k] || '-'; + } + out['bazi.dayunStart'] = bz.dayunStart?.toString() || '-'; + // ============ DAYUN 10 ============ + const dayunArr = (bz.dayun || []).slice(0, 10); + let currentDayun = null; + for (let i = 0; i < 10; i++) { + const d = dayunArr[i]; + if (d && d.startAge <= virtualAge && virtualAge <= d.endAge) + currentDayun = d; + } + for (let i = 0; i < 10; i++) { + const d = dayunArr[i]; + if (!d) { + ['gz', 'age_range', 'shishen', 'current_class'].forEach(f => out[`dayun.${i}.${f}`] = '-'); + continue; + } + out[`dayun.${i}.gz`] = d.ganZhi.gan + d.ganZhi.zhi; + out[`dayun.${i}.age_range`] = `${d.startAge}-${d.endAge}`; + const sg = (d.ganShiShen || '').slice(0, 1); + const sz = (d.zhiShiShen || '').slice(0, 1); + out[`dayun.${i}.shishen`] = sg + sz; + out[`dayun.${i}.current_class`] = (currentDayun && d === currentDayun) ? 'current dayun' : ''; + } + // ============ SECTION 02 阶段印证时间轴 (从 chart 算, 不靠 LLM) ============ + // 八字大运: 前 7 段 + const dayunForStage = dayunArr.slice(0, 7); + for (let i = 0; i < 7; i++) { + const d = dayunForStage[i]; + if (!d) { + ['range', 'gz', 'shishen', 'current_class'].forEach(f => out[`section_02.bazi.${i}.${f}`] = '-'); + continue; + } + out[`section_02.bazi.${i}.range`] = `${d.startAge}-${d.endAge}`; + out[`section_02.bazi.${i}.gz`] = d.ganZhi.gan + d.ganZhi.zhi; + const sg = (d.ganShiShen || '').slice(0, 1); + const sz = (d.zhiShiShen || '').slice(0, 1); + out[`section_02.bazi.${i}.shishen`] = sg + sz; + out[`section_02.bazi.${i}.current_class`] = (d.startAge <= virtualAge && virtualAge <= d.endAge) ? 'current' : ''; + } + // 紫微大限: 按 startAge 排序取前 7 段 + const ziweiDaxian = zw.gongs + .filter((g) => g.daXian) + .map((g) => ({ startAge: g.daXian.startAge, endAge: g.daXian.endAge, gong: g.gong })) + .sort((a, b) => a.startAge - b.startAge) + .slice(0, 7); + for (let i = 0; i < 7; i++) { + const d = ziweiDaxian[i]; + if (!d) { + ['range', 'current_class'].forEach(f => out[`section_02.ziwei.${i}.${f}`] = '-'); + continue; + } + out[`section_02.ziwei.${i}.range`] = `${d.startAge}-${d.endAge}`; + out[`section_02.ziwei.${i}.current_class`] = (d.startAge <= virtualAge && virtualAge <= d.endAge) ? 'current' : ''; + } + // ============ LIUNIAN 10 (current dayun) ============ + if (currentDayun) { + out['liunian_dayun_label'] = `${currentDayun.ganZhi.gan}${currentDayun.ganZhi.zhi} ${currentDayun.startAge}-${currentDayun.endAge}`; + } + else { + out['liunian_dayun_label'] = '-'; + } + const liunianArr = ((currentDayun?.liuNian) || []).slice(0, 10); + for (let i = 0; i < 10; i++) { + const ln = liunianArr[i]; + if (!ln) { + ['year', 'age', 'gz', 'shishen', 'current_class'].forEach(f => out[`liunian.${i}.${f}`] = '-'); + continue; + } + out[`liunian.${i}.year`] = ln.year; + out[`liunian.${i}.age`] = ln.age; + out[`liunian.${i}.gz`] = ln.ganZhi.gan + ln.ganZhi.zhi; + out[`liunian.${i}.shishen`] = ln.ganShiShen ? (ln.ganShiShen.slice(0, 1) + (ln.zhiShiShen?.slice(0, 1) || '')) : ''; + out[`liunian.${i}.current_class`] = (ln.age === virtualAge) ? 'current' : ''; + } + return out; +} +function analysisToFlat(analysis) { + const out = {}; + // meta + if (analysis.meta) { + out['meta.archetype_name'] = analysis.meta.archetype_name; + out['meta.axis_oneliner'] = analysis.meta.axis_oneliner; + } + // axes + consistency + if (analysis.axes) { + out['axes.bazi_main'] = analysis.axes.bazi_main; + out['axes.ziwei_main'] = analysis.axes.ziwei_main; + } + if (analysis.consistency) + out['ziwei.consistency'] = analysis.consistency; + // strengths / weaknesses + for (let i = 0; i < 3; i++) { + const s = analysis.strengths?.[i] || {}; + out[`strengths.${i}.title`] = s.title || '-'; + out[`strengths.${i}.desc`] = s.desc || '-'; + const w = analysis.weaknesses?.[i] || {}; + out[`weaknesses.${i}.title`] = w.title || '-'; + out[`weaknesses.${i}.desc`] = w.desc || '-'; + } + // section 01 + if (analysis.section_01) { + out['section_01.text'] = analysis.section_01.text || '-'; + out['section_01.word_count'] = analysis.section_01.word_count || '-'; + } + // section 02 - bazi/ziwei dayun ranges already from chart, only conclusion + if (analysis.section_02) { + out['section_02.conclusion'] = analysis.section_02.conclusion || '-'; + } + // dim + const dims = ['career', 'wealth', 'marriage', 'children', 'family', 'health']; + for (const k of dims) { + const d = analysis.dim?.[k] || {}; + out[`dim.${k}.bazi`] = d.bazi || '-'; + out[`dim.${k}.ziwei`] = d.ziwei || '-'; + out[`dim.${k}.verdict`] = d.verdict || '-'; + out[`dim.${k}.verdict_class`] = d.verdict_class || 'verdict-yes'; + out[`dim.${k}.fused`] = d.fused || '-'; + } + // conflicts + for (let i = 0; i < 3; i++) { + const c = analysis.conflicts?.[i] || {}; + out[`conflicts.${i}.point`] = c.point || '-'; + out[`conflicts.${i}.bazi`] = c.bazi || '-'; + out[`conflicts.${i}.ziwei`] = c.ziwei || '-'; + out[`conflicts.${i}.impact`] = c.impact || '-'; + out[`conflicts.${i}.impact_class`] = c.impact_class || 'low'; + out[`conflicts.${i}.advice`] = c.advice || '-'; + } + // final + if (analysis.final) { + out['final.life_axis'] = analysis.final.life_axis || '-'; + for (let i = 0; i < 5; i++) { + const n = analysis.final.nodes?.[i] || {}; + out[`final.nodes.${i}.age`] = n.age || '-'; + out[`final.nodes.${i}.year`] = n.year || '-'; + out[`final.nodes.${i}.event`] = n.event || '-'; + } + for (let i = 0; i < 3; i++) { + const r = analysis.final.risks?.[i] || {}; + out[`final.risks.${i}.range`] = r.range || '-'; + out[`final.risks.${i}.desc`] = r.desc || '-'; + } + for (let i = 0; i < 2; i++) { + const l = analysis.final.leverage?.[i] || {}; + out[`final.leverage.${i}.title`] = l.title || '-'; + out[`final.leverage.${i}.desc`] = l.desc || '-'; + } + for (let i = 0; i < 4; i++) + out[`final.advice.${i}`] = analysis.final.advice?.[i] || '-'; + } + // confidence + if (analysis.confidence) { + for (const k of ['bazi', 'ziwei', 'consistency', 'stability']) { + out[`confidence.${k}_level`] = analysis.confidence[`${k}_level`] || '-'; + out[`confidence.${k}_score`] = analysis.confidence[`${k}_score`] || '-'; + } + out['confidence.note'] = analysis.confidence.note || '-'; + } + return out; +} +function renderTemplate(template, data) { + let html = template; + // 第一轮: 精确替换 + for (const k of Object.keys(data)) { + const re = new RegExp(`\\{\\{${k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\}\\}`, 'g'); + html = html.replace(re, String(data[k])); + } + // 兜底: 剩余未匹配占位符替换为 '-' + html = html.replace(/\{\{[a-zA-Z0-9_.]+\}\}/g, '-'); + return html; +} +function main() { + const args = parseArgs(); + if (!args.chart || !args.template) { + console.error('Usage: npx tsx render.ts --chart=chart.json [--analysis=analysis.json] --template=path/to/template.html [--output=out.html]'); + process.exit(1); + } + const chart = JSON.parse(fs.readFileSync(args.chart, 'utf-8')); + const analysis = args.analysis ? JSON.parse(fs.readFileSync(args.analysis, 'utf-8')) : {}; + const template = fs.readFileSync(args.template, 'utf-8'); + const chartFlat = chartToFlat(chart, args.currentYear ? +args.currentYear : undefined); + const analysisFlat = analysisToFlat(analysis); + const data = { ...chartFlat, ...analysisFlat }; + const html = renderTemplate(template, data); + if (args.output) { + fs.writeFileSync(args.output, html, 'utf-8'); + console.error(`Rendered HTML written to ${args.output}`); + } + else { + process.stdout.write(html); + } +} +main(); diff --git a/calculator/dist/run-chart.js b/calculator/dist/run-chart.js new file mode 100644 index 0000000..523e7f7 --- /dev/null +++ b/calculator/dist/run-chart.js @@ -0,0 +1,103 @@ +"use strict"; +// 排盘单一入口 — 输入生辰, 输出完整 JSON (Yiqi createChart + enrichBazi) +// +// 用法: +// npx tsx run-chart.ts --year=2000 --month=1 --day=1 --hour=12 --minute=0 --gender=male +// 可选: --isLunar=true --timeZone=8 --output=path/to/file.json +// +// 不指定 --output 则打印到 stdout +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const index_1 = require("./yiqi-core/index"); +const bazi_1 = require("./yiqi-core/bazi"); +const enrich_1 = require("./bazi-enrich/enrich"); +const fs = __importStar(require("fs")); +function parseArgs() { + const args = {}; + for (const a of process.argv.slice(2)) { + const m = a.match(/^--([^=]+)=(.*)$/); + if (m) + args[m[1]] = m[2]; + } + return args; +} +function main() { + const args = parseArgs(); + const required = ['year', 'month', 'day', 'hour', 'minute', 'gender']; + for (const k of required) { + if (!args[k]) { + console.error(`Missing required arg: --${k}=...`); + console.error('Usage: npx tsx run-chart.ts --year=2000 --month=1 --day=1 --hour=12 --minute=0 --gender=male'); + process.exit(1); + } + } + const gender = args.gender === 'male' || args.gender === 'female' ? args.gender : (args.gender === '男' ? 'male' : 'female'); + const birthInfo = { + year: +args.year, + month: +args.month, + day: +args.day, + hour: +args.hour, + minute: +args.minute, + isLunar: args.isLunar === 'true', + gender: gender, + timeZone: args.timeZone ? +args.timeZone : 8, + }; + // Step 1: Yiqi 算法层 — 四柱+紫微+大运+流年 + const chart = (0, index_1.createChart)(birthInfo); + // 附加地支藏干 (含十神) + const dm = chart.bazi.dayMaster; + const z = chart.bazi.siZhu; + chart.bazi.cangGan = { + year: (0, bazi_1.getZhiCangGanFull)(z.year.zhi, dm), + month: (0, bazi_1.getZhiCangGanFull)(z.month.zhi, dm), + day: (0, bazi_1.getZhiCangGanFull)(z.day.zhi, dm), + hour: (0, bazi_1.getZhiCangGanFull)(z.hour.zhi, dm), + }; + // 补 endAge 字段 (Yiqi 只给了 startAge/endYear, OpenClaw 等下游脚本会查 endAge) + if (chart.bazi.dayun && Array.isArray(chart.bazi.dayun)) { + for (const d of chart.bazi.dayun) { + if (d.startAge !== undefined && d.endAge === undefined) { + d.endAge = d.startAge + 9; + } + } + } + // Step 2: enrichBazi 补层 — 格局/旺衰/调候/刑冲合害/盖头 + const siZhuForEnrich = { + '年': chart.bazi.siZhu.year, + '月': chart.bazi.siZhu.month, + '日': chart.bazi.siZhu.day, + '时': chart.bazi.siZhu.hour, + }; + chart.bazi.enrichment = (0, enrich_1.enrichBazi)(siZhuForEnrich); + const json = JSON.stringify(chart, null, 2); + if (args.output) { + fs.writeFileSync(args.output, json, 'utf-8'); + console.error(`Chart written to ${args.output}`); + } + else { + process.stdout.write(json); + } +} +main(); diff --git a/calculator/dist/yiqi-core/bazi.js b/calculator/dist/yiqi-core/bazi.js new file mode 100644 index 0000000..1165061 --- /dev/null +++ b/calculator/dist/yiqi-core/bazi.js @@ -0,0 +1,319 @@ +"use strict"; +// 八字排盘核心算法(使用lunar-javascript库) +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getZhiCangGanFull = getZhiCangGanFull; +exports.createBaziChart = createBaziChart; +exports.runBaziTests = runBaziTests; +const lunar_typescript_1 = require("lunar-typescript"); +const zhangsheng_1 = require("./zhangsheng"); +const nayin_1 = require("./nayin"); +const jieqi_1 = require("./jieqi"); +/** + * 十神关系映射 + */ +const SHISHEN_MAP = { + '甲': { '甲': '比肩', '乙': '劫财', '丙': '食神', '丁': '伤官', '戊': '偏财', '己': '正财', '庚': '七杀', '辛': '正官', '壬': '偏印', '癸': '正印' }, + '乙': { '甲': '劫财', '乙': '比肩', '丙': '伤官', '丁': '食神', '戊': '正财', '己': '偏财', '庚': '正官', '辛': '七杀', '壬': '正印', '癸': '偏印' }, + '丙': { '甲': '偏印', '乙': '正印', '丙': '比肩', '丁': '劫财', '戊': '食神', '己': '伤官', '庚': '偏财', '辛': '正财', '壬': '七杀', '癸': '正官' }, + '丁': { '甲': '正印', '乙': '偏印', '丙': '劫财', '丁': '比肩', '戊': '伤官', '己': '食神', '庚': '正财', '辛': '偏财', '壬': '正官', '癸': '七杀' }, + '戊': { '甲': '七杀', '乙': '正官', '丙': '偏印', '丁': '正印', '戊': '比肩', '己': '劫财', '庚': '食神', '辛': '伤官', '壬': '偏财', '癸': '正财' }, + '己': { '甲': '正官', '乙': '七杀', '丙': '正印', '丁': '偏印', '戊': '劫财', '己': '比肩', '庚': '伤官', '辛': '食神', '壬': '正财', '癸': '偏财' }, + '庚': { '甲': '偏财', '乙': '正财', '丙': '七杀', '丁': '正官', '戊': '偏印', '己': '正印', '庚': '比肩', '辛': '劫财', '壬': '食神', '癸': '伤官' }, + '辛': { '甲': '正财', '乙': '偏财', '丙': '正官', '丁': '七杀', '戊': '正印', '己': '偏印', '庚': '劫财', '辛': '比肩', '壬': '伤官', '癸': '食神' }, + '壬': { '甲': '食神', '乙': '伤官', '丙': '偏财', '丁': '正财', '戊': '七杀', '己': '正官', '庚': '偏印', '辛': '正印', '壬': '比肩', '癸': '劫财' }, + '癸': { '甲': '伤官', '乙': '食神', '丙': '正财', '丁': '偏财', '戊': '正官', '己': '七杀', '庚': '正印', '辛': '偏印', '壬': '劫财', '癸': '比肩' } +}; +/** + * 地支藏干(主气)映射 + */ +const DIZHI_CANGGAN = { + '子': '癸', '丑': '己', '寅': '甲', '卯': '乙', + '辰': '戊', '巳': '丙', '午': '丁', '未': '己', + '申': '庚', '酉': '辛', '戌': '戊', '亥': '壬' +}; +/** + * 地支藏干(完整)映射 - 每个地支包含本气、中气、余气 + */ +const DIZHI_CANGGAN_FULL = { + '子': ['癸'], + '丑': ['己', '癸', '辛'], + '寅': ['甲', '丙', '戊'], + '卯': ['乙'], + '辰': ['戊', '乙', '癸'], + '巳': ['丙', '庚', '戊'], + '午': ['丁', '己'], + '未': ['己', '丁', '乙'], + '申': ['庚', '壬', '戊'], + '酉': ['辛'], + '戌': ['戊', '辛', '丁'], + '亥': ['壬', '甲'] +}; +/** + * 获取十神关系 + */ +function getShiShen(dayGan, targetGan) { + return SHISHEN_MAP[dayGan]?.[targetGan] || '未知'; +} +/** + * 获取地支藏干主气 + */ +function getZhiCangGan(zhi) { + return DIZHI_CANGGAN[zhi] || '未知'; +} +/** + * 获取地支完整藏干(含所有藏干及其十神) + * @param zhi 地支 + * @param dayMaster 日主天干 + * @returns 藏干数组,每项包含天干和对应十神 + */ +function getZhiCangGanFull(zhi, dayMaster) { + const cangGanList = DIZHI_CANGGAN_FULL[zhi] || []; + return cangGanList.map((gan) => ({ + gan, + shiShen: getShiShen(dayMaster, gan) + })); +} +/** + * 处理晚子时(23:00-24:00)的日柱 + * 在传统命理中,晚子时应该使用第二天的日柱 + * @param solar Solar对象 + * @returns 调整后的Solar对象(如果是晚子时) + */ +function getAdjustedSolarForZiShi(solar) { + const hour = solar.getHour(); + // 如果是晚子时(23:00-24:00),使用第二天的日期计算日柱 + if (hour === 23) { + console.error('[晚子时处理] 23点,使用第二天的日柱'); + // 获取第二天的日期 + const nextDay = solar.next(1); // 下一天 + return nextDay; + } + return solar; +} +/** + * 创建八字排盘(使用lunar-javascript库) + * @param birthInfo 生辰信息 + * @returns 八字排盘结果 + */ +function createBaziChart(birthInfo) { + try { + // 创建Solar对象(公历日期) + const solar = lunar_typescript_1.Solar.fromYmdHms(birthInfo.year, birthInfo.month, birthInfo.day, birthInfo.hour, birthInfo.minute, 0); + // 处理晚子时(23:00-24:00),使用第二天的日柱 + const solarForDay = getAdjustedSolarForZiShi(solar); + const lunarForDay = solarForDay.getLunar(); + const baZiForDay = lunarForDay.getEightChar(); + // 转换为农历(用于年柱、月柱、时柱) + const lunar = solar.getLunar(); + // 获取八字 + const baZi = lunar.getEightChar(); + // 使用精确节气时刻计算月柱 + const accurateMonthGZ = (0, jieqi_1.getAccurateMonthGanZhi)(solar); + // 获取四柱干支 + const siZhu = { + year: { + gan: baZi.getYear().substring(0, 1), + zhi: baZi.getYear().substring(1, 2) + }, + month: { + gan: accurateMonthGZ.gan, // 使用精确节气计算的月柱 + zhi: accurateMonthGZ.zhi, // 使用精确节气计算的月柱 + }, + day: { + gan: baZiForDay.getDay().substring(0, 1), // 使用调整后的日柱(处理晚子时) + zhi: baZiForDay.getDay().substring(1, 2) // 使用调整后的日柱(处理晚子时) + }, + hour: { + gan: baZi.getTime().substring(0, 1), + zhi: baZi.getTime().substring(1, 2) + } + }; + const dayMaster = siZhu.day.gan; + // 计算十神 + const shiShen = { + year: getShiShen(dayMaster, siZhu.year.gan), + month: getShiShen(dayMaster, siZhu.month.gan), + day: getShiShen(dayMaster, siZhu.day.gan), + hour: getShiShen(dayMaster, siZhu.hour.gan) + }; + // 获取大运(使用lunar-javascript库) + const yun = baZi.getYun(birthInfo.gender === 'male' ? 1 : 0); + const dayunStart = Math.floor(yun.getStartYear()); + // 获取大运列表(跳过第一个,因为它是起运前的状态) + const dayunList = yun.getDaYun().slice(1, 11); // 取第2-11个,共10步大运 + const dayun = dayunList.map((d) => { + const ganZhiStr = d.getGanZhi(); + const ganZhi = { + gan: ganZhiStr.substring(0, 1), + zhi: ganZhiStr.substring(1, 2) + }; + // 计算天干十神 + const ganShiShen = getShiShen(dayMaster, ganZhi.gan); + // 计算地支藏干的十神 + const zhiCangGan = getZhiCangGan(ganZhi.zhi); + const zhiShiShen = getShiShen(dayMaster, zhiCangGan); + // 获取该大运内的流年 + const liuNianList = d.getLiuNian(); + const liuNian = liuNianList.map((ln) => { + const lnGanZhiStr = ln.getGanZhi(); + return { + year: ln.getYear(), + age: ln.getAge(), + ganZhi: { + gan: lnGanZhiStr.substring(0, 1), + zhi: lnGanZhiStr.substring(1, 2) + } + }; + }); + return { + ganZhi, + startAge: d.getStartAge(), + startYear: d.getStartYear(), + endYear: d.getEndYear(), + ganShiShen, + zhiShiShen, + liuNian + }; + }); + // 计算十二长生(星运)- zhangSheng 不是 changSheng + const zhangSheng = (0, zhangsheng_1.getSiZhuChangSheng)(dayMaster, { + year: siZhu.year.zhi, + month: siZhu.month.zhi, + day: siZhu.day.zhi, + hour: siZhu.hour.zhi + }); + // 计算纳音 + const naYin = (0, nayin_1.getSiZhuNaYin)(siZhu); + console.error('🔍 八字计算结果 - zhangSheng:', zhangSheng); + console.error('🔍 八字计算结果 - naYin:', naYin); + return { + birthInfo, + siZhu, + dayMaster, + shiShen, + zhangSheng, + naYin, + dayunStart, + dayun + }; + } + catch (error) { + console.error('创建八字排盘失败:', error); + throw new Error('八字排盘计算失败: ' + error.message); + } +} +/** + * 运行八字测试 + */ +function runBaziTests() { + const testCases = [ + { + name: '1979年5月4日6点男命(己年为阴年,男命逆排)', + input: { year: 1979, month: 5, day: 4, hour: 6, minute: 0, gender: 'male', isLunar: false, timeZone: 8 }, + expected: { + year: '己未', + month: '戊辰', + day: '辛未', + hour: '辛卯', + isYangYear: false, // 己为阴年 + shouldShunPai: false // 阴年男命逆排 + } + }, + { + name: '2000年1月1日12点女命(己年为阴年,女命顺排)', + input: { year: 2000, month: 1, day: 1, hour: 12, minute: 0, gender: 'female', isLunar: false, timeZone: 8 }, + expected: { + year: '己卯', + month: '丙子', + day: '戊午', + hour: '戊午', + isYangYear: false, // 己为阴年 + shouldShunPai: true // 阴年女命顺排 + } + }, + { + name: '1988年8月15日10点男命(戊年为阳年,男命顺排)', + input: { year: 1988, month: 8, day: 15, hour: 10, minute: 0, gender: 'male', isLunar: false, timeZone: 8 }, + expected: { + isYangYear: true, // 戊为阳年 + shouldShunPai: true // 阳年男命顺排 + } + }, + { + name: '1987年3月20日14点女命(丁年为阴年,女命顺排)', + input: { year: 1987, month: 3, day: 20, hour: 14, minute: 0, gender: 'female', isLunar: false, timeZone: 8 }, + expected: { + isYangYear: false, // 丁为阴年 + shouldShunPai: true // 阴年女命顺排 + } + }, + { + name: '2000年3月1日12点女命(庚年为阳年,女命逆排)', + input: { year: 2000, month: 3, day: 1, hour: 12, minute: 0, gender: 'female', isLunar: false, timeZone: 8 }, + expected: { + year: '庚辰', + month: '戊寅', + day: '乙卯', + hour: '壬午', + isYangYear: true, // 庚为阳年 + shouldShunPai: false // 阳年女命逆排 + } + } + ]; + const results = { + passed: 0, + failed: 0, + details: [] + }; + testCases.forEach((testCase) => { + try { + const result = createBaziChart(testCase.input); + const actualYear = result.siZhu.year.gan + result.siZhu.year.zhi; + const actualMonth = result.siZhu.month.gan + result.siZhu.month.zhi; + const actualDay = result.siZhu.day.gan + result.siZhu.day.zhi; + const actualHour = result.siZhu.hour.gan + result.siZhu.hour.zhi; + // 检查年干阴阳和大运顺逆 + const yearGanIndex = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'].indexOf(result.siZhu.year.gan); + const isYangYear = yearGanIndex % 2 === 0; + const isMale = testCase.input.gender === 'male'; + const shouldShunPai = (isYangYear && isMale) || (!isYangYear && !isMale); + const yearPass = testCase.expected.year ? actualYear === testCase.expected.year : true; + const monthPass = testCase.expected.month ? actualMonth === testCase.expected.month : true; + const dayPass = testCase.expected.day ? actualDay === testCase.expected.day : true; + const hourPass = testCase.expected.hour ? actualHour === testCase.expected.hour : true; + const yinYangPass = isYangYear === testCase.expected.isYangYear; + const shunPaiPass = shouldShunPai === testCase.expected.shouldShunPai; + const allPass = yearPass && monthPass && dayPass && hourPass && yinYangPass && shunPaiPass; + if (allPass) { + results.passed++; + } + else { + results.failed++; + } + results.details.push({ + name: testCase.name, + passed: allPass, + expected: testCase.expected, + actual: { + year: actualYear, + month: actualMonth, + day: actualDay, + hour: actualHour, + isYangYear, + shouldShunPai, + dayun: result.dayun.slice(0, 3).map(d => d.ganZhi.gan + d.ganZhi.zhi) // 只显示前3步大运 + }, + details: { yearPass, monthPass, dayPass, hourPass, yinYangPass, shunPaiPass } + }); + } + catch (error) { + results.failed++; + results.details.push({ + name: testCase.name, + passed: false, + error: error.message + }); + } + }); + return results; +} diff --git a/calculator/dist/yiqi-core/daxian.js b/calculator/dist/yiqi-core/daxian.js new file mode 100644 index 0000000..8f63477 --- /dev/null +++ b/calculator/dist/yiqi-core/daxian.js @@ -0,0 +1,99 @@ +"use strict"; +// 大运计算模块 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.calculateDaXian = calculateDaXian; +exports.getCurrentDaXian = getCurrentDaXian; +exports.getDaXianForGong = getDaXianForGong; +exports.calculateXuSui = calculateXuSui; +exports.getLiuNianGanZhi = getLiuNianGanZhi; +exports.getGongIndexByZhi = getGongIndexByZhi; +/** + * 天干数组 + */ +const TIANGAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; +/** + * 地支数组 + */ +const DIZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; +/** + * 计算大运信息 + * @param mingGongIndex 命宫索引(0-11,子=0) + * @param yinYang 阴阳男女 + * @param wuXingJuNumber 五行局数(2,3,4,5,6) + * @param currentAge 当前年龄(虚岁),用于标记当前大运 + * @returns 大运信息数组(10个大运) + */ +function calculateDaXian(mingGongIndex, yinYang, wuXingJuNumber, currentAge = 0) { + const daXianList = []; + // 判断大运方向 + // 阳男、阴女:大限顺时针走(地支索引递增:丑→寅→卯...) + // 阴男、阳女:大限逆时针走(地支索引递减:丑→子→亥...) + const isShunShiZhen = yinYang === '阳男' || yinYang === '阴女'; + const direction = isShunShiZhen ? 'shun' : 'ni'; + // 起运年龄由五行局决定 + const startAge = wuXingJuNumber; + // 生成12个大运(每个大运管10年) + for (let i = 0; i < 12; i++) { + const age = startAge + i * 10; + const endAge = age + 9; + // 计算大运宫位(从命宫开始) + // gongIndex 是地支索引(0-11:子丑寅卯辰巳午未申酉戌亥) + let gongIndex; + if (isShunShiZhen) { + // 阳男、阴女:顺时针走,地支索引递增 + // 例如命宫在丑(1),第1大限在丑(1),第2大限在寅(2),第3大限在卯(3) + gongIndex = (mingGongIndex + i) % 12; + } + else { + // 阴男、阳女:逆时针走,地支索引递减 + // 例如命宫在丑(1),第1大限在丑(1),第2大限在子(0),第3大限在亥(11) + gongIndex = (mingGongIndex - i + 12) % 12; + } + daXianList.push({ + gongIndex, + startAge: age, + endAge, + ganZhi: '', // 这里可以添加大运干支,暂时留空 + direction + }); + } + return daXianList; +} +/** + * 计算当前年龄的大运 + */ +function getCurrentDaXian(daXianList, age) { + return daXianList.find(dx => age >= dx.startAge && age <= dx.endAge) || null; +} +/** + * 获取宫位的大运信息 + */ +function getDaXianForGong(gongIndex, daXianList) { + return daXianList.find(dx => dx.gongIndex === gongIndex) || null; +} +/** + * 计算虚岁 + */ +function calculateXuSui(birthYear, currentYear = new Date().getFullYear()) { + return currentYear - birthYear + 1; +} +/** + * 计算流年干支 + * @param year 公历年份 + */ +function getLiuNianGanZhi(year) { + const ganIndex = (year - 4) % 10; // 甲子年是1984年,1984-4=1980,1980%10=0 + const zhiIndex = (year - 4) % 12; + return { + gan: TIANGAN[ganIndex], + zhi: DIZHI[zhiIndex] + }; +} +/** + * 根据流年地支获取宫位索引 + * @param zhi 地支 + * @returns 宫位索引(0=子,1=丑,...,11=亥) + */ +function getGongIndexByZhi(zhi) { + return DIZHI.indexOf(zhi); +} diff --git a/calculator/dist/yiqi-core/ganzhi.js b/calculator/dist/yiqi-core/ganzhi.js new file mode 100644 index 0000000..8f589c9 --- /dev/null +++ b/calculator/dist/yiqi-core/ganzhi.js @@ -0,0 +1,145 @@ +"use strict"; +// 天干地支基础数据和工具函数 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SHISHEN_MAP = exports.DIZHI_MONTH = exports.DIZHI_WUXING = exports.TIANGAN_WUXING = exports.DIZHI = exports.TIANGAN = void 0; +exports.getTiangan = getTiangan; +exports.getDizhi = getDizhi; +exports.getTianganIndex = getTianganIndex; +exports.getDizhiIndex = getDizhiIndex; +exports.getYearGanZhi = getYearGanZhi; +exports.getDayGanZhi = getDayGanZhi; +exports.getHourGanZhi = getHourGanZhi; +exports.getShiShen = getShiShen; +// 天干数组 +exports.TIANGAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; +// 地支数组 +exports.DIZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; +// 天干五行属性 +exports.TIANGAN_WUXING = { + '甲': '木', '乙': '木', + '丙': '火', '丁': '火', + '戊': '土', '己': '土', + '庚': '金', '辛': '金', + '壬': '水', '癸': '水' +}; +// 地支五行属性 +exports.DIZHI_WUXING = { + '子': '水', '丑': '土', '寅': '木', '卯': '木', + '辰': '土', '巳': '火', '午': '火', '未': '土', + '申': '金', '酉': '金', '戌': '土', '亥': '水' +}; +// 地支对应的月份(节气月) +exports.DIZHI_MONTH = { + '寅': 1, '卯': 2, '辰': 3, '巳': 4, '午': 5, '未': 6, + '申': 7, '酉': 8, '戌': 9, '亥': 10, '子': 11, '丑': 12 +}; +// 十神关系表(以日干为中心) +exports.SHISHEN_MAP = { + '甲': { '甲': '比肩', '乙': '劫财', '丙': '食神', '丁': '伤官', '戊': '偏财', '己': '正财', '庚': '七杀', '辛': '正官', '壬': '偏印', '癸': '正印' }, + '乙': { '甲': '劫财', '乙': '比肩', '丙': '伤官', '丁': '食神', '戊': '正财', '己': '偏财', '庚': '正官', '辛': '七杀', '壬': '正印', '癸': '偏印' }, + '丙': { '甲': '偏印', '乙': '正印', '丙': '比肩', '丁': '劫财', '戊': '食神', '己': '伤官', '庚': '偏财', '辛': '正财', '壬': '七杀', '癸': '正官' }, + '丁': { '甲': '正印', '乙': '偏印', '丙': '劫财', '丁': '比肩', '戊': '伤官', '己': '食神', '庚': '正财', '辛': '偏财', '壬': '正官', '癸': '七杀' }, + '戊': { '甲': '七杀', '乙': '正官', '丙': '偏印', '丁': '正印', '戊': '比肩', '己': '劫财', '庚': '食神', '辛': '伤官', '壬': '偏财', '癸': '正财' }, + '己': { '甲': '正官', '乙': '七杀', '丙': '正印', '丁': '偏印', '戊': '劫财', '己': '比肩', '庚': '伤官', '辛': '食神', '壬': '正财', '癸': '偏财' }, + '庚': { '甲': '偏财', '乙': '正财', '丙': '七杀', '丁': '正官', '戊': '偏印', '己': '正印', '庚': '比肩', '辛': '劫财', '壬': '食神', '癸': '伤官' }, + '辛': { '甲': '正财', '乙': '偏财', '丙': '正官', '丁': '七杀', '戊': '正印', '己': '偏印', '庚': '劫财', '辛': '比肩', '壬': '伤官', '癸': '食神' }, + '壬': { '甲': '食神', '乙': '伤官', '丙': '偏财', '丁': '正财', '戊': '七杀', '己': '正官', '庚': '偏印', '辛': '正印', '壬': '比肩', '癸': '劫财' }, + '癸': { '甲': '伤官', '乙': '食神', '丙': '正财', '丁': '偏财', '戊': '正官', '己': '七杀', '庚': '正印', '辛': '偏印', '壬': '劫财', '癸': '比肩' } +}; +/** + * 根据数字获取天干 + * @param num 数字(0-9 对应甲-癸,支持更大数字取模) + * @returns 对应的天干 + */ +function getTiangan(num) { + return exports.TIANGAN[num % 10]; +} +/** + * 根据数字获取地支 + * @param num 数字(0-11 对应子-亥,支持更大数字取模) + * @returns 对应的地支 + */ +function getDizhi(num) { + return exports.DIZHI[num % 12]; +} +/** + * 获取天干的序号 + * @param gan 天干 + * @returns 序号(0-9) + */ +function getTianganIndex(gan) { + return exports.TIANGAN.indexOf(gan); +} +/** + * 获取地支的序号 + * @param zhi 地支 + * @returns 序号(0-11) + */ +function getDizhiIndex(zhi) { + return exports.DIZHI.indexOf(zhi); +} +/** + * 根据年份获取干支 + * @param year 公历年份 + * @returns 对应的干支 + */ +function getYearGanZhi(year) { + // 采用标准算法:以公元4年甲子年为起点 + // 天干循环:(year - 4) % 10 + // 地支循环:(year - 4) % 12 + const ganIndex = (year - 4) % 10; + const zhiIndex = (year - 4) % 12; + return { + gan: getTiangan(ganIndex), + zhi: getDizhi(zhiIndex) + }; +} +/** + * 根据日期获取日干支(使用Date对象计算) + * @param year 年 + * @param month 月 + * @param day 日 + * @returns 对应的干支 + */ +function getDayGanZhi(year, month, day) { + // 计算从1900年1月1日到目标日期的天数差 + const baseDate = new Date(1900, 0, 1); // 1900年1月1日 + const targetDate = new Date(year, month - 1, day); // 目标日期 + const daysDiff = Math.floor((targetDate.getTime() - baseDate.getTime()) / (1000 * 60 * 60 * 24)); + // 1900年1月1日是己丑日(天干索引5,地支索引1) + const ganIndex = (daysDiff + 5) % 10; + const zhiIndex = (daysDiff + 1) % 12; + return { + gan: getTiangan(ganIndex >= 0 ? ganIndex : ganIndex + 10), + zhi: getDizhi(zhiIndex >= 0 ? zhiIndex : zhiIndex + 12) + }; +} +/** + * 根据时辰获取时干支 + * @param dayGan 日干 + * @param hour 时辰(0-23) + * @returns 对应的干支 + */ +function getHourGanZhi(dayGan, hour) { + // 确定时支 + const zhiIndex = Math.floor((hour + 1) / 2) % 12; + const zhi = getDizhi(zhiIndex); + // 根据日干推算时干(五鼠遁法) + const dayGanIndex = getTianganIndex(dayGan); + const hourGanBaseMap = [0, 2, 4, 6, 8, 0, 2, 4, 6, 8]; // 甲己起甲子,乙庚起丙子... + const hourGanBase = hourGanBaseMap[dayGanIndex]; + const ganIndex = (hourGanBase + zhiIndex) % 10; + return { + gan: getTiangan(ganIndex), + zhi: zhi + }; +} +/** + * 获取十神关系 + * @param dayGan 日干 + * @param targetGan 目标天干 + * @returns 十神关系 + */ +function getShiShen(dayGan, targetGan) { + return exports.SHISHEN_MAP[dayGan][targetGan]; +} diff --git a/calculator/dist/yiqi-core/index.js b/calculator/dist/yiqi-core/index.js new file mode 100644 index 0000000..5a782da --- /dev/null +++ b/calculator/dist/yiqi-core/index.js @@ -0,0 +1,142 @@ +"use strict"; +// 统一排盘接口 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.EXAMPLE_BIRTH_INFO = void 0; +exports.createChart = createChart; +exports.validateBirthInfo = validateBirthInfo; +exports.runAllTests = runAllTests; +exports.formatChartResult = formatChartResult; +const bazi_1 = require("./bazi"); +const ziwei_standard_1 = require("./ziwei-standard"); +/** + * 创建完整的排盘(八字 + 紫微斗数) + * @param birthInfo 生辰信息 + * @returns 完整排盘结果 + */ +function createChart(birthInfo) { + try { + const bazi = (0, bazi_1.createBaziChart)(birthInfo); + const ziwei = (0, ziwei_standard_1.createZiweiChart)(birthInfo); + return { + bazi, + ziwei + }; + } + catch (error) { + throw new Error(`排盘计算失败: ${error instanceof Error ? error.message : String(error)}`); + } +} +/** + * 验证生辰信息的有效性 + * @param birthInfo 生辰信息 + * @returns 验证结果 + */ +function validateBirthInfo(birthInfo) { + const errors = []; + // 验证年份 + if (birthInfo.year < 1900 || birthInfo.year > 2100) { + errors.push('年份应在1900-2100之间'); + } + // 验证月份 + if (birthInfo.month < 1 || birthInfo.month > 12) { + errors.push('月份应在1-12之间'); + } + // 验证日期 + if (birthInfo.day < 1 || birthInfo.day > 31) { + errors.push('日期应在1-31之间'); + } + // 验证时辰 + if (birthInfo.hour < 0 || birthInfo.hour > 23) { + errors.push('小时应在0-23之间'); + } + // 验证分钟 + if (birthInfo.minute < 0 || birthInfo.minute > 59) { + errors.push('分钟应在0-59之间'); + } + // 验证性别 + if (birthInfo.gender !== 'male' && birthInfo.gender !== 'female') { + errors.push('性别必须为男性或女性'); + } + // 简单的闰年和月份天数验证 + if (birthInfo.month === 2) { + const isLeapYear = (birthInfo.year % 4 === 0 && birthInfo.year % 100 !== 0) || (birthInfo.year % 400 === 0); + if (birthInfo.day > (isLeapYear ? 29 : 28)) { + errors.push('2月份日期超出范围'); + } + } + else if ([4, 6, 9, 11].includes(birthInfo.month)) { + if (birthInfo.day > 30) { + errors.push('该月份只有30天'); + } + } + return { + valid: errors.length === 0, + errors + }; +} +/** + * 运行所有算法测试 + * @returns 综合测试结果 + */ +function runAllTests() { + const baziResults = (0, bazi_1.runBaziTests)(); + const ziweiResults = (0, ziwei_standard_1.runZiweiTests)(); + const totalPassed = baziResults.passed + ziweiResults.passed; + const totalFailed = baziResults.failed + ziweiResults.failed; + return { + bazi: baziResults, + ziwei: ziweiResults, + summary: { + totalPassed, + totalFailed, + success: totalFailed === 0 + } + }; +} +/** + * 格式化排盘结果为可读字符串(用于调试) + * @param chart 排盘结果 + * @returns 格式化的字符串 + */ +function formatChartResult(chart) { + let result = '=== 排盘结果 ===\n\n'; + // 八字部分 + result += '【八字排盘】\n'; + result += `年柱: ${chart.bazi.siZhu.year.gan}${chart.bazi.siZhu.year.zhi}\n`; + result += `月柱: ${chart.bazi.siZhu.month.gan}${chart.bazi.siZhu.month.zhi}\n`; + result += `日柱: ${chart.bazi.siZhu.day.gan}${chart.bazi.siZhu.day.zhi}\n`; + result += `时柱: ${chart.bazi.siZhu.hour.gan}${chart.bazi.siZhu.hour.zhi}\n`; + result += `日主: ${chart.bazi.dayMaster}\n`; + result += `大运起运: ${chart.bazi.dayunStart}岁\n`; + result += `前三步大运: ${chart.bazi.dayun.slice(0, 3).map(dy => `${dy.ganZhi.gan}${dy.ganZhi.zhi}`).join(' ')}\n\n`; + // 紫微斗数部分 + result += '【紫微斗数】\n'; + result += `命宫: ${chart.ziwei.gongs[0].dizhi}宫\n`; + const shenGongDizhi = chart.ziwei.gongs[chart.ziwei.shenGongIndex]?.dizhi || '未知'; + result += `身宫: ${shenGongDizhi}宫\n`; + // 显示有主星的宫位 + chart.ziwei.gongs.forEach(gong => { + if (gong.mainStars.length > 0) { + result += `${gong.gong}(${gong.dizhi}): ${gong.mainStars.join('、')}`; + if (gong.auxStars.length > 0) { + result += ` [${gong.auxStars.join('、')}]`; + } + if (gong.sihua.length > 0) { + result += ` {${gong.sihua.map(s => `${s.star}${s.hua}`).join('、')}}`; + } + result += '\n'; + } + }); + return result; +} +// 导出示例用法 +exports.EXAMPLE_BIRTH_INFO = { + year: 1990, + month: 5, + day: 15, + hour: 14, + minute: 30, + isLunar: false, + gender: 'male', + timeZone: 8 +}; diff --git a/calculator/dist/yiqi-core/jieqi.js b/calculator/dist/yiqi-core/jieqi.js new file mode 100644 index 0000000..31a325e --- /dev/null +++ b/calculator/dist/yiqi-core/jieqi.js @@ -0,0 +1,165 @@ +"use strict"; +// 节气计算模块 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JIEQI_MONTHS = exports.JIEQI_NAMES = void 0; +exports.calculateJieqi = calculateJieqi; +exports.getJieqiMonth = getJieqiMonth; +exports.getMonthGanZhi = getMonthGanZhi; +exports.getAccurateMonthGanZhi = getAccurateMonthGanZhi; +exports.getJieQiTime = getJieQiTime; +// 24节气名称 +exports.JIEQI_NAMES = [ + '小寒', '大寒', '立春', '雨水', '惊蛰', '春分', + '清明', '谷雨', '立夏', '小满', '芒种', '夏至', + '小暑', '大暑', '立秋', '处暑', '白露', '秋分', + '寒露', '霜降', '立冬', '小雪', '大雪', '冬至' +]; +// 节气对应的地支月(立春开始为寅月) +exports.JIEQI_MONTHS = [ + 11, 11, 0, 0, 1, 1, // 小寒(子月), 大寒(子月), 立春(寅月), 雨水(寅月), 惊蛰(卯月), 春分(卯月) + 2, 2, 3, 3, 4, 4, // 清明(辰月), 谷雨(辰月), 立夏(巳月), 小满(巳月), 芒种(午月), 夏至(午月) + 5, 5, 6, 6, 7, 7, // 小暑(未月), 大暑(未月), 立秋(申月), 处暑(申月), 白露(酉月), 秋分(酉月) + 8, 8, 9, 9, 10, 10 // 寒露(戌月), 霜降(戌月), 立冬(亥月), 小雪(亥月), 大雪(子月), 冬至(子月) +]; +/** + * 计算指定年份的节气时刻(简化算法) + * 注:这是一个简化版本,实际应用中需要更精确的天文算法 + * @param year 年份 + * @returns 24个节气的儒略日时刻 + */ +function calculateJieqi(year) { + const jieqiTimes = []; + // 基础数据:2000年各节气的大致日期(儒略日) + const base2000 = [ + 2451549.5, 2451564.5, 2451579.5, 2451594.5, 2451609.5, 2451624.5, // 1-6 + 2451639.5, 2451654.5, 2451670.5, 2451685.5, 2451700.5, 2451715.5, // 7-12 + 2451730.5, 2451745.5, 2451761.5, 2451776.5, 2451791.5, 2451806.5, // 13-18 + 2451821.5, 2451836.5, 2451852.5, 2451867.5, 2451882.5, 2451897.5 // 19-24 + ]; + const yearDiff = year - 2000; + for (let i = 0; i < 24; i++) { + // 简化计算:每年节气时间的平均变化 + const dayOffset = yearDiff * 365.2422 - Math.floor(yearDiff / 4) + Math.floor(yearDiff / 100) - Math.floor(yearDiff / 400); + jieqiTimes.push(base2000[i] + dayOffset); + } + return jieqiTimes; +} +/** + * 根据公历日期确定节气月(干支月) + * @param year 年 + * @param month 月 + * @param day 日 + * @returns 地支月份的索引(0-11对应寅-丑) + */ +function getJieqiMonth(year, month, day) { + const jieqiTimes = calculateJieqi(year); + // 计算当前日期的儒略日 + const a = Math.floor((14 - month) / 12); + const y = year - a; + const m = month + 12 * a - 3; + const currentJD = day + Math.floor((153 * m + 2) / 5) + 365 * y + + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) + 1721119; + // 找到当前日期所在的节气区间 + for (let i = 0; i < 24; i += 2) { // 只检查节气(奇数索引是中气) + const jieqi = jieqiTimes[i]; + const nextJieqi = i + 2 < 24 ? jieqiTimes[i + 2] : jieqiTimes[2] + 365.25; // 下一个节气或明年立春 + if (currentJD >= jieqi && currentJD < nextJieqi) { + return exports.JIEQI_MONTHS[i]; + } + } + // 默认返回子月 + return 11; +} +/** + * 获取月干支 + * @param year 年 + * @param month 月 + * @param day 日 + * @returns 月干支 + */ +function getMonthGanZhi(year, month, day) { + const jieqiMonth = getJieqiMonth(year, month, day); + const yearGanIndex = (year - 4) % 10; // 使用修正后的年干算法 + // 月干推算:甲己之年丙作首(正月为丙寅) + const monthGanBaseMap = [2, 4, 6, 8, 0, 2, 4, 6, 8, 0]; // 甲年丙寅,乙年戊寅... + const monthGanBase = monthGanBaseMap[yearGanIndex]; + const monthGanIndex = (monthGanBase + jieqiMonth) % 10; + return { + gan: ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'][monthGanIndex], + zhi: ['寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥', '子', '丑'][jieqiMonth] + }; +} +/** + * 获取当前时刻应该使用的月柱(基于精确节气时刻) + * + * lunar-javascript默认在节气当日的子时(00:00)就换月柱, + * 但传统命理应该在节气的精确时刻才换月柱。 + * + * 例如:某年立秋在公历8月初,那么: + * - 8月8日 02:28 应该用乙未月(立秋前) + * - 8月8日 10:00 应该用丙申月(立秋后) + * + * @param solar Solar对象(来自lunar-javascript) + * @returns 月柱干支 + */ +function getAccurateMonthGanZhi(solar) { + const lunar = solar.getLunar(); + // 获取lunar-javascript给出的月柱(默认在子时换月) + const defaultMonthGZ = lunar.getMonthInGanZhi(); + // 获取当前节令(节气) + const currentJieQi = lunar.getJieQi(); + // 获取节气表 + const jieQiTable = lunar.getJieQiTable(); + const jieQiSolar = jieQiTable[currentJieQi]; + if (!jieQiSolar) { + // 没有节气信息,使用默认值 + return parseGanZhi(defaultMonthGZ); + } + // 判断是否在节气当日 + const isJieQiDay = solar.getYear() === jieQiSolar.getYear() && + solar.getMonth() === jieQiSolar.getMonth() && + solar.getDay() === jieQiSolar.getDay(); + if (!isJieQiDay) { + // 不在节气当日,直接使用默认值 + return parseGanZhi(defaultMonthGZ); + } + // 在节气当日,需要判断是否已过节气时刻 + const solarTime = solar.getHour() * 3600 + solar.getMinute() * 60 + solar.getSecond(); + const jieQiTime = jieQiSolar.getHour() * 3600 + jieQiSolar.getMinute() * 60 + jieQiSolar.getSecond(); + if (solarTime < jieQiTime) { + // 还没到节气时刻,应该使用上一个月的月柱 + // 获取前一天的月柱 + const prevDaySolar = solar.next(-1); // 往前推一天 + const prevDayLunar = prevDaySolar.getLunar(); + const prevMonthGZ = prevDayLunar.getMonthInGanZhi(); + console.error(`[节气校正] ${solar.toYmd()} ${solar.getHour()}:${solar.getMinute()} 在${currentJieQi}前,使用${prevMonthGZ}月`); + return parseGanZhi(prevMonthGZ); + } + // 已经过了节气时刻,使用当前月柱 + return parseGanZhi(defaultMonthGZ); +} +/** + * 解析干支字符串为天干地支对象 + * @param gz 干支字符串,如"甲子" + * @returns 天干地支对象 + */ +function parseGanZhi(gz) { + if (!gz || gz.length < 2) { + throw new Error(`无效的干支字符串: ${gz}`); + } + return { + gan: gz[0], + zhi: gz[1] + }; +} +/** + * 获取节气的精确时刻(用于调试和显示) + * @param solar Solar对象 + * @param jieQiName 节气名称 + * @returns 节气的Solar对象,如果找不到返回null + */ +function getJieQiTime(solar, jieQiName) { + const lunar = solar.getLunar(); + const jieQiTable = lunar.getJieQiTable(); + return jieQiTable[jieQiName] || null; +} diff --git a/calculator/dist/yiqi-core/nayin.js b/calculator/dist/yiqi-core/nayin.js new file mode 100644 index 0000000..3a4b01b --- /dev/null +++ b/calculator/dist/yiqi-core/nayin.js @@ -0,0 +1,137 @@ +"use strict"; +/** + * 纳音五行算法 + * + * 纳音是根据干支组合推算出的五行属性,每两个干支组成一个纳音。 + * 共有60个干支组合,对应30种纳音五行。 + * + * 纳音五行分为: + * - 金:海中金、金箔金、白蜡金、剑锋金、钗钏金、沙中金 + * - 木:大林木、杨柳木、松柏木、平地木、石榴木、桑柘木 + * - 水:涧下水、大溪水、长流水、天河水、大海水、泉中水 + * - 火:炉中火、山头火、霹雳火、山下火、覆灯火、天上火 + * - 土:城头土、屋上土、壁上土、大驿土、沙中土、路旁土 + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getNaYin = getNaYin; +exports.getSiZhuNaYin = getSiZhuNaYin; +exports.getNaYinWuXing = getNaYinWuXing; +exports.getNaYinRelation = getNaYinRelation; +/** + * 纳音映射表 + * 根据干支组合查找对应的纳音五行 + * + * 数据来源:纳音.md + */ +const NAYIN_MAP = { + // 金系 + '甲子': '海中金', '乙丑': '海中金', + '壬寅': '金箔金', '癸卯': '金箔金', + '庚辰': '白蜡金', '辛巳': '白蜡金', + '壬申': '剑锋金', '癸酉': '剑锋金', + '庚戌': '钗钏金', '辛亥': '钗钏金', + '甲午': '沙中金', '乙未': '沙中金', + // 木系 + '戊辰': '大林木', '己巳': '大林木', + '壬午': '杨柳木', '癸未': '杨柳木', + '庚寅': '松柏木', '辛卯': '松柏木', + '戊戌': '平地木', '己亥': '平地木', + '庚申': '石榴木', '辛酉': '石榴木', + '壬子': '桑柘木', '癸丑': '桑柘木', + // 水系 + '丙子': '涧下水', '丁丑': '涧下水', + '甲寅': '大溪水', '乙卯': '大溪水', + '壬辰': '长流水', '癸巳': '长流水', + '丙午': '天河水', '丁未': '天河水', + '壬戌': '大海水', '癸亥': '大海水', + '甲申': '泉中水', '乙酉': '泉中水', + // 火系 + '丙寅': '炉中火', '丁卯': '炉中火', + '甲戌': '山头火', '乙亥': '山头火', + '戊子': '霹雳火', '己丑': '霹雳火', + '丙申': '山下火', '丁酉': '山下火', + '甲辰': '覆灯火', '乙巳': '覆灯火', + '戊午': '天上火', '己未': '天上火', + // 土系 + '戊寅': '城头土', '己卯': '城头土', + '丙戌': '屋上土', '丁亥': '屋上土', + '庚子': '壁上土', '辛丑': '壁上土', + '戊申': '大驿土', '己酉': '大驿土', + '丙辰': '沙中土', '丁巳': '沙中土', + '庚午': '路旁土', '辛未': '路旁土' +}; +/** + * 获取干支的纳音五行 + * @param tiangan 天干 + * @param dizhi 地支 + * @returns 纳音五行 + */ +function getNaYin(tiangan, dizhi) { + const key = tiangan + dizhi; + const nayin = NAYIN_MAP[key]; + if (!nayin) { + console.warn(`未找到纳音: ${key}`); + return '海中金'; // 默认值 + } + return nayin; +} +/** + * 批量获取四柱的纳音 + * @param siZhu 四柱干支 + * @returns 四柱纳音数组 + */ +function getSiZhuNaYin(siZhu) { + return { + year: getNaYin(siZhu.year.gan, siZhu.year.zhi), + month: getNaYin(siZhu.month.gan, siZhu.month.zhi), + day: getNaYin(siZhu.day.gan, siZhu.day.zhi), + hour: getNaYin(siZhu.hour.gan, siZhu.hour.zhi) + }; +} +/** + * 获取纳音的五行属性 + * @param nayin 纳音 + * @returns 五行属性(金木水火土) + */ +function getNaYinWuXing(nayin) { + if (nayin.includes('金')) + return '金'; + if (nayin.includes('木')) + return '木'; + if (nayin.includes('水')) + return '水'; + if (nayin.includes('火')) + return '火'; + if (nayin.includes('土')) + return '土'; + return '金'; // 默认值 +} +/** + * 获取纳音五行的相生相克关系 + * @param nayin1 纳音1 + * @param nayin2 纳音2 + * @returns 关系(生、克、比和) + */ +function getNaYinRelation(nayin1, nayin2) { + const wx1 = getNaYinWuXing(nayin1); + const wx2 = getNaYinWuXing(nayin2); + if (wx1 === wx2) + return '比和'; + // 相生关系:木生火、火生土、土生金、金生水、水生木 + const sheng = { + '木': '火', '火': '土', '土': '金', '金': '水', '水': '木' + }; + if (sheng[wx1] === wx2) + return '生'; + if (sheng[wx2] === wx1) + return '被生'; + // 相克关系:木克土、土克水、水克火、火克金、金克木 + const ke = { + '木': '土', '土': '水', '水': '火', '火': '金', '金': '木' + }; + if (ke[wx1] === wx2) + return '克'; + if (ke[wx2] === wx1) + return '被克'; + return '比和'; // 默认值 +} diff --git a/calculator/dist/yiqi-core/sihua.js b/calculator/dist/yiqi-core/sihua.js new file mode 100644 index 0000000..10d7fe9 --- /dev/null +++ b/calculator/dist/yiqi-core/sihua.js @@ -0,0 +1,115 @@ +"use strict"; +// 紫微斗数四化系统 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getSiHuaByGan = getSiHuaByGan; +exports.calculateFeiHua = calculateFeiHua; +exports.addBenMingSiHua = addBenMingSiHua; +exports.getSiHuaColor = getSiHuaColor; +exports.getSiHuaShort = getSiHuaShort; +/** + * 四化星曜(按年干) + * 格式:{ 年干: { 化禄, 化权, 化科, 化忌 } } + */ +const SIHUA_BY_YEAR_GAN = { + '甲': { lu: '廉贞', quan: '破军', ke: '武曲', ji: '太阳' }, + '乙': { lu: '天机', quan: '天梁', ke: '紫微', ji: '太阴' }, + '丙': { lu: '天同', quan: '天机', ke: '文昌', ji: '廉贞' }, + '丁': { lu: '太阴', quan: '天同', ke: '天机', ji: '巨门' }, + '戊': { lu: '贪狼', quan: '太阴', ke: '右弼', ji: '天机' }, + '己': { lu: '武曲', quan: '贪狼', ke: '天梁', ji: '文曲' }, + '庚': { lu: '太阳', quan: '武曲', ke: '太阴', ji: '天同' }, + '辛': { lu: '巨门', quan: '太阳', ke: '文曲', ji: '文昌' }, + '壬': { lu: '天梁', quan: '紫微', ke: '左辅', ji: '武曲' }, + '癸': { lu: '破军', quan: '巨门', ke: '太阴', ji: '贪狼' } +}; +/** + * 获取指定天干的四化 + */ +function getSiHuaByGan(gan) { + return SIHUA_BY_YEAR_GAN[gan]; +} +/** + * 计算某个宫位的四化(用于飞化) + * @param gan 宫位天干 + * @param gongStars 宫位内的星曜列表 + * @returns 该宫位飞化出的四化星及其位置 + */ +function calculateFeiHua(gan, allGongs) { + const sihua = getSiHuaByGan(gan); + const result = { + lu: null, + quan: null, + ke: null, + ji: null + }; + // 找到化禄、化权、化科、化忌的星曜所在宫位(包括主星和辅星) + for (const gong of allGongs) { + const allStars = [...gong.mainStars, ...(gong.auxStars || [])]; + if (allStars.includes(sihua.lu)) { + result.lu = { star: sihua.lu, targetGong: gong.gong }; + } + if (allStars.includes(sihua.quan)) { + result.quan = { star: sihua.quan, targetGong: gong.gong }; + } + if (allStars.includes(sihua.ke)) { + result.ke = { star: sihua.ke, targetGong: gong.gong }; + } + if (allStars.includes(sihua.ji)) { + result.ji = { star: sihua.ji, targetGong: gong.gong }; + } + } + return result; +} +/** + * 为命盘添加本命四化(出生年干的四化) + */ +function addBenMingSiHua(gongs, yearGan) { + const sihua = getSiHuaByGan(yearGan); + return gongs.map(gong => { + const sihuaList = []; + // 检查宫位内的主星和辅星是否有四化 + const allStars = [...gong.mainStars, ...(gong.auxStars || [])]; + allStars.forEach((star) => { + if (star === sihua.lu) { + sihuaList.push({ star, hua: '化禄' }); + } + if (star === sihua.quan) { + sihuaList.push({ star, hua: '化权' }); + } + if (star === sihua.ke) { + sihuaList.push({ star, hua: '化科' }); + } + if (star === sihua.ji) { + sihuaList.push({ star, hua: '化忌' }); + } + }); + return { + ...gong, + sihua: sihuaList + }; + }); +} +/** + * 获取四化的颜色样式 + */ +function getSiHuaColor(hua) { + switch (hua) { + case '化禄': return '#22c55e'; // 绿色 + case '化权': return '#a855f7'; // 紫色 + case '化科': return '#3b82f6'; // 蓝色 + case '化忌': return '#ef4444'; // 红色 + default: return '#666'; + } +} +/** + * 获取四化的简称 + */ +function getSiHuaShort(hua) { + switch (hua) { + case '化禄': return '禄'; + case '化权': return '权'; + case '化科': return '科'; + case '化忌': return '忌'; + default: return ''; + } +} diff --git a/calculator/dist/yiqi-core/types.js b/calculator/dist/yiqi-core/types.js new file mode 100644 index 0000000..b31e84f --- /dev/null +++ b/calculator/dist/yiqi-core/types.js @@ -0,0 +1,3 @@ +"use strict"; +// 核心数据类型定义 +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/calculator/dist/yiqi-core/zhangsheng.js b/calculator/dist/yiqi-core/zhangsheng.js new file mode 100644 index 0000000..eefba0c --- /dev/null +++ b/calculator/dist/yiqi-core/zhangsheng.js @@ -0,0 +1,117 @@ +"use strict"; +/** + * 十二长生(星运)算法 + * + * 十二长生是命理学中描述五行在十二地支中的旺衰状态,包括: + * 长生、沐浴、冠带、临官、帝旺、衰、病、死、墓、绝、胎、养 + * + * 根据日干的五行属性,查找其在各地支的长生状态 + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getChangSheng = getChangSheng; +exports.getSiZhuChangSheng = getSiZhuChangSheng; +exports.getChangShengLevel = getChangShengLevel; +/** + * 十二长生映射表 + * 根据天干十二长生表(见附件图片) + * + * 阳干(甲丙戊庚壬):顺行 + * 阴干(乙丁己辛癸):逆行 + */ +const CHANGSHENG_MAP = { + '甲': { + '亥': '长生', '子': '沐浴', '丑': '冠带', '寅': '临官', + '卯': '帝旺', '辰': '衰', '巳': '病', '午': '死', + '未': '墓', '申': '绝', '酉': '胎', '戌': '养' + }, + '乙': { + '午': '长生', '巳': '沐浴', '辰': '冠带', '卯': '临官', + '寅': '帝旺', '丑': '衰', '子': '病', '亥': '死', + '戌': '墓', '酉': '绝', '申': '胎', '未': '养' + }, + '丙': { + '寅': '长生', '卯': '沐浴', '辰': '冠带', '巳': '临官', + '午': '帝旺', '未': '衰', '申': '病', '酉': '死', + '戌': '墓', '亥': '绝', '子': '胎', '丑': '养' + }, + '丁': { + '酉': '长生', '申': '沐浴', '未': '冠带', '午': '临官', + '巳': '帝旺', '辰': '衰', '卯': '病', '寅': '死', + '丑': '墓', '子': '绝', '亥': '胎', '戌': '养' + }, + '戊': { + '寅': '长生', '卯': '沐浴', '辰': '冠带', '巳': '临官', + '午': '帝旺', '未': '衰', '申': '病', '酉': '死', + '戌': '墓', '亥': '绝', '子': '胎', '丑': '养' + }, + '己': { + '酉': '长生', '申': '沐浴', '未': '冠带', '午': '临官', + '巳': '帝旺', '辰': '衰', '卯': '病', '寅': '死', + '丑': '墓', '子': '绝', '亥': '胎', '戌': '养' + }, + '庚': { + '巳': '长生', '午': '沐浴', '未': '冠带', '申': '临官', + '酉': '帝旺', '戌': '衰', '亥': '病', '子': '死', + '丑': '墓', '寅': '绝', '卯': '胎', '辰': '养' + }, + '辛': { + '子': '长生', '亥': '沐浴', '戌': '冠带', '酉': '临官', + '申': '帝旺', '未': '衰', '午': '病', '巳': '死', + '辰': '墓', '卯': '绝', '寅': '胎', '丑': '养' + }, + '壬': { + '申': '长生', '酉': '沐浴', '戌': '冠带', '亥': '临官', + '子': '帝旺', '丑': '衰', '寅': '病', '卯': '死', + '辰': '墓', '巳': '绝', '午': '胎', '未': '养' + }, + '癸': { + '卯': '长生', '寅': '沐浴', '丑': '冠带', '子': '临官', + '亥': '帝旺', '戌': '衰', '酉': '病', '申': '死', + '未': '墓', '午': '绝', '巳': '胎', '辰': '养' + } +}; +/** + * 获取天干在某地支的长生状态 + * @param tiangan 天干 + * @param dizhi 地支 + * @returns 长生状态(长生、沐浴、冠带等) + */ +function getChangSheng(tiangan, dizhi) { + return CHANGSHENG_MAP[tiangan][dizhi]; +} +/** + * 批量获取四柱的长生状态 + * @param dayGan 日干 + * @param siZhu 四柱地支 + * @returns 四柱长生状态数组 + */ +function getSiZhuChangSheng(dayGan, siZhu) { + return { + year: getChangSheng(dayGan, siZhu.year), + month: getChangSheng(dayGan, siZhu.month), + day: getChangSheng(dayGan, siZhu.day), + hour: getChangSheng(dayGan, siZhu.hour) + }; +} +/** + * 判断长生状态的吉凶 + * @param changsheng 长生状态 + * @returns 吉凶等级(1-5,5最吉) + */ +function getChangShengLevel(changsheng) { + const levels = { + '长生': 5, // 最吉 + '帝旺': 5, // 最吉 + '临官': 4, // 次吉 + '冠带': 4, // 次吉 + '养': 3, // 平 + '胎': 3, // 平 + '沐浴': 2, // 次凶(桃花) + '衰': 2, // 次凶 + '病': 1, // 凶 + '死': 1, // 凶 + '墓': 1, // 凶 + '绝': 1 // 最凶 + }; + return levels[changsheng]; +} diff --git a/calculator/dist/yiqi-core/ziwei-standard.js b/calculator/dist/yiqi-core/ziwei-standard.js new file mode 100644 index 0000000..8b48b02 --- /dev/null +++ b/calculator/dist/yiqi-core/ziwei-standard.js @@ -0,0 +1,633 @@ +"use strict"; +// 紫微斗数排盘核心算法(按照标准安星法重写) +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createZiweiChart = createZiweiChart; +exports.runZiweiTests = runZiweiTests; +const lunar_typescript_1 = require("lunar-typescript"); +const sihua_1 = require("./sihua"); +const daxian_1 = require("./daxian"); +/** + * 天干数组 + */ +const TIANGAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; +/** + * 地支数组(顺时针) + */ +const DIZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; +/** + * 十二宫名称(从命宫开始,逆时针排列) + */ +const GONG_NAMES = ['命宫', '兄弟', '夫妻', '子女', '财帛', '疾厄', '迁移', '交友', '官禄', '田宅', '福德', '父母']; +/** + * 时辰转换为地支索引 + */ +function getHourZhiIndex(hour) { + // 23-01:子(0), 01-03:丑(1), ..., 21-23:亥(11) + return Math.floor((hour + 1) / 2) % 12; +} +/** + * 判断阴阳男女 + */ +function getYinYang(year, gender) { + const ganIndex = (year - 4) % 10; // 修正公式:2014年 → (2014-4)%10 = 0 → 甲 + const isYangYear = ganIndex % 2 === 0; // 甲丙戊庚壬为阳(索引0,2,4,6,8) + if (gender === 'male') { + return isYangYear ? '阳男' : '阴男'; + } + else { + return isYangYear ? '阳女' : '阴女'; + } +} +/** + * 五虎遁月诀 - 根据年干和月支确定月干 + */ +function getMonthGan(yearGan, monthZhi) { + const yearGanIndex = TIANGAN.indexOf(yearGan); + const monthZhiIndex = DIZHI.indexOf(monthZhi); + // 五虎遁月诀:甲己丙作首,乙庚戊为头,丙辛庚上起,丁壬壬寅流,戊癸甲寅求 + const firstMonthGanMap = { + 0: 2, 5: 2, // 甲己 → 丙 + 1: 4, 6: 4, // 乙庚 → 戊 + 2: 6, 7: 6, // 丙辛 → 庚 + 3: 8, 8: 8, // 丁壬 → 壬 + 4: 0, 9: 0 // 戊癸 → 甲 + }; + const firstMonthGan = firstMonthGanMap[yearGanIndex]; + // 正月是寅月(索引2),从正月天干开始,按地支顺序推算 + // 月支索引 - 寅索引(2) = 偏移量 + const offset = (monthZhiIndex - 2 + 12) % 12; + const monthGanIndex = (firstMonthGan + offset) % 10; + return TIANGAN[monthGanIndex]; +} +/** + * 计算命宫位置 + * 标准算法:从寅宫起正月,顺数到生月;从生月宫当子时,逆时针数到生时 + * 例如:八月在酉宫,申时是第9个时辰(子丑寅卯辰巳午未申),从酉宫逆数8位到丑宫 + */ +function calculateMingGong(lunarMonth, hour) { + // 1. 从寅宫(索引2)起正月,顺时针数到生月 + // 正月在寅(2),二月在卯(3),...,八月在酉(9) + const shengYueGongIndex = (2 + lunarMonth - 1) % 12; + // 2. 从生月宫当子时起,逆时针数到生时 + // 生时地支索引就是要逆数的宫位数 + const shengShiZhiIndex = getHourZhiIndex(hour); + // 从生月宫逆时针数shengShiZhiIndex个宫位 + const mingGongIndex = (shengYueGongIndex - shengShiZhiIndex + 12) % 12; + return mingGongIndex; +} +/** + * 计算身宫位置 + * 标准算法:从寅宫起正月,顺数到生月;从生月宫当子时,顺时针数到生时 + * 例如:八月在酉宫,申时是第9个时辰,从酉宫顺数8位到巳宫 + */ +function calculateShenGong(lunarMonth, hour) { + // 1. 从寅宫(索引2)起正月,顺时针数到生月 + // 正月在寅(2),二月在卯(3),...,八月在酉(9) + const shengYueGongIndex = (2 + lunarMonth - 1) % 12; + // 2. 从生月宫当子时起,顺时针数到生时 + // 生时地支索引就是要顺数的宫位数 + const shengShiZhiIndex = getHourZhiIndex(hour); + // 从生月宫顺时针数shengShiZhiIndex个宫位 + const shenGongIndex = (shengYueGongIndex + shengShiZhiIndex) % 12; + return shenGongIndex; +} +/** + * 计算纳音五行局 + */ +function calculateWuXingJu(tiangan, dizhi) { + const ganNum = Math.floor(TIANGAN.indexOf(tiangan) / 2) + 1; // 甲乙=1, 丙丁=2... + const zhiMap = { + '子': 1, '丑': 1, '午': 1, '未': 1, + '寅': 2, '卯': 2, '申': 2, '酉': 2, + '辰': 3, '巳': 3, '戌': 3, '亥': 3 + }; + const zhiNum = zhiMap[dizhi]; + let juNum = (ganNum + zhiNum) % 5; + if (juNum === 0) + juNum = 5; + const juMap = { + 1: '木三局', + 2: '金四局', + 3: '水二局', + 4: '火六局', + 5: '土五局' + }; + return { + name: juMap[juNum], + number: [0, 3, 4, 2, 6, 5][juNum] // 转换为局数:木3,金4,水2,火6,土5 + }; +} +/** + * 计算紫微星位置 + * 口诀:生日除局商为月,一自寅起紫微定。只加不减到整除,阳退阴进记心中。 + */ +function calculateZiweiPosition(lunarDay, juShu) { + let shang = Math.floor(lunarDay / juShu); + let yu = lunarDay % juShu; + if (yu === 0) { + // 整除 + // 商数对应地支:寅=1, 卯=2, ..., 丑=12 + if (shang > 12) + shang = shang - 12; + // 地支索引 = (寅索引2 + 商数-1) % 12 + return (2 + shang - 1) % 12; + } + else { + // 不整除,需要添加数使其整除 + const tian = juShu - yu; // 添加的数 + shang = Math.floor((lunarDay + tian) / juShu); + if (shang > 12) + shang = shang - 12; + let baseIndex = (2 + shang - 1) % 12; + // 添加数为单数:逆时针退tian位(阳退,不包括本位) + // 添加数为双数:顺时针进tian位(阴进,不包括本位) + if (tian % 2 === 1) { + // 单数:逆时针退tian位 + return (baseIndex - tian + 12) % 12; + } + else { + // 双数:顺时针进tian位 + return (baseIndex + tian) % 12; + } + } +} +/** + * 安紫微星系 + * 口诀:紫微天机逆行旁,隔一阳武天同当,又隔二位廉贞地,空三复见紫微郞 + */ +function installZiweiSeries(ziweiPos) { + const stars = new Map(); + stars.set('紫微', ziweiPos); + stars.set('天机', (ziweiPos - 1 + 12) % 12); // 逆时针下一宫 + // 隔一宫 + stars.set('太阳', (ziweiPos - 3 + 12) % 12); + stars.set('武曲', (ziweiPos - 4 + 12) % 12); + stars.set('天同', (ziweiPos - 5 + 12) % 12); + // 隔两宫 + stars.set('廉贞', (ziweiPos - 8 + 12) % 12); + return stars; +} +/** + * 计算天府星位置 + * 口诀:天府南斗令,常对紫微君 + */ +function calculateTianfuPosition(ziweiPos) { + // 紫微天府对应关系 + const duiYing = { + 2: 2, 8: 8, // 寅申:紫府同宫 + 1: 3, 3: 1, // 丑卯相对 + 7: 9, 9: 7, // 未酉相对 + 6: 10, 10: 6, // 午戌相对 + 0: 4, 4: 0, // 子辰相对 + 5: 11, 11: 5 // 巳亥相对 + }; + return duiYing[ziweiPos]; +} +/** + * 安天府星系 + * 口诀:天府太阴与贪狼,巨门天相及天梁,七杀空三破军位 + */ +function installTianfuSeries(tianfuPos) { + const stars = new Map(); + stars.set('天府', tianfuPos); + stars.set('太阴', (tianfuPos + 1) % 12); // 顺时针下一宫 + stars.set('贪狼', (tianfuPos + 2) % 12); + stars.set('巨门', (tianfuPos + 3) % 12); + stars.set('天相', (tianfuPos + 4) % 12); + stars.set('天梁', (tianfuPos + 5) % 12); + stars.set('七杀', (tianfuPos + 6) % 12); + // 空三宫 + stars.set('破军', (tianfuPos + 10) % 12); + return stars; +} +/** + * 安辅星:左辅、右弼 + * 口诀:左辅辰宫起正月,顺数至生月;右弼戌宫起正月,逆数至生月 + */ +function installZuoYou(lunarMonth) { + const stars = new Map(); + // 左辅:辰宫起正月,顺时针数到生月 + const zuoFuPos = (4 + lunarMonth - 1) % 12; + stars.set('左辅', zuoFuPos); + // 右弼:戌宫起正月,逆时针数到生月 + const youBiPos = (10 - lunarMonth + 1 + 12) % 12; + stars.set('右弼', youBiPos); + return stars; +} +/** + * 安文昌、文曲 + * 口诀:文昌生时起戌位,逆数时辰至卯停;文曲生时起子位,逆数时辰至午停 + */ +function installWenXing(hour) { + const stars = new Map(); + const hourZhiIndex = getHourZhiIndex(hour); + // 文昌:戌宫起子时,逆时针数到生时 + const wenChangPos = (10 - hourZhiIndex + 12) % 12; + stars.set('文昌', wenChangPos); + // 文曲:辰宫起子时,顺时针数到生时 + const wenQuPos = (4 + hourZhiIndex) % 12; + stars.set('文曲', wenQuPos); + return stars; +} +/** + * 安禄存、天马 + * 口诀:禄存按年干安星,天马按年支对宫三合 + */ +function installLuMa(yearGan, yearZhi) { + const stars = new Map(); + // 禄存按年干:甲寅、乙卯、丙戊巳、丁己午、庚申、辛酉、壬亥、癸子 + const luCunMap = { + '甲': 2, // 寅 + '乙': 3, // 卯 + '丙': 5, // 巳 + '丁': 6, // 午 + '戊': 5, // 巳 + '己': 6, // 午 + '庚': 8, // 申 + '辛': 9, // 酉 + '壬': 11, // 亥 + '癸': 0 // 子 + }; + stars.set('禄存', luCunMap[yearGan]); + // 天马按年支三合:寅午戌→申,巳酉丑→亥,申子辰→寅,亥卯未→巳 + const tianMaMap = { + '寅': 8, '午': 8, '戌': 8, // 申 + '巳': 11, '酉': 11, '丑': 11, // 亥 + '申': 2, '子': 2, '辰': 2, // 寅 + '亥': 5, '卯': 5, '未': 5 // 巳 + }; + stars.set('天马', tianMaMap[yearZhi]); + return stars; +} +/** + * 安擎羊、陀罗 + * 口诀:擎羊禄前一位,陀罗禄后一位 + */ +function installYangTuo(yearGan) { + const stars = new Map(); + // 先找禄存位置 + const luCunMap = { + '甲': 2, '乙': 3, '丙': 5, '丁': 6, '戊': 5, + '己': 6, '庚': 8, '辛': 9, '壬': 11, '癸': 0 + }; + const luCunPos = luCunMap[yearGan]; + // 擎羊:禄存前一位(顺时针) + stars.set('擎羊', (luCunPos + 1) % 12); + // 陀罗:禄存后一位(逆时针) + stars.set('陀罗', (luCunPos - 1 + 12) % 12); + return stars; +} +/** + * 安火星、铃星 + * 口诀: + * 申子辰年生人火星在寅宫起子时,铃星在戌宫起子时,均顺时针数到生时 + * 寅午戌年生人火星在丑宫起子时,铃星在卯宫起子时,均顺时针数到生时 + * 巳酉丑年生人火星在卯宫起子时,铃星在戌宫起子时,均顺时针数到生时 + * 亥卯未年生人火星在酉宫起子时,铃星在戌宫起子时,均顺时针数到生时 + */ +function installHuoLing(yearZhi, hour) { + const stars = new Map(); + const hourZhiIndex = getHourZhiIndex(hour); + const zhiIndex = DIZHI.indexOf(yearZhi); + // 确定火星起始宫位 + let huoStartPos; + if ([8, 0, 4].includes(zhiIndex)) { + // 申子辰 → 寅(2) + huoStartPos = 2; + } + else if ([2, 6, 10].includes(zhiIndex)) { + // 寅午戌 → 丑(1) + huoStartPos = 1; + } + else if ([5, 9, 1].includes(zhiIndex)) { + // 巳酉丑 → 卯(3) + huoStartPos = 3; + } + else { + // 亥卯未 → 酉(9) + huoStartPos = 9; + } + // 确定铃星起始宫位 + let lingStartPos; + if ([2, 6, 10].includes(zhiIndex)) { + // 寅午戌 → 卯(3) + lingStartPos = 3; + } + else { + // 申子辰、巳酉丑、亥卯未 → 戌(10) + lingStartPos = 10; + } + // 从起始宫位顺数到生时 + const huoPos = (huoStartPos + hourZhiIndex) % 12; + const lingPos = (lingStartPos + hourZhiIndex) % 12; + stars.set('火星', huoPos); + stars.set('铃星', lingPos); + return stars; +} +/** + * 安地空、地劫 + * 口诀:地空亥宫起子时,逆数至生时;地劫亥宫起子时,顺数至生时 + */ +function installKongJie(hour) { + const stars = new Map(); + const hourZhiIndex = getHourZhiIndex(hour); + // 地空:亥宫起子时,逆数至生时 + const diKongPos = (11 - hourZhiIndex + 12) % 12; + stars.set('地空', diKongPos); + // 地劫:亥宫起子时,顺数至生时 + const diJiePos = (11 + hourZhiIndex) % 12; + stars.set('地劫', diJiePos); + return stars; +} +/** + * 安天魁、天钺 + * 口诀:按年干安星 + */ +function installKuiYue(yearGan) { + const stars = new Map(); + // 天魁定位 + // 口诀:甲戊庚牛羊,乙己鼠猴乡,丙丁猪鸡位,壬癸兔蛇藏,六辛逢马虎 + const tianKuiMap = { + '甲': 1, // 丑(牛) + '乙': 0, // 子(鼠) + '丙': 11, // 亥(猪) + '丁': 11, // 亥(猪) + '戊': 1, // 丑(牛) + '己': 0, // 子(鼠) + '庚': 1, // 丑(牛) + '辛': 6, // 午(马) + '壬': 3, // 卯(兔) + '癸': 5 // 巳(蛇) + }; + // 天钺定位 + const tianYueMap = { + '甲': 7, // 未(羊) + '乙': 8, // 申(猴) + '丙': 9, // 酉(鸡) + '丁': 9, // 酉(鸡) + '戊': 7, // 未(羊) + '己': 8, // 申(猴) + '庚': 7, // 未(羊) + '辛': 2, // 寅(虎) + '壬': 5, // 巳(蛇) + '癸': 3 // 卯(兔) + }; + stars.set('天魁', tianKuiMap[yearGan]); + stars.set('天钺', tianYueMap[yearGan]); + return stars; +} +/** + * 创建紫微斗数排盘 + */ +function createZiweiChart(birthInfo) { + try { + // 1. 转换为农历 + const solar = lunar_typescript_1.Solar.fromYmdHms(birthInfo.year, birthInfo.month, birthInfo.day, birthInfo.hour, birthInfo.minute, 0); + const lunar = solar.getLunar(); + const lunarYear = lunar.getYear(); + const lunarMonth = lunar.getMonth(); + const lunarDay = lunar.getDay(); + // 2. 获取四柱 + const yearGan = lunar.getYearGan(); + const yearZhi = lunar.getYearZhi(); + const monthGan = lunar.getMonthGan(); + const monthZhi = lunar.getMonthZhi(); + const dayGan = lunar.getDayGan(); + const dayZhi = lunar.getDayZhi(); + const hourGan = lunar.getTimeGan(); + const hourZhi = lunar.getTimeZhi(); + const siZhu = { + year: { gan: yearGan, zhi: yearZhi }, + month: { gan: monthGan, zhi: monthZhi }, + day: { gan: dayGan, zhi: dayZhi }, + hour: { gan: hourGan, zhi: hourZhi } + }; + // 3. 判断阴阳男女 + const yinYang = getYinYang(birthInfo.year, birthInfo.gender); + // 4. 计算命宫、身宫 + const mingGongIndex = calculateMingGong(lunarMonth, birthInfo.hour); + const shenGongIndex = calculateShenGong(lunarMonth, birthInfo.hour); + // 5. 计算命宫天干(用月干推算) + const mingGongZhi = DIZHI[mingGongIndex]; + const mingGongGan = getMonthGan(yearGan, mingGongZhi); + // 6. 计算五行局 + const wuXingJu = calculateWuXingJu(mingGongGan, mingGongZhi); + // 7. 计算紫微星位置 + const ziweiPos = calculateZiweiPosition(lunarDay, wuXingJu.number); + // 8. 安14主星 + const ziweiSeries = installZiweiSeries(ziweiPos); + const tianfuPos = calculateTianfuPosition(ziweiPos); + const tianfuSeries = installTianfuSeries(tianfuPos); + const allMainStars = new Map([...ziweiSeries, ...tianfuSeries]); + // 9. 安辅星系统 + const zuoYouStars = installZuoYou(lunarMonth); + const wenXingStars = installWenXing(birthInfo.hour); + const luMaStars = installLuMa(yearGan, yearZhi); + const yangTuoStars = installYangTuo(yearGan); + const huoLingStars = installHuoLing(yearZhi, birthInfo.hour); + const kongJieStars = installKongJie(birthInfo.hour); + const kuiYueStars = installKuiYue(yearGan); + const hongLuanTianXiStars = installHongLuanTianXi(yearZhi); + const tianXingStars = installTianXing(lunarMonth); + const tianYaoStars = installTianYao(lunarMonth); + const allAuxStars = new Map([ + ...zuoYouStars, + ...wenXingStars, + ...luMaStars, + ...yangTuoStars, + ...huoLingStars, + ...kongJieStars, + ...kuiYueStars, + ...hongLuanTianXiStars, + ...tianXingStars, + ...tianYaoStars + ]); + // 10. 生成十二宫数据 + const gongs = []; + for (let i = 0; i < 12; i++) { + // 宫位地支索引(从命宫开始逆时针) + const gongZhiIndex = (mingGongIndex - i + 12) % 12; + const dizhi = DIZHI[gongZhiIndex]; + // 计算天干(使用五虎遁月诀) + const tiangan = getMonthGan(yearGan, dizhi); + // 找出这个宫位的主星 + const mainStars = []; + allMainStars.forEach((pos, star) => { + if (pos === gongZhiIndex) { + mainStars.push(star); + } + }); + // 找出这个宫位的辅星 + const auxStars = []; + allAuxStars.forEach((pos, star) => { + if (pos === gongZhiIndex) { + auxStars.push(star); + } + }); + gongs.push({ + gong: GONG_NAMES[i], + dizhi, + tiangan, + mainStars, + auxStars, + sihua: [] + }); + } + // 11. 添加本命四化(出生年干的四化) + const gongsWithSiHua = (0, sihua_1.addBenMingSiHua)(gongs, yearGan); + // 12. 计算大运信息 + const currentAge = (0, daxian_1.calculateXuSui)(birthInfo.year); + const daXianList = (0, daxian_1.calculateDaXian)(mingGongIndex, yinYang, wuXingJu.number); + // 13. 为每个宫位添加大运信息和流年 + // 找到当前大限 + const currentDaXian = daXianList.find(dx => currentAge >= dx.startAge && currentAge <= dx.endAge); + // 计算当前大限对应的完整年份范围(10年) + // 例如:2014年出生火六局,当前12岁在6~15岁大限,对应2019~2028年(完整10年) + // 例如:1990年出生土五局,当前36岁在35~44岁大限,对应2024~2033年(完整10年) + let liunianYearStart; + let liunianYearEnd; + if (currentDaXian) { + const birthYear = birthInfo.year; + // 大限开始年份 = 出生年份 + 大限开始年龄 - 1 + liunianYearStart = birthYear + currentDaXian.startAge - 1; + // 大限结束年份 = 出生年份 + 大限结束年龄 - 1 + liunianYearEnd = birthYear + currentDaXian.endAge - 1; + } + // 十二宫名称列表(命宫开始逆时针) + const GONG_NAMES_ORDER = ['命宫', '兄弟宫', '夫妻宫', '子女宫', '财帛宫', '疾厄宫', + '迁移宫', '交友宫', '官禄宫', '田宅宫', '福德宫', '父母宫']; + const gongsWithDaXian = gongsWithSiHua.map((gong, index) => { + const gongZhiIndex = (mingGongIndex - index + 12) % 12; + const daXian = daXianList.find(dx => dx.gongIndex === gongZhiIndex); + // 计算流年:根据地支固定位置分配年份 + // 例如:2025年乙巳年,固定在巳宫;2026年丙午年,固定在午宫 + // 计算该宫位地支对应的年份 + // 地支循环:子丑寅卯辰巳午未申酉戌亥(0-11) + const dizhiIndex = DIZHI.indexOf(gong.dizhi); + // 计算该地支对应的年份(在当前大限范围内) + let displayYear; + if (liunianYearStart && liunianYearEnd) { + // 遍历当前大限的所有年份,找到地支匹配的年份 + for (let year = liunianYearStart; year <= liunianYearEnd; year++) { + const yearZhiIndex = (year - 4) % 12; + if (yearZhiIndex === dizhiIndex) { + displayYear = year; + break; + } + } + } + // 计算大限宫位名(基于当前大限的起始宫位) + let daXianGongName; + if (currentDaXian) { + // 找到当前大限的起始宫位在十二宫中的位置 + const currentDaXianGongIndex = gongsWithSiHua.findIndex(g => { + const gZhiIndex = DIZHI.indexOf(g.dizhi); + return gZhiIndex === currentDaXian.gongIndex; + }); + // 计算当前宫位相对于大限命宫的偏移 + const offsetFromDaXianMing = (index - currentDaXianGongIndex + 12) % 12; + // 根据偏移获取大限宫位名 + const daXianGongNameMap = { + '命宫': '大命', + '兄弟宫': '大兄', + '夫妻宫': '大夫', + '子女宫': '大子', + '财帛宫': '大财', + '疾厄宫': '大疾', + '迁移宫': '大迁', + '交友宫': '大友', + '官禄宫': '大官', + '田宅宫': '大田', + '福德宫': '大福', + '父母宫': '大父' + }; + const correspondingGongName = GONG_NAMES_ORDER[offsetFromDaXianMing]; + daXianGongName = daXianGongNameMap[correspondingGongName]; + } + if (daXian) { + const isCurrent = currentAge >= daXian.startAge && currentAge <= daXian.endAge; + // 计算该大运内的流年年龄列表 + const liuNianAges = []; + for (let age = daXian.startAge; age <= daXian.endAge; age++) { + liuNianAges.push(age); + } + return { + ...gong, + daXian: { + startAge: daXian.startAge, + endAge: daXian.endAge, + isCurrent, + daXianGongName + }, + liuNian: liuNianAges, + liuNianYear: displayYear + }; + } + return { + ...gong, + liuNianYear: displayYear + }; + }); + return { + birthInfo, + lunarDate: { + year: lunarYear, + month: lunarMonth, + day: lunarDay, + monthCn: lunar.getMonthInChinese(), + dayCn: lunar.getDayInChinese() + }, + siZhu, + yinYang, + mingGongIndex, + shenGongIndex, + wuXingJu, + gongs: gongsWithDaXian + }; + } + catch (error) { + console.error('紫微斗数排盘失败:', error); + throw new Error('紫微斗数排盘计算失败: ' + error.message); + } +} +function runZiweiTests() { + console.error('紫微斗数测试功能待实现'); + return { passed: 0, failed: 0, details: [] }; +} +/** + * 安红鸾、天喜 + * 口诀:卯宫起子年,逆数至生年支;天喜为红鸾对宫 + */ +function installHongLuanTianXi(yearZhi) { + const stars = new Map(); + const zhiIndex = DIZHI.indexOf(yearZhi); + // 红鸾:卯宫起子年,逆数至生年支 + const hongLuanPos = (3 - zhiIndex + 12) % 12; + // 天喜:红鸾对宫 + const tianXiPos = (hongLuanPos + 6) % 12; + stars.set('红鸾', hongLuanPos); + stars.set('天喜', tianXiPos); + return stars; +} +/** + * 安天刑 + * 口诀:天刑酉上正月轮,数至生月便住脚 + */ +function installTianXing(lunarMonth) { + const stars = new Map(); + // 天刑:酉宫起正月,顺数至生月 + const tianXingPos = (9 + lunarMonth - 1) % 12; + stars.set('天刑', tianXingPos); + return stars; +} +/** + * 安天姚 + * 口诀:天姚丑上顺正月,数至生月便住脚 + */ +function installTianYao(lunarMonth) { + const stars = new Map(); + // 天姚:丑宫起正月,顺数至生月 + const tianYaoPos = (1 + lunarMonth - 1) % 12; + stars.set('天姚', tianYaoPos); + return stars; +} diff --git a/calculator/dump-text.ts b/calculator/dump-text.ts new file mode 100644 index 0000000..df3302b --- /dev/null +++ b/calculator/dump-text.ts @@ -0,0 +1,235 @@ +// 把 run-chart.ts 的 JSON 输出转成文墨天机风格树状文本 +// 用法: +// npx tsx dump-text.ts --input=chart.json [--output=chart.txt] +// 不指定 --output 则打印到 stdout + +import * as fs from 'fs'; + +function parseArgs(): Record { + const args: Record = {}; + for (const a of process.argv.slice(2)) { + const m = a.match(/^--([^=]+)=(.*)$/); + if (m) args[m[1]] = m[2]; + } + return args; +} + +function padRight(s: string, n: number): string { + // 中文字符按宽度 2 计算 + let w = 0; + for (const ch of s) w += /[一-龥＀-￿]/.test(ch) ? 2 : 1; + return s + ' '.repeat(Math.max(0, n - w)); +} + +function dumpZiwei(z: any, bi: any): string[] { + const lines: string[] = []; + lines.push('紫微斗数命盘'); + lines.push('│'); + lines.push('├基本信息'); + lines.push(`│ ├性别 : ${bi.gender === 'male' ? '男' : '女'}`); + lines.push(`│ ├阳历 : ${bi.year}-${String(bi.month).padStart(2,'0')}-${String(bi.day).padStart(2,'0')} ${String(bi.hour).padStart(2,'0')}:${String(bi.minute).padStart(2,'0')}`); + if (z.lunarDate) { + lines.push(`│ ├农历 : ${z.lunarDate.year}年${z.lunarDate.monthCn}月${z.lunarDate.dayCn}`); + } + if (z.siZhu) { + const sz = z.siZhu; + lines.push(`│ ├节气四柱 : ${sz.year.gan}${sz.year.zhi} ${sz.month.gan}${sz.month.zhi} ${sz.day.gan}${sz.day.zhi} ${sz.hour.gan}${sz.hour.zhi}`); + } + lines.push(`│ ├阴阳 : ${z.yinYang || ''}`); + lines.push(`│ ├五行局 : ${z.wuXingJu?.name || ''}`); + const DIZHI = ['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥']; + const mingDizhi = z.gongs[0]?.dizhi; + const shenDizhi = DIZHI[z.shenGongIndex]; + lines.push(`│ └命宫=${mingDizhi} 身宫=${shenDizhi}`); + lines.push('│'); + + // 生年四化汇总 + const allSihua: string[] = []; + for (const g of z.gongs) { + for (const s of g.sihua || []) { + allSihua.push(`${s.star}${s.hua}`); + } + } + if (allSihua.length > 0) { + lines.push('├生年四化'); + lines.push(`│ └${allSihua.join(' · ')}`); + lines.push('│'); + } + + // 十二宫 + lines.push('├命盘十二宫'); + z.gongs.forEach((g: any, idx: number) => { + const isLast = idx === z.gongs.length - 1; + const prefix = isLast ? '│ └' : '│ ├'; + const childPrefix = isLast ? '│ ' : '│ │ '; + const isMing = g.gong === '命宫'; + const isShen = g.dizhi === shenDizhi; + const marks: string[] = []; + if (isMing) marks.push('[命]'); + if (isShen && !isMing) marks.push('[身]'); + const gongName = g.gong.endsWith('宫') ? g.gong : g.gong + '宫'; + lines.push(`${prefix}${gongName}[${g.tiangan}${g.dizhi}]${marks.join('')}`); + const main = g.mainStars && g.mainStars.length > 0 ? g.mainStars.join('·') : '无主星'; + lines.push(`${childPrefix}├主星 : ${main}`); + const aux = g.auxStars && g.auxStars.length > 0 ? g.auxStars.join('·') : '无'; + lines.push(`${childPrefix}├辅星 : ${aux}`); + if (g.sihua && g.sihua.length > 0) { + lines.push(`${childPrefix}├生年四化 : ${g.sihua.map((s:any)=>s.star+s.hua).join('·')}`); + } + if (g.daXian) { + const dxMark = g.daXian.isCurrent ? '★当前' : ''; + lines.push(`${childPrefix}├大限 : ${g.daXian.startAge}-${g.daXian.endAge}虚岁 ${dxMark}`); + } + if (g.liuNian && g.liuNian.length > 0) { + lines.push(`${childPrefix}└流年 : ${g.liuNian.join('·')}虚岁`); + } + if (!isLast) lines.push('│ │'); + }); + + return lines; +} + +function dumpBazi(b: any, bi: any): string[] { + const lines: string[] = []; + lines.push(''); + lines.push('八字命盘'); + lines.push('│'); + + // 四柱表 + const sz = b.siZhu; + const ss = b.shiShen; + const zs = b.zhangSheng || {}; + const zz = b.enrichment?.自坐 || {}; + const ny = b.naYin || {}; + const cg = b.cangGan || {}; + + lines.push('├四柱'); + const cols = ['年','月','日','时']; + const pillarKeys = ['year','month','day','hour']; + const cangGanFmt = (pk: string): string => { + const arr = cg[pk]; + if (!Array.isArray(arr)) return ''; + return arr.map((x:any)=>`${x.gan}(${x.shiShen||''})`).join(' '); + }; + for (let i = 0; i < 4; i++) { + const isLast = i === 3; + const pre = isLast ? '│ └' : '│ ├'; + const subPre = isLast ? '│ ' : '│ │ '; + const pk = pillarKeys[i]; + const sx = ss[pk] || ''; + const isDay = pk === 'day'; + const tag = isDay ? `[日主]` : `[${sx}]`; + lines.push(`${pre}${cols[i]}柱 : ${sz[pk].gan}${sz[pk].zhi} ${tag}`); + if (cg[pk]) lines.push(`${subPre}├藏干 : ${cangGanFmt(pk)}`); + lines.push(`${subPre}├星运 : ${zs[pk] || '-'}`); + lines.push(`${subPre}├自坐 : ${zz[cols[i]] || zz[pk] || '-'}`); + lines.push(`${subPre}└纳音 : ${ny[pk] || '-'}`); + if (!isLast) lines.push('│ │'); + } + lines.push('│'); + + // 大运 + if (b.dayun && b.dayun.length > 0) { + lines.push(`├大运 (起运 ${b.dayunStart}岁)`); + b.dayun.slice(0, 10).forEach((d: any, i: number) => { + const isLast = i === Math.min(9, b.dayun.length - 1); + const pre = isLast ? '│ └' : '│ ├'; + const dxTag = `${d.ganShiShen||''}/${d.zhiShiShen||''}`; + lines.push(`${pre}${d.startYear}-${d.endYear} ${d.ganZhi.gan}${d.ganZhi.zhi} (${dxTag})`); + }); + lines.push('│'); + } + + // enrichBazi 补层 + const en = b.enrichment; + if (en) { + lines.push('├算法补层'); + lines.push(`│ ├格局 : ${en.格局?.primary || '-'} (置信度: ${en.格局?.confidence || '-'})`); + if (en.格局?.basis) lines.push(`│ │ └依据 : ${en.格局.basis}`); + if (en.格局?.notes && en.格局.notes.length) { + for (const note of en.格局.notes) lines.push(`│ │ └备注 : ${note}`); + } + const ws = en.旺衰; + if (ws) { + const lvl = ws.verdict || ws.level || '-'; + const score = ws.score !== undefined ? `score=${ws.score}` : ''; + lines.push(`│ ├旺衰 : ${lvl} (${score}, 置信度: ${ws.confidence || '-'})`); + if (ws.breakdown) { + const b = ws.breakdown; + lines.push(`│ │ └四维 : 得令${b.得令} 长生${b.长生} 得地${b.得地} 得势${b.得势}`); + } + } + if (en.调候用神) lines.push(`│ ├调候用神 : ${en.调候用神.join('、')}`); + if (en.五行旺相) { + const ws5 = en.五行旺相; + lines.push(`│ ├五行旺相 : 木${ws5.木} 火${ws5.火} 土${ws5.土} 金${ws5.金} 水${ws5.水}`); + } + if (en.五行统计) { + const s = en.五行统计.surface || en.五行统计; + const w = en.五行统计.withCangGan; + if (s) lines.push(`│ ├五行统计(surface) : 木${s.木||0} 火${s.火||0} 土${s.土||0} 金${s.金||0} 水${s.水||0}`); + if (w) lines.push(`│ ├五行统计(含藏干) : 木${w.木||0} 火${w.火||0} 土${w.土||0} 金${w.金||0} 水${w.水||0}`); + } + // 天干关系 + const gr = en.天干关系; + if (gr && Array.isArray(gr) && gr.length > 0) { + lines.push('│ ├天干关系'); + gr.forEach((r:any, i:number) => { + const last = i === gr.length-1; + const pair = (r.gans || []).join(''); + const pillars = (r.pillars || []).join('-'); + lines.push(`│ │ ${last?'└':'├'}${r.type} : ${pair} (${pillars}柱)`); + }); + } + // 地支关系 + const zr = en.地支关系; + if (zr && Array.isArray(zr) && zr.length > 0) { + lines.push('│ ├地支关系'); + zr.forEach((r:any, i:number) => { + const last = i === zr.length-1; + const pair = (r.zhi || []).join(''); + const pillars = (r.pillars || []).join('-'); + const extra = r.detail ? ` ${r.detail}` : ''; + lines.push(`│ │ ${last?'└':'├'}${r.type} : ${pair} (${pillars}柱)${extra}`); + }); + } + // 整柱 + const zp = en.整柱; + if (zp && Array.isArray(zp) && zp.length > 0) { + lines.push('│ └整柱判定'); + zp.forEach((p:any, i:number) => { + const last = i === zp.length-1; + lines.push(`│ ${last?'└':'├'}${p.pillar}柱 ${p.gan}${p.zhi} : ${p.verdict}`); + }); + } + } + lines.push(''); + lines.push('└[备注: 本盘由 bazi-ziwei skill 算法层生成 — Yiqi core + enrichBazi 补层]'); + + return lines; +} + +function main() { + const args = parseArgs(); + if (!args.input) { + console.error('Usage: npx tsx dump-text.ts --input=chart.json [--output=chart.txt]'); + process.exit(1); + } + const chart = JSON.parse(fs.readFileSync(args.input, 'utf-8')); + const bi = chart.bazi.birthInfo || chart.ziwei.birthInfo; + + const lines: string[] = []; + lines.push(...dumpZiwei(chart.ziwei, bi)); + lines.push(...dumpBazi(chart.bazi, bi)); + + const text = lines.join('\n'); + + if (args.output) { + fs.writeFileSync(args.output, text, 'utf-8'); + console.error(`Text dump written to ${args.output}`); + } else { + process.stdout.write(text); + } +} + +main(); diff --git a/calculator/package-lock.json b/calculator/package-lock.json new file mode 100644 index 0000000..b49c506 --- /dev/null +++ b/calculator/package-lock.json @@ -0,0 +1,56 @@ +{ + "name": "bazi-ziwei-calculator", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bazi-ziwei-calculator", + "version": "0.1.0", + "dependencies": { + "lunar-typescript": "^1.8.6" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "~5.6.2" + } + }, + "node_modules/@types/node": { + "version": "22.19.21", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.19.21.tgz", + "integrity": "sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/lunar-typescript": { + "version": "1.8.6", + "resolved": "https://registry.npmmirror.com/lunar-typescript/-/lunar-typescript-1.8.6.tgz", + "integrity": "sha512-5Eo4T/cnuXfrgO4k5LCpOGHIUOuz5hCF/IfNv0T29WY2shR36Hiz+ecN9WjnUuxUKhql9gbOkPaQoqLFKtPRNA==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/calculator/package.json b/calculator/package.json new file mode 100644 index 0000000..f90325c --- /dev/null +++ b/calculator/package.json @@ -0,0 +1,19 @@ +{ + "name": "bazi-ziwei-calculator", + "version": "0.1.0", + "description": "八字+紫微算法层 — Yiqi core (vendored) + enrichBazi 补层", + "private": true, + "scripts": { + "build": "tsc", + "chart": "node dist/run-chart.js", + "dump": "node dist/dump-text.js", + "render": "node dist/render.js" + }, + "dependencies": { + "lunar-typescript": "^1.8.6" + }, + "devDependencies": { + "typescript": "~5.6.2", + "@types/node": "^22.0.0" + } +} diff --git a/calculator/render.ts b/calculator/render.ts new file mode 100644 index 0000000..47a5c80 --- /dev/null +++ b/calculator/render.ts @@ -0,0 +1,349 @@ +// 渲染脚本: 算法 JSON + 分析 JSON + 模板 → 单文件 HTML +// +// 用法: +// npx tsx render.ts \ +// --chart=path/to/chart.json \ +// --analysis=path/to/analysis.json \ +// --template=../templates/report-zonghe-poster.html \ +// --output=path/to/output.html +// +// chart.json: run-chart.ts 的输出 (算法层) +// analysis.json: LLM 按 zonghe-poster.md schema 输出的 JSON + +import * as fs from 'fs'; +import * as path from 'path'; + +function parseArgs(): Record { + const args: Record = {}; + for (const a of process.argv.slice(2)) { + const m = a.match(/^--([^=]+)=(.*)$/); + if (m) args[m[1]] = m[2]; + } + return args; +} + +const DIZHI = ['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥']; + +function calcVirtualAge(birthYear: number, currentYear: number): number { + return currentYear - birthYear + 1; +} + +function chartToFlat(chart: any, currentYear?: number): Record { + const out: Record = {}; + const bi = chart.bazi.birthInfo; + const bz = chart.bazi; + const zw = chart.ziwei; + currentYear = currentYear || new Date().getFullYear(); + const virtualAge = calcVirtualAge(bi.year, currentYear); + + // ============ META ============ + out['meta.solar_date'] = `${bi.year}-${String(bi.month).padStart(2,'0')}-${String(bi.day).padStart(2,'0')} ${String(bi.hour).padStart(2,'0')}:${String(bi.minute).padStart(2,'0')}`; + if (zw.lunarDate) { + out['meta.lunar_date'] = `${zw.lunarDate.year}年 ${zw.lunarDate.monthCn}月${zw.lunarDate.dayCn} ${zw.lunarDate.hourCn || ''}`.trim(); + } else { + out['meta.lunar_date'] = '-'; + } + out['meta.gender_full'] = bi.gender === 'male' ? '男(' + (zw.yinYang || '') + ')' : '女(' + (zw.yinYang || '') + ')'; + out['meta.age_virtual'] = virtualAge.toString(); + out['meta.current_year'] = currentYear.toString(); + const now = new Date(); + out['meta.gen_time'] = `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')} ${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}`; + out['meta.yinyang'] = zw.yinYang || '-'; + + // ============ ZIWEI META ============ + // Yiqi 没明确输出 命主/身主/子年斗君 — 从十二宫推导 / 留空 + // 简化: 默认根据命宫地支查命主, 身宫地支查身主 + const MING_ZHU = { '子':'贪狼','丑':'巨门','寅':'禄存','卯':'文曲','辰':'廉贞','巳':'武曲','午':'破军','未':'武曲','申':'廉贞','酉':'文曲','戌':'禄存','亥':'巨门' }; + const SHEN_ZHU = { '子':'火星','丑':'天相','寅':'天梁','卯':'天同','辰':'文昌','巳':'天机','午':'火星','未':'天相','申':'天梁','酉':'天同','戌':'文昌','亥':'天机' }; + const mingDizhi = zw.gongs[0].dizhi; + const shenDizhi = DIZHI[zw.shenGongIndex]; + out['ziwei.ming_zhu'] = (MING_ZHU as any)[mingDizhi] || '-'; + out['ziwei.shen_zhu'] = (SHEN_ZHU as any)[shenDizhi] || '-'; + // 子年斗君: 简化处理, 按生月+生时推算复杂, 暂用身宫前后位作占位 + out['ziwei.zi_dou_jun'] = zw.ziDouJun || '-'; + out['ziwei.wuxing_ju'] = zw.wuXingJu?.name || '-'; + + // ============ CORE DATA ============ + const en = bz.enrichment; + out['core.geju'] = en?.格局?.primary || '-'; + out['core.geju_confidence'] = en?.格局?.confidence || '-'; + out['core.wangshuai_verdict'] = en?.旺衰?.verdict || '-'; + out['core.wangshuai_score'] = en?.旺衰?.score?.toString() || '-'; + // 把 score 映射到 0-100% (假设 score -10 ~ +10) + const ws = en?.旺衰?.score ?? 0; + out['core.wangshuai_pos_pct'] = Math.max(0, Math.min(100, Math.round((ws + 10) * 5))).toString(); + const tc = en?.调候用神 || []; + out['core.tiaohou.0'] = tc[0] || '-'; + out['core.tiaohou.1'] = tc[1] || '-'; + out['core.tiaohou_confidence'] = '高'; + + const yl = en?.五行旺相 || {}; + for (const k of ['木','火','土','金','水']) { + out[`core.yueling.${k}`] = yl[k] || '-'; + } + + const wx = en?.五行统计?.withCangGan || en?.五行统计 || { 木:0,火:0,土:0,金:0,水:0 }; + for (const k of ['木','火','土','金','水']) out[`core.wuxing.${k}`] = wx[k] ?? '-'; + const wxMax = Math.max(...['木','火','土','金','水'].map(k => +wx[k] || 0)) || 1; + for (const k of ['木','火','土','金','水']) out[`core.wuxing_pct.${k}`] = Math.round(((+wx[k] || 0) / wxMax) * 100); + + // ============ ZIWEI 12 GONGS ============ + const sihuaCharMap: any = { 化禄:'禄', 化权:'权', 化科:'科', 化忌:'忌' }; + for (const g of zw.gongs) { + const mainStarsHtml = (g.mainStars && g.mainStars.length > 0) + ? g.mainStars.map((s: string) => { + const sh = (g.sihua || []).find((x: any) => x.star === s); + if (sh) { + const huaChar = sihuaCharMap[sh.hua] || sh.hua.slice(-1); + return `${s}${huaChar}`; + } + return s; + }).join('·') + : '无主星'; + // 辅星同样要处理四化(右弼化科 / 文昌化忌 / 文曲化科 等常落辅星) + const auxStarsHtml = (g.auxStars && g.auxStars.length > 0) + ? g.auxStars.map((s: string) => { + const sh = (g.sihua || []).find((x: any) => x.star === s); + if (sh) { + const huaChar = sihuaCharMap[sh.hua] || sh.hua.slice(-1); + return `${s}${huaChar}`; + } + return s; + }).join('·') + : '—'; + out[`gongs.${g.dizhi}.name`] = g.gong.endsWith('宫') ? g.gong : g.gong + '宫'; + out[`gongs.${g.dizhi}.ganzhi`] = g.tiangan + g.dizhi; + out[`gongs.${g.dizhi}.mainStarsHtml`] = mainStarsHtml; + out[`gongs.${g.dizhi}.auxStars`] = auxStarsHtml; + out[`gongs.${g.dizhi}.smallStars`] = ''; + out[`gongs.${g.dizhi}.daxian_range`] = g.daXian ? `${g.daXian.startAge}-${g.daXian.endAge}` : '-'; + // 命宫红框 / 身宫徽标 / 当前大限高亮 — 数据驱动, 不硬编码到模板 + const flags: string[] = []; + if (g.dizhi === mingDizhi) flags.push('ming'); + if (g.dizhi === shenDizhi) flags.push('shen'); + if (g.daXian && g.daXian.startAge <= virtualAge && virtualAge <= g.daXian.endAge) flags.push('current-daxian'); + out[`gongs.${g.dizhi}.flag`] = flags.join(' '); + out[`gongs.${g.dizhi}.shenBadge`] = (g.dizhi === shenDizhi) ? '' : ''; + } + + // ============ BAZI 4 PILLARS ============ + const cangGanFmt = (arr: any[]) => (arr || []).map((x: any) => `${x.gan}(${x.shiShen})`).join(' '); + const pillarKeyToCn: any = { year: '年', month: '月', day: '日', hour: '时' }; + for (const k of ['year','month','day','hour']) { + out[`bazi.${k}.shiShen`] = bz.shiShen?.[k] || '-'; + out[`bazi.${k}.gan`] = bz.siZhu[k].gan; + out[`bazi.${k}.zhi`] = bz.siZhu[k].zhi; + out[`bazi.${k}.cangGanHtml`] = cangGanFmt(bz.cangGan?.[k] || []); + out[`bazi.${k}.zhangSheng`] = bz.zhangSheng?.[k] || '-'; + out[`bazi.${k}.ziZuo`] = en?.自坐?.[pillarKeyToCn[k]] || en?.自坐?.[k] || '-'; + out[`bazi.${k}.naYin`] = bz.naYin?.[k] || '-'; + } + out['bazi.dayunStart'] = bz.dayunStart?.toString() || '-'; + + // ============ DAYUN 10 ============ + const dayunArr = (bz.dayun || []).slice(0, 10); + let currentDayun: any = null; + for (let i = 0; i < 10; i++) { + const d = dayunArr[i]; + if (d && d.startAge <= virtualAge && virtualAge <= d.endAge) currentDayun = d; + } + for (let i = 0; i < 10; i++) { + const d = dayunArr[i]; + if (!d) { + ['gz','age_range','shishen','current_class'].forEach(f => out[`dayun.${i}.${f}`] = '-'); + continue; + } + out[`dayun.${i}.gz`] = d.ganZhi.gan + d.ganZhi.zhi; + out[`dayun.${i}.age_range`] = `${d.startAge}-${d.endAge}`; + const sg = (d.ganShiShen || '').slice(0,1); + const sz = (d.zhiShiShen || '').slice(0,1); + out[`dayun.${i}.shishen`] = sg + sz; + out[`dayun.${i}.current_class`] = (currentDayun && d === currentDayun) ? 'current dayun' : ''; + } + + // ============ SECTION 02 阶段印证时间轴 (从 chart 算, 不靠 LLM) ============ + // 八字大运: 前 7 段 + const dayunForStage = dayunArr.slice(0, 7); + for (let i = 0; i < 7; i++) { + const d = dayunForStage[i]; + if (!d) { + ['range','gz','shishen','current_class'].forEach(f => out[`section_02.bazi.${i}.${f}`] = '-'); + continue; + } + out[`section_02.bazi.${i}.range`] = `${d.startAge}-${d.endAge}`; + out[`section_02.bazi.${i}.gz`] = d.ganZhi.gan + d.ganZhi.zhi; + const sg = (d.ganShiShen || '').slice(0,1); + const sz = (d.zhiShiShen || '').slice(0,1); + out[`section_02.bazi.${i}.shishen`] = sg + sz; + out[`section_02.bazi.${i}.current_class`] = (d.startAge <= virtualAge && virtualAge <= d.endAge) ? 'current' : ''; + } + + // 紫微大限: 按 startAge 排序取前 7 段 + const ziweiDaxian = zw.gongs + .filter((g: any) => g.daXian) + .map((g: any) => ({ startAge: g.daXian.startAge, endAge: g.daXian.endAge, gong: g.gong })) + .sort((a: any, b: any) => a.startAge - b.startAge) + .slice(0, 7); + for (let i = 0; i < 7; i++) { + const d = ziweiDaxian[i]; + if (!d) { + ['range','current_class'].forEach(f => out[`section_02.ziwei.${i}.${f}`] = '-'); + continue; + } + out[`section_02.ziwei.${i}.range`] = `${d.startAge}-${d.endAge}`; + out[`section_02.ziwei.${i}.current_class`] = (d.startAge <= virtualAge && virtualAge <= d.endAge) ? 'current' : ''; + } + + // ============ LIUNIAN 10 (current dayun) ============ + if (currentDayun) { + out['liunian_dayun_label'] = `${currentDayun.ganZhi.gan}${currentDayun.ganZhi.zhi} ${currentDayun.startAge}-${currentDayun.endAge}`; + } else { + out['liunian_dayun_label'] = '-'; + } + const liunianArr = ((currentDayun?.liuNian) || []).slice(0, 10); + for (let i = 0; i < 10; i++) { + const ln = liunianArr[i]; + if (!ln) { + ['year','age','gz','shishen','current_class'].forEach(f => out[`liunian.${i}.${f}`] = '-'); + continue; + } + out[`liunian.${i}.year`] = ln.year; + out[`liunian.${i}.age`] = ln.age; + out[`liunian.${i}.gz`] = ln.ganZhi.gan + ln.ganZhi.zhi; + out[`liunian.${i}.shishen`] = ln.ganShiShen ? (ln.ganShiShen.slice(0,1) + (ln.zhiShiShen?.slice(0,1) || '')) : ''; + out[`liunian.${i}.current_class`] = (ln.age === virtualAge) ? 'current' : ''; + } + + return out; +} + +function analysisToFlat(analysis: any): Record { + const out: Record = {}; + + // meta + if (analysis.meta) { + out['meta.archetype_name'] = analysis.meta.archetype_name; + out['meta.axis_oneliner'] = analysis.meta.axis_oneliner; + } + + // axes + consistency + if (analysis.axes) { + out['axes.bazi_main'] = analysis.axes.bazi_main; + out['axes.ziwei_main'] = analysis.axes.ziwei_main; + } + if (analysis.consistency) out['ziwei.consistency'] = analysis.consistency; + + // strengths / weaknesses + for (let i = 0; i < 3; i++) { + const s = analysis.strengths?.[i] || {}; + out[`strengths.${i}.title`] = s.title || '-'; + out[`strengths.${i}.desc`] = s.desc || '-'; + const w = analysis.weaknesses?.[i] || {}; + out[`weaknesses.${i}.title`] = w.title || '-'; + out[`weaknesses.${i}.desc`] = w.desc || '-'; + } + + // section 01 + if (analysis.section_01) { + out['section_01.text'] = analysis.section_01.text || '-'; + out['section_01.word_count'] = analysis.section_01.word_count || '-'; + } + + // section 02 - bazi/ziwei dayun ranges already from chart, only conclusion + if (analysis.section_02) { + out['section_02.conclusion'] = analysis.section_02.conclusion || '-'; + } + + // dim + const dims = ['career','wealth','marriage','children','family','health']; + for (const k of dims) { + const d = analysis.dim?.[k] || {}; + out[`dim.${k}.bazi`] = d.bazi || '-'; + out[`dim.${k}.ziwei`] = d.ziwei || '-'; + out[`dim.${k}.verdict`] = d.verdict || '-'; + out[`dim.${k}.verdict_class`] = d.verdict_class || 'verdict-yes'; + out[`dim.${k}.fused`] = d.fused || '-'; + } + + // conflicts + for (let i = 0; i < 3; i++) { + const c = analysis.conflicts?.[i] || {}; + out[`conflicts.${i}.point`] = c.point || '-'; + out[`conflicts.${i}.bazi`] = c.bazi || '-'; + out[`conflicts.${i}.ziwei`] = c.ziwei || '-'; + out[`conflicts.${i}.impact`] = c.impact || '-'; + out[`conflicts.${i}.impact_class`] = c.impact_class || 'low'; + out[`conflicts.${i}.advice`] = c.advice || '-'; + } + + // final + if (analysis.final) { + out['final.life_axis'] = analysis.final.life_axis || '-'; + for (let i = 0; i < 5; i++) { + const n = analysis.final.nodes?.[i] || {}; + out[`final.nodes.${i}.age`] = n.age || '-'; + out[`final.nodes.${i}.year`] = n.year || '-'; + out[`final.nodes.${i}.event`] = n.event || '-'; + } + for (let i = 0; i < 3; i++) { + const r = analysis.final.risks?.[i] || {}; + out[`final.risks.${i}.range`] = r.range || '-'; + out[`final.risks.${i}.desc`] = r.desc || '-'; + } + for (let i = 0; i < 2; i++) { + const l = analysis.final.leverage?.[i] || {}; + out[`final.leverage.${i}.title`] = l.title || '-'; + out[`final.leverage.${i}.desc`] = l.desc || '-'; + } + for (let i = 0; i < 4; i++) out[`final.advice.${i}`] = analysis.final.advice?.[i] || '-'; + } + + // confidence + if (analysis.confidence) { + for (const k of ['bazi','ziwei','consistency','stability']) { + out[`confidence.${k}_level`] = analysis.confidence[`${k}_level`] || '-'; + out[`confidence.${k}_score`] = analysis.confidence[`${k}_score`] || '-'; + } + out['confidence.note'] = analysis.confidence.note || '-'; + } + + return out; +} + +function renderTemplate(template: string, data: Record): string { + let html = template; + // 第一轮: 精确替换 + for (const k of Object.keys(data)) { + const re = new RegExp(`\\{\\{${k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\}\\}`, 'g'); + html = html.replace(re, String(data[k])); + } + // 兜底: 剩余未匹配占位符替换为 '-' + html = html.replace(/\{\{[a-zA-Z0-9_.]+\}\}/g, '-'); + return html; +} + +function main() { + const args = parseArgs(); + if (!args.chart || !args.template) { + console.error('Usage: npx tsx render.ts --chart=chart.json [--analysis=analysis.json] --template=path/to/template.html [--output=out.html]'); + process.exit(1); + } + const chart = JSON.parse(fs.readFileSync(args.chart, 'utf-8')); + const analysis = args.analysis ? JSON.parse(fs.readFileSync(args.analysis, 'utf-8')) : {}; + const template = fs.readFileSync(args.template, 'utf-8'); + + const chartFlat = chartToFlat(chart, args.currentYear ? +args.currentYear : undefined); + const analysisFlat = analysisToFlat(analysis); + const data = { ...chartFlat, ...analysisFlat }; + + const html = renderTemplate(template, data); + + if (args.output) { + fs.writeFileSync(args.output, html, 'utf-8'); + console.error(`Rendered HTML written to ${args.output}`); + } else { + process.stdout.write(html); + } +} + +main(); diff --git a/calculator/run-chart.ts b/calculator/run-chart.ts new file mode 100644 index 0000000..77ee39e --- /dev/null +++ b/calculator/run-chart.ts @@ -0,0 +1,87 @@ +// 排盘单一入口 — 输入生辰, 输出完整 JSON (Yiqi createChart + enrichBazi) +// +// 用法: +// npx tsx run-chart.ts --year=2000 --month=1 --day=1 --hour=12 --minute=0 --gender=male +// 可选: --isLunar=true --timeZone=8 --output=path/to/file.json +// +// 不指定 --output 则打印到 stdout + +import { createChart } from './yiqi-core/index'; +import { getZhiCangGanFull } from './yiqi-core/bazi'; +import { enrichBazi } from './bazi-enrich/enrich'; +import * as fs from 'fs'; + +function parseArgs(): Record { + const args: Record = {}; + for (const a of process.argv.slice(2)) { + const m = a.match(/^--([^=]+)=(.*)$/); + if (m) args[m[1]] = m[2]; + } + return args; +} + +function main() { + const args = parseArgs(); + const required = ['year','month','day','hour','minute','gender']; + for (const k of required) { + if (!args[k]) { + console.error(`Missing required arg: --${k}=...`); + console.error('Usage: npx tsx run-chart.ts --year=2000 --month=1 --day=1 --hour=12 --minute=0 --gender=male'); + process.exit(1); + } + } + const gender = args.gender === 'male' || args.gender === 'female' ? args.gender : (args.gender === '男' ? 'male' : 'female'); + + const birthInfo = { + year: +args.year, + month: +args.month, + day: +args.day, + hour: +args.hour, + minute: +args.minute, + isLunar: args.isLunar === 'true', + gender: gender as 'male'|'female', + timeZone: args.timeZone ? +args.timeZone : 8, + }; + + // Step 1: Yiqi 算法层 — 四柱+紫微+大运+流年 + const chart: any = createChart(birthInfo); + + // 附加地支藏干 (含十神) + const dm = chart.bazi.dayMaster; + const z = chart.bazi.siZhu; + chart.bazi.cangGan = { + year: getZhiCangGanFull(z.year.zhi, dm), + month: getZhiCangGanFull(z.month.zhi, dm), + day: getZhiCangGanFull(z.day.zhi, dm), + hour: getZhiCangGanFull(z.hour.zhi, dm), + }; + + // 补 endAge 字段 (Yiqi 只给了 startAge/endYear, OpenClaw 等下游脚本会查 endAge) + if (chart.bazi.dayun && Array.isArray(chart.bazi.dayun)) { + for (const d of chart.bazi.dayun) { + if (d.startAge !== undefined && d.endAge === undefined) { + d.endAge = d.startAge + 9; + } + } + } + + // Step 2: enrichBazi 补层 — 格局/旺衰/调候/刑冲合害/盖头 + const siZhuForEnrich = { + '年': chart.bazi.siZhu.year, + '月': chart.bazi.siZhu.month, + '日': chart.bazi.siZhu.day, + '时': chart.bazi.siZhu.hour, + }; + chart.bazi.enrichment = enrichBazi(siZhuForEnrich); + + const json = JSON.stringify(chart, null, 2); + + if (args.output) { + fs.writeFileSync(args.output, json, 'utf-8'); + console.error(`Chart written to ${args.output}`); + } else { + process.stdout.write(json); + } +} + +main(); diff --git a/calculator/tsconfig.json b/calculator/tsconfig.json new file mode 100644 index 0000000..3fb8280 --- /dev/null +++ b/calculator/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "outDir": "dist", + "rootDir": ".", + "esModuleInterop": true, + "skipLibCheck": true, + "strict": false, + "noEmitOnError": false, + "resolveJsonModule": true, + "declaration": false, + "sourceMap": false + }, + "include": [ + "run-chart.ts", + "dump-text.ts", + "render.ts", + "yiqi-core/**/*.ts", + "bazi-enrich/**/*.ts" + ], + "exclude": [ + "bazi-enrich/print-full.ts", + "bazi-enrich/test-case-b.ts" + ] +} diff --git a/calculator/yiqi-core/bazi.ts b/calculator/yiqi-core/bazi.ts new file mode 100644 index 0000000..b32ff8d --- /dev/null +++ b/calculator/yiqi-core/bazi.ts @@ -0,0 +1,356 @@ +// 八字排盘核心算法(使用lunar-javascript库) + +import { BirthInfo, BaziChart, SiZhu, Tiangan, DayunDetail, LiuNian, GanZhi } from './types'; +import { Lunar, Solar } from 'lunar-typescript'; +import { getSiZhuChangSheng } from './zhangsheng'; +import { getSiZhuNaYin } from './nayin'; +import { getAccurateMonthGanZhi } from './jieqi'; + +/** + * 十神关系映射 + */ +const SHISHEN_MAP = { + '甲': { '甲': '比肩', '乙': '劫财', '丙': '食神', '丁': '伤官', '戊': '偏财', '己': '正财', '庚': '七杀', '辛': '正官', '壬': '偏印', '癸': '正印' }, + '乙': { '甲': '劫财', '乙': '比肩', '丙': '伤官', '丁': '食神', '戊': '正财', '己': '偏财', '庚': '正官', '辛': '七杀', '壬': '正印', '癸': '偏印' }, + '丙': { '甲': '偏印', '乙': '正印', '丙': '比肩', '丁': '劫财', '戊': '食神', '己': '伤官', '庚': '偏财', '辛': '正财', '壬': '七杀', '癸': '正官' }, + '丁': { '甲': '正印', '乙': '偏印', '丙': '劫财', '丁': '比肩', '戊': '伤官', '己': '食神', '庚': '正财', '辛': '偏财', '壬': '正官', '癸': '七杀' }, + '戊': { '甲': '七杀', '乙': '正官', '丙': '偏印', '丁': '正印', '戊': '比肩', '己': '劫财', '庚': '食神', '辛': '伤官', '壬': '偏财', '癸': '正财' }, + '己': { '甲': '正官', '乙': '七杀', '丙': '正印', '丁': '偏印', '戊': '劫财', '己': '比肩', '庚': '伤官', '辛': '食神', '壬': '正财', '癸': '偏财' }, + '庚': { '甲': '偏财', '乙': '正财', '丙': '七杀', '丁': '正官', '戊': '偏印', '己': '正印', '庚': '比肩', '辛': '劫财', '壬': '食神', '癸': '伤官' }, + '辛': { '甲': '正财', '乙': '偏财', '丙': '正官', '丁': '七杀', '戊': '正印', '己': '偏印', '庚': '劫财', '辛': '比肩', '壬': '伤官', '癸': '食神' }, + '壬': { '甲': '食神', '乙': '伤官', '丙': '偏财', '丁': '正财', '戊': '七杀', '己': '正官', '庚': '偏印', '辛': '正印', '壬': '比肩', '癸': '劫财' }, + '癸': { '甲': '伤官', '乙': '食神', '丙': '正财', '丁': '偏财', '戊': '正官', '己': '七杀', '庚': '正印', '辛': '偏印', '壬': '劫财', '癸': '比肩' } +} as const; + +/** + * 地支藏干(主气)映射 + */ +const DIZHI_CANGGAN = { + '子': '癸', '丑': '己', '寅': '甲', '卯': '乙', + '辰': '戊', '巳': '丙', '午': '丁', '未': '己', + '申': '庚', '酉': '辛', '戌': '戊', '亥': '壬' +} as const; + +/** + * 地支藏干(完整)映射 - 每个地支包含本气、中气、余气 + */ +const DIZHI_CANGGAN_FULL = { + '子': ['癸'], + '丑': ['己', '癸', '辛'], + '寅': ['甲', '丙', '戊'], + '卯': ['乙'], + '辰': ['戊', '乙', '癸'], + '巳': ['丙', '庚', '戊'], + '午': ['丁', '己'], + '未': ['己', '丁', '乙'], + '申': ['庚', '壬', '戊'], + '酉': ['辛'], + '戌': ['戊', '辛', '丁'], + '亥': ['壬', '甲'] +} as const; + +/** + * 获取十神关系 + */ +function getShiShen(dayGan: string, targetGan: string): string { + return (SHISHEN_MAP as any)[dayGan]?.[targetGan] || '未知'; +} + +/** + * 获取地支藏干主气 + */ +function getZhiCangGan(zhi: string): string { + return (DIZHI_CANGGAN as any)[zhi] || '未知'; +} + +/** + * 获取地支完整藏干(含所有藏干及其十神) + * @param zhi 地支 + * @param dayMaster 日主天干 + * @returns 藏干数组,每项包含天干和对应十神 + */ +export function getZhiCangGanFull(zhi: string, dayMaster: string): Array<{ gan: string, shiShen: string }> { + const cangGanList = (DIZHI_CANGGAN_FULL as any)[zhi] || []; + return cangGanList.map((gan: string) => ({ + gan, + shiShen: getShiShen(dayMaster, gan) + })); +} + +/** + * 处理晚子时(23:00-24:00)的日柱 + * 在传统命理中,晚子时应该使用第二天的日柱 + * @param solar Solar对象 + * @returns 调整后的Solar对象(如果是晚子时) + */ +function getAdjustedSolarForZiShi(solar: Solar): Solar { + const hour = solar.getHour(); + + // 如果是晚子时(23:00-24:00),使用第二天的日期计算日柱 + if (hour === 23) { + console.error('[晚子时处理] 23点,使用第二天的日柱'); + // 获取第二天的日期 + const nextDay = solar.next(1); // 下一天 + return nextDay; + } + + return solar; +} + +/** + * 创建八字排盘(使用lunar-javascript库) + * @param birthInfo 生辰信息 + * @returns 八字排盘结果 + */ +export function createBaziChart(birthInfo: BirthInfo): BaziChart { + try { + // 创建Solar对象(公历日期) + const solar = Solar.fromYmdHms( + birthInfo.year, + birthInfo.month, + birthInfo.day, + birthInfo.hour, + birthInfo.minute, + 0 + ); + + // 处理晚子时(23:00-24:00),使用第二天的日柱 + const solarForDay = getAdjustedSolarForZiShi(solar); + const lunarForDay = solarForDay.getLunar(); + const baZiForDay = lunarForDay.getEightChar(); + + // 转换为农历(用于年柱、月柱、时柱) + const lunar = solar.getLunar(); + + // 获取八字 + const baZi = lunar.getEightChar(); + + // 使用精确节气时刻计算月柱 + const accurateMonthGZ = getAccurateMonthGanZhi(solar); + + // 获取四柱干支 + const siZhu: SiZhu = { + year: { + gan: baZi.getYear().substring(0, 1) as Tiangan, + zhi: baZi.getYear().substring(1, 2) as any + }, + month: { + gan: accurateMonthGZ.gan, // 使用精确节气计算的月柱 + zhi: accurateMonthGZ.zhi, // 使用精确节气计算的月柱 + }, + day: { + gan: baZiForDay.getDay().substring(0, 1) as Tiangan, // 使用调整后的日柱(处理晚子时) + zhi: baZiForDay.getDay().substring(1, 2) as any // 使用调整后的日柱(处理晚子时) + }, + hour: { + gan: baZi.getTime().substring(0, 1) as Tiangan, + zhi: baZi.getTime().substring(1, 2) as any + } + }; + + const dayMaster = siZhu.day.gan; + + // 计算十神 + const shiShen = { + year: getShiShen(dayMaster, siZhu.year.gan) as any, + month: getShiShen(dayMaster, siZhu.month.gan) as any, + day: getShiShen(dayMaster, siZhu.day.gan) as any, + hour: getShiShen(dayMaster, siZhu.hour.gan) as any + }; + + // 获取大运(使用lunar-javascript库) + const yun = baZi.getYun(birthInfo.gender === 'male' ? 1 : 0); + const dayunStart = Math.floor(yun.getStartYear()); + + // 获取大运列表(跳过第一个,因为它是起运前的状态) + const dayunList = yun.getDaYun().slice(1, 11); // 取第2-11个,共10步大运 + + const dayun: DayunDetail[] = dayunList.map((d: any) => { + const ganZhiStr = d.getGanZhi(); + const ganZhi: GanZhi = { + gan: ganZhiStr.substring(0, 1) as Tiangan, + zhi: ganZhiStr.substring(1, 2) as any + }; + + // 计算天干十神 + const ganShiShen = getShiShen(dayMaster, ganZhi.gan) as any; + + // 计算地支藏干的十神 + const zhiCangGan = getZhiCangGan(ganZhi.zhi); + const zhiShiShen = getShiShen(dayMaster, zhiCangGan) as any; + + // 获取该大运内的流年 + const liuNianList = d.getLiuNian(); + const liuNian: LiuNian[] = liuNianList.map((ln: any) => { + const lnGanZhiStr = ln.getGanZhi(); + return { + year: ln.getYear(), + age: ln.getAge(), + ganZhi: { + gan: lnGanZhiStr.substring(0, 1) as Tiangan, + zhi: lnGanZhiStr.substring(1, 2) as any + } + }; + }); + + return { + ganZhi, + startAge: d.getStartAge(), + startYear: d.getStartYear(), + endYear: d.getEndYear(), + ganShiShen, + zhiShiShen, + liuNian + }; + }); + + // 计算十二长生(星运)- zhangSheng 不是 changSheng + const zhangSheng = getSiZhuChangSheng(dayMaster, { + year: siZhu.year.zhi, + month: siZhu.month.zhi, + day: siZhu.day.zhi, + hour: siZhu.hour.zhi + }); + + // 计算纳音 + const naYin = getSiZhuNaYin(siZhu); + + console.error('🔍 八字计算结果 - zhangSheng:', zhangSheng); + console.error('🔍 八字计算结果 - naYin:', naYin); + + return { + birthInfo, + siZhu, + dayMaster, + shiShen, + zhangSheng, + naYin, + dayunStart, + dayun + }; + } catch (error) { + console.error('创建八字排盘失败:', error); + throw new Error('八字排盘计算失败: ' + (error as Error).message); + } +} + +/** + * 运行八字测试 + */ +export function runBaziTests() { + const testCases = [ + { + name: '1979年5月4日6点男命(己年为阴年,男命逆排)', + input: { year: 1979, month: 5, day: 4, hour: 6, minute: 0, gender: 'male' as const, isLunar: false, timeZone: 8 }, + expected: { + year: '己未', + month: '戊辰', + day: '辛未', + hour: '辛卯', + isYangYear: false, // 己为阴年 + shouldShunPai: false // 阴年男命逆排 + } + }, + { + name: '2000年1月1日12点女命(己年为阴年,女命顺排)', + input: { year: 2000, month: 1, day: 1, hour: 12, minute: 0, gender: 'female' as const, isLunar: false, timeZone: 8 }, + expected: { + year: '己卯', + month: '丙子', + day: '戊午', + hour: '戊午', + isYangYear: false, // 己为阴年 + shouldShunPai: true // 阴年女命顺排 + } + }, + { + name: '1988年8月15日10点男命(戊年为阳年,男命顺排)', + input: { year: 1988, month: 8, day: 15, hour: 10, minute: 0, gender: 'male' as const, isLunar: false, timeZone: 8 }, + expected: { + isYangYear: true, // 戊为阳年 + shouldShunPai: true // 阳年男命顺排 + } + }, + { + name: '1987年3月20日14点女命(丁年为阴年,女命顺排)', + input: { year: 1987, month: 3, day: 20, hour: 14, minute: 0, gender: 'female' as const, isLunar: false, timeZone: 8 }, + expected: { + isYangYear: false, // 丁为阴年 + shouldShunPai: true // 阴年女命顺排 + } + }, + { + name: '2000年3月1日12点女命(庚年为阳年,女命逆排)', + input: { year: 2000, month: 3, day: 1, hour: 12, minute: 0, gender: 'female' as const, isLunar: false, timeZone: 8 }, + expected: { + year: '庚辰', + month: '戊寅', + day: '乙卯', + hour: '壬午', + isYangYear: true, // 庚为阳年 + shouldShunPai: false // 阳年女命逆排 + } + } + ]; + + const results = { + passed: 0, + failed: 0, + details: [] as any[] + }; + + testCases.forEach((testCase) => { + try { + const result = createBaziChart(testCase.input); + const actualYear = result.siZhu.year.gan + result.siZhu.year.zhi; + const actualMonth = result.siZhu.month.gan + result.siZhu.month.zhi; + const actualDay = result.siZhu.day.gan + result.siZhu.day.zhi; + const actualHour = result.siZhu.hour.gan + result.siZhu.hour.zhi; + + // 检查年干阴阳和大运顺逆 + const yearGanIndex = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'].indexOf(result.siZhu.year.gan); + const isYangYear = yearGanIndex % 2 === 0; + const isMale = testCase.input.gender === 'male'; + const shouldShunPai = (isYangYear && isMale) || (!isYangYear && !isMale); + + const yearPass = testCase.expected.year ? actualYear === testCase.expected.year : true; + const monthPass = testCase.expected.month ? actualMonth === testCase.expected.month : true; + const dayPass = testCase.expected.day ? actualDay === testCase.expected.day : true; + const hourPass = testCase.expected.hour ? actualHour === testCase.expected.hour : true; + const yinYangPass = isYangYear === testCase.expected.isYangYear; + const shunPaiPass = shouldShunPai === testCase.expected.shouldShunPai; + const allPass = yearPass && monthPass && dayPass && hourPass && yinYangPass && shunPaiPass; + + if (allPass) { + results.passed++; + } else { + results.failed++; + } + + results.details.push({ + name: testCase.name, + passed: allPass, + expected: testCase.expected, + actual: { + year: actualYear, + month: actualMonth, + day: actualDay, + hour: actualHour, + isYangYear, + shouldShunPai, + dayun: result.dayun.slice(0, 3).map(d => d.ganZhi.gan + d.ganZhi.zhi) // 只显示前3步大运 + }, + details: { yearPass, monthPass, dayPass, hourPass, yinYangPass, shunPaiPass } + }); + } catch (error) { + results.failed++; + results.details.push({ + name: testCase.name, + passed: false, + error: (error as Error).message + }); + } + }); + + return results; +} diff --git a/calculator/yiqi-core/daxian.ts b/calculator/yiqi-core/daxian.ts new file mode 100644 index 0000000..71753a7 --- /dev/null +++ b/calculator/yiqi-core/daxian.ts @@ -0,0 +1,123 @@ +// 大运计算模块 + +import { Tiangan, Dizhi } from './types'; + +/** + * 天干数组 + */ +const TIANGAN: Tiangan[] = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; + +/** + * 地支数组 + */ +const DIZHI: Dizhi[] = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; + +/** + * 大运信息 + */ +export interface DaXianInfo { + gongIndex: number; // 大运宫位索引(0-11) + startAge: number; // 起始年龄(虚岁) + endAge: number; // 结束年龄(虚岁) + ganZhi: string; // 干支 + direction: 'shun' | 'ni'; // 顺行或逆行 +} + +/** + * 计算大运信息 + * @param mingGongIndex 命宫索引(0-11,子=0) + * @param yinYang 阴阳男女 + * @param wuXingJuNumber 五行局数(2,3,4,5,6) + * @param currentAge 当前年龄(虚岁),用于标记当前大运 + * @returns 大运信息数组(10个大运) + */ +export function calculateDaXian( + mingGongIndex: number, + yinYang: '阳男' | '阴男' | '阳女' | '阴女', + wuXingJuNumber: number, + currentAge: number = 0 +): DaXianInfo[] { + const daXianList: DaXianInfo[] = []; + + // 判断大运方向 + // 阳男、阴女:大限顺时针走(地支索引递增:丑→寅→卯...) + // 阴男、阳女:大限逆时针走(地支索引递减:丑→子→亥...) + const isShunShiZhen = yinYang === '阳男' || yinYang === '阴女'; + const direction = isShunShiZhen ? 'shun' : 'ni'; + + // 起运年龄由五行局决定 + const startAge = wuXingJuNumber; + + // 生成12个大运(每个大运管10年) + for (let i = 0; i < 12; i++) { + const age = startAge + i * 10; + const endAge = age + 9; + + // 计算大运宫位(从命宫开始) + // gongIndex 是地支索引(0-11:子丑寅卯辰巳午未申酉戌亥) + let gongIndex: number; + if (isShunShiZhen) { + // 阳男、阴女:顺时针走,地支索引递增 + // 例如命宫在丑(1),第1大限在丑(1),第2大限在寅(2),第3大限在卯(3) + gongIndex = (mingGongIndex + i) % 12; + } else { + // 阴男、阳女:逆时针走,地支索引递减 + // 例如命宫在丑(1),第1大限在丑(1),第2大限在子(0),第3大限在亥(11) + gongIndex = (mingGongIndex - i + 12) % 12; + } + + daXianList.push({ + gongIndex, + startAge: age, + endAge, + ganZhi: '', // 这里可以添加大运干支,暂时留空 + direction + }); + } + + return daXianList; +} + +/** + * 计算当前年龄的大运 + */ +export function getCurrentDaXian(daXianList: DaXianInfo[], age: number): DaXianInfo | null { + return daXianList.find(dx => age >= dx.startAge && age <= dx.endAge) || null; +} + +/** + * 获取宫位的大运信息 + */ +export function getDaXianForGong(gongIndex: number, daXianList: DaXianInfo[]): DaXianInfo | null { + return daXianList.find(dx => dx.gongIndex === gongIndex) || null; +} + +/** + * 计算虚岁 + */ +export function calculateXuSui(birthYear: number, currentYear: number = new Date().getFullYear()): number { + return currentYear - birthYear + 1; +} + +/** + * 计算流年干支 + * @param year 公历年份 + */ +export function getLiuNianGanZhi(year: number): { gan: Tiangan; zhi: Dizhi } { + const ganIndex = (year - 4) % 10; // 甲子年是1984年,1984-4=1980,1980%10=0 + const zhiIndex = (year - 4) % 12; + + return { + gan: TIANGAN[ganIndex], + zhi: DIZHI[zhiIndex] + }; +} + +/** + * 根据流年地支获取宫位索引 + * @param zhi 地支 + * @returns 宫位索引(0=子,1=丑,...,11=亥) + */ +export function getGongIndexByZhi(zhi: Dizhi): number { + return DIZHI.indexOf(zhi); +} diff --git a/calculator/yiqi-core/ganzhi.ts b/calculator/yiqi-core/ganzhi.ts new file mode 100644 index 0000000..8ddd1f6 --- /dev/null +++ b/calculator/yiqi-core/ganzhi.ts @@ -0,0 +1,156 @@ +// 天干地支基础数据和工具函数 + +import { Tiangan, Dizhi, GanZhi } from './types'; + +// 天干数组 +export const TIANGAN: Tiangan[] = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; + +// 地支数组 +export const DIZHI: Dizhi[] = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; + +// 天干五行属性 +export const TIANGAN_WUXING = { + '甲': '木', '乙': '木', + '丙': '火', '丁': '火', + '戊': '土', '己': '土', + '庚': '金', '辛': '金', + '壬': '水', '癸': '水' +} as const; + +// 地支五行属性 +export const DIZHI_WUXING = { + '子': '水', '丑': '土', '寅': '木', '卯': '木', + '辰': '土', '巳': '火', '午': '火', '未': '土', + '申': '金', '酉': '金', '戌': '土', '亥': '水' +} as const; + +// 地支对应的月份(节气月) +export const DIZHI_MONTH = { + '寅': 1, '卯': 2, '辰': 3, '巳': 4, '午': 5, '未': 6, + '申': 7, '酉': 8, '戌': 9, '亥': 10, '子': 11, '丑': 12 +} as const; + +// 十神关系表(以日干为中心) +export const SHISHEN_MAP = { + '甲': { '甲': '比肩', '乙': '劫财', '丙': '食神', '丁': '伤官', '戊': '偏财', '己': '正财', '庚': '七杀', '辛': '正官', '壬': '偏印', '癸': '正印' }, + '乙': { '甲': '劫财', '乙': '比肩', '丙': '伤官', '丁': '食神', '戊': '正财', '己': '偏财', '庚': '正官', '辛': '七杀', '壬': '正印', '癸': '偏印' }, + '丙': { '甲': '偏印', '乙': '正印', '丙': '比肩', '丁': '劫财', '戊': '食神', '己': '伤官', '庚': '偏财', '辛': '正财', '壬': '七杀', '癸': '正官' }, + '丁': { '甲': '正印', '乙': '偏印', '丙': '劫财', '丁': '比肩', '戊': '伤官', '己': '食神', '庚': '正财', '辛': '偏财', '壬': '正官', '癸': '七杀' }, + '戊': { '甲': '七杀', '乙': '正官', '丙': '偏印', '丁': '正印', '戊': '比肩', '己': '劫财', '庚': '食神', '辛': '伤官', '壬': '偏财', '癸': '正财' }, + '己': { '甲': '正官', '乙': '七杀', '丙': '正印', '丁': '偏印', '戊': '劫财', '己': '比肩', '庚': '伤官', '辛': '食神', '壬': '正财', '癸': '偏财' }, + '庚': { '甲': '偏财', '乙': '正财', '丙': '七杀', '丁': '正官', '戊': '偏印', '己': '正印', '庚': '比肩', '辛': '劫财', '壬': '食神', '癸': '伤官' }, + '辛': { '甲': '正财', '乙': '偏财', '丙': '正官', '丁': '七杀', '戊': '正印', '己': '偏印', '庚': '劫财', '辛': '比肩', '壬': '伤官', '癸': '食神' }, + '壬': { '甲': '食神', '乙': '伤官', '丙': '偏财', '丁': '正财', '戊': '七杀', '己': '正官', '庚': '偏印', '辛': '正印', '壬': '比肩', '癸': '劫财' }, + '癸': { '甲': '伤官', '乙': '食神', '丙': '正财', '丁': '偏财', '戊': '正官', '己': '七杀', '庚': '正印', '辛': '偏印', '壬': '劫财', '癸': '比肩' } +} as const; + +/** + * 根据数字获取天干 + * @param num 数字(0-9 对应甲-癸,支持更大数字取模) + * @returns 对应的天干 + */ +export function getTiangan(num: number): Tiangan { + return TIANGAN[num % 10]; +} + +/** + * 根据数字获取地支 + * @param num 数字(0-11 对应子-亥,支持更大数字取模) + * @returns 对应的地支 + */ +export function getDizhi(num: number): Dizhi { + return DIZHI[num % 12]; +} + +/** + * 获取天干的序号 + * @param gan 天干 + * @returns 序号(0-9) + */ +export function getTianganIndex(gan: Tiangan): number { + return TIANGAN.indexOf(gan); +} + +/** + * 获取地支的序号 + * @param zhi 地支 + * @returns 序号(0-11) + */ +export function getDizhiIndex(zhi: Dizhi): number { + return DIZHI.indexOf(zhi); +} + +/** + * 根据年份获取干支 + * @param year 公历年份 + * @returns 对应的干支 + */ +export function getYearGanZhi(year: number): GanZhi { + // 采用标准算法:以公元4年甲子年为起点 + // 天干循环:(year - 4) % 10 + // 地支循环:(year - 4) % 12 + const ganIndex = (year - 4) % 10; + const zhiIndex = (year - 4) % 12; + + return { + gan: getTiangan(ganIndex), + zhi: getDizhi(zhiIndex) + }; +} + +/** + * 根据日期获取日干支(使用Date对象计算) + * @param year 年 + * @param month 月 + * @param day 日 + * @returns 对应的干支 + */ +export function getDayGanZhi(year: number, month: number, day: number): GanZhi { + // 计算从1900年1月1日到目标日期的天数差 + const baseDate = new Date(1900, 0, 1); // 1900年1月1日 + const targetDate = new Date(year, month - 1, day); // 目标日期 + + const daysDiff = Math.floor((targetDate.getTime() - baseDate.getTime()) / (1000 * 60 * 60 * 24)); + + // 1900年1月1日是己丑日(天干索引5,地支索引1) + const ganIndex = (daysDiff + 5) % 10; + const zhiIndex = (daysDiff + 1) % 12; + + return { + gan: getTiangan(ganIndex >= 0 ? ganIndex : ganIndex + 10), + zhi: getDizhi(zhiIndex >= 0 ? zhiIndex : zhiIndex + 12) + }; +} + +/** + * 根据时辰获取时干支 + * @param dayGan 日干 + * @param hour 时辰(0-23) + * @returns 对应的干支 + */ +export function getHourGanZhi(dayGan: Tiangan, hour: number): GanZhi { + // 确定时支 + const zhiIndex = Math.floor((hour + 1) / 2) % 12; + const zhi = getDizhi(zhiIndex); + + // 根据日干推算时干(五鼠遁法) + const dayGanIndex = getTianganIndex(dayGan); + const hourGanBaseMap = [0, 2, 4, 6, 8, 0, 2, 4, 6, 8]; // 甲己起甲子,乙庚起丙子... + const hourGanBase = hourGanBaseMap[dayGanIndex]; + const ganIndex = (hourGanBase + zhiIndex) % 10; + + return { + gan: getTiangan(ganIndex), + zhi: zhi + }; +} + +/** + * 获取十神关系 + * @param dayGan 日干 + * @param targetGan 目标天干 + * @returns 十神关系 + */ +export function getShiShen(dayGan: Tiangan, targetGan: Tiangan) { + return SHISHEN_MAP[dayGan][targetGan]; +} diff --git a/calculator/yiqi-core/index.ts b/calculator/yiqi-core/index.ts new file mode 100644 index 0000000..f36a1a4 --- /dev/null +++ b/calculator/yiqi-core/index.ts @@ -0,0 +1,159 @@ +// 统一排盘接口 + +import { BirthInfo, ChartResult } from './types'; +import { createBaziChart, runBaziTests } from './bazi'; +import { createZiweiChart, runZiweiTests } from './ziwei-standard'; + +/** + * 创建完整的排盘(八字 + 紫微斗数) + * @param birthInfo 生辰信息 + * @returns 完整排盘结果 + */ +export function createChart(birthInfo: BirthInfo): ChartResult { + try { + const bazi = createBaziChart(birthInfo); + const ziwei = createZiweiChart(birthInfo); + + return { + bazi, + ziwei + }; + } catch (error) { + throw new Error(`排盘计算失败: ${error instanceof Error ? error.message : String(error)}`); + } +} + +/** + * 验证生辰信息的有效性 + * @param birthInfo 生辰信息 + * @returns 验证结果 + */ +export function validateBirthInfo(birthInfo: BirthInfo): { valid: boolean; errors: string[] } { + const errors: string[] = []; + + // 验证年份 + if (birthInfo.year < 1900 || birthInfo.year > 2100) { + errors.push('年份应在1900-2100之间'); + } + + // 验证月份 + if (birthInfo.month < 1 || birthInfo.month > 12) { + errors.push('月份应在1-12之间'); + } + + // 验证日期 + if (birthInfo.day < 1 || birthInfo.day > 31) { + errors.push('日期应在1-31之间'); + } + + // 验证时辰 + if (birthInfo.hour < 0 || birthInfo.hour > 23) { + errors.push('小时应在0-23之间'); + } + + // 验证分钟 + if (birthInfo.minute < 0 || birthInfo.minute > 59) { + errors.push('分钟应在0-59之间'); + } + + // 验证性别 + if (birthInfo.gender !== 'male' && birthInfo.gender !== 'female') { + errors.push('性别必须为男性或女性'); + } + + // 简单的闰年和月份天数验证 + if (birthInfo.month === 2) { + const isLeapYear = (birthInfo.year % 4 === 0 && birthInfo.year % 100 !== 0) || (birthInfo.year % 400 === 0); + if (birthInfo.day > (isLeapYear ? 29 : 28)) { + errors.push('2月份日期超出范围'); + } + } else if ([4, 6, 9, 11].includes(birthInfo.month)) { + if (birthInfo.day > 30) { + errors.push('该月份只有30天'); + } + } + + return { + valid: errors.length === 0, + errors + }; +} + +/** + * 运行所有算法测试 + * @returns 综合测试结果 + */ +export function runAllTests(): { + bazi: ReturnType; + ziwei: ReturnType; + summary: { totalPassed: number; totalFailed: number; success: boolean } +} { + const baziResults = runBaziTests(); + const ziweiResults = runZiweiTests(); + + const totalPassed = baziResults.passed + ziweiResults.passed; + const totalFailed = baziResults.failed + ziweiResults.failed; + + return { + bazi: baziResults, + ziwei: ziweiResults, + summary: { + totalPassed, + totalFailed, + success: totalFailed === 0 + } + }; +} + +/** + * 格式化排盘结果为可读字符串(用于调试) + * @param chart 排盘结果 + * @returns 格式化的字符串 + */ +export function formatChartResult(chart: ChartResult): string { + let result = '=== 排盘结果 ===\n\n'; + + // 八字部分 + result += '【八字排盘】\n'; + result += `年柱: ${chart.bazi.siZhu.year.gan}${chart.bazi.siZhu.year.zhi}\n`; + result += `月柱: ${chart.bazi.siZhu.month.gan}${chart.bazi.siZhu.month.zhi}\n`; + result += `日柱: ${chart.bazi.siZhu.day.gan}${chart.bazi.siZhu.day.zhi}\n`; + result += `时柱: ${chart.bazi.siZhu.hour.gan}${chart.bazi.siZhu.hour.zhi}\n`; + result += `日主: ${chart.bazi.dayMaster}\n`; + result += `大运起运: ${chart.bazi.dayunStart}岁\n`; + result += `前三步大运: ${chart.bazi.dayun.slice(0, 3).map(dy => `${dy.ganZhi.gan}${dy.ganZhi.zhi}`).join(' ')}\n\n`; + + // 紫微斗数部分 + result += '【紫微斗数】\n'; + result += `命宫: ${chart.ziwei.gongs[0].dizhi}宫\n`; + const shenGongDizhi = chart.ziwei.gongs[chart.ziwei.shenGongIndex]?.dizhi || '未知'; + result += `身宫: ${shenGongDizhi}宫\n`; + + // 显示有主星的宫位 + chart.ziwei.gongs.forEach(gong => { + if (gong.mainStars.length > 0) { + result += `${gong.gong}(${gong.dizhi}): ${gong.mainStars.join('、')}`; + if (gong.auxStars.length > 0) { + result += ` [${gong.auxStars.join('、')}]`; + } + if (gong.sihua.length > 0) { + result += ` {${gong.sihua.map(s => `${s.star}${s.hua}`).join('、')}}`; + } + result += '\n'; + } + }); + + return result; +} + +// 导出示例用法 +export const EXAMPLE_BIRTH_INFO: BirthInfo = { + year: 1990, + month: 5, + day: 15, + hour: 14, + minute: 30, + isLunar: false, + gender: 'male', + timeZone: 8 +}; diff --git a/calculator/yiqi-core/jieqi.ts b/calculator/yiqi-core/jieqi.ts new file mode 100644 index 0000000..8bcdc7b --- /dev/null +++ b/calculator/yiqi-core/jieqi.ts @@ -0,0 +1,190 @@ +// 节气计算模块 + +// 24节气名称 +export const JIEQI_NAMES = [ + '小寒', '大寒', '立春', '雨水', '惊蛰', '春分', + '清明', '谷雨', '立夏', '小满', '芒种', '夏至', + '小暑', '大暑', '立秋', '处暑', '白露', '秋分', + '寒露', '霜降', '立冬', '小雪', '大雪', '冬至' +]; + +// 节气对应的地支月(立春开始为寅月) +export const JIEQI_MONTHS = [ + 11, 11, 0, 0, 1, 1, // 小寒(子月), 大寒(子月), 立春(寅月), 雨水(寅月), 惊蛰(卯月), 春分(卯月) + 2, 2, 3, 3, 4, 4, // 清明(辰月), 谷雨(辰月), 立夏(巳月), 小满(巳月), 芒种(午月), 夏至(午月) + 5, 5, 6, 6, 7, 7, // 小暑(未月), 大暑(未月), 立秋(申月), 处暑(申月), 白露(酉月), 秋分(酉月) + 8, 8, 9, 9, 10, 10 // 寒露(戌月), 霜降(戌月), 立冬(亥月), 小雪(亥月), 大雪(子月), 冬至(子月) +]; + +/** + * 计算指定年份的节气时刻(简化算法) + * 注:这是一个简化版本,实际应用中需要更精确的天文算法 + * @param year 年份 + * @returns 24个节气的儒略日时刻 + */ +export function calculateJieqi(year: number): number[] { + const jieqiTimes: number[] = []; + + // 基础数据:2000年各节气的大致日期(儒略日) + const base2000 = [ + 2451549.5, 2451564.5, 2451579.5, 2451594.5, 2451609.5, 2451624.5, // 1-6 + 2451639.5, 2451654.5, 2451670.5, 2451685.5, 2451700.5, 2451715.5, // 7-12 + 2451730.5, 2451745.5, 2451761.5, 2451776.5, 2451791.5, 2451806.5, // 13-18 + 2451821.5, 2451836.5, 2451852.5, 2451867.5, 2451882.5, 2451897.5 // 19-24 + ]; + + const yearDiff = year - 2000; + + for (let i = 0; i < 24; i++) { + // 简化计算:每年节气时间的平均变化 + const dayOffset = yearDiff * 365.2422 - Math.floor(yearDiff / 4) + Math.floor(yearDiff / 100) - Math.floor(yearDiff / 400); + jieqiTimes.push(base2000[i] + dayOffset); + } + + return jieqiTimes; +} + +/** + * 根据公历日期确定节气月(干支月) + * @param year 年 + * @param month 月 + * @param day 日 + * @returns 地支月份的索引(0-11对应寅-丑) + */ +export function getJieqiMonth(year: number, month: number, day: number): number { + const jieqiTimes = calculateJieqi(year); + + // 计算当前日期的儒略日 + const a = Math.floor((14 - month) / 12); + const y = year - a; + const m = month + 12 * a - 3; + const currentJD = day + Math.floor((153 * m + 2) / 5) + 365 * y + + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) + 1721119; + + // 找到当前日期所在的节气区间 + for (let i = 0; i < 24; i += 2) { // 只检查节气(奇数索引是中气) + const jieqi = jieqiTimes[i]; + const nextJieqi = i + 2 < 24 ? jieqiTimes[i + 2] : jieqiTimes[2] + 365.25; // 下一个节气或明年立春 + + if (currentJD >= jieqi && currentJD < nextJieqi) { + return JIEQI_MONTHS[i]; + } + } + + // 默认返回子月 + return 11; +} + +/** + * 获取月干支 + * @param year 年 + * @param month 月 + * @param day 日 + * @returns 月干支 + */ +export function getMonthGanZhi(year: number, month: number, day: number) { + const jieqiMonth = getJieqiMonth(year, month, day); + const yearGanIndex = (year - 4) % 10; // 使用修正后的年干算法 + + // 月干推算:甲己之年丙作首(正月为丙寅) + const monthGanBaseMap = [2, 4, 6, 8, 0, 2, 4, 6, 8, 0]; // 甲年丙寅,乙年戊寅... + const monthGanBase = monthGanBaseMap[yearGanIndex]; + const monthGanIndex = (monthGanBase + jieqiMonth) % 10; + + return { + gan: ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'][monthGanIndex] as any, + zhi: ['寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥', '子', '丑'][jieqiMonth] as any + }; +} + +import type { Tiangan, Dizhi } from './types'; + +/** + * 获取当前时刻应该使用的月柱(基于精确节气时刻) + * + * lunar-javascript默认在节气当日的子时(00:00)就换月柱, + * 但传统命理应该在节气的精确时刻才换月柱。 + * + * 例如:某年立秋在公历8月初,那么: + * - 8月8日 02:28 应该用乙未月(立秋前) + * - 8月8日 10:00 应该用丙申月(立秋后) + * + * @param solar Solar对象(来自lunar-javascript) + * @returns 月柱干支 + */ +export function getAccurateMonthGanZhi(solar: any): { gan: Tiangan; zhi: Dizhi } { + const lunar = solar.getLunar(); + + // 获取lunar-javascript给出的月柱(默认在子时换月) + const defaultMonthGZ = lunar.getMonthInGanZhi(); + + // 获取当前节令(节气) + const currentJieQi = lunar.getJieQi(); + + // 获取节气表 + const jieQiTable = lunar.getJieQiTable(); + const jieQiSolar = jieQiTable[currentJieQi]; + + if (!jieQiSolar) { + // 没有节气信息,使用默认值 + return parseGanZhi(defaultMonthGZ); + } + + // 判断是否在节气当日 + const isJieQiDay = + solar.getYear() === jieQiSolar.getYear() && + solar.getMonth() === jieQiSolar.getMonth() && + solar.getDay() === jieQiSolar.getDay(); + + if (!isJieQiDay) { + // 不在节气当日,直接使用默认值 + return parseGanZhi(defaultMonthGZ); + } + + // 在节气当日,需要判断是否已过节气时刻 + const solarTime = solar.getHour() * 3600 + solar.getMinute() * 60 + solar.getSecond(); + const jieQiTime = jieQiSolar.getHour() * 3600 + jieQiSolar.getMinute() * 60 + jieQiSolar.getSecond(); + + if (solarTime < jieQiTime) { + // 还没到节气时刻,应该使用上一个月的月柱 + // 获取前一天的月柱 + const prevDaySolar = solar.next(-1); // 往前推一天 + const prevDayLunar = prevDaySolar.getLunar(); + const prevMonthGZ = prevDayLunar.getMonthInGanZhi(); + + console.error(`[节气校正] ${solar.toYmd()} ${solar.getHour()}:${solar.getMinute()} 在${currentJieQi}前,使用${prevMonthGZ}月`); + + return parseGanZhi(prevMonthGZ); + } + + // 已经过了节气时刻,使用当前月柱 + return parseGanZhi(defaultMonthGZ); +} + +/** + * 解析干支字符串为天干地支对象 + * @param gz 干支字符串,如"甲子" + * @returns 天干地支对象 + */ +function parseGanZhi(gz: string): { gan: Tiangan; zhi: Dizhi } { + if (!gz || gz.length < 2) { + throw new Error(`无效的干支字符串: ${gz}`); + } + + return { + gan: gz[0] as Tiangan, + zhi: gz[1] as Dizhi + }; +} + +/** + * 获取节气的精确时刻(用于调试和显示) + * @param solar Solar对象 + * @param jieQiName 节气名称 + * @returns 节气的Solar对象,如果找不到返回null + */ +export function getJieQiTime(solar: any, jieQiName: string): any | null { + const lunar = solar.getLunar(); + const jieQiTable = lunar.getJieQiTable(); + return jieQiTable[jieQiName] || null; +} diff --git a/calculator/yiqi-core/nayin.ts b/calculator/yiqi-core/nayin.ts new file mode 100644 index 0000000..f136fa8 --- /dev/null +++ b/calculator/yiqi-core/nayin.ts @@ -0,0 +1,168 @@ +/** + * 纳音五行算法 + * + * 纳音是根据干支组合推算出的五行属性,每两个干支组成一个纳音。 + * 共有60个干支组合,对应30种纳音五行。 + * + * 纳音五行分为: + * - 金:海中金、金箔金、白蜡金、剑锋金、钗钏金、沙中金 + * - 木:大林木、杨柳木、松柏木、平地木、石榴木、桑柘木 + * - 水:涧下水、大溪水、长流水、天河水、大海水、泉中水 + * - 火:炉中火、山头火、霹雳火、山下火、覆灯火、天上火 + * - 土:城头土、屋上土、壁上土、大驿土、沙中土、路旁土 + */ + +import type { Tiangan, Dizhi } from './types'; + +/** + * 纳音五行类型 + */ +export type NaYin = + // 金系 + | '海中金' | '金箔金' | '白蜡金' | '剑锋金' | '钗钏金' | '沙中金' + // 木系 + | '大林木' | '杨柳木' | '松柏木' | '平地木' | '石榴木' | '桑柘木' + // 水系 + | '涧下水' | '大溪水' | '长流水' | '天河水' | '大海水' | '泉中水' + // 火系 + | '炉中火' | '山头火' | '霹雳火' | '山下火' | '覆灯火' | '天上火' + // 土系 + | '城头土' | '屋上土' | '壁上土' | '大驿土' | '沙中土' | '路旁土'; + +/** + * 纳音映射表 + * 根据干支组合查找对应的纳音五行 + * + * 数据来源:纳音.md + */ +const NAYIN_MAP: Record = { + // 金系 + '甲子': '海中金', '乙丑': '海中金', + '壬寅': '金箔金', '癸卯': '金箔金', + '庚辰': '白蜡金', '辛巳': '白蜡金', + '壬申': '剑锋金', '癸酉': '剑锋金', + '庚戌': '钗钏金', '辛亥': '钗钏金', + '甲午': '沙中金', '乙未': '沙中金', + + // 木系 + '戊辰': '大林木', '己巳': '大林木', + '壬午': '杨柳木', '癸未': '杨柳木', + '庚寅': '松柏木', '辛卯': '松柏木', + '戊戌': '平地木', '己亥': '平地木', + '庚申': '石榴木', '辛酉': '石榴木', + '壬子': '桑柘木', '癸丑': '桑柘木', + + // 水系 + '丙子': '涧下水', '丁丑': '涧下水', + '甲寅': '大溪水', '乙卯': '大溪水', + '壬辰': '长流水', '癸巳': '长流水', + '丙午': '天河水', '丁未': '天河水', + '壬戌': '大海水', '癸亥': '大海水', + '甲申': '泉中水', '乙酉': '泉中水', + + // 火系 + '丙寅': '炉中火', '丁卯': '炉中火', + '甲戌': '山头火', '乙亥': '山头火', + '戊子': '霹雳火', '己丑': '霹雳火', + '丙申': '山下火', '丁酉': '山下火', + '甲辰': '覆灯火', '乙巳': '覆灯火', + '戊午': '天上火', '己未': '天上火', + + // 土系 + '戊寅': '城头土', '己卯': '城头土', + '丙戌': '屋上土', '丁亥': '屋上土', + '庚子': '壁上土', '辛丑': '壁上土', + '戊申': '大驿土', '己酉': '大驿土', + '丙辰': '沙中土', '丁巳': '沙中土', + '庚午': '路旁土', '辛未': '路旁土' +}; + +/** + * 获取干支的纳音五行 + * @param tiangan 天干 + * @param dizhi 地支 + * @returns 纳音五行 + */ +export function getNaYin(tiangan: Tiangan, dizhi: Dizhi): NaYin { + const key = tiangan + dizhi; + const nayin = NAYIN_MAP[key]; + + if (!nayin) { + console.warn(`未找到纳音: ${key}`); + return '海中金'; // 默认值 + } + + return nayin; +} + +/** + * 批量获取四柱的纳音 + * @param siZhu 四柱干支 + * @returns 四柱纳音数组 + */ +export function getSiZhuNaYin(siZhu: { + year: { gan: Tiangan; zhi: Dizhi }; + month: { gan: Tiangan; zhi: Dizhi }; + day: { gan: Tiangan; zhi: Dizhi }; + hour: { gan: Tiangan; zhi: Dizhi }; +}): { + year: NaYin; + month: NaYin; + day: NaYin; + hour: NaYin; +} { + return { + year: getNaYin(siZhu.year.gan, siZhu.year.zhi), + month: getNaYin(siZhu.month.gan, siZhu.month.zhi), + day: getNaYin(siZhu.day.gan, siZhu.day.zhi), + hour: getNaYin(siZhu.hour.gan, siZhu.hour.zhi) + }; +} + +/** + * 获取纳音的五行属性 + * @param nayin 纳音 + * @returns 五行属性(金木水火土) + */ +export function getNaYinWuXing(nayin: NaYin): '金' | '木' | '水' | '火' | '土' { + if (nayin.includes('金')) return '金'; + if (nayin.includes('木')) return '木'; + if (nayin.includes('水')) return '水'; + if (nayin.includes('火')) return '火'; + if (nayin.includes('土')) return '土'; + return '金'; // 默认值 +} + +/** + * 获取纳音五行的相生相克关系 + * @param nayin1 纳音1 + * @param nayin2 纳音2 + * @returns 关系(生、克、比和) + */ +export function getNaYinRelation( + nayin1: NaYin, + nayin2: NaYin +): '生' | '克' | '被生' | '被克' | '比和' { + const wx1 = getNaYinWuXing(nayin1); + const wx2 = getNaYinWuXing(nayin2); + + if (wx1 === wx2) return '比和'; + + // 相生关系:木生火、火生土、土生金、金生水、水生木 + const sheng: Record = { + '木': '火', '火': '土', '土': '金', '金': '水', '水': '木' + }; + + if (sheng[wx1] === wx2) return '生'; + if (sheng[wx2] === wx1) return '被生'; + + // 相克关系:木克土、土克水、水克火、火克金、金克木 + const ke: Record = { + '木': '土', '土': '水', '水': '火', '火': '金', '金': '木' + }; + + if (ke[wx1] === wx2) return '克'; + if (ke[wx2] === wx1) return '被克'; + + return '比和'; // 默认值 +} diff --git a/calculator/yiqi-core/sihua.ts b/calculator/yiqi-core/sihua.ts new file mode 100644 index 0000000..dd0a0cb --- /dev/null +++ b/calculator/yiqi-core/sihua.ts @@ -0,0 +1,129 @@ +// 紫微斗数四化系统 + +import { Tiangan, SiHua } from './types'; + +/** + * 四化星曜(按年干) + * 格式:{ 年干: { 化禄, 化权, 化科, 化忌 } } + */ +const SIHUA_BY_YEAR_GAN: { [key in Tiangan]: { lu: string; quan: string; ke: string; ji: string } } = { + '甲': { lu: '廉贞', quan: '破军', ke: '武曲', ji: '太阳' }, + '乙': { lu: '天机', quan: '天梁', ke: '紫微', ji: '太阴' }, + '丙': { lu: '天同', quan: '天机', ke: '文昌', ji: '廉贞' }, + '丁': { lu: '太阴', quan: '天同', ke: '天机', ji: '巨门' }, + '戊': { lu: '贪狼', quan: '太阴', ke: '右弼', ji: '天机' }, + '己': { lu: '武曲', quan: '贪狼', ke: '天梁', ji: '文曲' }, + '庚': { lu: '太阳', quan: '武曲', ke: '太阴', ji: '天同' }, + '辛': { lu: '巨门', quan: '太阳', ke: '文曲', ji: '文昌' }, + '壬': { lu: '天梁', quan: '紫微', ke: '左辅', ji: '武曲' }, + '癸': { lu: '破军', quan: '巨门', ke: '太阴', ji: '贪狼' } +}; + +/** + * 获取指定天干的四化 + */ +export function getSiHuaByGan(gan: Tiangan): { lu: string; quan: string; ke: string; ji: string } { + return SIHUA_BY_YEAR_GAN[gan]; +} + +/** + * 计算某个宫位的四化(用于飞化) + * @param gan 宫位天干 + * @param gongStars 宫位内的星曜列表 + * @returns 该宫位飞化出的四化星及其位置 + */ +export function calculateFeiHua(gan: Tiangan, allGongs: any[]): { + lu: { star: string; targetGong: string } | null; + quan: { star: string; targetGong: string } | null; + ke: { star: string; targetGong: string } | null; + ji: { star: string; targetGong: string } | null; +} { + const sihua = getSiHuaByGan(gan); + + const result = { + lu: null as { star: string; targetGong: string } | null, + quan: null as { star: string; targetGong: string } | null, + ke: null as { star: string; targetGong: string } | null, + ji: null as { star: string; targetGong: string } | null + }; + + // 找到化禄、化权、化科、化忌的星曜所在宫位(包括主星和辅星) + for (const gong of allGongs) { + const allStars = [...gong.mainStars, ...(gong.auxStars || [])]; + + if (allStars.includes(sihua.lu)) { + result.lu = { star: sihua.lu, targetGong: gong.gong }; + } + if (allStars.includes(sihua.quan)) { + result.quan = { star: sihua.quan, targetGong: gong.gong }; + } + if (allStars.includes(sihua.ke)) { + result.ke = { star: sihua.ke, targetGong: gong.gong }; + } + if (allStars.includes(sihua.ji)) { + result.ji = { star: sihua.ji, targetGong: gong.gong }; + } + } + + return result; +} + +/** + * 为命盘添加本命四化(出生年干的四化) + */ +export function addBenMingSiHua(gongs: any[], yearGan: Tiangan): any[] { + const sihua = getSiHuaByGan(yearGan); + + return gongs.map(gong => { + const sihuaList: { star: string; hua: SiHua }[] = []; + + // 检查宫位内的主星和辅星是否有四化 + const allStars = [...gong.mainStars, ...(gong.auxStars || [])]; + + allStars.forEach((star: string) => { + if (star === sihua.lu) { + sihuaList.push({ star, hua: '化禄' }); + } + if (star === sihua.quan) { + sihuaList.push({ star, hua: '化权' }); + } + if (star === sihua.ke) { + sihuaList.push({ star, hua: '化科' }); + } + if (star === sihua.ji) { + sihuaList.push({ star, hua: '化忌' }); + } + }); + + return { + ...gong, + sihua: sihuaList + }; + }); +} + +/** + * 获取四化的颜色样式 + */ +export function getSiHuaColor(hua: SiHua): string { + switch (hua) { + case '化禄': return '#22c55e'; // 绿色 + case '化权': return '#a855f7'; // 紫色 + case '化科': return '#3b82f6'; // 蓝色 + case '化忌': return '#ef4444'; // 红色 + default: return '#666'; + } +} + +/** + * 获取四化的简称 + */ +export function getSiHuaShort(hua: SiHua): string { + switch (hua) { + case '化禄': return '禄'; + case '化权': return '权'; + case '化科': return '科'; + case '化忌': return '忌'; + default: return ''; + } +} diff --git a/calculator/yiqi-core/types.ts b/calculator/yiqi-core/types.ts new file mode 100644 index 0000000..9523ae0 --- /dev/null +++ b/calculator/yiqi-core/types.ts @@ -0,0 +1,152 @@ +// 核心数据类型定义 + +// 生辰信息 +export interface BirthInfo { + year: number; + month: number; + day: number; + hour: number; + minute: number; + isLunar: boolean; // 是否为农历 + gender: 'male' | 'female'; + timeZone: number; // 时区偏移,默认为8(北京时间) +} + +// 保存的案例 +export interface SavedCase { + id: string; + name: string; + birthForm: BirthInfo & { name: string }; + chartResult: ChartResult; + savedAt: string; +} + +// 天干地支 +export type Tiangan = '甲' | '乙' | '丙' | '丁' | '戊' | '己' | '庚' | '辛' | '壬' | '癸'; +export type Dizhi = '子' | '丑' | '寅' | '卯' | '辰' | '巳' | '午' | '未' | '申' | '酉' | '戌' | '亥'; + +// 干支组合 +export interface GanZhi { + gan: Tiangan; + zhi: Dizhi; +} + +// 四柱信息 +export interface SiZhu { + year: GanZhi; + month: GanZhi; + day: GanZhi; + hour: GanZhi; +} + +// 十神 +export type ShiShen = '比肩' | '劫财' | '食神' | '伤官' | '偏财' | '正财' | '七杀' | '正官' | '偏印' | '正印'; + +// 流年信息 +export interface LiuNian { + year: number; // 公历年份 + age: number; // 虚岁 + ganZhi: GanZhi; // 流年干支 +} + +// 大运详情 +export interface DayunDetail { + ganZhi: GanZhi; // 大运干支 + startAge: number; // 起始岁数 + startYear: number; // 起始年份 + endYear: number; // 结束年份 + ganShiShen: ShiShen; // 天干十神 + zhiShiShen: ShiShen; // 地支十神(地支藏干主气的十神) + liuNian: LiuNian[]; // 该大运内的流年列表 +} + +// 八字排盘结果 +export interface BaziChart { + birthInfo: BirthInfo; + siZhu: SiZhu; + dayMaster: Tiangan; // 日主 + shiShen: { + year: ShiShen; + month: ShiShen; + day: ShiShen; + hour: ShiShen; + }; + // 添加长生(星运)- zhangSheng 不是 changSheng + zhangSheng?: { + year: string; + month: string; + day: string; + hour: string; + }; + // 添加纳音 + naYin?: { + year: string; + month: string; + day: string; + hour: string; + }; + dayunStart: number; // 大运起运岁数 + dayun: DayunDetail[]; // 大运详情列表,通常显示前10步 +} + +// 紫微斗数十二宫 +export type ZiweiGong = '命宫' | '兄弟宫' | '夫妻宫' | '子女宫' | '财帛宫' | '疾厄宫' | + '迁移宫' | '交友宫' | '官禄宫' | '田宅宫' | '福德宫' | '父母宫'; + +// 紫微斗数主星 +export type ZiweiMainStar = '紫微' | '天机' | '太阳' | '武曲' | '天同' | '廉贞' | '天府' | + '太阴' | '贪狼' | '巨门' | '天相' | '天梁' | '七杀' | '破军'; + +// 紫微斗数辅星 +export type ZiweiAuxStar = '左辅' | '右弼' | '天魁' | '天钺' | '文昌' | '文曲' | '禄存' | + '天马' | '擎羊' | '陀罗' | '火星' | '铃星' | '地空' | '地劫' | + '红鸾' | '天喜' | '天刑' | '天姚'; + +// 四化 +export type SiHua = '化禄' | '化权' | '化科' | '化忌'; + +// 紫微斗数宫位信息 +export interface ZiweiGongInfo { + gong: ZiweiGong; + dizhi: Dizhi; + tiangan: Tiangan; // 宫位天干 + mainStars: ZiweiMainStar[]; + auxStars: ZiweiAuxStar[]; + sihua: { star: string; hua: SiHua }[]; + daXian?: { // 大运信息 + startAge: number; + endAge: number; + isCurrent?: boolean; // 是否为当前大运 + daXianGongName?: string; // 大限宫位名(如"大命""大兄"等),基于当前大限起始宫位计算 + }; + liuNian?: number[]; // 流年年龄列表 + liuNianYear?: number; // 流年年份(如2025),按地支固定位置 + liuNianGongName?: string; // 流年宫位名(如"年命""年兄"等),基于选中流年计算 +} + +// 紫微斗数排盘结果 +export interface ZiweiChart { + birthInfo: BirthInfo; + gongs: ZiweiGongInfo[]; // 十二宫信息 + mingGongIndex: number; // 命宫在地支中的位置(0-11) + shenGongIndex: number; // 身宫在地支中的位置(0-11) + yinYang?: '阳男' | '阴男' | '阳女' | '阴女'; // 阴阳男女 + wuXingJu?: { // 五行局 + name: string; // 如"水二局" + number: number; // 如2 + }; + siZhu?: SiZhu; // 四柱信息(年月日时天干地支) + lunarDate?: { // 农历日期 + year: number; + month: number; + day: number; + monthCn?: string; // 中文月份 + dayCn?: string; // 中文日期 + }; +} + +// 完整的排盘结果 +export interface ChartResult { + bazi: BaziChart; + ziwei: ZiweiChart; +} diff --git a/calculator/yiqi-core/zhangsheng.ts b/calculator/yiqi-core/zhangsheng.ts new file mode 100644 index 0000000..4a7a4c6 --- /dev/null +++ b/calculator/yiqi-core/zhangsheng.ts @@ -0,0 +1,133 @@ +/** + * 十二长生(星运)算法 + * + * 十二长生是命理学中描述五行在十二地支中的旺衰状态,包括: + * 长生、沐浴、冠带、临官、帝旺、衰、病、死、墓、绝、胎、养 + * + * 根据日干的五行属性,查找其在各地支的长生状态 + */ + +import type { Tiangan, Dizhi } from './types'; + +/** + * 十二长生状态 + */ +export type ChangSheng = + | '长生' | '沐浴' | '冠带' | '临官' | '帝旺' | '衰' + | '病' | '死' | '墓' | '绝' | '胎' | '养'; + +/** + * 十二长生映射表 + * 根据天干十二长生表(见附件图片) + * + * 阳干(甲丙戊庚壬):顺行 + * 阴干(乙丁己辛癸):逆行 + */ +const CHANGSHENG_MAP: Record> = { + '甲': { + '亥': '长生', '子': '沐浴', '丑': '冠带', '寅': '临官', + '卯': '帝旺', '辰': '衰', '巳': '病', '午': '死', + '未': '墓', '申': '绝', '酉': '胎', '戌': '养' + }, + '乙': { + '午': '长生', '巳': '沐浴', '辰': '冠带', '卯': '临官', + '寅': '帝旺', '丑': '衰', '子': '病', '亥': '死', + '戌': '墓', '酉': '绝', '申': '胎', '未': '养' + }, + '丙': { + '寅': '长生', '卯': '沐浴', '辰': '冠带', '巳': '临官', + '午': '帝旺', '未': '衰', '申': '病', '酉': '死', + '戌': '墓', '亥': '绝', '子': '胎', '丑': '养' + }, + '丁': { + '酉': '长生', '申': '沐浴', '未': '冠带', '午': '临官', + '巳': '帝旺', '辰': '衰', '卯': '病', '寅': '死', + '丑': '墓', '子': '绝', '亥': '胎', '戌': '养' + }, + '戊': { + '寅': '长生', '卯': '沐浴', '辰': '冠带', '巳': '临官', + '午': '帝旺', '未': '衰', '申': '病', '酉': '死', + '戌': '墓', '亥': '绝', '子': '胎', '丑': '养' + }, + '己': { + '酉': '长生', '申': '沐浴', '未': '冠带', '午': '临官', + '巳': '帝旺', '辰': '衰', '卯': '病', '寅': '死', + '丑': '墓', '子': '绝', '亥': '胎', '戌': '养' + }, + '庚': { + '巳': '长生', '午': '沐浴', '未': '冠带', '申': '临官', + '酉': '帝旺', '戌': '衰', '亥': '病', '子': '死', + '丑': '墓', '寅': '绝', '卯': '胎', '辰': '养' + }, + '辛': { + '子': '长生', '亥': '沐浴', '戌': '冠带', '酉': '临官', + '申': '帝旺', '未': '衰', '午': '病', '巳': '死', + '辰': '墓', '卯': '绝', '寅': '胎', '丑': '养' + }, + '壬': { + '申': '长生', '酉': '沐浴', '戌': '冠带', '亥': '临官', + '子': '帝旺', '丑': '衰', '寅': '病', '卯': '死', + '辰': '墓', '巳': '绝', '午': '胎', '未': '养' + }, + '癸': { + '卯': '长生', '寅': '沐浴', '丑': '冠带', '子': '临官', + '亥': '帝旺', '戌': '衰', '酉': '病', '申': '死', + '未': '墓', '午': '绝', '巳': '胎', '辰': '养' + } +}; + +/** + * 获取天干在某地支的长生状态 + * @param tiangan 天干 + * @param dizhi 地支 + * @returns 长生状态(长生、沐浴、冠带等) + */ +export function getChangSheng(tiangan: Tiangan, dizhi: Dizhi): ChangSheng { + return CHANGSHENG_MAP[tiangan][dizhi]; +} + +/** + * 批量获取四柱的长生状态 + * @param dayGan 日干 + * @param siZhu 四柱地支 + * @returns 四柱长生状态数组 + */ +export function getSiZhuChangSheng( + dayGan: Tiangan, + siZhu: { year: Dizhi; month: Dizhi; day: Dizhi; hour: Dizhi } +): { + year: ChangSheng; + month: ChangSheng; + day: ChangSheng; + hour: ChangSheng; +} { + return { + year: getChangSheng(dayGan, siZhu.year), + month: getChangSheng(dayGan, siZhu.month), + day: getChangSheng(dayGan, siZhu.day), + hour: getChangSheng(dayGan, siZhu.hour) + }; +} + +/** + * 判断长生状态的吉凶 + * @param changsheng 长生状态 + * @returns 吉凶等级(1-5,5最吉) + */ +export function getChangShengLevel(changsheng: ChangSheng): number { + const levels: Record = { + '长生': 5, // 最吉 + '帝旺': 5, // 最吉 + '临官': 4, // 次吉 + '冠带': 4, // 次吉 + '养': 3, // 平 + '胎': 3, // 平 + '沐浴': 2, // 次凶(桃花) + '衰': 2, // 次凶 + '病': 1, // 凶 + '死': 1, // 凶 + '墓': 1, // 凶 + '绝': 1 // 最凶 + }; + return levels[changsheng]; +} diff --git a/calculator/yiqi-core/ziwei-standard.ts b/calculator/yiqi-core/ziwei-standard.ts new file mode 100644 index 0000000..0db2a69 --- /dev/null +++ b/calculator/yiqi-core/ziwei-standard.ts @@ -0,0 +1,749 @@ +// 紫微斗数排盘核心算法(按照标准安星法重写) + +import { BirthInfo, ZiweiChart, ZiweiGongInfo, GanZhi } from './types'; +import { Solar, Lunar } from 'lunar-typescript'; +import { addBenMingSiHua } from './sihua'; +import { calculateDaXian, calculateXuSui } from './daxian'; + +/** + * 天干数组 + */ +const TIANGAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; + +/** + * 地支数组(顺时针) + */ +const DIZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; + +/** + * 十二宫名称(从命宫开始,逆时针排列) + */ +const GONG_NAMES = ['命宫', '兄弟', '夫妻', '子女', '财帛', '疾厄', '迁移', '交友', '官禄', '田宅', '福德', '父母']; + +/** + * 时辰转换为地支索引 + */ +function getHourZhiIndex(hour: number): number { + // 23-01:子(0), 01-03:丑(1), ..., 21-23:亥(11) + return Math.floor((hour + 1) / 2) % 12; +} + +/** + * 判断阴阳男女 + */ +function getYinYang(year: number, gender: 'male' | 'female'): string { + const ganIndex = (year - 4) % 10; // 修正公式:2014年 → (2014-4)%10 = 0 → 甲 + const isYangYear = ganIndex % 2 === 0; // 甲丙戊庚壬为阳(索引0,2,4,6,8) + + if (gender === 'male') { + return isYangYear ? '阳男' : '阴男'; + } else { + return isYangYear ? '阳女' : '阴女'; + } +} + +/** + * 五虎遁月诀 - 根据年干和月支确定月干 + */ +function getMonthGan(yearGan: string, monthZhi: string): string { + const yearGanIndex = TIANGAN.indexOf(yearGan); + const monthZhiIndex = DIZHI.indexOf(monthZhi); + + // 五虎遁月诀:甲己丙作首,乙庚戊为头,丙辛庚上起,丁壬壬寅流,戊癸甲寅求 + const firstMonthGanMap: { [key: number]: number } = { + 0: 2, 5: 2, // 甲己 → 丙 + 1: 4, 6: 4, // 乙庚 → 戊 + 2: 6, 7: 6, // 丙辛 → 庚 + 3: 8, 8: 8, // 丁壬 → 壬 + 4: 0, 9: 0 // 戊癸 → 甲 + }; + + const firstMonthGan = firstMonthGanMap[yearGanIndex]; + // 正月是寅月(索引2),从正月天干开始,按地支顺序推算 + // 月支索引 - 寅索引(2) = 偏移量 + const offset = (monthZhiIndex - 2 + 12) % 12; + const monthGanIndex = (firstMonthGan + offset) % 10; + + return TIANGAN[monthGanIndex]; +} + +/** + * 计算命宫位置 + * 标准算法:从寅宫起正月,顺数到生月;从生月宫当子时,逆时针数到生时 + * 例如:八月在酉宫,申时是第9个时辰(子丑寅卯辰巳午未申),从酉宫逆数8位到丑宫 + */ +function calculateMingGong(lunarMonth: number, hour: number): number { + // 1. 从寅宫(索引2)起正月,顺时针数到生月 + // 正月在寅(2),二月在卯(3),...,八月在酉(9) + const shengYueGongIndex = (2 + lunarMonth - 1) % 12; + + // 2. 从生月宫当子时起,逆时针数到生时 + // 生时地支索引就是要逆数的宫位数 + const shengShiZhiIndex = getHourZhiIndex(hour); + + // 从生月宫逆时针数shengShiZhiIndex个宫位 + const mingGongIndex = (shengYueGongIndex - shengShiZhiIndex + 12) % 12; + + return mingGongIndex; +} + +/** + * 计算身宫位置 + * 标准算法:从寅宫起正月,顺数到生月;从生月宫当子时,顺时针数到生时 + * 例如:八月在酉宫,申时是第9个时辰,从酉宫顺数8位到巳宫 + */ +function calculateShenGong(lunarMonth: number, hour: number): number { + // 1. 从寅宫(索引2)起正月,顺时针数到生月 + // 正月在寅(2),二月在卯(3),...,八月在酉(9) + const shengYueGongIndex = (2 + lunarMonth - 1) % 12; + + // 2. 从生月宫当子时起,顺时针数到生时 + // 生时地支索引就是要顺数的宫位数 + const shengShiZhiIndex = getHourZhiIndex(hour); + + // 从生月宫顺时针数shengShiZhiIndex个宫位 + const shenGongIndex = (shengYueGongIndex + shengShiZhiIndex) % 12; + + return shenGongIndex; +} + +/** + * 计算纳音五行局 + */ +function calculateWuXingJu(tiangan: string, dizhi: string): { name: string; number: number } { + const ganNum = Math.floor(TIANGAN.indexOf(tiangan) / 2) + 1; // 甲乙=1, 丙丁=2... + + const zhiMap: { [key: string]: number } = { + '子': 1, '丑': 1, '午': 1, '未': 1, + '寅': 2, '卯': 2, '申': 2, '酉': 2, + '辰': 3, '巳': 3, '戌': 3, '亥': 3 + }; + const zhiNum = zhiMap[dizhi]; + + let juNum = (ganNum + zhiNum) % 5; + if (juNum === 0) juNum = 5; + + const juMap: { [key: number]: string } = { + 1: '木三局', + 2: '金四局', + 3: '水二局', + 4: '火六局', + 5: '土五局' + }; + + return { + name: juMap[juNum], + number: [0, 3, 4, 2, 6, 5][juNum] // 转换为局数:木3,金4,水2,火6,土5 + }; +} + +/** + * 计算紫微星位置 + * 口诀:生日除局商为月,一自寅起紫微定。只加不减到整除,阳退阴进记心中。 + */ +function calculateZiweiPosition(lunarDay: number, juShu: number): number { + let shang = Math.floor(lunarDay / juShu); + let yu = lunarDay % juShu; + + if (yu === 0) { + // 整除 + // 商数对应地支:寅=1, 卯=2, ..., 丑=12 + if (shang > 12) shang = shang - 12; + // 地支索引 = (寅索引2 + 商数-1) % 12 + return (2 + shang - 1) % 12; + } else { + // 不整除,需要添加数使其整除 + const tian = juShu - yu; // 添加的数 + shang = Math.floor((lunarDay + tian) / juShu); + + if (shang > 12) shang = shang - 12; + let baseIndex = (2 + shang - 1) % 12; + + // 添加数为单数:逆时针退tian位(阳退,不包括本位) + // 添加数为双数:顺时针进tian位(阴进,不包括本位) + if (tian % 2 === 1) { + // 单数:逆时针退tian位 + return (baseIndex - tian + 12) % 12; + } else { + // 双数:顺时针进tian位 + return (baseIndex + tian) % 12; + } + } +} + +/** + * 安紫微星系 + * 口诀:紫微天机逆行旁,隔一阳武天同当,又隔二位廉贞地,空三复见紫微郞 + */ +function installZiweiSeries(ziweiPos: number): Map { + const stars = new Map(); + + stars.set('紫微', ziweiPos); + stars.set('天机', (ziweiPos - 1 + 12) % 12); // 逆时针下一宫 + // 隔一宫 + stars.set('太阳', (ziweiPos - 3 + 12) % 12); + stars.set('武曲', (ziweiPos - 4 + 12) % 12); + stars.set('天同', (ziweiPos - 5 + 12) % 12); + // 隔两宫 + stars.set('廉贞', (ziweiPos - 8 + 12) % 12); + + return stars; +} + +/** + * 计算天府星位置 + * 口诀:天府南斗令,常对紫微君 + */ +function calculateTianfuPosition(ziweiPos: number): number { + // 紫微天府对应关系 + const duiYing: { [key: number]: number } = { + 2: 2, 8: 8, // 寅申:紫府同宫 + 1: 3, 3: 1, // 丑卯相对 + 7: 9, 9: 7, // 未酉相对 + 6: 10, 10: 6, // 午戌相对 + 0: 4, 4: 0, // 子辰相对 + 5: 11, 11: 5 // 巳亥相对 + }; + + return duiYing[ziweiPos]; +} + +/** + * 安天府星系 + * 口诀:天府太阴与贪狼,巨门天相及天梁,七杀空三破军位 + */ +function installTianfuSeries(tianfuPos: number): Map { + const stars = new Map(); + + stars.set('天府', tianfuPos); + stars.set('太阴', (tianfuPos + 1) % 12); // 顺时针下一宫 + stars.set('贪狼', (tianfuPos + 2) % 12); + stars.set('巨门', (tianfuPos + 3) % 12); + stars.set('天相', (tianfuPos + 4) % 12); + stars.set('天梁', (tianfuPos + 5) % 12); + stars.set('七杀', (tianfuPos + 6) % 12); + // 空三宫 + stars.set('破军', (tianfuPos + 10) % 12); + + return stars; +} + +/** + * 安辅星:左辅、右弼 + * 口诀:左辅辰宫起正月,顺数至生月;右弼戌宫起正月,逆数至生月 + */ +function installZuoYou(lunarMonth: number): Map { + const stars = new Map(); + + // 左辅:辰宫起正月,顺时针数到生月 + const zuoFuPos = (4 + lunarMonth - 1) % 12; + stars.set('左辅', zuoFuPos); + + // 右弼:戌宫起正月,逆时针数到生月 + const youBiPos = (10 - lunarMonth + 1 + 12) % 12; + stars.set('右弼', youBiPos); + + return stars; +} + +/** + * 安文昌、文曲 + * 口诀:文昌生时起戌位,逆数时辰至卯停;文曲生时起子位,逆数时辰至午停 + */ +function installWenXing(hour: number): Map { + const stars = new Map(); + const hourZhiIndex = getHourZhiIndex(hour); + + // 文昌:戌宫起子时,逆时针数到生时 + const wenChangPos = (10 - hourZhiIndex + 12) % 12; + stars.set('文昌', wenChangPos); + + // 文曲:辰宫起子时,顺时针数到生时 + const wenQuPos = (4 + hourZhiIndex) % 12; + stars.set('文曲', wenQuPos); + + return stars; +} + +/** + * 安禄存、天马 + * 口诀:禄存按年干安星,天马按年支对宫三合 + */ +function installLuMa(yearGan: string, yearZhi: string): Map { + const stars = new Map(); + + // 禄存按年干:甲寅、乙卯、丙戊巳、丁己午、庚申、辛酉、壬亥、癸子 + const luCunMap: { [key: string]: number } = { + '甲': 2, // 寅 + '乙': 3, // 卯 + '丙': 5, // 巳 + '丁': 6, // 午 + '戊': 5, // 巳 + '己': 6, // 午 + '庚': 8, // 申 + '辛': 9, // 酉 + '壬': 11, // 亥 + '癸': 0 // 子 + }; + + stars.set('禄存', luCunMap[yearGan]); + + // 天马按年支三合:寅午戌→申,巳酉丑→亥,申子辰→寅,亥卯未→巳 + const tianMaMap: { [key: string]: number } = { + '寅': 8, '午': 8, '戌': 8, // 申 + '巳': 11, '酉': 11, '丑': 11, // 亥 + '申': 2, '子': 2, '辰': 2, // 寅 + '亥': 5, '卯': 5, '未': 5 // 巳 + }; + + stars.set('天马', tianMaMap[yearZhi]); + + return stars; +} + +/** + * 安擎羊、陀罗 + * 口诀:擎羊禄前一位,陀罗禄后一位 + */ +function installYangTuo(yearGan: string): Map { + const stars = new Map(); + + // 先找禄存位置 + const luCunMap: { [key: string]: number } = { + '甲': 2, '乙': 3, '丙': 5, '丁': 6, '戊': 5, + '己': 6, '庚': 8, '辛': 9, '壬': 11, '癸': 0 + }; + + const luCunPos = luCunMap[yearGan]; + + // 擎羊:禄存前一位(顺时针) + stars.set('擎羊', (luCunPos + 1) % 12); + + // 陀罗:禄存后一位(逆时针) + stars.set('陀罗', (luCunPos - 1 + 12) % 12); + + return stars; +} + +/** + * 安火星、铃星 + * 口诀: + * 申子辰年生人火星在寅宫起子时,铃星在戌宫起子时,均顺时针数到生时 + * 寅午戌年生人火星在丑宫起子时,铃星在卯宫起子时,均顺时针数到生时 + * 巳酉丑年生人火星在卯宫起子时,铃星在戌宫起子时,均顺时针数到生时 + * 亥卯未年生人火星在酉宫起子时,铃星在戌宫起子时,均顺时针数到生时 + */ +function installHuoLing(yearZhi: string, hour: number): Map { + const stars = new Map(); + const hourZhiIndex = getHourZhiIndex(hour); + const zhiIndex = DIZHI.indexOf(yearZhi); + + // 确定火星起始宫位 + let huoStartPos: number; + if ([8, 0, 4].includes(zhiIndex)) { + // 申子辰 → 寅(2) + huoStartPos = 2; + } else if ([2, 6, 10].includes(zhiIndex)) { + // 寅午戌 → 丑(1) + huoStartPos = 1; + } else if ([5, 9, 1].includes(zhiIndex)) { + // 巳酉丑 → 卯(3) + huoStartPos = 3; + } else { + // 亥卯未 → 酉(9) + huoStartPos = 9; + } + + // 确定铃星起始宫位 + let lingStartPos: number; + if ([2, 6, 10].includes(zhiIndex)) { + // 寅午戌 → 卯(3) + lingStartPos = 3; + } else { + // 申子辰、巳酉丑、亥卯未 → 戌(10) + lingStartPos = 10; + } + + // 从起始宫位顺数到生时 + const huoPos = (huoStartPos + hourZhiIndex) % 12; + const lingPos = (lingStartPos + hourZhiIndex) % 12; + + stars.set('火星', huoPos); + stars.set('铃星', lingPos); + + return stars; +} + +/** + * 安地空、地劫 + * 口诀:地空亥宫起子时,逆数至生时;地劫亥宫起子时,顺数至生时 + */ +function installKongJie(hour: number): Map { + const stars = new Map(); + const hourZhiIndex = getHourZhiIndex(hour); + + // 地空:亥宫起子时,逆数至生时 + const diKongPos = (11 - hourZhiIndex + 12) % 12; + stars.set('地空', diKongPos); + + // 地劫:亥宫起子时,顺数至生时 + const diJiePos = (11 + hourZhiIndex) % 12; + stars.set('地劫', diJiePos); + + return stars; +} + +/** + * 安天魁、天钺 + * 口诀:按年干安星 + */ +function installKuiYue(yearGan: string): Map { + const stars = new Map(); + + // 天魁定位 + // 口诀:甲戊庚牛羊,乙己鼠猴乡,丙丁猪鸡位,壬癸兔蛇藏,六辛逢马虎 + const tianKuiMap: { [key: string]: number } = { + '甲': 1, // 丑(牛) + '乙': 0, // 子(鼠) + '丙': 11, // 亥(猪) + '丁': 11, // 亥(猪) + '戊': 1, // 丑(牛) + '己': 0, // 子(鼠) + '庚': 1, // 丑(牛) + '辛': 6, // 午(马) + '壬': 3, // 卯(兔) + '癸': 5 // 巳(蛇) + }; + + // 天钺定位 + const tianYueMap: { [key: string]: number } = { + '甲': 7, // 未(羊) + '乙': 8, // 申(猴) + '丙': 9, // 酉(鸡) + '丁': 9, // 酉(鸡) + '戊': 7, // 未(羊) + '己': 8, // 申(猴) + '庚': 7, // 未(羊) + '辛': 2, // 寅(虎) + '壬': 5, // 巳(蛇) + '癸': 3 // 卯(兔) + }; + + stars.set('天魁', tianKuiMap[yearGan]); + stars.set('天钺', tianYueMap[yearGan]); + + return stars; +} + +/** + * 创建紫微斗数排盘 + */ +export function createZiweiChart(birthInfo: BirthInfo): ZiweiChart { + try { + // 1. 转换为农历 + const solar = Solar.fromYmdHms( + birthInfo.year, + birthInfo.month, + birthInfo.day, + birthInfo.hour, + birthInfo.minute, + 0 + ); + const lunar: Lunar = solar.getLunar(); + + const lunarYear = lunar.getYear(); + const lunarMonth = lunar.getMonth(); + const lunarDay = lunar.getDay(); + + // 2. 获取四柱 + const yearGan = lunar.getYearGan(); + const yearZhi = lunar.getYearZhi(); + const monthGan = lunar.getMonthGan(); + const monthZhi = lunar.getMonthZhi(); + const dayGan = lunar.getDayGan(); + const dayZhi = lunar.getDayZhi(); + const hourGan = lunar.getTimeGan(); + const hourZhi = lunar.getTimeZhi(); + + const siZhu = { + year: { gan: yearGan as any, zhi: yearZhi as any }, + month: { gan: monthGan as any, zhi: monthZhi as any }, + day: { gan: dayGan as any, zhi: dayZhi as any }, + hour: { gan: hourGan as any, zhi: hourZhi as any } + }; + + // 3. 判断阴阳男女 + const yinYang = getYinYang(birthInfo.year, birthInfo.gender) as any; + + // 4. 计算命宫、身宫 + const mingGongIndex = calculateMingGong(lunarMonth, birthInfo.hour); + const shenGongIndex = calculateShenGong(lunarMonth, birthInfo.hour); + + // 5. 计算命宫天干(用月干推算) + const mingGongZhi = DIZHI[mingGongIndex]; + const mingGongGan = getMonthGan(yearGan, mingGongZhi); + + // 6. 计算五行局 + const wuXingJu = calculateWuXingJu(mingGongGan, mingGongZhi); + + // 7. 计算紫微星位置 + const ziweiPos = calculateZiweiPosition(lunarDay, wuXingJu.number); + + // 8. 安14主星 + const ziweiSeries = installZiweiSeries(ziweiPos); + const tianfuPos = calculateTianfuPosition(ziweiPos); + const tianfuSeries = installTianfuSeries(tianfuPos); + + const allMainStars = new Map([...ziweiSeries, ...tianfuSeries]); + + // 9. 安辅星系统 + const zuoYouStars = installZuoYou(lunarMonth); + const wenXingStars = installWenXing(birthInfo.hour); + const luMaStars = installLuMa(yearGan, yearZhi); + const yangTuoStars = installYangTuo(yearGan); + const huoLingStars = installHuoLing(yearZhi, birthInfo.hour); + const kongJieStars = installKongJie(birthInfo.hour); + const kuiYueStars = installKuiYue(yearGan); + const hongLuanTianXiStars = installHongLuanTianXi(yearZhi); + const tianXingStars = installTianXing(lunarMonth); + const tianYaoStars = installTianYao(lunarMonth); + + const allAuxStars = new Map([ + ...zuoYouStars, + ...wenXingStars, + ...luMaStars, + ...yangTuoStars, + ...huoLingStars, + ...kongJieStars, + ...kuiYueStars, + ...hongLuanTianXiStars, + ...tianXingStars, + ...tianYaoStars + ]); + + // 10. 生成十二宫数据 + const gongs: ZiweiGongInfo[] = []; + + for (let i = 0; i < 12; i++) { + // 宫位地支索引(从命宫开始逆时针) + const gongZhiIndex = (mingGongIndex - i + 12) % 12; + const dizhi = DIZHI[gongZhiIndex] as any; + + // 计算天干(使用五虎遁月诀) + const tiangan = getMonthGan(yearGan, dizhi) as any; + + // 找出这个宫位的主星 + const mainStars: any[] = []; + allMainStars.forEach((pos, star) => { + if (pos === gongZhiIndex) { + mainStars.push(star); + } + }); + + // 找出这个宫位的辅星 + const auxStars: any[] = []; + allAuxStars.forEach((pos, star) => { + if (pos === gongZhiIndex) { + auxStars.push(star); + } + }); + + gongs.push({ + gong: GONG_NAMES[i] as any, + dizhi, + tiangan, + mainStars, + auxStars, + sihua: [] + }); + } + + // 11. 添加本命四化(出生年干的四化) + const gongsWithSiHua = addBenMingSiHua(gongs, yearGan as any); + + // 12. 计算大运信息 + const currentAge = calculateXuSui(birthInfo.year); + const daXianList = calculateDaXian(mingGongIndex, yinYang as any, wuXingJu.number); + + // 13. 为每个宫位添加大运信息和流年 + // 找到当前大限 + const currentDaXian = daXianList.find(dx => currentAge >= dx.startAge && currentAge <= dx.endAge); + + // 计算当前大限对应的完整年份范围(10年) + // 例如:2014年出生火六局,当前12岁在6~15岁大限,对应2019~2028年(完整10年) + // 例如:1990年出生土五局,当前36岁在35~44岁大限,对应2024~2033年(完整10年) + let liunianYearStart: number | undefined; + let liunianYearEnd: number | undefined; + if (currentDaXian) { + const birthYear = birthInfo.year; + // 大限开始年份 = 出生年份 + 大限开始年龄 - 1 + liunianYearStart = birthYear + currentDaXian.startAge - 1; + // 大限结束年份 = 出生年份 + 大限结束年龄 - 1 + liunianYearEnd = birthYear + currentDaXian.endAge - 1; + } + + // 十二宫名称列表(命宫开始逆时针) + const GONG_NAMES_ORDER = ['命宫', '兄弟宫', '夫妻宫', '子女宫', '财帛宫', '疾厄宫', + '迁移宫', '交友宫', '官禄宫', '田宅宫', '福德宫', '父母宫']; + + const gongsWithDaXian = gongsWithSiHua.map((gong, index) => { + const gongZhiIndex = (mingGongIndex - index + 12) % 12; + const daXian = daXianList.find(dx => dx.gongIndex === gongZhiIndex); + + // 计算流年:根据地支固定位置分配年份 + // 例如:2025年乙巳年,固定在巳宫;2026年丙午年,固定在午宫 + + // 计算该宫位地支对应的年份 + // 地支循环:子丑寅卯辰巳午未申酉戌亥(0-11) + const dizhiIndex = DIZHI.indexOf(gong.dizhi); + + // 计算该地支对应的年份(在当前大限范围内) + let displayYear: number | undefined; + if (liunianYearStart && liunianYearEnd) { + // 遍历当前大限的所有年份,找到地支匹配的年份 + for (let year = liunianYearStart; year <= liunianYearEnd; year++) { + const yearZhiIndex = (year - 4) % 12; + if (yearZhiIndex === dizhiIndex) { + displayYear = year; + break; + } + } + } + + // 计算大限宫位名(基于当前大限的起始宫位) + let daXianGongName: string | undefined; + if (currentDaXian) { + // 找到当前大限的起始宫位在十二宫中的位置 + const currentDaXianGongIndex = gongsWithSiHua.findIndex(g => { + const gZhiIndex = DIZHI.indexOf(g.dizhi); + return gZhiIndex === currentDaXian.gongIndex; + }); + + // 计算当前宫位相对于大限命宫的偏移 + const offsetFromDaXianMing = (index - currentDaXianGongIndex + 12) % 12; + + // 根据偏移获取大限宫位名 + const daXianGongNameMap: { [key: string]: string } = { + '命宫': '大命', + '兄弟宫': '大兄', + '夫妻宫': '大夫', + '子女宫': '大子', + '财帛宫': '大财', + '疾厄宫': '大疾', + '迁移宫': '大迁', + '交友宫': '大友', + '官禄宫': '大官', + '田宅宫': '大田', + '福德宫': '大福', + '父母宫': '大父' + }; + + const correspondingGongName = GONG_NAMES_ORDER[offsetFromDaXianMing]; + daXianGongName = daXianGongNameMap[correspondingGongName]; + } + + if (daXian) { + const isCurrent = currentAge >= daXian.startAge && currentAge <= daXian.endAge; + + // 计算该大运内的流年年龄列表 + const liuNianAges: number[] = []; + for (let age = daXian.startAge; age <= daXian.endAge; age++) { + liuNianAges.push(age); + } + + return { + ...gong, + daXian: { + startAge: daXian.startAge, + endAge: daXian.endAge, + isCurrent, + daXianGongName + }, + liuNian: liuNianAges, + liuNianYear: displayYear + }; + } + + return { + ...gong, + liuNianYear: displayYear + }; + }); + + return { + birthInfo, + lunarDate: { + year: lunarYear, + month: lunarMonth, + day: lunarDay, + monthCn: lunar.getMonthInChinese(), + dayCn: lunar.getDayInChinese() + }, + siZhu, + yinYang, + mingGongIndex, + shenGongIndex, + wuXingJu, + gongs: gongsWithDaXian + }; + + } catch (error) { + console.error('紫微斗数排盘失败:', error); + throw new Error('紫微斗数排盘计算失败: ' + (error as Error).message); + } +} + +export function runZiweiTests() { + console.error('紫微斗数测试功能待实现'); + return { passed: 0, failed: 0, details: [] }; +} + +/** + * 安红鸾、天喜 + * 口诀:卯宫起子年,逆数至生年支;天喜为红鸾对宫 + */ +function installHongLuanTianXi(yearZhi: string): Map { + const stars = new Map(); + const zhiIndex = DIZHI.indexOf(yearZhi); + + // 红鸾:卯宫起子年,逆数至生年支 + const hongLuanPos = (3 - zhiIndex + 12) % 12; + + // 天喜:红鸾对宫 + const tianXiPos = (hongLuanPos + 6) % 12; + + stars.set('红鸾', hongLuanPos); + stars.set('天喜', tianXiPos); + + return stars; +} + +/** + * 安天刑 + * 口诀:天刑酉上正月轮,数至生月便住脚 + */ +function installTianXing(lunarMonth: number): Map { + const stars = new Map(); + + // 天刑:酉宫起正月,顺数至生月 + const tianXingPos = (9 + lunarMonth - 1) % 12; + + stars.set('天刑', tianXingPos); + + return stars; +} + +/** + * 安天姚 + * 口诀:天姚丑上顺正月,数至生月便住脚 + */ +function installTianYao(lunarMonth: number): Map { + const stars = new Map(); + + // 天姚:丑宫起正月,顺数至生月 + const tianYaoPos = (1 + lunarMonth - 1) % 12; + + stars.set('天姚', tianYaoPos); + + return stars; +} diff --git a/docs/jietu.png b/docs/jietu.png new file mode 100644 index 0000000..6bae065 Binary files /dev/null and b/docs/jietu.png differ diff --git a/examples/sample-analysis.json b/examples/sample-analysis.json new file mode 100644 index 0000000..171cbdf --- /dev/null +++ b/examples/sample-analysis.json @@ -0,0 +1,73 @@ +{ + "meta": { + "archetype_name": "厚土紫微的守成者", + "axis_oneliner": "紫微坐命气度沉稳,正财厚土宜守成积累,忌冲动投机" + }, + "axes": { + "bazi_main": "戊午日主火土厚重,正财格中和偏稳,宜积累守成,不宜投机冒进。", + "ziwei_main": "紫微午宫坐命,沉稳有威仪,武曲化禄主正业生财,文曲化忌防文书契约。" + }, + "consistency": "同向印证", + "strengths": [ + { "title": "气度沉稳", "desc": "紫微坐命,临事不乱,自带威仪" }, + { "title": "正财踏实", "desc": "正财格中和,理财稳健不投机" }, + { "title": "财禄有源", "desc": "财帛武曲化禄,正业生财力强" } + ], + "weaknesses": [ + { "title": "命迁相冲", "desc": "子午相冲,环境多变,内心易起伏" }, + { "title": "金水偏弱", "desc": "食伤财稍欠,开拓与变现略弱" }, + { "title": "文曲化忌", "desc": "文书契约易疏失,防签约纠纷" } + ], + "section_01": { + "text": "八字戊午日生于子月,火土两旺、金水偏弱,正财格成中和之局,喜丙火暖局、甲木疏土,主性情厚重务实,财来于稳健积累而非投机。紫微午宫坐命,气度沉稳、自带威仪,与八字的厚土守成之性高度同向;财帛宫武曲化禄,印证八字以正财为用、正业生财的主轴。两盘一致指向:此命宜在稳定平台中长期深耕,靠专业与信用立身。需共同留意者,八字命局子午相冲、紫微命迁线动荡,皆提示环境与人事多变;加之官禄宫文曲化忌,文书、契约、合约类事务须格外谨慎。整体为稳中求成、厚积薄发之格,忌急功近利。", + "word_count": 218 + }, + "section_02": { + "conclusion": "少年打基础,十九岁后入甲戌运立业起步;二十九岁后财路渐宽,三十九岁后偏财食神并见为财富扩张期;两盘阶段节奏一致,皆主中年后稳步上行。" + }, + "dim": { + "career": { "bazi": "比劫厚重,宜稳定职场或大机构深耕", "ziwei": "官禄廉贞天府,适合管理/资产型岗位", "verdict": "🟢 同向", "verdict_class": "verdict-yes", "fused": "适合大平台长期深耕,靠专业与信用立身" }, + "wealth": { "bazi": "正财格中和,理财稳健不爱投机", "ziwei": "财帛武曲化禄,正业生财能力强", "verdict": "🟢 同向", "verdict_class": "verdict-yes", "fused": "正财稳进型,宜积累与资产配置" }, + "marriage": { "bazi": "日支午被子冲,配偶宫动荡需磨合", "ziwei": "夫妻宫七杀,对象刚强有主见", "verdict": "⚠ 部分冲突", "verdict_class": "verdict-partial", "fused": "配偶能力强但需磨合,宜晚婚稳定" }, + "children": { "bazi": "时柱比肩,子女缘分中等", "ziwei": "子女宫平稳,重在教育投入", "verdict": "🟢 同向", "verdict_class": "verdict-yes", "fused": "缘分中等,重视陪伴与教育" }, + "family": { "bazi": "年月截脚,长辈助力有限须自立", "ziwei": "父母宫平淡,早年靠自身打拼", "verdict": "🟢 同向", "verdict_class": "verdict-yes", "fused": "自立型,中年后反哺家庭" }, + "health": { "bazi": "火土过旺金水弱,注意心血管脾胃", "ziwei": "疾厄天同巨门,留意消化与情绪", "verdict": "🟢 同向", "verdict_class": "verdict-yes", "fused": "心血管与脾胃为重,规律作息饮食清淡" } + }, + "conflicts": [ + { "point": "命迁相冲", "bazi": "子午相冲,环境与人事多变", "ziwei": "命迁线动荡,迁移变动频繁", "impact": "中", "impact_class": "mid", "advice": "重大变动避开冲动期,三思后行" }, + { "point": "文曲化忌", "bazi": "金水弱,文书表达欠周", "ziwei": "官禄文曲化忌,契约易出错", "impact": "中", "impact_class": "mid", "advice": "合同签约务必复核,留存凭据" }, + { "point": "火土偏燥", "bazi": "火土过旺缺金水调候", "ziwei": "疾厄主消化情绪", "impact": "低", "impact_class": "low", "advice": "饮食清淡、多近水、规律作息" } + ], + "final": { + "life_axis": "稳中求成,厚积薄发;靠专业与信用立身,忌急功近利。", + "nodes": [ + { "age": 19, "year": 2018, "event": "入甲戌大运,七杀比肩并见,立业起步" }, + { "age": 27, "year": 2026, "event": "甲戌运中,事业平台选择的关键窗口" }, + { "age": 29, "year": 2028, "event": "转癸酉大运,正财伤官,财路渐宽" }, + { "age": 39, "year": 2038, "event": "转壬申大运,偏财食神,财富扩张期" }, + { "age": 49, "year": 2048, "event": "转辛未大运,回归稳守与传承" } + ], + "risks": [ + { "range": "2026-2028 (27-29岁)", "desc": "大运交接 + 命迁相冲,环境变动期,决策宜稳" }, + { "range": "2031-2032 (32-33岁)", "desc": "文书契约高发期,签约务必复核防纠纷" }, + { "range": "2038-2040 (39-41岁)", "desc": "财富扩张伴随压力,节制扩张防过劳" } + ], + "leverage": [ + { "title": "走管理与资产路线", "desc": "紫微威仪 + 正财稳健,适合管理岗与资产型积累" }, + { "title": "深耕正业生财", "desc": "财帛武曲化禄,专注主业比多元投机更稳健" } + ], + "advice": [ + "选择稳定大平台长期深耕,靠信用立身", + "合同契约类事务务必复核留底", + "理财以稳健配置为主,远离投机", + "饮食清淡作息规律,护心血管脾胃" + ] + }, + "confidence": { + "bazi_level": "中", "bazi_score": "0.64", + "ziwei_level": "中高", "ziwei_score": "0.70", + "consistency_level": "中高", "consistency_score": "0.72", + "stability_level": "中", "stability_score": "0.66", + "note": "以上为传统命理与算法模型综合得出的概率性评估,仅作研究与娱乐参考,请结合自身实际决策。" + } +} diff --git a/examples/sample-chart.json b/examples/sample-chart.json new file mode 100644 index 0000000..96d8599 --- /dev/null +++ b/examples/sample-chart.json @@ -0,0 +1,1541 @@ +{ + "bazi": { + "birthInfo": { + "year": 2000, + "month": 1, + "day": 1, + "hour": 12, + "minute": 0, + "isLunar": false, + "gender": "male", + "timeZone": 8 + }, + "siZhu": { + "year": { + "gan": "己", + "zhi": "卯" + }, + "month": { + "gan": "丙", + "zhi": "子" + }, + "day": { + "gan": "戊", + "zhi": "午" + }, + "hour": { + "gan": "戊", + "zhi": "午" + } + }, + "dayMaster": "戊", + "shiShen": { + "year": "劫财", + "month": "偏印", + "day": "比肩", + "hour": "比肩" + }, + "zhangSheng": { + "year": "沐浴", + "month": "胎", + "day": "帝旺", + "hour": "帝旺" + }, + "naYin": { + "year": "城头土", + "month": "涧下水", + "day": "天上火", + "hour": "天上火" + }, + "dayunStart": 8, + "dayun": [ + { + "ganZhi": { + "gan": "乙", + "zhi": "亥" + }, + "startAge": 9, + "startYear": 2008, + "endYear": 2017, + "ganShiShen": "正官", + "zhiShiShen": "偏财", + "liuNian": [ + { + "year": 2008, + "age": 9, + "ganZhi": { + "gan": "戊", + "zhi": "子" + } + }, + { + "year": 2009, + "age": 10, + "ganZhi": { + "gan": "己", + "zhi": "丑" + } + }, + { + "year": 2010, + "age": 11, + "ganZhi": { + "gan": "庚", + "zhi": "寅" + } + }, + { + "year": 2011, + "age": 12, + "ganZhi": { + "gan": "辛", + "zhi": "卯" + } + }, + { + "year": 2012, + "age": 13, + "ganZhi": { + "gan": "壬", + "zhi": "辰" + } + }, + { + "year": 2013, + "age": 14, + "ganZhi": { + "gan": "癸", + "zhi": "巳" + } + }, + { + "year": 2014, + "age": 15, + "ganZhi": { + "gan": "甲", + "zhi": "午" + } + }, + { + "year": 2015, + "age": 16, + "ganZhi": { + "gan": "乙", + "zhi": "未" + } + }, + { + "year": 2016, + "age": 17, + "ganZhi": { + "gan": "丙", + "zhi": "申" + } + }, + { + "year": 2017, + "age": 18, + "ganZhi": { + "gan": "丁", + "zhi": "酉" + } + } + ], + "endAge": 18 + }, + { + "ganZhi": { + "gan": "甲", + "zhi": "戌" + }, + "startAge": 19, + "startYear": 2018, + "endYear": 2027, + "ganShiShen": "七杀", + "zhiShiShen": "比肩", + "liuNian": [ + { + "year": 2018, + "age": 19, + "ganZhi": { + "gan": "戊", + "zhi": "戌" + } + }, + { + "year": 2019, + "age": 20, + "ganZhi": { + "gan": "己", + "zhi": "亥" + } + }, + { + "year": 2020, + "age": 21, + "ganZhi": { + "gan": "庚", + "zhi": "子" + } + }, + { + "year": 2021, + "age": 22, + "ganZhi": { + "gan": "辛", + "zhi": "丑" + } + }, + { + "year": 2022, + "age": 23, + "ganZhi": { + "gan": "壬", + "zhi": "寅" + } + }, + { + "year": 2023, + "age": 24, + "ganZhi": { + "gan": "癸", + "zhi": "卯" + } + }, + { + "year": 2024, + "age": 25, + "ganZhi": { + "gan": "甲", + "zhi": "辰" + } + }, + { + "year": 2025, + "age": 26, + "ganZhi": { + "gan": "乙", + "zhi": "巳" + } + }, + { + "year": 2026, + "age": 27, + "ganZhi": { + "gan": "丙", + "zhi": "午" + } + }, + { + "year": 2027, + "age": 28, + "ganZhi": { + "gan": "丁", + "zhi": "未" + } + } + ], + "endAge": 28 + }, + { + "ganZhi": { + "gan": "癸", + "zhi": "酉" + }, + "startAge": 29, + "startYear": 2028, + "endYear": 2037, + "ganShiShen": "正财", + "zhiShiShen": "伤官", + "liuNian": [ + { + "year": 2028, + "age": 29, + "ganZhi": { + "gan": "戊", + "zhi": "申" + } + }, + { + "year": 2029, + "age": 30, + "ganZhi": { + "gan": "己", + "zhi": "酉" + } + }, + { + "year": 2030, + "age": 31, + "ganZhi": { + "gan": "庚", + "zhi": "戌" + } + }, + { + "year": 2031, + "age": 32, + "ganZhi": { + "gan": "辛", + "zhi": "亥" + } + }, + { + "year": 2032, + "age": 33, + "ganZhi": { + "gan": "壬", + "zhi": "子" + } + }, + { + "year": 2033, + "age": 34, + "ganZhi": { + "gan": "癸", + "zhi": "丑" + } + }, + { + "year": 2034, + "age": 35, + "ganZhi": { + "gan": "甲", + "zhi": "寅" + } + }, + { + "year": 2035, + "age": 36, + "ganZhi": { + "gan": "乙", + "zhi": "卯" + } + }, + { + "year": 2036, + "age": 37, + "ganZhi": { + "gan": "丙", + "zhi": "辰" + } + }, + { + "year": 2037, + "age": 38, + "ganZhi": { + "gan": "丁", + "zhi": "巳" + } + } + ], + "endAge": 38 + }, + { + "ganZhi": { + "gan": "壬", + "zhi": "申" + }, + "startAge": 39, + "startYear": 2038, + "endYear": 2047, + "ganShiShen": "偏财", + "zhiShiShen": "食神", + "liuNian": [ + { + "year": 2038, + "age": 39, + "ganZhi": { + "gan": "戊", + "zhi": "午" + } + }, + { + "year": 2039, + "age": 40, + "ganZhi": { + "gan": "己", + "zhi": "未" + } + }, + { + "year": 2040, + "age": 41, + "ganZhi": { + "gan": "庚", + "zhi": "申" + } + }, + { + "year": 2041, + "age": 42, + "ganZhi": { + "gan": "辛", + "zhi": "酉" + } + }, + { + "year": 2042, + "age": 43, + "ganZhi": { + "gan": "壬", + "zhi": "戌" + } + }, + { + "year": 2043, + "age": 44, + "ganZhi": { + "gan": "癸", + "zhi": "亥" + } + }, + { + "year": 2044, + "age": 45, + "ganZhi": { + "gan": "甲", + "zhi": "子" + } + }, + { + "year": 2045, + "age": 46, + "ganZhi": { + "gan": "乙", + "zhi": "丑" + } + }, + { + "year": 2046, + "age": 47, + "ganZhi": { + "gan": "丙", + "zhi": "寅" + } + }, + { + "year": 2047, + "age": 48, + "ganZhi": { + "gan": "丁", + "zhi": "卯" + } + } + ], + "endAge": 48 + }, + { + "ganZhi": { + "gan": "辛", + "zhi": "未" + }, + "startAge": 49, + "startYear": 2048, + "endYear": 2057, + "ganShiShen": "伤官", + "zhiShiShen": "劫财", + "liuNian": [ + { + "year": 2048, + "age": 49, + "ganZhi": { + "gan": "戊", + "zhi": "辰" + } + }, + { + "year": 2049, + "age": 50, + "ganZhi": { + "gan": "己", + "zhi": "巳" + } + }, + { + "year": 2050, + "age": 51, + "ganZhi": { + "gan": "庚", + "zhi": "午" + } + }, + { + "year": 2051, + "age": 52, + "ganZhi": { + "gan": "辛", + "zhi": "未" + } + }, + { + "year": 2052, + "age": 53, + "ganZhi": { + "gan": "壬", + "zhi": "申" + } + }, + { + "year": 2053, + "age": 54, + "ganZhi": { + "gan": "癸", + "zhi": "酉" + } + }, + { + "year": 2054, + "age": 55, + "ganZhi": { + "gan": "甲", + "zhi": "戌" + } + }, + { + "year": 2055, + "age": 56, + "ganZhi": { + "gan": "乙", + "zhi": "亥" + } + }, + { + "year": 2056, + "age": 57, + "ganZhi": { + "gan": "丙", + "zhi": "子" + } + }, + { + "year": 2057, + "age": 58, + "ganZhi": { + "gan": "丁", + "zhi": "丑" + } + } + ], + "endAge": 58 + }, + { + "ganZhi": { + "gan": "庚", + "zhi": "午" + }, + "startAge": 59, + "startYear": 2058, + "endYear": 2067, + "ganShiShen": "食神", + "zhiShiShen": "正印", + "liuNian": [ + { + "year": 2058, + "age": 59, + "ganZhi": { + "gan": "戊", + "zhi": "寅" + } + }, + { + "year": 2059, + "age": 60, + "ganZhi": { + "gan": "己", + "zhi": "卯" + } + }, + { + "year": 2060, + "age": 61, + "ganZhi": { + "gan": "庚", + "zhi": "辰" + } + }, + { + "year": 2061, + "age": 62, + "ganZhi": { + "gan": "辛", + "zhi": "巳" + } + }, + { + "year": 2062, + "age": 63, + "ganZhi": { + "gan": "壬", + "zhi": "午" + } + }, + { + "year": 2063, + "age": 64, + "ganZhi": { + "gan": "癸", + "zhi": "未" + } + }, + { + "year": 2064, + "age": 65, + "ganZhi": { + "gan": "甲", + "zhi": "申" + } + }, + { + "year": 2065, + "age": 66, + "ganZhi": { + "gan": "乙", + "zhi": "酉" + } + }, + { + "year": 2066, + "age": 67, + "ganZhi": { + "gan": "丙", + "zhi": "戌" + } + }, + { + "year": 2067, + "age": 68, + "ganZhi": { + "gan": "丁", + "zhi": "亥" + } + } + ], + "endAge": 68 + }, + { + "ganZhi": { + "gan": "己", + "zhi": "巳" + }, + "startAge": 69, + "startYear": 2068, + "endYear": 2077, + "ganShiShen": "劫财", + "zhiShiShen": "偏印", + "liuNian": [ + { + "year": 2068, + "age": 69, + "ganZhi": { + "gan": "戊", + "zhi": "子" + } + }, + { + "year": 2069, + "age": 70, + "ganZhi": { + "gan": "己", + "zhi": "丑" + } + }, + { + "year": 2070, + "age": 71, + "ganZhi": { + "gan": "庚", + "zhi": "寅" + } + }, + { + "year": 2071, + "age": 72, + "ganZhi": { + "gan": "辛", + "zhi": "卯" + } + }, + { + "year": 2072, + "age": 73, + "ganZhi": { + "gan": "壬", + "zhi": "辰" + } + }, + { + "year": 2073, + "age": 74, + "ganZhi": { + "gan": "癸", + "zhi": "巳" + } + }, + { + "year": 2074, + "age": 75, + "ganZhi": { + "gan": "甲", + "zhi": "午" + } + }, + { + "year": 2075, + "age": 76, + "ganZhi": { + "gan": "乙", + "zhi": "未" + } + }, + { + "year": 2076, + "age": 77, + "ganZhi": { + "gan": "丙", + "zhi": "申" + } + }, + { + "year": 2077, + "age": 78, + "ganZhi": { + "gan": "丁", + "zhi": "酉" + } + } + ], + "endAge": 78 + }, + { + "ganZhi": { + "gan": "戊", + "zhi": "辰" + }, + "startAge": 79, + "startYear": 2078, + "endYear": 2087, + "ganShiShen": "比肩", + "zhiShiShen": "比肩", + "liuNian": [ + { + "year": 2078, + "age": 79, + "ganZhi": { + "gan": "戊", + "zhi": "戌" + } + }, + { + "year": 2079, + "age": 80, + "ganZhi": { + "gan": "己", + "zhi": "亥" + } + }, + { + "year": 2080, + "age": 81, + "ganZhi": { + "gan": "庚", + "zhi": "子" + } + }, + { + "year": 2081, + "age": 82, + "ganZhi": { + "gan": "辛", + "zhi": "丑" + } + }, + { + "year": 2082, + "age": 83, + "ganZhi": { + "gan": "壬", + "zhi": "寅" + } + }, + { + "year": 2083, + "age": 84, + "ganZhi": { + "gan": "癸", + "zhi": "卯" + } + }, + { + "year": 2084, + "age": 85, + "ganZhi": { + "gan": "甲", + "zhi": "辰" + } + }, + { + "year": 2085, + "age": 86, + "ganZhi": { + "gan": "乙", + "zhi": "巳" + } + }, + { + "year": 2086, + "age": 87, + "ganZhi": { + "gan": "丙", + "zhi": "午" + } + }, + { + "year": 2087, + "age": 88, + "ganZhi": { + "gan": "丁", + "zhi": "未" + } + } + ], + "endAge": 88 + }, + { + "ganZhi": { + "gan": "丁", + "zhi": "卯" + }, + "startAge": 89, + "startYear": 2088, + "endYear": 2097, + "ganShiShen": "正印", + "zhiShiShen": "正官", + "liuNian": [ + { + "year": 2088, + "age": 89, + "ganZhi": { + "gan": "戊", + "zhi": "申" + } + }, + { + "year": 2089, + "age": 90, + "ganZhi": { + "gan": "己", + "zhi": "酉" + } + }, + { + "year": 2090, + "age": 91, + "ganZhi": { + "gan": "庚", + "zhi": "戌" + } + }, + { + "year": 2091, + "age": 92, + "ganZhi": { + "gan": "辛", + "zhi": "亥" + } + }, + { + "year": 2092, + "age": 93, + "ganZhi": { + "gan": "壬", + "zhi": "子" + } + }, + { + "year": 2093, + "age": 94, + "ganZhi": { + "gan": "癸", + "zhi": "丑" + } + }, + { + "year": 2094, + "age": 95, + "ganZhi": { + "gan": "甲", + "zhi": "寅" + } + }, + { + "year": 2095, + "age": 96, + "ganZhi": { + "gan": "乙", + "zhi": "卯" + } + }, + { + "year": 2096, + "age": 97, + "ganZhi": { + "gan": "丙", + "zhi": "辰" + } + }, + { + "year": 2097, + "age": 98, + "ganZhi": { + "gan": "丁", + "zhi": "巳" + } + } + ], + "endAge": 98 + } + ], + "cangGan": { + "year": [ + { + "gan": "乙", + "shiShen": "正官" + } + ], + "month": [ + { + "gan": "癸", + "shiShen": "正财" + } + ], + "day": [ + { + "gan": "丁", + "shiShen": "正印" + }, + { + "gan": "己", + "shiShen": "劫财" + } + ], + "hour": [ + { + "gan": "丁", + "shiShen": "正印" + }, + { + "gan": "己", + "shiShen": "劫财" + } + ] + }, + "enrichment": { + "自坐": { + "年": "病", + "月": "胎", + "日": "帝旺", + "时": "帝旺" + }, + "五行旺相": { + "木": "相", + "火": "死", + "土": "囚", + "金": "休", + "水": "旺" + }, + "五行统计": { + "surface": { + "木": 1, + "火": 3, + "土": 3, + "金": 0, + "水": 1 + }, + "withCangGan": { + "木": 1, + "火": 3, + "土": 4, + "金": 0, + "水": 1 + }, + "missing": [ + "金" + ], + "strongest": [ + "火", + "土" + ], + "shiShenGroups": { + "木": { + "十神类": "官杀", + "实例数": 1 + }, + "火": { + "十神类": "印", + "实例数": 3 + }, + "土": { + "十神类": "比劫", + "实例数": 3 + }, + "金": { + "十神类": "食伤", + "实例数": 0 + }, + "水": { + "十神类": "财", + "实例数": 1 + } + } + }, + "调候用神": [ + "丙", + "甲" + ], + "格局": { + "primary": "正财格", + "basis": "月支子本气癸(正财) — 纯气月支直接立格", + "透干": [], + "confidence": "高", + "notes": [] + }, + "旺衰": { + "score": -1.7, + "verdict": "中和", + "confidence": "低", + "breakdown": { + "得令": -5, + "长生": -3, + "得地": 3.6, + "得势": 2.7, + "details": [ + "月支本气癸=正财 -5", + "日主戊在月支子为胎 (-3)", + "日支午藏丁(正印, 本气) +1", + "日支午藏己(劫财, 中气) +0.8", + "时支午藏丁(正印, 本气) +1", + "时支午藏己(劫财, 中气) +0.8", + "年干己(劫财) +1", + "月干丙(偏印) +0.7", + "时干戊(比肩) +1" + ] + } + }, + "天干关系": [], + "地支关系": [ + { + "type": "六冲", + "zhi": [ + "子", + "午" + ], + "pillars": [ + "月", + "日" + ] + }, + { + "type": "六冲", + "zhi": [ + "子", + "午" + ], + "pillars": [ + "月", + "时" + ] + }, + { + "type": "相刑", + "zhi": [ + "卯", + "子" + ], + "pillars": [ + "年", + "月" + ], + "detail": "无礼之刑" + }, + { + "type": "自刑", + "zhi": [ + "午", + "午" + ], + "pillars": [ + "日", + "时" + ] + } + ], + "整柱": [ + { + "pillar": "年", + "gan": "己", + "zhi": "卯", + "verdict": "截脚" + }, + { + "pillar": "月", + "gan": "丙", + "zhi": "子", + "verdict": "截脚" + }, + { + "pillar": "日", + "gan": "戊", + "zhi": "午", + "verdict": "地支生天干" + }, + { + "pillar": "时", + "gan": "戊", + "zhi": "午", + "verdict": "地支生天干" + } + ] + } + }, + "ziwei": { + "birthInfo": { + "year": 2000, + "month": 1, + "day": 1, + "hour": 12, + "minute": 0, + "isLunar": false, + "gender": "male", + "timeZone": 8 + }, + "lunarDate": { + "year": 1999, + "month": 11, + "day": 25, + "monthCn": "冬", + "dayCn": "廿五" + }, + "siZhu": { + "year": { + "gan": "己", + "zhi": "卯" + }, + "month": { + "gan": "丙", + "zhi": "子" + }, + "day": { + "gan": "戊", + "zhi": "午" + }, + "hour": { + "gan": "戊", + "zhi": "午" + } + }, + "yinYang": "阳男", + "mingGongIndex": 6, + "shenGongIndex": 6, + "wuXingJu": { + "name": "土五局", + "number": 5 + }, + "gongs": [ + { + "gong": "命宫", + "dizhi": "午", + "tiangan": "庚", + "mainStars": [ + "紫微" + ], + "auxStars": [ + "禄存", + "天喜" + ], + "sihua": [], + "daXian": { + "startAge": 5, + "endAge": 14, + "isCurrent": false, + "daXianGongName": "大夫" + }, + "liuNian": [ + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14 + ], + "liuNianYear": 2026 + }, + { + "gong": "兄弟", + "dizhi": "巳", + "tiangan": "己", + "mainStars": [ + "天机" + ], + "auxStars": [ + "天马", + "陀罗", + "地空", + "地劫" + ], + "sihua": [], + "daXian": { + "startAge": 115, + "endAge": 124, + "isCurrent": false, + "daXianGongName": "大子" + }, + "liuNian": [ + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124 + ], + "liuNianYear": 2025 + }, + { + "gong": "夫妻", + "dizhi": "辰", + "tiangan": "戊", + "mainStars": [ + "七杀" + ], + "auxStars": [ + "文昌", + "铃星" + ], + "sihua": [], + "daXian": { + "startAge": 105, + "endAge": 114, + "isCurrent": false, + "daXianGongName": "大财" + }, + "liuNian": [ + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114 + ], + "liuNianYear": 2024 + }, + { + "gong": "子女", + "dizhi": "卯", + "tiangan": "丁", + "mainStars": [ + "太阳", + "天梁" + ], + "auxStars": [ + "火星" + ], + "sihua": [ + { + "star": "天梁", + "hua": "化科" + } + ], + "daXian": { + "startAge": 95, + "endAge": 104, + "isCurrent": false, + "daXianGongName": "大疾" + }, + "liuNian": [ + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104 + ] + }, + { + "gong": "财帛", + "dizhi": "寅", + "tiangan": "丙", + "mainStars": [ + "武曲", + "天相" + ], + "auxStars": [ + "左辅" + ], + "sihua": [ + { + "star": "武曲", + "hua": "化禄" + } + ], + "daXian": { + "startAge": 85, + "endAge": 94, + "isCurrent": false, + "daXianGongName": "大迁" + }, + "liuNian": [ + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94 + ] + }, + { + "gong": "疾厄", + "dizhi": "丑", + "tiangan": "丁", + "mainStars": [ + "天同", + "巨门" + ], + "auxStars": [], + "sihua": [], + "daXian": { + "startAge": 75, + "endAge": 84, + "isCurrent": false, + "daXianGongName": "大友" + }, + "liuNian": [ + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84 + ], + "liuNianYear": 2033 + }, + { + "gong": "迁移", + "dizhi": "子", + "tiangan": "丙", + "mainStars": [ + "贪狼" + ], + "auxStars": [ + "右弼", + "天魁", + "红鸾" + ], + "sihua": [ + { + "star": "贪狼", + "hua": "化权" + } + ], + "daXian": { + "startAge": 65, + "endAge": 74, + "isCurrent": false, + "daXianGongName": "大官" + }, + "liuNian": [ + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74 + ], + "liuNianYear": 2032 + }, + { + "gong": "交友", + "dizhi": "亥", + "tiangan": "乙", + "mainStars": [ + "太阴" + ], + "auxStars": [ + "天姚" + ], + "sihua": [], + "daXian": { + "startAge": 55, + "endAge": 64, + "isCurrent": false, + "daXianGongName": "大田" + }, + "liuNian": [ + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64 + ], + "liuNianYear": 2031 + }, + { + "gong": "官禄", + "dizhi": "戌", + "tiangan": "甲", + "mainStars": [ + "廉贞", + "天府" + ], + "auxStars": [ + "文曲" + ], + "sihua": [ + { + "star": "文曲", + "hua": "化忌" + } + ], + "daXian": { + "startAge": 45, + "endAge": 54, + "isCurrent": false, + "daXianGongName": "大福" + }, + "liuNian": [ + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54 + ], + "liuNianYear": 2030 + }, + { + "gong": "田宅", + "dizhi": "酉", + "tiangan": "癸", + "mainStars": [], + "auxStars": [], + "sihua": [], + "daXian": { + "startAge": 35, + "endAge": 44, + "isCurrent": false, + "daXianGongName": "大父" + }, + "liuNian": [ + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44 + ], + "liuNianYear": 2029 + }, + { + "gong": "福德", + "dizhi": "申", + "tiangan": "壬", + "mainStars": [ + "破军" + ], + "auxStars": [ + "天钺" + ], + "sihua": [], + "daXian": { + "startAge": 25, + "endAge": 34, + "isCurrent": true, + "daXianGongName": "大命" + }, + "liuNian": [ + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34 + ], + "liuNianYear": 2028 + }, + { + "gong": "父母", + "dizhi": "未", + "tiangan": "辛", + "mainStars": [], + "auxStars": [ + "擎羊", + "天刑" + ], + "sihua": [], + "daXian": { + "startAge": 15, + "endAge": 24, + "isCurrent": false, + "daXianGongName": "大兄" + }, + "liuNian": [ + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24 + ], + "liuNianYear": 2027 + } + ] + } +} \ No newline at end of file diff --git a/examples/sample-chart.txt b/examples/sample-chart.txt new file mode 100644 index 0000000..e65b99c --- /dev/null +++ b/examples/sample-chart.txt @@ -0,0 +1,150 @@ +紫微斗数命盘 +│ +├基本信息 +│ ├性别 : 男 +│ ├阳历 : 2000-01-01 12:00 +│ ├农历 : 1999年冬月廿五 +│ ├节气四柱 : 己卯 丙子 戊午 戊午 +│ ├阴阳 : 阳男 +│ ├五行局 : 土五局 +│ └命宫=午 身宫=午 +│ +├生年四化 +│ └天梁化科 · 武曲化禄 · 贪狼化权 · 文曲化忌 +│ +├命盘十二宫 +│ ├命宫[庚午][命] +│ │ ├主星 : 紫微 +│ │ ├辅星 : 禄存·天喜 +│ │ ├大限 : 5-14虚岁 +│ │ └流年 : 5·6·7·8·9·10·11·12·13·14虚岁 +│ │ +│ ├兄弟宫[己巳] +│ │ ├主星 : 天机 +│ │ ├辅星 : 天马·陀罗·地空·地劫 +│ │ ├大限 : 115-124虚岁 +│ │ └流年 : 115·116·117·118·119·120·121·122·123·124虚岁 +│ │ +│ ├夫妻宫[戊辰] +│ │ ├主星 : 七杀 +│ │ ├辅星 : 文昌·铃星 +│ │ ├大限 : 105-114虚岁 +│ │ └流年 : 105·106·107·108·109·110·111·112·113·114虚岁 +│ │ +│ ├子女宫[丁卯] +│ │ ├主星 : 太阳·天梁 +│ │ ├辅星 : 火星 +│ │ ├生年四化 : 天梁化科 +│ │ ├大限 : 95-104虚岁 +│ │ └流年 : 95·96·97·98·99·100·101·102·103·104虚岁 +│ │ +│ ├财帛宫[丙寅] +│ │ ├主星 : 武曲·天相 +│ │ ├辅星 : 左辅 +│ │ ├生年四化 : 武曲化禄 +│ │ ├大限 : 85-94虚岁 +│ │ └流年 : 85·86·87·88·89·90·91·92·93·94虚岁 +│ │ +│ ├疾厄宫[丁丑] +│ │ ├主星 : 天同·巨门 +│ │ ├辅星 : 无 +│ │ ├大限 : 75-84虚岁 +│ │ └流年 : 75·76·77·78·79·80·81·82·83·84虚岁 +│ │ +│ ├迁移宫[丙子] +│ │ ├主星 : 贪狼 +│ │ ├辅星 : 右弼·天魁·红鸾 +│ │ ├生年四化 : 贪狼化权 +│ │ ├大限 : 65-74虚岁 +│ │ └流年 : 65·66·67·68·69·70·71·72·73·74虚岁 +│ │ +│ ├交友宫[乙亥] +│ │ ├主星 : 太阴 +│ │ ├辅星 : 天姚 +│ │ ├大限 : 55-64虚岁 +│ │ └流年 : 55·56·57·58·59·60·61·62·63·64虚岁 +│ │ +│ ├官禄宫[甲戌] +│ │ ├主星 : 廉贞·天府 +│ │ ├辅星 : 文曲 +│ │ ├生年四化 : 文曲化忌 +│ │ ├大限 : 45-54虚岁 +│ │ └流年 : 45·46·47·48·49·50·51·52·53·54虚岁 +│ │ +│ ├田宅宫[癸酉] +│ │ ├主星 : 无主星 +│ │ ├辅星 : 无 +│ │ ├大限 : 35-44虚岁 +│ │ └流年 : 35·36·37·38·39·40·41·42·43·44虚岁 +│ │ +│ ├福德宫[壬申] +│ │ ├主星 : 破军 +│ │ ├辅星 : 天钺 +│ │ ├大限 : 25-34虚岁 ★当前 +│ │ └流年 : 25·26·27·28·29·30·31·32·33·34虚岁 +│ │ +│ └父母宫[辛未] +│ ├主星 : 无主星 +│ ├辅星 : 擎羊·天刑 +│ ├大限 : 15-24虚岁 +│ └流年 : 15·16·17·18·19·20·21·22·23·24虚岁 + +八字命盘 +│ +├四柱 +│ ├年柱 : 己卯 [劫财] +│ │ ├藏干 : 乙(正官) +│ │ ├星运 : 沐浴 +│ │ ├自坐 : 病 +│ │ └纳音 : 城头土 +│ │ +│ ├月柱 : 丙子 [偏印] +│ │ ├藏干 : 癸(正财) +│ │ ├星运 : 胎 +│ │ ├自坐 : 胎 +│ │ └纳音 : 涧下水 +│ │ +│ ├日柱 : 戊午 [日主] +│ │ ├藏干 : 丁(正印) 己(劫财) +│ │ ├星运 : 帝旺 +│ │ ├自坐 : 帝旺 +│ │ └纳音 : 天上火 +│ │ +│ └时柱 : 戊午 [比肩] +│ ├藏干 : 丁(正印) 己(劫财) +│ ├星运 : 帝旺 +│ ├自坐 : 帝旺 +│ └纳音 : 天上火 +│ +├大运 (起运 8岁) +│ ├2008-2017 乙亥 (正官/偏财) +│ ├2018-2027 甲戌 (七杀/比肩) +│ ├2028-2037 癸酉 (正财/伤官) +│ ├2038-2047 壬申 (偏财/食神) +│ ├2048-2057 辛未 (伤官/劫财) +│ ├2058-2067 庚午 (食神/正印) +│ ├2068-2077 己巳 (劫财/偏印) +│ ├2078-2087 戊辰 (比肩/比肩) +│ └2088-2097 丁卯 (正印/正官) +│ +├算法补层 +│ ├格局 : 正财格 (置信度: 高) +│ │ └依据 : 月支子本气癸(正财) — 纯气月支直接立格 +│ ├旺衰 : 中和 (score=-1.7, 置信度: 低) +│ │ └四维 : 得令-5 长生-3 得地3.6 得势2.7 +│ ├调候用神 : 丙、甲 +│ ├五行旺相 : 木相 火死 土囚 金休 水旺 +│ ├五行统计(surface) : 木1 火3 土3 金0 水1 +│ ├五行统计(含藏干) : 木1 火3 土4 金0 水1 +│ ├地支关系 +│ │ ├六冲 : 子午 (月-日柱) +│ │ ├六冲 : 子午 (月-时柱) +│ │ ├相刑 : 卯子 (年-月柱) 无礼之刑 +│ │ └自刑 : 午午 (日-时柱) +│ └整柱判定 +│ ├年柱 己卯 : 截脚 +│ ├月柱 丙子 : 截脚 +│ ├日柱 戊午 : 地支生天干 +│ └时柱 戊午 : 地支生天干 + +└[备注: 本盘由 bazi-ziwei skill 算法层生成 — Yiqi core + enrichBazi 补层] \ No newline at end of file diff --git a/examples/sample-report.html b/examples/sample-report.html new file mode 100644 index 0000000..76f3e81 --- /dev/null +++ b/examples/sample-report.html @@ -0,0 +1,1063 @@ + + + + +厚土紫微的守成者 · 综合印证报告 + + + +
+ + +
+
命理
印证
+
+
综合印证报告
+

厚土紫微的守成者

+
紫微坐命气度沉稳,正财厚土宜守成积累,忌冲动投机
+
+
+
生成时间:2026-06-14 10:19
+
综合印证评级
+
✓ 同向印证
+
+ 命主星:破军 + 身主星:火星 +
+
+ 子年斗君:- + 土五局 +
+
+
+ + +
+ + +
+
阳历2000-01-01 12:00
+
农历1999年 冬月廿五
+
性别男(阳男)
+
年龄虚岁 27(截至 2026
+
+ + +
+
+
两盘主轴印证
+
+
+
八字主轴
+
戊午日主火土厚重,正财格中和偏稳,宜积累守成,不宜投机冒进。
+
+
+
紫微主轴
+
紫微午宫坐命,沉稳有威仪,武曲化禄主正业生财,文曲化忌防文书契约。
+
+
+
+
+
+

★ 三大优势

+
气度沉稳紫微坐命,临事不乱,自带威仪
+
正财踏实正财格中和,理财稳健不投机
+
财禄有源财帛武曲化禄,正业生财力强
+
+
+

⊗ 三大隐忧

+
命迁相冲子午相冲,环境多变,内心易起伏
+
金水偏弱食伤财稍欠,开拓与变现略弱
+
文曲化忌文书契约易疏失,防签约纠纷
+
+
+
+
+ + +
+
命盘核心数据
+
+
+
+
格局
+
正财格
+
置信度:高
+
+
+
+
旺衰
+
中和
+
-1.7
+
+
+
+
+
+
+
+
调候用神
+
+
置信度:高
+
+
+
月令旺相
+
+ 水旺·木相·金休·土囚·火死 +
+
+
+
五行统计(含藏干)
+
+
1
+
3
+
4
+
0
+
1
+
+
+
+
+ + +
+ +
+

紫微斗数 · 十二宫盘 生年四化:禄朱砂 / 权青蓝 / 科翠绿 / 忌玄黑

+
+ +
+
兄弟宫己巳
+
天机
+
天马·陀罗·地空·地劫
+
+
大限 115-124
+
+ +
+
命宫庚午
+
紫微
+
禄存·天喜
+
+
大限 5-14
+
+ +
+
父母宫辛未
+
无主星
+
擎羊·天刑
+
+
大限 15-24
+
+ +
+
福德宫壬申
+
破军
+
天钺
+
+
大限 25-34
+
+ +
+
夫妻宫戊辰
+
七杀
+
文昌·铃星
+
+
大限 105-114
+
+ +
+

中宫 · 命盘信息

+
阳男 土五局
+
命主:破军 身主:火星
+
+
+
+
+
+
+
+ +
+
田宅宫癸酉
+
无主星
+
+
+
大限 35-44
+
+ +
+
子女宫丁卯
+
太阳·天梁
+
火星
+
+
大限 95-104
+
+ +
+
官禄宫甲戌
+
廉贞·天府
+
文曲
+
+
大限 45-54
+
+ +
+
财帛宫丙寅
+
武曲·天相
+
左辅
+
+
大限 85-94
+
+ +
+
疾厄宫丁丑
+
天同·巨门
+
+
+
大限 75-84
+
+ +
+
迁移宫丙子
+
贪狼
+
右弼·天魁·红鸾
+
+
大限 65-74
+
+ +
+
交友宫乙亥
+
太阴
+
天姚
+
+
大限 55-64
+
+
+
+ + +
+

八字四柱盘 藏干含十神 / 星运 / 自坐 / 纳音

+
+
+
+
年柱
+
劫财
+
+
+
乙(正官)
+
+
星运沐浴
+
自坐
+
纳音城头土
+
+
+
+
月柱
+
偏印
+
+
+
癸(正财)
+
+
星运
+
自坐
+
纳音涧下水
+
+
+
+
日柱
+
日主
+
+
+
丁(正印) 己(劫财)
+
+
星运帝旺
+
自坐帝旺
+
纳音天上火
+
+
+
+
时柱
+
比肩
+
+
+
丁(正印) 己(劫财)
+
+
星运帝旺
+
自坐帝旺
+
纳音天上火
+
+
+
+ + +
+
大运 (起运 8 岁)
+
+
乙亥
9-18
正偏
+
甲戌
19-28
七比
+
癸酉
29-38
正伤
+
壬申
39-48
偏食
+
辛未
49-58
伤劫
+
庚午
59-68
食正
+
己巳
69-78
劫偏
+
戊辰
79-88
比比
+
丁卯
89-98
正正
+
-
-
-
+
+
+ + +
+
流年 (当前大运:甲戌 19-28)
+
+
2018
19
戊戌
+
2019
20
己亥
+
2020
21
庚子
+
2021
22
辛丑
+
2022
23
壬寅
+
2023
24
癸卯
+
2024
25
甲辰
+
2025
26
乙巳
+
2026
27
丙午
+
2027
28
丁未
+
+
+
+
+
+ + + +
+ +
+

01主轴印证结论

+
+ 八字戊午日生于子月,火土两旺、金水偏弱,正财格成中和之局,喜丙火暖局、甲木疏土,主性情厚重务实,财来于稳健积累而非投机。紫微午宫坐命,气度沉稳、自带威仪,与八字的厚土守成之性高度同向;财帛宫武曲化禄,印证八字以正财为用、正业生财的主轴。两盘一致指向:此命宜在稳定平台中长期深耕,靠专业与信用立身。需共同留意者,八字命局子午相冲、紫微命迁线动荡,皆提示环境与人事多变;加之官禄宫文曲化忌,文书、契约、合约类事务须格外谨慎。整体为稳中求成、厚积薄发之格,忌急功近利。 +
字数:218
+
+
+ + +
+

02阶段印证时间轴 (水平双轨)

+
+ 关键上升期 + 调整期 + 压力期 +
+
+
八字大运
+
9-18
+
19-28
+
29-38
+
39-48
+
49-58
+
59-68
+
69-78
+ +
八字大运
+
乙亥
正偏
+
甲戌
七比
+
癸酉
正伤
+
壬申
偏食
+
辛未
伤劫
+
庚午
食正
+
己巳
劫偏
+ +
紫微大限
+
5-14
+
15-24
+
25-34
+
35-44
+
45-54
+
55-64
+
65-74
+
+
+ 印证结论:少年打基础,十九岁后入甲戌运立业起步;二十九岁后财路渐宽,三十九岁后偏财食神并见为财富扩张期;两盘阶段节奏一致,皆主中年后稳步上行。 +
+
+
+ + +
+
+

03六维交叉对账

+
+
+
事业
财运
婚恋
+
子女
六亲
健康
+ +
八字观点
+
比劫厚重,宜稳定职场或大机构深耕
正财格中和,理财稳健不爱投机
日支午被子冲,配偶宫动荡需磨合
+
时柱比肩,子女缘分中等
年月截脚,长辈助力有限须自立
火土过旺金水弱,注意心血管脾胃
+ +
紫微观点
+
官禄廉贞天府,适合管理/资产型岗位
财帛武曲化禄,正业生财能力强
夫妻宫七杀,对象刚强有主见
+
子女宫平稳,重在教育投入
父母宫平淡,早年靠自身打拼
疾厄天同巨门,留意消化与情绪
+ +
印证标记
+
🟢 同向
+
🟢 同向
+
⚠ 部分冲突
+
🟢 同向
+
🟢 同向
+
🟢 同向
+ +
融合结论
+
适合大平台长期深耕,靠专业与信用立身
正财稳进型,宜积累与资产配置
配偶能力强但需磨合,宜晚婚稳定
+
缘分中等,重视陪伴与教育
自立型,中年后反哺家庭
心血管与脾胃为重,规律作息饮食清淡
+
+
+ +
+

04冲突清单 (已识别的主要冲突点)

+
+
冲突点
+
八字视角
+
紫微视角
+
影响程度
+
建议
+ +
命迁相冲
+
子午相冲,环境与人事多变
+
命迁线动荡,迁移变动频繁
+
+
重大变动避开冲动期,三思后行
+ +
文曲化忌
+
金水弱,文书表达欠周
+
官禄文曲化忌,契约易出错
+
+
合同签约务必复核,留存凭据
+ +
火土偏燥
+
火土过旺缺金水调候
+
疾厄主消化情绪
+
+
饮食清淡、多近水、规律作息
+
+
+
+ + +
+

05综合定论

+
+
+

终极主轴

+
稳中求成,厚积薄发;靠专业与信用立身,忌急功近利。
+
+
+

5 个关键节点(人生里程碑)

+
    +
  • 19岁(2018):入甲戌大运,七杀比肩并见,立业起步
  • +
  • 27岁(2026):甲戌运中,事业平台选择的关键窗口
  • +
  • 29岁(2028):转癸酉大运,正财伤官,财路渐宽
  • +
  • 39岁(2038):转壬申大运,偏财食神,财富扩张期
  • +
  • 49岁(2048):转辛未大运,回归稳守与传承
  • +
+
+
+

3 个高风险窗口 (需谨慎)

+
2026-2028 (27-29岁)
大运交接 + 命迁相冲,环境变动期,决策宜稳
+
2031-2032 (32-33岁)
文书契约高发期,签约务必复核防纠纷
+
2038-2040 (39-41岁)
财富扩张伴随压力,节制扩张防过劳
+
+
+

2 条优势放大策略

+
▸ 走管理与资产路线
紫微威仪 + 正财稳健,适合管理岗与资产型积累
+
▸ 深耕正业生财
财帛武曲化禄,专注主业比多元投机更稳健
+
+
+

行动建议清单 (优先级)

+
1选择稳定大平台长期深耕,靠信用立身
+
2合同契约类事务务必复核留底
+
3理财以稳健配置为主,远离投机
+
4饮食清淡作息规律,护心血管脾胃
+
+
+
+ + +
+

06置信度自评 (综合模型不确定性)

+
+
+
八字模型置信度
+
+
(0.64)
+
+
+
紫微模型置信度
+
中高
+
(0.70)
+
+
+
两盘印证一致性
+
中高
+
(0.72)
+
+
+
关键结论稳定性
+
+
(0.66)
+
+
+ 说明:以上为传统命理与算法模型综合得出的概率性评估,仅作研究与娱乐参考,请结合自身实际决策。 +
+
+
+ + + + +
+ + diff --git a/prompts/bazi-prompt.md b/prompts/bazi-prompt.md new file mode 100644 index 0000000..04bd159 --- /dev/null +++ b/prompts/bazi-prompt.md @@ -0,0 +1,81 @@ +# 八字分析提示词 v1.0 + +## 角色 +你是精通国学易经术数的资深命理分析师,核心擅长主流子平派格局理论与新派命理技法。你基于固定命盘信息精准分析五行生克、十神组合得出喜用忌神,围绕事业、财运、婚恋、子女、六亲、健康等维度展开深度解析。 + +关键事件须给出**发生时间范围、喜忌属性、对命主的影响程度(轻/中/重)**,结合命主客观条件提供精准可落地的建议。 + +## 技能 1:命盘技法解读 +依据用户提供的命盘核心数据展开。命盘数据包含但不限于: + +``` +性别 / 年龄 / 阳历(真太阳时)/ 农历 +四柱(年月日时,含十神) +星运 / 自坐 / 纳音 +小运 / 大运(含十神) +日元 / 日支 / 月令 +五行旺相休囚死 +格局类型 / 旺衰类型 +最旺五行 / 五行缺失 +调候用神 +原局天干关系(合/克)/ 地支关系(刑冲合害/拱合拱会/暗合) +原局整柱(盖头/截脚/天地同气) +``` + +> **重要原则**:所有结论须严格对应提供的命盘数据,不引入胎元、命宫等未提供的变量。 + +## 技能 2:命盘事项解读(六大维度) + +- **事业**:职业倾向(技术/策划/管理/创意类)、工作层次(稳定/波动)、创业 vs 打工适配性、合伙 vs 单干建议 +- **财运**:财富级别(平民/小富/中富)、主要来源(正财/偏财/其他)、聚财与耗财特征 +- **婚恋**:感情性格、适配对象画像、早婚/晚婚倾向、潜在婚姻风险 +- **子女**:头胎性别倾向、生育时间节点、子女缘分深浅 +- **六亲**:与父母/兄弟姐妹的亲疏与帮扶力度 +- **健康**:先天薄弱器官、易患疾病类型、需重点关注的体质问题 + +每节末附:**一句话本节小结** + +## 技能 3:当前大限 + 前后流年走势 + +**按大运切片,不按物理年龄切片**: +- 详讲当前所在大运 + 前一个大运 + 后一个大运的整体走势 +- 详讲当前流年 ±5 年的具体事件 +- 衔接点(换大运那一年)单独标注 + +分领域预测: +- 事业:岗位调动 / 换工作 / 创业 / 合伙风险期 / 跳槽良机的具体年份 +- 财运:收入增长期 / 破财风险期 / 财富来源 +- 婚恋:恋爱萌芽 / 矛盾高发 / 结婚可能 / 婚姻风险 +- 子女:怀孕生产可能年 / 育儿注意节点 +- 六亲:关系波动期 / 长辈健康关注年 +- 健康:平稳期 / 疾病易发期 / 外伤手术风险 + +## 技能 4:核心建议与风险规避 +- **发展方向**:适合的城市属性(南/北/东/西、五行属性)、行业五行 +- **人际建议**:适配的合伙人/朋友类型、需规避的五行/性格类型 +- **风险提示**:重点防范的年份、事件类型、化解思路 + +## 关键约束 +1. **冲突要说出来**:当格局、旺衰、调候、刑冲合害等多个信号互相矛盾时,明说**哪个信号优先、为什么**。不要平均、不要和稀泥 +2. **不堆术语**:术语后必须跟一句白话解释 +3. **结论要可落地**:建议须能转为行动,不要"调整心态"这类空话 +4. **置信度标注**:边界情况(如旺衰临界、格局模糊)须标"此判断置信度中/低" +5. **敏感问题**:涉及下蛊、破坏他人命运、断人财路等内容,直接拒绝回答 +6. **后续问答**:用户后续每个问题,均须依据已提供的命盘数据展开,有理有据 + +## 输出格式 +``` +0. 前置声明 +1. 命盘技法解读(格局/旺衰/调候/喜用忌的整体定调) +2. 命盘事项解读(事业/财运/婚恋/子女/六亲/健康) + 每节末附:一句话本节小结 +3. 大运流年走势(当前大运 + 前后大运 + 流年 ±5 年) +4. 核心建议与风险规避 +5. 免责声明 +``` + +## 前置声明(输出开头必带) +> 本分析基于传统八字命理理论框架,仅供文化研究与娱乐参考,非专业决策依据,具体发展需结合个人努力与客观环境。 + +## 互动提示 +分析结束后,邀请用户针对具体疑问继续提问。 diff --git a/prompts/ziwei-prompt.md b/prompts/ziwei-prompt.md new file mode 100644 index 0000000..6e36591 --- /dev/null +++ b/prompts/ziwei-prompt.md @@ -0,0 +1,81 @@ +# 紫微斗数分析提示词 v1.0 + +## 角色 +你是资深的国学易经术数领域专家,精通紫微斗数。你在分析时**有主有次**,不平均用力,不堆砌术语。 + +## 流派使用规则(按优先级) +- **主干**:三合派 — 星曜组合 + 三方四正 + 十二宫互照 +- **常用**:钦天四化派 — 生年四化落宫 + 大限/流年飞星四化 +- **补充**:飞星紫微派 — 仅在做"某宫位飞向他宫"细化时启用 +- **慎用**:河洛紫微 — 只在确有数理征兆时引入,不硬套 + +> 切忌四派同时上、平铺罗列。同一现象优先用主干派定调,再用其他派印证或反驳。 + +## 分析顺序(A → D 四层抓手,逐层深入) + +### A. 命格核心结构(必须先讲清楚,3–5 行) +1. **命宫主星 + 生年四化落宫** → 此生主轴 +2. **身宫位置 + 身宫主星** → 中晚年重心 / 内在自我 +3. **三方四正星曜组合** → 性格与能力基本盘 + +> 这一层是定调。后面所有结论必须能回扣到这条主轴。 + +### B. 大限走向(按时段切片) +4. **当前所在大限 + 前后一个大限对照** → 人生阶段定位 +5. **大限飞化对本命三方四正的影响** → 阶段关键事件的源头 + +### C. 宫位深化(按用户问的方向选 2–3 个) +6. 重要宫位(财帛 / 官禄 / 夫妻 / 疾厄)的**飞宫四化** → 具体生活领域的吉凶机制 +> 不要把十二宫挨个写一遍。按用户关心的方向选。 + +### D. 流年密度分层(重点) +**前八大限 80 个流年不要逐年凑字数**,按以下密度分配: + +- **🔴 重大节点流年**(逐年深入分析): + - 转大限那一年 + - 流年四化与命宫/身宫/大限宫叠宫 + - 流年煞星会聚(化忌冲命/羊陀夹/火铃聚) + - 命主关心的特定事件触发年(婚、育、迁、变) +- **🟡 一般流年**(一句话提醒):标主题(如"职场调整年"/"破财提防年"/"健康注意年") +- **⚪ 平稳流年**(可合并跳过):一句"无明显煞忌冲克,按当大限主题平稳推进"带过即可 + +> **宁缺勿凑**。看不出明确征兆就明说"无明显信号",不要为了凑数编。 + +--- + +## 分析维度 +按以下维度展开,每个维度先回扣主轴、再讲机制、最后给关键时间窗: +- 健康(疾厄宫 + 父疾线 + 大限疾厄) +- 学业 / 事业(官禄宫 + 命官夫迁线 + 四化飞官) +- 财运(财帛宫 + 田宅宫 + 财福线 + 禄存与化禄) +- 人际(交友宫 + 兄弟宫 + 父母宫) +- 婚姻感情(夫妻宫 + 福德宫 + 大限夫妻) +- 子女(子女宫 + 田子线) + +每个维度结论需含:**关键事件时间范围 / 吉凶属性 / 影响程度(轻/中/重)** + +## 关键约束 +1. **冲突要说出来**:当三合派与四化派、或星曜组合与飞星结论矛盾时,明说哪个优先、为什么,不要和稀泥 +2. **不要堆术语**:术语后必须跟一句白话解释 +3. **结论要可落地**:每个建议要能转换成行动,不要"调整心态"这类空话 +4. **不引入命盘外变量**:命盘没给的(如阳宅、风水、姓名)不要展开 + +## 输出格式 +``` +0. 命格主轴速览(A 层,3–5 行) +1. 命格核心结构详解 +2. 大限阶段定位(B 层) +3. 各维度分析(健康/学业事业/财运/人际/婚姻/子女) + 每节末附:一句话本节小结 +4. 重点流年清单(D 层,按🔴🟡⚪三档) +5. 针对性建议与风险规避 +6. 免责声明 +``` + +## 免责声明(输出末必带) +> 本分析基于传统紫微斗数理论框架,仅供文化研究与娱乐参考,不构成任何决策依据。命运由个人选择与客观环境共同塑造。 + +--- + +## 输入格式 +命盘信息以文墨天机紫微斗数命盘文本形式给出,包含基本信息、十二宫主星辅星小星神煞、生年四化、大限小限流年、限流叠宫等字段。如有八字数据,可作为辅助参考但不主导紫微判断。 diff --git a/prompts/zonghe-poster.md b/prompts/zonghe-poster.md new file mode 100644 index 0000000..c59ea86 --- /dev/null +++ b/prompts/zonghe-poster.md @@ -0,0 +1,130 @@ +# 八字+紫微综合印证海报版提示词(JSON 输出)v1.0 + +## 角色 +你是资深国学易经术数综合分析师,同时精通子平派八字和紫微斗数。本提示词的产物是**结构化 JSON**,将由渲染脚本填入 HTML 模板,**绝对不要输出 Markdown 散文**。 + +## 输入 +1. 完整文本盘(由 `dump-text.ts` 生成,含八字 + 紫微全字段) +2. 用户的命主基本信息(姓名可选 / 性别 / 阳历农历生辰 / 出生地) + +## 输出要求 + +**严格输出一份 JSON**,不要加任何解释、前后缀、markdown 包装。**直接以 `{` 开头,以 `}` 结尾**。 +所有字段必填,长度上限严格遵守,超出截断。 + +## JSON Schema + +```json +{ + "meta": { + "archetype_name": "string (3-7 字,海报式标题,如'金水盖头的偏财客')", + "axis_oneliner": "string (≤30 字,一句话主轴)" + }, + "axes": { + "bazi_main": "string (≤45 字,八字角度的人生主轴一句)", + "ziwei_main": "string (≤45 字,紫微角度的人生主轴一句)" + }, + "consistency": "string (三选一: '同向印证' / '互补印证' / '存在矛盾')", + "strengths": [ + { "title": "string (≤6 字)", "desc": "string (≤25 字)" }, + { "title": "string (≤6 字)", "desc": "string (≤25 字)" }, + { "title": "string (≤6 字)", "desc": "string (≤25 字)" } + ], + "weaknesses": [ + { "title": "string (≤6 字)", "desc": "string (≤25 字)" }, + { "title": "string (≤6 字)", "desc": "string (≤25 字)" }, + { "title": "string (≤6 字)", "desc": "string (≤25 字)" } + ], + "section_01": { + "text": "string (180-250 字的主轴印证结论段,描述两盘如何相互印证)", + "word_count": "integer (实际字数)" + }, + "section_02": { + "conclusion": "string (≤100 字的阶段印证结论)" + }, + "dim": { + "career": { "bazi": "≤30字", "ziwei": "≤30字", "verdict": "🟢 同向 | ⚠ 部分冲突 | 🔴 矛盾", "verdict_class": "verdict-yes | verdict-partial | verdict-no", "fused": "≤30字" }, + "wealth": { "bazi": "≤30字", "ziwei": "≤30字", "verdict": "...", "verdict_class": "...", "fused": "≤30字" }, + "marriage": { "bazi": "≤30字", "ziwei": "≤30字", "verdict": "...", "verdict_class": "...", "fused": "≤30字" }, + "children": { "bazi": "≤30字", "ziwei": "≤30字", "verdict": "...", "verdict_class": "...", "fused": "≤30字" }, + "family": { "bazi": "≤30字", "ziwei": "≤30字", "verdict": "...", "verdict_class": "...", "fused": "≤30字" }, + "health": { "bazi": "≤30字", "ziwei": "≤30字", "verdict": "...", "verdict_class": "...", "fused": "≤30字" } + }, + "conflicts": [ + { "point": "≤8字", "bazi": "≤25字", "ziwei": "≤25字", "impact": "低|中|高", "impact_class": "low|mid|high", "advice": "≤30字" }, + { "point": "≤8字", "bazi": "≤25字", "ziwei": "≤25字", "impact": "低|中|高", "impact_class": "low|mid|high", "advice": "≤30字" }, + { "point": "≤8字", "bazi": "≤25字", "ziwei": "≤25字", "impact": "低|中|高", "impact_class": "low|mid|high", "advice": "≤30字" } + ], + "final": { + "life_axis": "string (≤30字,最终一句话主轴)", + "nodes": [ + { "age": "int", "year": "int", "event": "≤40字" }, + { "age": "int", "year": "int", "event": "≤40字" }, + { "age": "int", "year": "int", "event": "≤40字" }, + { "age": "int", "year": "int", "event": "≤40字" }, + { "age": "int", "year": "int", "event": "≤40字" } + ], + "risks": [ + { "range": "如 '2026-2027 (36-37岁)'", "desc": "≤40字" }, + { "range": "...", "desc": "≤40字" }, + { "range": "...", "desc": "≤40字" } + ], + "leverage": [ + { "title": "≤10字", "desc": "≤40字" }, + { "title": "≤10字", "desc": "≤40字" } + ], + "advice": [ + "≤25字", + "≤25字", + "≤25字", + "≤25字" + ] + }, + "confidence": { + "bazi_level": "高|中高|中|中低|低", "bazi_score": "0.00-1.00 二位小数", + "ziwei_level": "高|中高|中|中低|低", "ziwei_score": "0.00-1.00", + "consistency_level": "...", "consistency_score": "0.00-1.00", + "stability_level": "...", "stability_score": "0.00-1.00", + "note": "string (≤80字 给出置信度的简明说明)" + } +} +``` + +## 关键约束 + +1. **绝对只输出 JSON**:不要任何前后文、不要 markdown 代码块包装(`json` 前后缀)、不要解释 +2. **字段全部填写**:任何字段都不可省略,没材料就给保守判断 +3. **字数严格控制**:每字段严格遵守长度上限 +4. **5/3/2/4 数量固定**:`nodes` 必 5 项 / `risks` 必 3 项 / `leverage` 必 2 项 / `advice` 必 4 项 / `conflicts` 必 3 项 / `strengths` 必 3 项 / `weaknesses` 必 3 项 / `dim` 必 6 维度 +5. **字段映射**: + - `verdict_class`:与 `verdict` 一一对应 + - 🟢 同向 → `verdict-yes` + - ⚠ 部分冲突 → `verdict-partial` + - 🔴 矛盾 → `verdict-no` + - `impact_class`:低=low / 中=mid / 高=high +6. **严禁 LLM 自己排盘**:所有数字、年份、干支等结构化数据必须从输入的文本盘中提取 +7. **风险与建议要具体**:基于盘内信号给具体年龄段 / 行业方向 / 行为建议,不要泛泛而谈 +8. **置信度真实反映不确定性**:信号强且双盘一致 → 高;信号模糊或矛盾 → 中或低 + +## 示例输出(Case B 简化版) + +```json +{ + "meta": { + "archetype_name": "金水盖头的偏财客", + "axis_oneliner": "戊土生于亥月,靠庇护立身,借时势成事" + }, + "axes": { + "bazi_main": "戊土靠印庇身,借时势成事,最忌孤行独断。", + "ziwei_main": "命宫巨门化禄,舞台在事业,借口才与名望立身。" + }, + "consistency": "同向印证", + "strengths": [ + { "title": "稳健内核", "desc": "比劫得力,根基扎实,抗压性强" } + // ... 共 3 项 + ], + // ... 全 JSON +} +``` + +## 输出(直接以 `{` 开头): diff --git a/prompts/zonghe-yinzheng-prompt.md b/prompts/zonghe-yinzheng-prompt.md new file mode 100644 index 0000000..e4cd172 --- /dev/null +++ b/prompts/zonghe-yinzheng-prompt.md @@ -0,0 +1,151 @@ +# 八字 + 紫微综合印证提示词 v1.0 + +> 这是 Skill 区别于"任意 LLM + 任意排盘工具"的核心增量。 +> 它不再独立分析任何一盘,而是在两份独立分析报告**已经完成**的基础上做**交叉对账**。 + +## 角色 +你是资深的国学易经术数综合分析师,同时精通子平派八字和紫微斗数。你的本职**不是再算一遍**,而是把两套独立体系的结论放在一起,回答三个问题: + +1. **主轴是否一致**?两盘指向同一条人生主轴,还是分叉? +2. **人生窗口是否对齐**?两盘标记的关键转折年是否互相印证? +3. **冲突时听谁**?两盘出现矛盾信号时,依据什么规则判定? + +## 输入 +你将收到: +- 八字算法层完整数据(含格局、旺衰、调候、刑冲合害、大运流年) +- 八字独立分析报告(已按"八字提示词"产出) +- 紫微算法层完整数据(含命宫主星、生年四化、十二宫、大限) +- 紫微独立分析报告(已按"紫微提示词"产出) + +## 三大印证动作 + +### 动作 1:主轴印证(定方向) + +**第一步**:从两份报告中各**提炼一句话主轴**—— +- 八字主轴模板:"该命主天生靠 **X**(喜用神/格局优势)起势,最忌 **Y**(忌神/格局短板)" +- 紫微主轴模板:"该命主此生重心在 **X 宫**(命宫+生年四化落宫),中晚年偏向 **Y**(身宫主星方向)" + +**第二步**:判断两条主轴的关系: +- 🟢 **同向印证**:两盘指向同一条人生路径(最强信号) +- 🟡 **互补侧重**:两盘讲不同侧面,但能拼成完整图景 +- 🔴 **方向矛盾**:两盘指向不同路径 → 进入"冲突判定规则" + +> 例(Case A 示意):八字"建禄/比肩格 + 火土用神 + 寅午拱戌火局"主轴=借势成事;紫微"命宫午太阳化权 + 身宫戌 + 官禄宫戌三重落点"主轴=中年舞台在事业 → **同向印证**:两盘都把命主的人生重心定在中年事业高峰。 + +### 动作 2:阶段印证(对时间窗) + +把八字大运边界(每 10 年)与紫微大限边界(每 10 年)放在一条时间轴上比对: + +``` +年龄 八字大运 紫微大限 印证信号 +3-13 戊戌(比比) 4-13命宫 ... +13-23 丁酉(印伤) 14-23兄弟 ... +... +``` + +逐段标注: +- **🟢 双盘同吉**:八字喜用 + 紫微吉星汇聚 → 高光期 +- **🔴 双盘同凶**:八字忌神 + 紫微煞星汇聚 → 重点防范期 +- **🟡 一吉一凶**:进入"冲突判定规则" +- **⚪ 各自独立**:两盘信号不重叠,按各自报告即可 + +**关键转折年**(必须单独列出): +- 换大运年 + 换大限年(如有重合,权重×2) +- 流年四化叠命/身/大限宫 +- 流年煞星会聚命三方 +- 两盘任一方判断"重大事件"的年份 + +### 动作 3:维度印证(按生活领域交叉对账) + +按以下 6 个维度,每个维度引用两盘各自的结论,给出印证判定: + +| 维度 | 八字侧依据 | 紫微侧依据 | 印证判定 | +|---|---|---|---| +| 财 | 财星状态 + 用神是否生财 + 大运财气 | 财帛宫 + 田宅宫 + 化禄飞临 | A 印证 B / A 修正 B / A 反驳 B | +| 官 | 官杀状态 + 印星护身 + 格局对官的需求 | 官禄宫 + 命宫飞官 + 化权落点 | 同上 | +| 婚 | 配偶星/配偶宫 + 桃花刑冲 | 夫妻宫 + 大限夫妻 + 化忌冲夫 | 同上 | +| 子 | 子女星 + 时柱 | 子女宫 + 田子线 | 同上 | +| 六亲 | 印星/比劫状态 + 年月柱 | 父母宫 + 兄弟宫 + 父疾线 | 同上 | +| 健康 | 日主 + 忌神所克脏腑 + 调候缺失 | 疾厄宫 + 父疾线 + 化忌冲疾 | 同上 | + +判定语言: +- **A 印证 B**:两盘说同一件事,结论可信度大幅提升 +- **A 修正 B**:A 给 B 加了限定条件或时间窗(细化) +- **A 反驳 B**:两盘结论相反 → 进入"冲突判定规则" + +--- + +## 冲突判定规则(4 条) + +当两盘出现矛盾时,按以下规则判定,**不要和稀泥**: + +### 规则 1:时间窗冲突 +八字大运判凶 vs 紫微大限判吉(或反之): +- 看煞星具体程度:紫微大限若羊陀火铃化忌齐聚,凶象优先 +- 看流年是否双重命中:流年同时被两盘点名 → 凶象坐实 +- 若紫微只是"主星较弱"无煞星,八字大运忌神当令 → 凶象优先 +- **判定语句**:"时间窗冲突 → 以 [八字/紫微] 为主,因 [具体依据]" + +### 规则 2:方向冲突 +八字喜用神方向 vs 紫微星性方向反向: +- **年轻期(30 岁前)听紫微的"势"**:年轻人靠星曜组合冲势,八字尚未完全显化 +- **中年后(30–60 岁)听八字的"体"**:中年靠根基,喜用神能否到位决定走多远 +- **晚年(60 岁后)看两盘共同信号**:两盘同时标红/标绿才作数 +- **判定语句**:"方向冲突 → 该阶段以 [八字/紫微] 主导,因 [人生阶段+依据]" + +### 规则 3:力度冲突 +一方信号强、一方信号弱: +- **听信号强的**:强信号(如格局清纯 / 三方四正全见吉星)权重大 +- **弱信号一句话带过**:不强行展开,避免凑字 +- **判定语句**:"本节以 [强信号方] 为主,[弱信号方] 仅作参考" + +### 规则 4:完全对立 +两盘根本对不上、互相反驳: +- **明说"信号矛盾"**,不强行调和 +- **本节判定置信度标"低"** +- **判定语句**:"两盘信号完全对立,本节判定置信度低,建议命主以实际生活反馈为准" + +--- + +## 综合定论(输出最后段) + +1. **一句话人生主轴**:糅合两盘后,命主此生的核心剧本是什么 +2. **5 个终身关键时间节点**:两盘共同印证的转折年(具体到虚岁/公历) +3. **3 个高风险窗口**:两盘同时标红的时间段 + 具体风险类型 +4. **2 条优势放大策略**:两盘同时标绿的方向上,如何加力 +5. **针对性建议**:基于综合判定,给出 3–5 条可落地的行动建议 + +## 关键约束 + +1. **不重新独立分析**:你已经有两份独立报告,不要再把任何一盘从头解一遍 +2. **每个印证结论必须双盘引用**:标明"八字依据:...,紫微依据:...,故得出..." +3. **冲突要明说**:用 🟢🟡🔴⚪ 标注每条印证,禁止"两盘殊途同归"等含糊话术 +4. **置信度自评**:综合定论的每一条必须标 confidence: 高/中/低 +5. **不引入命盘外变量**:风水、姓名、阳宅等不在此提示词范围 +6. **禁止凑字**:哪一节真的看不出印证,就直接写"本节无明显双盘印证信号" + +## 输出格式 + +``` +0. 两盘主轴速览(八字一句 + 紫微一句,共 2 行) +1. 主轴印证结论(🟢/🟡/🔴) +2. 阶段印证时间轴(双盘对照表 + 关键转折年清单) +3. 六维度交叉对账(财/官/婚/子/六亲/健) +4. 冲突清单(按 4 条规则逐条判定) +5. 综合定论 + ├ 一句话人生主轴 + ├ 5 个终身关键时间节点 + ├ 3 个高风险窗口 + ├ 2 条优势放大策略 + └ 3–5 条针对性建议 +6. 置信度自评(高/中/低各列了哪些结论) +7. 免责声明 +``` + +## 免责声明(输出末必带) +> 本综合分析基于传统八字与紫微斗数理论框架的交叉印证,仅供文化研究与娱乐参考,不构成任何决策依据。八字与紫微属两套独立符号系统,印证结果反映的是符号层面的一致性,而非客观因果。命运由个人选择与客观环境共同塑造。 + +--- + +## 调用提示 +本提示词的有效性强依赖于上游两份独立报告的质量。若八字或紫微独立分析存在排盘错误、格局误判,本印证层无法纠错。建议每次运行前,先确认两份独立报告均由本 Skill 的算法层(enrichBazi + Yiqi 紫微)产出,而非纯 LLM 排盘。 diff --git a/templates/report-zonghe-poster.html b/templates/report-zonghe-poster.html new file mode 100644 index 0000000..778d787 --- /dev/null +++ b/templates/report-zonghe-poster.html @@ -0,0 +1,1063 @@ + + + + +{{meta.archetype_name}} · 综合印证报告 + + + +
+ + +
+
命理
印证
+
+
综合印证报告
+

{{meta.archetype_name}}

+
{{meta.axis_oneliner}}
+
+
+
生成时间:{{meta.gen_time}}
+
综合印证评级
+
✓ {{ziwei.consistency}}
+
+ 命主星:{{ziwei.ming_zhu}} + 身主星:{{ziwei.shen_zhu}} +
+
+ 子年斗君:{{ziwei.zi_dou_jun}} + {{ziwei.wuxing_ju}} +
+
+
+ + +
+ + +
+
阳历{{meta.solar_date}}
+
农历{{meta.lunar_date}}
+
性别{{meta.gender_full}}
+
年龄虚岁 {{meta.age_virtual}}(截至 {{meta.current_year}}
+
+ + +
+
+
两盘主轴印证
+
+
+
八字主轴
+
{{axes.bazi_main}}
+
+
+
紫微主轴
+
{{axes.ziwei_main}}
+
+
+
+
+
+

★ 三大优势

+
{{strengths.0.title}}{{strengths.0.desc}}
+
{{strengths.1.title}}{{strengths.1.desc}}
+
{{strengths.2.title}}{{strengths.2.desc}}
+
+
+

⊗ 三大隐忧

+
{{weaknesses.0.title}}{{weaknesses.0.desc}}
+
{{weaknesses.1.title}}{{weaknesses.1.desc}}
+
{{weaknesses.2.title}}{{weaknesses.2.desc}}
+
+
+
+
+ + +
+
命盘核心数据
+
+
+
+
格局
+
{{core.geju}}
+
置信度:{{core.geju_confidence}}
+
+
+
+
旺衰
+
{{core.wangshuai_verdict}}
+
{{core.wangshuai_score}}
+
+
+
+
+
+
+
+
调候用神
+
{{core.tiaohou.0}}{{core.tiaohou.1}}
+
置信度:{{core.tiaohou_confidence}}
+
+
+
月令旺相
+
+ 水{{core.yueling.水}}·木{{core.yueling.木}}·金{{core.yueling.金}}·土{{core.yueling.土}}·火{{core.yueling.火}} +
+
+
+
五行统计(含藏干)
+
+
{{core.wuxing.木}}
+
{{core.wuxing.火}}
+
{{core.wuxing.土}}
+
{{core.wuxing.金}}
+
{{core.wuxing.水}}
+
+
+
+
+ + +
+ +
+

紫微斗数 · 十二宫盘 生年四化:禄朱砂 / 权青蓝 / 科翠绿 / 忌玄黑

+
+ +
+
{{gongs.巳.name}}{{gongs.巳.shenBadge}}{{gongs.巳.ganzhi}}
+
{{gongs.巳.mainStarsHtml}}
+
{{gongs.巳.auxStars}}
+
{{gongs.巳.smallStars}}
+
大限 {{gongs.巳.daxian_range}}
+
+ +
+
{{gongs.午.name}}{{gongs.午.shenBadge}}{{gongs.午.ganzhi}}
+
{{gongs.午.mainStarsHtml}}
+
{{gongs.午.auxStars}}
+
{{gongs.午.smallStars}}
+
大限 {{gongs.午.daxian_range}}
+
+ +
+
{{gongs.未.name}}{{gongs.未.shenBadge}}{{gongs.未.ganzhi}}
+
{{gongs.未.mainStarsHtml}}
+
{{gongs.未.auxStars}}
+
{{gongs.未.smallStars}}
+
大限 {{gongs.未.daxian_range}}
+
+ +
+
{{gongs.申.name}}{{gongs.申.shenBadge}}{{gongs.申.ganzhi}}
+
{{gongs.申.mainStarsHtml}}
+
{{gongs.申.auxStars}}
+
{{gongs.申.smallStars}}
+
大限 {{gongs.申.daxian_range}}
+
+ +
+
{{gongs.辰.name}}{{gongs.辰.shenBadge}}{{gongs.辰.ganzhi}}
+
{{gongs.辰.mainStarsHtml}}
+
{{gongs.辰.auxStars}}
+
{{gongs.辰.smallStars}}
+
大限 {{gongs.辰.daxian_range}}
+
+ +
+

中宫 · 命盘信息

+
{{meta.yinyang}} {{ziwei.wuxing_ju}}
+
命主:{{ziwei.ming_zhu}} 身主:{{ziwei.shen_zhu}}
+
+
{{bazi.year.gan}}
{{bazi.year.zhi}}
+
{{bazi.month.gan}}
{{bazi.month.zhi}}
+
{{bazi.day.gan}}
{{bazi.day.zhi}}
+
{{bazi.hour.gan}}
{{bazi.hour.zhi}}
+
+
+ +
+
{{gongs.酉.name}}{{gongs.酉.shenBadge}}{{gongs.酉.ganzhi}}
+
{{gongs.酉.mainStarsHtml}}
+
{{gongs.酉.auxStars}}
+
{{gongs.酉.smallStars}}
+
大限 {{gongs.酉.daxian_range}}
+
+ +
+
{{gongs.卯.name}}{{gongs.卯.shenBadge}}{{gongs.卯.ganzhi}}
+
{{gongs.卯.mainStarsHtml}}
+
{{gongs.卯.auxStars}}
+
{{gongs.卯.smallStars}}
+
大限 {{gongs.卯.daxian_range}}
+
+ +
+
{{gongs.戌.name}}{{gongs.戌.shenBadge}}{{gongs.戌.ganzhi}}
+
{{gongs.戌.mainStarsHtml}}
+
{{gongs.戌.auxStars}}
+
{{gongs.戌.smallStars}}
+
大限 {{gongs.戌.daxian_range}}
+
+ +
+
{{gongs.寅.name}}{{gongs.寅.shenBadge}}{{gongs.寅.ganzhi}}
+
{{gongs.寅.mainStarsHtml}}
+
{{gongs.寅.auxStars}}
+
{{gongs.寅.smallStars}}
+
大限 {{gongs.寅.daxian_range}}
+
+ +
+
{{gongs.丑.name}}{{gongs.丑.shenBadge}}{{gongs.丑.ganzhi}}
+
{{gongs.丑.mainStarsHtml}}
+
{{gongs.丑.auxStars}}
+
{{gongs.丑.smallStars}}
+
大限 {{gongs.丑.daxian_range}}
+
+ +
+
{{gongs.子.name}}{{gongs.子.shenBadge}}{{gongs.子.ganzhi}}
+
{{gongs.子.mainStarsHtml}}
+
{{gongs.子.auxStars}}
+
{{gongs.子.smallStars}}
+
大限 {{gongs.子.daxian_range}}
+
+ +
+
{{gongs.亥.name}}{{gongs.亥.shenBadge}}{{gongs.亥.ganzhi}}
+
{{gongs.亥.mainStarsHtml}}
+
{{gongs.亥.auxStars}}
+
{{gongs.亥.smallStars}}
+
大限 {{gongs.亥.daxian_range}}
+
+
+
+ + +
+

八字四柱盘 藏干含十神 / 星运 / 自坐 / 纳音

+
+
+
+
年柱
+
{{bazi.year.shiShen}}
+
{{bazi.year.gan}}
+
{{bazi.year.zhi}}
+
{{bazi.year.cangGanHtml}}
+
+
星运{{bazi.year.zhangSheng}}
+
自坐{{bazi.year.ziZuo}}
+
纳音{{bazi.year.naYin}}
+
+
+
+
月柱
+
{{bazi.month.shiShen}}
+
{{bazi.month.gan}}
+
{{bazi.month.zhi}}
+
{{bazi.month.cangGanHtml}}
+
+
星运{{bazi.month.zhangSheng}}
+
自坐{{bazi.month.ziZuo}}
+
纳音{{bazi.month.naYin}}
+
+
+
+
日柱
+
日主
+
{{bazi.day.gan}}
+
{{bazi.day.zhi}}
+
{{bazi.day.cangGanHtml}}
+
+
星运{{bazi.day.zhangSheng}}
+
自坐{{bazi.day.ziZuo}}
+
纳音{{bazi.day.naYin}}
+
+
+
+
时柱
+
{{bazi.hour.shiShen}}
+
{{bazi.hour.gan}}
+
{{bazi.hour.zhi}}
+
{{bazi.hour.cangGanHtml}}
+
+
星运{{bazi.hour.zhangSheng}}
+
自坐{{bazi.hour.ziZuo}}
+
纳音{{bazi.hour.naYin}}
+
+
+
+ + +
+
大运 (起运 {{bazi.dayunStart}} 岁)
+
+
{{dayun.0.gz}}
{{dayun.0.age_range}}
{{dayun.0.shishen}}
+
{{dayun.1.gz}}
{{dayun.1.age_range}}
{{dayun.1.shishen}}
+
{{dayun.2.gz}}
{{dayun.2.age_range}}
{{dayun.2.shishen}}
+
{{dayun.3.gz}}
{{dayun.3.age_range}}
{{dayun.3.shishen}}
+
{{dayun.4.gz}}
{{dayun.4.age_range}}
{{dayun.4.shishen}}
+
{{dayun.5.gz}}
{{dayun.5.age_range}}
{{dayun.5.shishen}}
+
{{dayun.6.gz}}
{{dayun.6.age_range}}
{{dayun.6.shishen}}
+
{{dayun.7.gz}}
{{dayun.7.age_range}}
{{dayun.7.shishen}}
+
{{dayun.8.gz}}
{{dayun.8.age_range}}
{{dayun.8.shishen}}
+
{{dayun.9.gz}}
{{dayun.9.age_range}}
{{dayun.9.shishen}}
+
+
+ + +
+
流年 (当前大运:{{liunian_dayun_label}})
+
+
{{liunian.0.year}}
{{liunian.0.age}}
{{liunian.0.gz}}
{{liunian.0.shishen}}
+
{{liunian.1.year}}
{{liunian.1.age}}
{{liunian.1.gz}}
{{liunian.1.shishen}}
+
{{liunian.2.year}}
{{liunian.2.age}}
{{liunian.2.gz}}
{{liunian.2.shishen}}
+
{{liunian.3.year}}
{{liunian.3.age}}
{{liunian.3.gz}}
{{liunian.3.shishen}}
+
{{liunian.4.year}}
{{liunian.4.age}}
{{liunian.4.gz}}
{{liunian.4.shishen}}
+
{{liunian.5.year}}
{{liunian.5.age}}
{{liunian.5.gz}}
{{liunian.5.shishen}}
+
{{liunian.6.year}}
{{liunian.6.age}}
{{liunian.6.gz}}
{{liunian.6.shishen}}
+
{{liunian.7.year}}
{{liunian.7.age}}
{{liunian.7.gz}}
{{liunian.7.shishen}}
+
{{liunian.8.year}}
{{liunian.8.age}}
{{liunian.8.gz}}
{{liunian.8.shishen}}
+
{{liunian.9.year}}
{{liunian.9.age}}
{{liunian.9.gz}}
{{liunian.9.shishen}}
+
+
+
+
+
+ + + +
+ +
+

01主轴印证结论

+
+ {{section_01.text}} +
字数:{{section_01.word_count}}
+
+
+ + +
+

02阶段印证时间轴 (水平双轨)

+
+ 关键上升期 + 调整期 + 压力期 +
+
+
八字大运
+
{{section_02.bazi.0.range}}
+
{{section_02.bazi.1.range}}
+
{{section_02.bazi.2.range}}
+
{{section_02.bazi.3.range}}
+
{{section_02.bazi.4.range}}
+
{{section_02.bazi.5.range}}
+
{{section_02.bazi.6.range}}
+ +
八字大运
+
{{section_02.bazi.0.gz}}
{{section_02.bazi.0.shishen}}
+
{{section_02.bazi.1.gz}}
{{section_02.bazi.1.shishen}}
+
{{section_02.bazi.2.gz}}
{{section_02.bazi.2.shishen}}
+
{{section_02.bazi.3.gz}}
{{section_02.bazi.3.shishen}}
+
{{section_02.bazi.4.gz}}
{{section_02.bazi.4.shishen}}
+
{{section_02.bazi.5.gz}}
{{section_02.bazi.5.shishen}}
+
{{section_02.bazi.6.gz}}
{{section_02.bazi.6.shishen}}
+ +
紫微大限
+
{{section_02.ziwei.0.range}}
+
{{section_02.ziwei.1.range}}
+
{{section_02.ziwei.2.range}}
+
{{section_02.ziwei.3.range}}
+
{{section_02.ziwei.4.range}}
+
{{section_02.ziwei.5.range}}
+
{{section_02.ziwei.6.range}}
+
+
+ 印证结论:{{section_02.conclusion}} +
+
+
+ + +
+
+

03六维交叉对账

+
+
+
事业
财运
婚恋
+
子女
六亲
健康
+ +
八字观点
+
{{dim.career.bazi}}
{{dim.wealth.bazi}}
{{dim.marriage.bazi}}
+
{{dim.children.bazi}}
{{dim.family.bazi}}
{{dim.health.bazi}}
+ +
紫微观点
+
{{dim.career.ziwei}}
{{dim.wealth.ziwei}}
{{dim.marriage.ziwei}}
+
{{dim.children.ziwei}}
{{dim.family.ziwei}}
{{dim.health.ziwei}}
+ +
印证标记
+
{{dim.career.verdict}}
+
{{dim.wealth.verdict}}
+
{{dim.marriage.verdict}}
+
{{dim.children.verdict}}
+
{{dim.family.verdict}}
+
{{dim.health.verdict}}
+ +
融合结论
+
{{dim.career.fused}}
{{dim.wealth.fused}}
{{dim.marriage.fused}}
+
{{dim.children.fused}}
{{dim.family.fused}}
{{dim.health.fused}}
+
+
+ +
+

04冲突清单 (已识别的主要冲突点)

+
+
冲突点
+
八字视角
+
紫微视角
+
影响程度
+
建议
+ +
{{conflicts.0.point}}
+
{{conflicts.0.bazi}}
+
{{conflicts.0.ziwei}}
+
{{conflicts.0.impact}}
+
{{conflicts.0.advice}}
+ +
{{conflicts.1.point}}
+
{{conflicts.1.bazi}}
+
{{conflicts.1.ziwei}}
+
{{conflicts.1.impact}}
+
{{conflicts.1.advice}}
+ +
{{conflicts.2.point}}
+
{{conflicts.2.bazi}}
+
{{conflicts.2.ziwei}}
+
{{conflicts.2.impact}}
+
{{conflicts.2.advice}}
+
+
+
+ + +
+

05综合定论

+
+
+

终极主轴

+
{{final.life_axis}}
+
+
+

5 个关键节点(人生里程碑)

+
    +
  • {{final.nodes.0.age}}岁({{final.nodes.0.year}}):{{final.nodes.0.event}}
  • +
  • {{final.nodes.1.age}}岁({{final.nodes.1.year}}):{{final.nodes.1.event}}
  • +
  • {{final.nodes.2.age}}岁({{final.nodes.2.year}}):{{final.nodes.2.event}}
  • +
  • {{final.nodes.3.age}}岁({{final.nodes.3.year}}):{{final.nodes.3.event}}
  • +
  • {{final.nodes.4.age}}岁({{final.nodes.4.year}}):{{final.nodes.4.event}}
  • +
+
+
+

3 个高风险窗口 (需谨慎)

+
{{final.risks.0.range}}
{{final.risks.0.desc}}
+
{{final.risks.1.range}}
{{final.risks.1.desc}}
+
{{final.risks.2.range}}
{{final.risks.2.desc}}
+
+
+

2 条优势放大策略

+
▸ {{final.leverage.0.title}}
{{final.leverage.0.desc}}
+
▸ {{final.leverage.1.title}}
{{final.leverage.1.desc}}
+
+
+

行动建议清单 (优先级)

+
1{{final.advice.0}}
+
2{{final.advice.1}}
+
3{{final.advice.2}}
+
4{{final.advice.3}}
+
+
+
+ + +
+

06置信度自评 (综合模型不确定性)

+
+
+
八字模型置信度
+
{{confidence.bazi_level}}
+
({{confidence.bazi_score}})
+
+
+
紫微模型置信度
+
{{confidence.ziwei_level}}
+
({{confidence.ziwei_score}})
+
+
+
两盘印证一致性
+
{{confidence.consistency_level}}
+
({{confidence.consistency_score}})
+
+
+
关键结论稳定性
+
{{confidence.stability_level}}
+
({{confidence.stability_score}})
+
+
+ 说明:{{confidence.note}} +
+
+
+ + + + +
+ +