chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:33:51 +08:00
commit d2d1f358ff
63 changed files with 12403 additions and 0 deletions
+28
View File
@@ -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
+21
View File
@@ -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.
+33
View File
@@ -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
View File
@@ -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: MIT](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE)
[![SKILL.md](https://img.shields.io/badge/SKILL.md-compatible-c1432f.svg)](#-installation)
[简体中文](./README.md) | English
<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
+184
View File
@@ -0,0 +1,184 @@
<div align="center">
# bazi-ziwei-skill
**AI 八字 + 紫微斗数排盘与综合印证 Skill**
精准排盘(不靠 LLM 猜)· 三种分析模式 · 一键生成水墨风 HTML 命盘海报
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE)
[![SKILL.md](https://img.shields.io/badge/SKILL.md-compatible-c1432f.svg)](#-安装)
简体中文 | [English](./README.en.md)
<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
+7
View File
@@ -0,0 +1,7 @@
# WeHub 来源说明
- 原始项目:`dzcmemory-web/bazi-ziwei-skill`
- 原始仓库:https://github.com/dzcmemory-web/bazi-ziwei-skill
- 导入方式:上游默认分支的最新快照
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
+209
View File
@@ -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 是纯 JSONstderr 是 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 ← 入口:生辰 → JSONstdout 纯 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
View File
@@ -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 把结果记下来,方便对比。
+51
View File
@@ -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)
};
}
+65
View File
@@ -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};
});
}
+108
View File
@@ -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};
}
+184
View File
@@ -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;
}
+7
View File
@@ -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];
}
+156
View File
@@ -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]
}
};
}
+62
View File
@@ -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);
}
+176
View File
@@ -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
View File
@@ -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
View File
@@ -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 };
});
}
+99
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+256
View File
@@ -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();
+351
View File
@@ -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();
+103
View File
@@ -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();
+319
View File
@@ -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;
}
+99
View File
@@ -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=19801980%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);
}
+145
View File
@@ -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];
}
+142
View File
@@ -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
};
+165
View File
@@ -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;
}
+137
View File
@@ -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 '比和'; // 默认值
}
+115
View File
@@ -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 '';
}
}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
// 核心数据类型定义
Object.defineProperty(exports, "__esModule", { value: true });
+117
View File
@@ -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-55最吉)
*/
function getChangShengLevel(changsheng) {
const levels = {
'长生': 5, // 最吉
'帝旺': 5, // 最吉
'临官': 4, // 次吉
'冠带': 4, // 次吉
'养': 3, // 平
'胎': 3, // 平
'沐浴': 2, // 次凶(桃花)
'衰': 2, // 次凶
'病': 1, // 凶
'死': 1, // 凶
'墓': 1, // 凶
'绝': 1 // 最凶
};
return levels[changsheng];
}
+633
View File
@@ -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;
}
+235
View File
@@ -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();
+56
View File
@@ -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"
}
}
}
+19
View File
@@ -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"
}
}
+349
View File
@@ -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();
+87
View File
@@ -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();
+26
View File
@@ -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"
]
}
+356
View File
@@ -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;
}
+123
View File
@@ -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=19801980%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);
}
+156
View File
@@ -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];
}
+159
View File
@@ -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
};
+190
View File
@@ -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;
}
+168
View File
@@ -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 '比和'; // 默认值
}
+129
View File
@@ -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 '';
}
}
+152
View File
@@ -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;
}
+133
View File
@@ -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-55最吉)
*/
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];
}
+749
View File
@@ -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;
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 810 KiB

+73
View File
@@ -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
+150
View File
@@ -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
+81
View File
@@ -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. 免责声明
```
## 前置声明(输出开头必带)
> 本分析基于传统八字命理理论框架,仅供文化研究与娱乐参考,非专业决策依据,具体发展需结合个人努力与客观环境。
## 互动提示
分析结束后,邀请用户针对具体疑问继续提问。
+81
View File
@@ -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. 免责声明
```
## 免责声明(输出末必带)
> 本分析基于传统紫微斗数理论框架,仅供文化研究与娱乐参考,不构成任何决策依据。命运由个人选择与客观环境共同塑造。
---
## 输入格式
命盘信息以文墨天机紫微斗数命盘文本形式给出,包含基本信息、十二宫主星辅星小星神煞、生年四化、大限小限流年、限流叠宫等字段。如有八字数据,可作为辅助参考但不主导紫微判断。
+130
View File
@@ -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
}
```
## 输出(直接以 `{` 开头):
+151
View File
@@ -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 岁前)听紫微的"势"**:年轻人靠星曜组合冲势,八字尚未完全显化
- **中年后(3060 岁)听八字的"体"**:中年靠根基,喜用神能否到位决定走多远
- **晚年(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 条优势放大策略
└ 35 条针对性建议
6. 置信度自评(高/中/低各列了哪些结论)
7. 免责声明
```
## 免责声明(输出末必带)
> 本综合分析基于传统八字与紫微斗数理论框架的交叉印证,仅供文化研究与娱乐参考,不构成任何决策依据。八字与紫微属两套独立符号系统,印证结果反映的是符号层面的一致性,而非客观因果。命运由个人选择与客观环境共同塑造。
---
## 调用提示
本提示词的有效性强依赖于上游两份独立报告的质量。若八字或紫微独立分析存在排盘错误、格局误判,本印证层无法纠错。建议每次运行前,先确认两份独立报告均由本 Skill 的算法层(enrichBazi + Yiqi 紫微)产出,而非纯 LLM 排盘。
File diff suppressed because it is too large Load Diff