chore: import upstream snapshot with attribution
This commit is contained in:
+28
@@ -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
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
================================================================================
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
<div align="center">
|
||||
|
||||
# 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)
|
||||
[](#-installation)
|
||||
|
||||
[简体中文](./README.md) | English
|
||||
|
||||
<br>
|
||||
|
||||
<img src="./docs/jietu.png" alt="Cross-validation poster sample" width="680">
|
||||
|
||||
<sub>Cross-validation poster sample (synthetic subject, for display only)</sub>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
@@ -0,0 +1,184 @@
|
||||
<div align="center">
|
||||
|
||||
# bazi-ziwei-skill
|
||||
|
||||
**AI 八字 + 紫微斗数排盘与综合印证 Skill**
|
||||
|
||||
精准排盘(不靠 LLM 猜)· 三种分析模式 · 一键生成水墨风 HTML 命盘海报
|
||||
|
||||
[](./LICENSE)
|
||||
[](#-安装)
|
||||
|
||||
简体中文 | [English](./README.en.md)
|
||||
|
||||
<br>
|
||||
|
||||
<img src="./docs/jietu.png" alt="综合印证海报示例" width="680">
|
||||
|
||||
<sub>综合印证海报示例(合成命主,仅供展示)</sub>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 这是什么
|
||||
|
||||
一个遵循 [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
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`dzcmemory-web/bazi-ziwei-skill`
|
||||
- 原始仓库:https://github.com/dzcmemory-web/bazi-ziwei-skill
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
@@ -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` | `<name>-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=<user-name>-<date>.html \
|
||||
--currentYear=<YYYY>
|
||||
```
|
||||
5. 把生成的 HTML 文件路径告诉用户,让其用浏览器打开
|
||||
|
||||
> **重要**:海报版的视觉由 HTML 模板决定,**LLM 只产数据不产 HTML**。如果 LLM 输出含 markdown 包装(如 \`\`\`json … \`\`\`),渲染前需剥掉。
|
||||
|
||||
---
|
||||
|
||||
## 安装后行为(重要)
|
||||
|
||||
**装好 Skill 后不要主动跑任何验证 / 自检命令。** 不要试 Smoke Test、不要排示例盘、不要分析示例命主。装好就是装好,等用户来给生辰再开始工作。
|
||||
|
||||
> 自检命令在 `TEST-GUIDE.md` 中由人工按需运行,不在 Agent 的职责范围内。Agent 主动跑会浪费 token + 触发上下文压缩。
|
||||
|
||||
依赖检查(仅在用户首次提供生辰、Agent 准备跑 run-chart.ts 报错时):
|
||||
```bash
|
||||
cd <skill-root>/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 |
|
||||
|
||||
---
|
||||
|
||||
## 免责声明(每次输出末必带)
|
||||
> 本分析基于传统八字与紫微斗数理论框架,仅供文化研究与娱乐参考,不构成医疗、投资、婚姻、法律等任何决策依据。命运由个人选择与客观环境共同塑造。
|
||||
+179
@@ -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 把结果记下来,方便对比。
|
||||
@@ -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<Pillar, string>; // 每柱 干 vs 自身支 的长生
|
||||
五行旺相: Record<string, '旺'|'相'|'休'|'囚'|'死'>;
|
||||
五行统计: ReturnType<typeof countWuXing>;
|
||||
调候用神: string[];
|
||||
格局: ReturnType<typeof judgeGeJu>;
|
||||
旺衰: ReturnType<typeof judgeWangShuai>;
|
||||
天干关系: ReturnType<typeof detectGanRelations>;
|
||||
地支关系: ReturnType<typeof detectZhiRelations>;
|
||||
整柱: ReturnType<typeof judgePillars>;
|
||||
};
|
||||
|
||||
export function enrichBazi(siZhu: Record<Pillar, GanZhi>): BaziEnrichment {
|
||||
const dm = siZhu.日.gan;
|
||||
const monthZhi = siZhu.月.zhi;
|
||||
|
||||
// 自坐 — 每柱干在自身支的长生位
|
||||
const ziZuo: Record<Pillar, string> = {} 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)
|
||||
};
|
||||
}
|
||||
@@ -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<Pillar, Tiangan>): 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<Pillar, {gan: Tiangan, zhi: Dizhi}>): 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};
|
||||
});
|
||||
}
|
||||
@@ -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<ShiShen, string> = {
|
||||
比肩: '比肩格',
|
||||
劫财: '劫财格',
|
||||
食神: '食神格',
|
||||
伤官: '伤官格',
|
||||
偏财: '偏财格',
|
||||
正财: '正财格',
|
||||
七杀: '七杀格',
|
||||
正官: '正官格',
|
||||
偏印: '偏印格',
|
||||
正印: '正印格'
|
||||
};
|
||||
|
||||
export function judgeGeJu(siZhu: Record<Pillar, {gan: Tiangan, zhi: Dizhi}>): 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};
|
||||
}
|
||||
@@ -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<Tiangan, WuXing> = {
|
||||
甲:'木',乙:'木',丙:'火',丁:'火',戊:'土',己:'土',庚:'金',辛:'金',壬:'水',癸:'水'
|
||||
};
|
||||
|
||||
export const GAN_YINYANG: Record<Tiangan, YinYang> = {
|
||||
甲:'阳',乙:'阴',丙:'阳',丁:'阴',戊:'阳',己:'阴',庚:'阳',辛:'阴',壬:'阳',癸:'阴'
|
||||
};
|
||||
|
||||
export const ZHI_WUXING: Record<Dizhi, WuXing> = {
|
||||
子:'水',丑:'土',寅:'木',卯:'木',辰:'土',巳:'火',午:'火',未:'土',申:'金',酉:'金',戌:'土',亥:'水'
|
||||
};
|
||||
|
||||
// 地支藏干 — 本气、中气、余气
|
||||
// [{gan, role}] 顺序:本气、中气、余气
|
||||
export const ZHI_CANG_GAN: Record<Dizhi, Array<{gan: Tiangan, 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:'本气'},{gan:'甲',role:'中气'}]
|
||||
};
|
||||
|
||||
// 五行生克
|
||||
export function shengKe(a: WuXing, b: WuXing): '生'|'克'|'同'|'被生'|'被克' {
|
||||
if (a === b) return '同';
|
||||
const sheng: Record<WuXing, WuXing> = {木:'火',火:'土',土:'金',金:'水',水:'木'};
|
||||
const ke: Record<WuXing, WuXing> = {木:'土',火:'金',土:'水',金:'木',水:'火'};
|
||||
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<Tiangan, Dizhi> = {
|
||||
甲:'亥',丙:'寅',戊:'寅',庚:'巳',壬:'申',
|
||||
乙:'午',丁:'酉',己:'酉',辛:'子',癸:'卯'
|
||||
};
|
||||
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<Tiangan, Record<Dizhi, string[]>> = {
|
||||
// 甲木
|
||||
甲: {
|
||||
子:['丁','庚','丙'], 丑:['丁','庚','丙'], 寅:['丙','癸'],
|
||||
卯:['庚','丙','丁','戊','己'], 辰:['庚','丁','壬'], 巳:['癸','丁','庚'],
|
||||
午:['癸','丁','庚'], 未:['癸','丁','庚'], 申:['庚','丁','壬'],
|
||||
酉:['庚','丙','丁'], 戌:['庚','甲','壬','癸'], 亥:['庚','丁','戊','丙']
|
||||
},
|
||||
// 乙木
|
||||
乙: {
|
||||
子:['丙','戊'], 丑:['丙'], 寅:['丙','癸'],
|
||||
卯:['丙','癸'], 辰:['癸','丙','戊'], 巳:['癸'],
|
||||
午:['癸','丙'], 未:['癸','丙'], 申:['丙','癸','己'],
|
||||
酉:['癸','丁','丙'], 戌:['癸','辛'], 亥:['丙','戊']
|
||||
},
|
||||
// 丙火
|
||||
丙: {
|
||||
子:['壬','戊','己'], 丑:['壬','甲'], 寅:['壬','庚'],
|
||||
卯:['壬','己'], 辰:['壬','甲'], 巳:['壬','庚','癸'],
|
||||
午:['壬','庚'], 未:['壬','庚'], 申:['壬','戊'],
|
||||
酉:['壬','癸'], 戌:['甲','壬'], 亥:['甲','戊','庚','壬']
|
||||
},
|
||||
// 丁火
|
||||
丁: {
|
||||
子:['甲','庚'], 丑:['甲','庚'], 寅:['庚','壬'],
|
||||
卯:['庚','甲'], 辰:['甲','庚'], 巳:['甲','庚'],
|
||||
午:['壬','庚','癸'], 未:['甲','壬','庚'], 申:['甲','庚','丙','戊'],
|
||||
酉:['甲','庚','丙','戊'], 戌:['甲','庚','戊'], 亥:['甲','庚']
|
||||
},
|
||||
// 戊土
|
||||
戊: {
|
||||
子:['丙','甲'], 丑:['丙','甲'], 寅:['丙','甲','癸'],
|
||||
卯:['丙','甲','癸'], 辰:['甲','丙','癸'], 巳:['甲','丙','癸'],
|
||||
午:['壬','甲','丙'], 未:['癸','丙','甲'], 申:['丙','癸','甲'],
|
||||
酉:['丙','癸'], 戌:['甲','丙','癸'], 亥:['甲','丙']
|
||||
},
|
||||
// 己土
|
||||
己: {
|
||||
子:['丙','甲','戊'], 丑:['丙','甲','戊'], 寅:['丙','庚','甲'],
|
||||
卯:['甲','癸','丙'], 辰:['丙','癸','甲'], 巳:['癸','丙'],
|
||||
午:['癸','丙'], 未:['癸','丙'], 申:['丙','癸'],
|
||||
酉:['丙','癸'], 戌:['甲','丙','癸'], 亥:['丙','甲','戊']
|
||||
},
|
||||
// 庚金
|
||||
庚: {
|
||||
子:['丁','甲','丙'], 丑:['丙','丁','甲'], 寅:['丙','甲','壬'],
|
||||
卯:['丁','甲','庚','丙'], 辰:['甲','丁','壬','癸'], 巳:['壬','戊','丙','丁'],
|
||||
午:['壬','癸'], 未:['丁','甲'], 申:['丁','甲'],
|
||||
酉:['丁','甲','丙'], 戌:['甲','壬'], 亥:['丁','丙']
|
||||
},
|
||||
// 辛金
|
||||
辛: {
|
||||
子:['丙','戊','壬','甲'], 丑:['丙','壬','戊','己'], 寅:['己','壬','庚'],
|
||||
卯:['壬','甲'], 辰:['壬','甲'], 巳:['壬','甲','癸'],
|
||||
午:['壬','己','癸'], 未:['壬','庚','甲'], 申:['壬','甲','戊'],
|
||||
酉:['壬','甲'], 戌:['壬','甲'], 亥:['壬','丙']
|
||||
},
|
||||
// 壬水
|
||||
壬: {
|
||||
子:['戊','丙'], 丑:['丙','丁','甲'], 寅:['庚','丙','戊'],
|
||||
卯:['戊','辛','庚'], 辰:['甲','庚'], 巳:['壬','辛','庚','癸'],
|
||||
午:['癸','庚','辛'], 未:['辛','甲'], 申:['戊','丁'],
|
||||
酉:['甲','庚'], 戌:['甲','丙'], 亥:['戊','丙','庚']
|
||||
},
|
||||
// 癸水
|
||||
癸: {
|
||||
子:['丙','辛'], 丑:['丙','丁'], 寅:['辛','丙'],
|
||||
卯:['庚','辛'], 辰:['丙','辛','甲'], 巳:['辛'],
|
||||
午:['庚','辛','壬','癸'], 未:['庚','辛','壬','癸'], 申:['丁'],
|
||||
酉:['辛','丙'], 戌:['辛','甲','壬','癸'], 亥:['庚','辛','戊','丁']
|
||||
}
|
||||
};
|
||||
|
||||
// 月令旺相休囚死 — 五行随月令的状态
|
||||
export function getWuXingMonthStatus(monthZhi: Dizhi): Record<WuXing, '旺'|'相'|'休'|'囚'|'死'> {
|
||||
const monthWx = ZHI_WUXING[monthZhi];
|
||||
// 当令=旺; 当令所生=相; 生当令者=休; 克当令者=囚; 当令所克=死
|
||||
const result: Record<WuXing, '旺'|'相'|'休'|'囚'|'死'> = {} 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;
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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<Pillar, {gan: Tiangan, zhi: Dizhi}>): {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<Pillar, {gan: Tiangan, zhi: Dizhi}>): {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<Pillar, {gan: Tiangan, zhi: Dizhi}>): 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]
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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<WuXing, number>;
|
||||
// 算藏干 (本气1分,中气0.5,余气0.3)
|
||||
withCangGan: Record<WuXing, number>;
|
||||
// 哪些五行缺失 (surface 计法)
|
||||
missing: WuXing[];
|
||||
// 最旺五行 (surface 计法)
|
||||
strongest: WuXing[];
|
||||
// 转十神: 日干视角下五行对应的十神类别
|
||||
shiShenGroups: Record<WuXing, {十神类: string, 实例数: number}>;
|
||||
};
|
||||
|
||||
export function countWuXing(
|
||||
siZhu: Record<Pillar, {gan: Tiangan, zhi: Dizhi}>,
|
||||
dayMaster: Tiangan
|
||||
): WuXingCount {
|
||||
const allWx: WuXing[] = ['木','火','土','金','水'];
|
||||
const surface: Record<WuXing, number> = {木:0,火:0,土:0,金:0,水:0};
|
||||
const withCangGan: Record<WuXing, number> = {木: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<WuXing, {十神类: string, 实例数: number}> = {} as any;
|
||||
const repGanByWx: Record<WuXing, Tiangan> = {木:'甲',火:'丙',土:'戊',金:'庚',水:'壬'};
|
||||
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);
|
||||
}
|
||||
@@ -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<Tiangan, Tiangan> = {
|
||||
甲:'己',己:'甲', 乙:'庚',庚:'乙', 丙:'辛',辛:'丙', 丁:'壬',壬:'丁', 戊:'癸',癸:'戊'
|
||||
};
|
||||
|
||||
type Pillar = '年'|'月'|'日'|'时';
|
||||
|
||||
export function detectZhiRelations(zhis: Record<Pillar, Dizhi>): 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;
|
||||
}
|
||||
+35
@@ -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)
|
||||
};
|
||||
}
|
||||
+52
@@ -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 };
|
||||
});
|
||||
}
|
||||
Vendored
+99
@@ -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 };
|
||||
}
|
||||
+184
@@ -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;
|
||||
}
|
||||
+8
@@ -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];
|
||||
}
|
||||
+163
@@ -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]
|
||||
}
|
||||
};
|
||||
}
|
||||
+48
@@ -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);
|
||||
}
|
||||
+155
@@ -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;
|
||||
}
|
||||
Vendored
+256
@@ -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();
|
||||
Vendored
+351
@@ -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 `<span class="sihua-${huaChar}">${s}<span class="sihua-tag">${huaChar}</span></span>`;
|
||||
}
|
||||
return s;
|
||||
}).join('·')
|
||||
: '<span style="color:var(--ink-faint)">无主星</span>';
|
||||
// 辅星同样要处理四化(右弼化科 / 文昌化忌 / 文曲化科 等常落辅星)
|
||||
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 `<span class="sihua-${huaChar}">${s}<span class="sihua-tag">${huaChar}</span></span>`;
|
||||
}
|
||||
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) ? '<span class="shen-badge">身</span>' : '';
|
||||
}
|
||||
// ============ 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();
|
||||
Vendored
+103
@@ -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();
|
||||
Vendored
+319
@@ -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;
|
||||
}
|
||||
Vendored
+99
@@ -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);
|
||||
}
|
||||
Vendored
+145
@@ -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];
|
||||
}
|
||||
Vendored
+142
@@ -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
|
||||
};
|
||||
Vendored
+165
@@ -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;
|
||||
}
|
||||
Vendored
+137
@@ -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 '比和'; // 默认值
|
||||
}
|
||||
Vendored
+115
@@ -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 '';
|
||||
}
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
// 核心数据类型定义
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
+117
@@ -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];
|
||||
}
|
||||
+633
@@ -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;
|
||||
}
|
||||
@@ -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<string, string> {
|
||||
const args: Record<string, string> = {};
|
||||
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();
|
||||
Generated
+56
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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<string, string> {
|
||||
const args: Record<string, string> = {};
|
||||
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<string, any> {
|
||||
const out: Record<string, any> = {};
|
||||
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 `<span class="sihua-${huaChar}">${s}<span class="sihua-tag">${huaChar}</span></span>`;
|
||||
}
|
||||
return s;
|
||||
}).join('·')
|
||||
: '<span style="color:var(--ink-faint)">无主星</span>';
|
||||
// 辅星同样要处理四化(右弼化科 / 文昌化忌 / 文曲化科 等常落辅星)
|
||||
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 `<span class="sihua-${huaChar}">${s}<span class="sihua-tag">${huaChar}</span></span>`;
|
||||
}
|
||||
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) ? '<span class="shen-badge">身</span>' : '';
|
||||
}
|
||||
|
||||
// ============ 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<string, any> {
|
||||
const out: Record<string, any> = {};
|
||||
|
||||
// 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, any>): 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();
|
||||
@@ -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<string, string> {
|
||||
const args: Record<string, string> = {};
|
||||
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();
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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<typeof runBaziTests>;
|
||||
ziwei: ReturnType<typeof runZiweiTests>;
|
||||
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
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* 纳音五行算法
|
||||
*
|
||||
* 纳音是根据干支组合推算出的五行属性,每两个干支组成一个纳音。
|
||||
* 共有60个干支组合,对应30种纳音五行。
|
||||
*
|
||||
* 纳音五行分为:
|
||||
* - 金:海中金、金箔金、白蜡金、剑锋金、钗钏金、沙中金
|
||||
* - 木:大林木、杨柳木、松柏木、平地木、石榴木、桑柘木
|
||||
* - 水:涧下水、大溪水、长流水、天河水、大海水、泉中水
|
||||
* - 火:炉中火、山头火、霹雳火、山下火、覆灯火、天上火
|
||||
* - 土:城头土、屋上土、壁上土、大驿土、沙中土、路旁土
|
||||
*/
|
||||
|
||||
import type { Tiangan, Dizhi } from './types';
|
||||
|
||||
/**
|
||||
* 纳音五行类型
|
||||
*/
|
||||
export type NaYin =
|
||||
// 金系
|
||||
| '海中金' | '金箔金' | '白蜡金' | '剑锋金' | '钗钏金' | '沙中金'
|
||||
// 木系
|
||||
| '大林木' | '杨柳木' | '松柏木' | '平地木' | '石榴木' | '桑柘木'
|
||||
// 水系
|
||||
| '涧下水' | '大溪水' | '长流水' | '天河水' | '大海水' | '泉中水'
|
||||
// 火系
|
||||
| '炉中火' | '山头火' | '霹雳火' | '山下火' | '覆灯火' | '天上火'
|
||||
// 土系
|
||||
| '城头土' | '屋上土' | '壁上土' | '大驿土' | '沙中土' | '路旁土';
|
||||
|
||||
/**
|
||||
* 纳音映射表
|
||||
* 根据干支组合查找对应的纳音五行
|
||||
*
|
||||
* 数据来源:纳音.md
|
||||
*/
|
||||
const NAYIN_MAP: Record<string, NaYin> = {
|
||||
// 金系
|
||||
'甲子': '海中金', '乙丑': '海中金',
|
||||
'壬寅': '金箔金', '癸卯': '金箔金',
|
||||
'庚辰': '白蜡金', '辛巳': '白蜡金',
|
||||
'壬申': '剑锋金', '癸酉': '剑锋金',
|
||||
'庚戌': '钗钏金', '辛亥': '钗钏金',
|
||||
'甲午': '沙中金', '乙未': '沙中金',
|
||||
|
||||
// 木系
|
||||
'戊辰': '大林木', '己巳': '大林木',
|
||||
'壬午': '杨柳木', '癸未': '杨柳木',
|
||||
'庚寅': '松柏木', '辛卯': '松柏木',
|
||||
'戊戌': '平地木', '己亥': '平地木',
|
||||
'庚申': '石榴木', '辛酉': '石榴木',
|
||||
'壬子': '桑柘木', '癸丑': '桑柘木',
|
||||
|
||||
// 水系
|
||||
'丙子': '涧下水', '丁丑': '涧下水',
|
||||
'甲寅': '大溪水', '乙卯': '大溪水',
|
||||
'壬辰': '长流水', '癸巳': '长流水',
|
||||
'丙午': '天河水', '丁未': '天河水',
|
||||
'壬戌': '大海水', '癸亥': '大海水',
|
||||
'甲申': '泉中水', '乙酉': '泉中水',
|
||||
|
||||
// 火系
|
||||
'丙寅': '炉中火', '丁卯': '炉中火',
|
||||
'甲戌': '山头火', '乙亥': '山头火',
|
||||
'戊子': '霹雳火', '己丑': '霹雳火',
|
||||
'丙申': '山下火', '丁酉': '山下火',
|
||||
'甲辰': '覆灯火', '乙巳': '覆灯火',
|
||||
'戊午': '天上火', '己未': '天上火',
|
||||
|
||||
// 土系
|
||||
'戊寅': '城头土', '己卯': '城头土',
|
||||
'丙戌': '屋上土', '丁亥': '屋上土',
|
||||
'庚子': '壁上土', '辛丑': '壁上土',
|
||||
'戊申': '大驿土', '己酉': '大驿土',
|
||||
'丙辰': '沙中土', '丁巳': '沙中土',
|
||||
'庚午': '路旁土', '辛未': '路旁土'
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取干支的纳音五行
|
||||
* @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<string, string> = {
|
||||
'木': '火', '火': '土', '土': '金', '金': '水', '水': '木'
|
||||
};
|
||||
|
||||
if (sheng[wx1] === wx2) return '生';
|
||||
if (sheng[wx2] === wx1) return '被生';
|
||||
|
||||
// 相克关系:木克土、土克水、水克火、火克金、金克木
|
||||
const ke: Record<string, string> = {
|
||||
'木': '土', '土': '水', '水': '火', '火': '金', '金': '木'
|
||||
};
|
||||
|
||||
if (ke[wx1] === wx2) return '克';
|
||||
if (ke[wx2] === wx1) return '被克';
|
||||
|
||||
return '比和'; // 默认值
|
||||
}
|
||||
@@ -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 '';
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 十二长生(星运)算法
|
||||
*
|
||||
* 十二长生是命理学中描述五行在十二地支中的旺衰状态,包括:
|
||||
* 长生、沐浴、冠带、临官、帝旺、衰、病、死、墓、绝、胎、养
|
||||
*
|
||||
* 根据日干的五行属性,查找其在各地支的长生状态
|
||||
*/
|
||||
|
||||
import type { Tiangan, Dizhi } from './types';
|
||||
|
||||
/**
|
||||
* 十二长生状态
|
||||
*/
|
||||
export type ChangSheng =
|
||||
| '长生' | '沐浴' | '冠带' | '临官' | '帝旺' | '衰'
|
||||
| '病' | '死' | '墓' | '绝' | '胎' | '养';
|
||||
|
||||
/**
|
||||
* 十二长生映射表
|
||||
* 根据天干十二长生表(见附件图片)
|
||||
*
|
||||
* 阳干(甲丙戊庚壬):顺行
|
||||
* 阴干(乙丁己辛癸):逆行
|
||||
*/
|
||||
const CHANGSHENG_MAP: Record<Tiangan, Record<Dizhi, ChangSheng>> = {
|
||||
'甲': {
|
||||
'亥': '长生', '子': '沐浴', '丑': '冠带', '寅': '临官',
|
||||
'卯': '帝旺', '辰': '衰', '巳': '病', '午': '死',
|
||||
'未': '墓', '申': '绝', '酉': '胎', '戌': '养'
|
||||
},
|
||||
'乙': {
|
||||
'午': '长生', '巳': '沐浴', '辰': '冠带', '卯': '临官',
|
||||
'寅': '帝旺', '丑': '衰', '子': '病', '亥': '死',
|
||||
'戌': '墓', '酉': '绝', '申': '胎', '未': '养'
|
||||
},
|
||||
'丙': {
|
||||
'寅': '长生', '卯': '沐浴', '辰': '冠带', '巳': '临官',
|
||||
'午': '帝旺', '未': '衰', '申': '病', '酉': '死',
|
||||
'戌': '墓', '亥': '绝', '子': '胎', '丑': '养'
|
||||
},
|
||||
'丁': {
|
||||
'酉': '长生', '申': '沐浴', '未': '冠带', '午': '临官',
|
||||
'巳': '帝旺', '辰': '衰', '卯': '病', '寅': '死',
|
||||
'丑': '墓', '子': '绝', '亥': '胎', '戌': '养'
|
||||
},
|
||||
'戊': {
|
||||
'寅': '长生', '卯': '沐浴', '辰': '冠带', '巳': '临官',
|
||||
'午': '帝旺', '未': '衰', '申': '病', '酉': '死',
|
||||
'戌': '墓', '亥': '绝', '子': '胎', '丑': '养'
|
||||
},
|
||||
'己': {
|
||||
'酉': '长生', '申': '沐浴', '未': '冠带', '午': '临官',
|
||||
'巳': '帝旺', '辰': '衰', '卯': '病', '寅': '死',
|
||||
'丑': '墓', '子': '绝', '亥': '胎', '戌': '养'
|
||||
},
|
||||
'庚': {
|
||||
'巳': '长生', '午': '沐浴', '未': '冠带', '申': '临官',
|
||||
'酉': '帝旺', '戌': '衰', '亥': '病', '子': '死',
|
||||
'丑': '墓', '寅': '绝', '卯': '胎', '辰': '养'
|
||||
},
|
||||
'辛': {
|
||||
'子': '长生', '亥': '沐浴', '戌': '冠带', '酉': '临官',
|
||||
'申': '帝旺', '未': '衰', '午': '病', '巳': '死',
|
||||
'辰': '墓', '卯': '绝', '寅': '胎', '丑': '养'
|
||||
},
|
||||
'壬': {
|
||||
'申': '长生', '酉': '沐浴', '戌': '冠带', '亥': '临官',
|
||||
'子': '帝旺', '丑': '衰', '寅': '病', '卯': '死',
|
||||
'辰': '墓', '巳': '绝', '午': '胎', '未': '养'
|
||||
},
|
||||
'癸': {
|
||||
'卯': '长生', '寅': '沐浴', '丑': '冠带', '子': '临官',
|
||||
'亥': '帝旺', '戌': '衰', '酉': '病', '申': '死',
|
||||
'未': '墓', '午': '绝', '巳': '胎', '辰': '养'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取天干在某地支的长生状态
|
||||
* @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<ChangSheng, number> = {
|
||||
'长生': 5, // 最吉
|
||||
'帝旺': 5, // 最吉
|
||||
'临官': 4, // 次吉
|
||||
'冠带': 4, // 次吉
|
||||
'养': 3, // 平
|
||||
'胎': 3, // 平
|
||||
'沐浴': 2, // 次凶(桃花)
|
||||
'衰': 2, // 次凶
|
||||
'病': 1, // 凶
|
||||
'死': 1, // 凶
|
||||
'墓': 1, // 凶
|
||||
'绝': 1 // 最凶
|
||||
};
|
||||
return levels[changsheng];
|
||||
}
|
||||
@@ -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<string, number> {
|
||||
const stars = new Map<string, number>();
|
||||
|
||||
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<string, number> {
|
||||
const stars = new Map<string, number>();
|
||||
|
||||
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<string, number> {
|
||||
const stars = new Map<string, number>();
|
||||
|
||||
// 左辅:辰宫起正月,顺时针数到生月
|
||||
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<string, number> {
|
||||
const stars = new Map<string, number>();
|
||||
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<string, number> {
|
||||
const stars = new Map<string, number>();
|
||||
|
||||
// 禄存按年干:甲寅、乙卯、丙戊巳、丁己午、庚申、辛酉、壬亥、癸子
|
||||
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<string, number> {
|
||||
const stars = new Map<string, number>();
|
||||
|
||||
// 先找禄存位置
|
||||
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<string, number> {
|
||||
const stars = new Map<string, number>();
|
||||
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<string, number> {
|
||||
const stars = new Map<string, number>();
|
||||
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<string, number> {
|
||||
const stars = new Map<string, number>();
|
||||
|
||||
// 天魁定位
|
||||
// 口诀:甲戊庚牛羊,乙己鼠猴乡,丙丁猪鸡位,壬癸兔蛇藏,六辛逢马虎
|
||||
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<string, number> {
|
||||
const stars = new Map<string, number>();
|
||||
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<string, number> {
|
||||
const stars = new Map<string, number>();
|
||||
|
||||
// 天刑:酉宫起正月,顺数至生月
|
||||
const tianXingPos = (9 + lunarMonth - 1) % 12;
|
||||
|
||||
stars.set('天刑', tianXingPos);
|
||||
|
||||
return stars;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安天姚
|
||||
* 口诀:天姚丑上顺正月,数至生月便住脚
|
||||
*/
|
||||
function installTianYao(lunarMonth: number): Map<string, number> {
|
||||
const stars = new Map<string, number>();
|
||||
|
||||
// 天姚:丑宫起正月,顺数至生月
|
||||
const tianYaoPos = (1 + lunarMonth - 1) % 12;
|
||||
|
||||
stars.set('天姚', tianYaoPos);
|
||||
|
||||
return stars;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 810 KiB |
@@ -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": "以上为传统命理与算法模型综合得出的概率性评估,仅作研究与娱乐参考,请结合自身实际决策。"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 补层]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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. 免责声明
|
||||
```
|
||||
|
||||
## 前置声明(输出开头必带)
|
||||
> 本分析基于传统八字命理理论框架,仅供文化研究与娱乐参考,非专业决策依据,具体发展需结合个人努力与客观环境。
|
||||
|
||||
## 互动提示
|
||||
分析结束后,邀请用户针对具体疑问继续提问。
|
||||
@@ -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. 免责声明
|
||||
```
|
||||
|
||||
## 免责声明(输出末必带)
|
||||
> 本分析基于传统紫微斗数理论框架,仅供文化研究与娱乐参考,不构成任何决策依据。命运由个人选择与客观环境共同塑造。
|
||||
|
||||
---
|
||||
|
||||
## 输入格式
|
||||
命盘信息以文墨天机紫微斗数命盘文本形式给出,包含基本信息、十二宫主星辅星小星神煞、生年四化、大限小限流年、限流叠宫等字段。如有八字数据,可作为辅助参考但不主导紫微判断。
|
||||
@@ -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
|
||||
}
|
||||
```
|
||||
|
||||
## 输出(直接以 `{` 开头):
|
||||
@@ -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 排盘。
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user