Metadata-Version: 2.4
Name: flowpillars
Version: 0.1.1
Summary: Generate Chinese metaphysical flowing pillars — 流年 (yearly), 流月 (monthly), 流日 (daily), 流時 (hourly) — for both 八字 (BaZi / Four Pillars of Destiny) and 紫微斗數 (Zi Wei Dou Shu / Purple Star Astrology), including 四化 (Four Transformations) and 流曜 (Flowing Stars).
Author-email: Jason Chao <chao@jasontc.net>
License: MIT License
        
        Copyright (c) 2025 flowpillars contributors
        
        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.
        
Project-URL: Homepage, https://github.com/jason-chao/flowpillars/
Project-URL: Bug Tracker, https://github.com/jason-chao/flowpillars/issues
Keywords: bazi,four pillars,eight characters,zi wei dou shu,purple star astrology,chinese astrology,chinese metaphysics,ganzhi,heavenly stems,earthly branches,sexagenary cycle,flowing pillars,liu nian,liu yue,liu ri,liu shi,si hua,liu yao,lunar calendar,八字,四柱,紫微斗數,干支,天干,地支,流年,流月,流日,流時,四化,流曜,農曆,命理,星盤
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Utilities
Classifier: Natural Language :: Chinese (Traditional)
Classifier: Natural Language :: English
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: lunar-python>=1.3.12
Requires-Dist: click>=8.1
Requires-Dist: pytz>=2023.3
Requires-Dist: python-dateutil>=2.8
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: pytest-cov>=4.1; extra == "dev"
Dynamic: license-file

# flowpillars 流柱

A Python package for generating Chinese metaphysical **flowing pillars** — annual (流年), monthly (流月), daily (流日), and hourly (流時) — using both the **八字** (BaZi) and **紫微斗數** (Purple Star Astrology) systems.

---

## Introduction

In Chinese metaphysics, time is divided into pillars (柱) using the sexagenary **干支** (Ganzhi) cycle — a 60-combination cycle formed from the ten **天干** (Heavenly Stems) and twelve **地支** (Earthly Branches).

| Heavenly Stems | 甲 乙 丙 丁 戊 己 庚 辛 壬 癸 |
|---|---|
| **Earthly Branches** | **子 丑 寅 卯 辰 巳 午 未 申 酉 戌 亥** |

A complete "四柱" (Four Pillars) consists of:

| Pillar | 八字 rule | 紫微斗數 (Purple Star) rule |
|--------|-----------|---------------------------|
| **年柱** (Year) | Solar year, changes at **立春** (~4 Feb) | Lunar year, changes at **農曆正月初一** (Chinese New Year) |
| **月柱** (Month) | Based on solar terms (節氣) | Calculated via **五虎遁年起月法** from the lunar year stem |
| **日柱** (Day) | Sexagenary day cycle | Identical to 八字 |
| **時柱** (Hour) | 2-hour 時辰 periods | Identical to 八字 |

### 立春 vs 農曆新年

This distinction is the main difference between the two systems' **year pillars**:

- **八字** uses the solar term **立春** (Start of Spring, around 4 February) as the New Year boundary. A date in January or early February is still counted as the previous year.
- **紫微斗數** (Purple Star Astrology) uses **農曆正月初一** (Chinese New Year, the first day of the first lunar month, typically between 21 January and 20 February) as the boundary.

For example, 1 January 2024 falls before both boundaries, so both systems give 癸卯 for that date. In a year where Chinese New Year falls before 立春, dates between those two events will have different year pillars in each system.

### 四化 (Four Transformations)

Purple Star Astrology includes **四化** (Four Transformations). This package calculates all four transformations for each Purple Star pillar. Each transformation star is determined by the Heavenly Stem of the pillar, according to the standard table:

| Stem | 化祿 (Hua Lu) | 化權 (Hua Quan) | 化科 (Hua Ke) | 化忌 (Hua Ji) |
|------|--------------|----------------|--------------|--------------|
| 甲 | 廉貞 | 破軍 | 武曲 | 太陽 |
| 乙 | 天機 | 天梁 | 紫微 | 太陰 |
| 丙 | 天同 | 天機 | 文昌 | 廉貞 |
| 丁 | 太陰 | 天同 | 天機 | 巨門 |
| 戊 | 貪狼 | 太陰 | 右弼 | 天機 |
| 己 | 武曲 | 貪狼 | 天梁 | 文曲 |
| 庚 | 太陽 | 武曲 | 太陰 | 天同 |
| 辛 | 巨門 | 太陽 | 文曲 | 文昌 |
| 壬 | 天梁 | 紫微 | 左輔 | 武曲 |
| 癸 | 破軍 | 巨門 | 太陰 | 貪狼 |

BaZi pillars do not include 四化.

### 流曜 (Flowing Stars)

For each Purple Star pillar this package also calculates six **流曜** (Flowing Stars), expressed as the 地支 (Earthly Branch) of their palace position. Three are derived from the pillar's **天干** (Heavenly Stem) and three from its **地支** (Earthly Branch):

| Star | Chinese | Derived from | Description |
|------|---------|-------------|-------------|
| `lusun` | 祿存 | Stem | Prosperity-preservation star |
| `qingyang` | 擎羊 | Stem | One branch after 祿存 |
| `tuoluo` | 陀羅 | Stem | One branch before 祿存 |
| `tianma` | 天馬 | Branch | Travelling horse star |
| `wenchang` | 文昌 | Branch | Literary star (counts down from 戌) |
| `wenqu` | 文曲 | Branch | Literary star (counts up from 辰) |

BaZi pillars do not include 流曜.

### Precision Levels

`flowpillars` supports four levels of input precision:

| Level | Input Example | Pillars Generated |
|-------|--------------|-------------------|
| `YEAR` | `"2023"` | Annual (流年) only |
| `MONTH` | `"2023-06"` | Annual + monthly (流年 + 流月) |
| `DAY` | `"2023-06-15"` | Annual + monthly + daily (流年 + 流月 + 流日) |
| `HOUR` | `"2023-06-15T12"` | Annual + monthly + daily + hourly (流年 + 流月 + 流日 + 流時) |

Pillars that cannot be calculated at the given precision are `null` in JSON output and empty strings in CSV output.

---

## Installation

```bash
# Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate

# Install the package and its dependencies
pip install -e ".[dev]"
```

### Dependencies

| Package | Version | Purpose |
|---------|---------|---------|
| `lunar-python` | ≥1.3.12 | Chinese lunar calendar and 八字 (EightChar) calculations. Complete offline support for solar/lunar conversion, solar terms (節氣), and sexagenary day cycles. |
| `click` | ≥8.1 | CLI framework providing argument parsing, help text generation, and a clean interface for the `flowpillars` command. |
| `pytz` | ≥2023.3 | Timezone handling and UTC offset parsing for date inputs. |
| `python-dateutil` | ≥2.8 | Date arithmetic — specifically `relativedelta` for correct month and year increments (e.g. adding 1 month to 31 January). |

---

## CLI Usage

The package installs a `flowpillars` command.

### General Options

```
Options:
  --system [bazi|purple-star|both]          Pillar system(s) to calculate. (default: both)
  --date TEXT                               Single date/datetime string.
  --dates TEXT                              Comma-separated list of date strings.
  --date-file PATH                          File with one date per line.
  --start TEXT                              Start date for range generation.
  --end TEXT                                End date for range generation (inclusive).
  --plus TEXT                               Duration to add to start (e.g. 5y, 3m, 10d, 24h).
  --minus TEXT                              Duration to subtract from start.
  --calendar [solar|lunar]                  Default calendar for input. (default: solar)
  --timezone TEXT                           Timezone name, e.g. Asia/Hong_Kong.
  --format [csv|json|plaintext]             Output format. If omitted, plaintext is printed to stdout.
  --output-dir TEXT                         Directory for output files. (default: .)
  --output-prefix TEXT                      Filename prefix for output files. (default: pillars)
  --help                                    Show this message and exit.
```

### Single Date

```bash
# Day precision — prints plaintext to stdout
flowpillars --date "2023-06-15"

# Hour precision
flowpillars --date "2023-06-15T12"

# With timezone
flowpillars --date "2023-06-15T12" --timezone "Asia/Hong_Kong"

# With UTC offset inline
flowpillars --date "2023-06-15T12+08:00"

# Lunar calendar input (prefix with L:)
flowpillars --date "L:2023-04-12"

# BaZi system only, JSON output
flowpillars --date "2023-06-15" --system bazi --format json

# Purple Star Astrology only (includes 化忌)
flowpillars --date "2023-06-15" --system purple-star --format json
```

### Range Modes

```bash
# Yearly range 2020–2024 (inclusive)
flowpillars --start "2020" --end "2024"

# Monthly range: January through June 2023
flowpillars --start "2023-01" --plus 5m

# Daily range: 6 days back from 2023-06-15
flowpillars --start "2023-06-15" --minus 6d

# Hourly range: 8 hours starting at 08:00
flowpillars --start "2023-06-15T08" --plus 8h
```

Duration strings: `Ny` (years), `Nm` (months), `Nd` (days), `Nh` (hours).

### List Mode

```bash
# Comma-separated dates (mixed precisions supported)
flowpillars --dates "2020,2021,2023-06,2024-03-15"

# From a file (one date per line; lines starting with # are ignored)
flowpillars --date-file my_dates.txt
```

### Output

```bash
# Write plaintext to a file (instead of stdout)
flowpillars --date "2023-06-15T12" --format plaintext --output-dir ./results --output-prefix june_2023
# Produces: ./results/june_2023.txt

# Write CSV or JSON to a file
flowpillars --date "2023-06-15" --format csv --output-dir ./results --output-prefix june_2023
# Produces: ./results/june_2023.csv
```

---

## Python API

```python
from flowpillars import PillarGenerator, PillarSystem, InputDateTime, Precision, CalendarInput

# Single date — HOUR precision, both systems
gen = PillarGenerator(system=PillarSystem.BOTH)
idt = InputDateTime(year=2023, month=6, day=15, hour=12)
result = gen.generate(idt)

print(result.bazi.year.pillar)              # e.g. "癸卯"
print(result.purple_star.month.pillar)      # e.g. "丁巳"
print(result.purple_star.year.hualu)        # e.g. "破軍" (化祿 star)
print(result.purple_star.year.huaquan)      # e.g. "巨門" (化權 star)
print(result.purple_star.year.huake)        # e.g. "太陰" (化科 star)
print(result.purple_star.year.huaji)        # e.g. "貪狼" (化忌 star)
print(result.purple_star.year.liuyao.lusun) # e.g. "子"   (祿存 palace)
print(result.lunar.month)                   # e.g. 4

# Parse from a string
from flowpillars.input_handler import parse_datetime
idt = parse_datetime("2023-06-15T12+08:00")

# Generate a range
from flowpillars.input_handler import parse_datetime, generate_range
start = parse_datetime("2023-01")
inputs = generate_range(start, plus="11m")   # 12 monthly entries
results = gen.generate_many(inputs)

# Write output
from flowpillars.output import write_csv, write_json, write_plaintext
write_csv(results, "output.csv")
write_json(results, "output.json", system="both")
write_plaintext(results, "output.txt")
```

### PillarResult fields

```python
result.solar_year      # int
result.solar_month     # int or None
result.solar_day       # int or None
result.solar_hour      # int or None
result.timezone        # str or None
result.lunar.year      # int
result.lunar.month     # int (negative = leap month)
result.lunar.day       # int
result.lunar.is_leap_month  # bool

result.bazi.year         # GanZhiPillar or None
result.bazi.year.pillar  # "癸卯"
result.bazi.year.stem    # "癸"
result.bazi.year.branch  # "卯"
result.bazi.year.hualu   # None  (BaZi does not use 四化)
result.bazi.year.huaquan # None
result.bazi.year.huake   # None
result.bazi.year.huaji   # None
result.bazi.year.liuyao  # None  (BaZi does not use 流曜)
# .month, .day, .hour follow the same pattern

result.purple_star.year            # GanZhiPillar or None
result.purple_star.year.pillar     # "癸卯"
result.purple_star.year.stem       # "癸"
result.purple_star.year.branch     # "卯"
result.purple_star.year.hualu      # "破軍"  (化祿 star for 癸 stem)
result.purple_star.year.huaquan    # "巨門"  (化權 star for 癸 stem)
result.purple_star.year.huake      # "太陰"  (化科 star for 癸 stem)
result.purple_star.year.huaji      # "貪狼"  (化忌 star for 癸 stem)
result.purple_star.year.liuyao.lusun    # "子"   (祿存 palace — 癸 stem → 子)
result.purple_star.year.liuyao.qingyang # "丑"   (擎羊 palace)
result.purple_star.year.liuyao.tuoluo   # "亥"   (陀羅 palace)
result.purple_star.year.liuyao.tianma   # "巳"   (天馬 palace — 卯 branch → 巳)
result.purple_star.year.liuyao.wenchang # "未"   (文昌 palace)
result.purple_star.year.liuyao.wenqu    # "未"   (文曲 palace)
```

---

## CSV Output Format

The CSV file uses UTF-8-BOM encoding (Excel-compatible) with the following columns:

| Column | Description |
|--------|-------------|
| `input_date` | Original input string (e.g. `2023-06-15T12` or `L:2023-04-12`) |
| `precision` | `year`, `month`, `day`, or `hour` |
| `calendar` | `solar` or `lunar` |
| `solar_year` | Solar year number |
| `solar_month` | Solar month (empty if YEAR precision) |
| `solar_day` | Solar day (empty if precision < DAY) |
| `solar_hour` | Solar hour 0–23 (empty if precision < HOUR) |
| `timezone` | Timezone string, e.g. `Asia/Hong_Kong` (empty if none) |
| `lunar_year` | Lunar year number |
| `lunar_month` | Lunar month (negative = leap month) |
| `lunar_day` | Lunar day |
| `bazi_year_pillar` | BaZi year 干支, e.g. `癸卯` |
| `bazi_year_stem` | BaZi year 天干, e.g. `癸` |
| `bazi_year_branch` | BaZi year 地支, e.g. `卯` |
| `bazi_month_pillar` | BaZi month 干支 (empty if precision < MONTH) |
| `bazi_month_stem` | BaZi month 天干 |
| `bazi_month_branch` | BaZi month 地支 |
| `bazi_day_pillar` | BaZi day 干支 (empty if precision < DAY) |
| `bazi_day_stem` | BaZi day 天干 |
| `bazi_day_branch` | BaZi day 地支 |
| `bazi_hour_pillar` | BaZi hour 干支 (empty if precision < HOUR) |
| `bazi_hour_stem` | BaZi hour 天干 |
| `bazi_hour_branch` | BaZi hour 地支 |
| `purple_star_year_pillar` | Purple Star year 干支 |
| `purple_star_year_stem` | Purple Star year 天干 |
| `purple_star_year_branch` | Purple Star year 地支 |
| `purple_star_year_hualu` | Purple Star year 化祿 star |
| `purple_star_year_huaquan` | Purple Star year 化權 star |
| `purple_star_year_huake` | Purple Star year 化科 star |
| `purple_star_year_huaji` | Purple Star year 化忌 star |
| `purple_star_year_liuyao_lusun` | Purple Star year 祿存 palace (地支) |
| `purple_star_year_liuyao_qingyang` | Purple Star year 擎羊 palace |
| `purple_star_year_liuyao_tuoluo` | Purple Star year 陀羅 palace |
| `purple_star_year_liuyao_tianma` | Purple Star year 天馬 palace |
| `purple_star_year_liuyao_wenchang` | Purple Star year 文昌 palace |
| `purple_star_year_liuyao_wenqu` | Purple Star year 文曲 palace |
| `purple_star_month_pillar` | Purple Star month 干支 (empty if precision < MONTH) |
| `purple_star_month_stem` | Purple Star month 天干 |
| `purple_star_month_branch` | Purple Star month 地支 |
| `purple_star_month_hualu` | Purple Star month 化祿 star |
| `purple_star_month_huaquan` | Purple Star month 化權 star |
| `purple_star_month_huake` | Purple Star month 化科 star |
| `purple_star_month_huaji` | Purple Star month 化忌 star |
| `purple_star_month_liuyao_lusun` | Purple Star month 祿存 palace |
| `purple_star_month_liuyao_qingyang` | Purple Star month 擎羊 palace |
| `purple_star_month_liuyao_tuoluo` | Purple Star month 陀羅 palace |
| `purple_star_month_liuyao_tianma` | Purple Star month 天馬 palace |
| `purple_star_month_liuyao_wenchang` | Purple Star month 文昌 palace |
| `purple_star_month_liuyao_wenqu` | Purple Star month 文曲 palace |
| `purple_star_day_pillar` | Purple Star day 干支 (empty if precision < DAY) |
| `purple_star_day_stem` | Purple Star day 天干 |
| `purple_star_day_branch` | Purple Star day 地支 |
| `purple_star_day_hualu` | Purple Star day 化祿 star |
| `purple_star_day_huaquan` | Purple Star day 化權 star |
| `purple_star_day_huake` | Purple Star day 化科 star |
| `purple_star_day_huaji` | Purple Star day 化忌 star |
| `purple_star_day_liuyao_lusun` | Purple Star day 祿存 palace |
| `purple_star_day_liuyao_qingyang` | Purple Star day 擎羊 palace |
| `purple_star_day_liuyao_tuoluo` | Purple Star day 陀羅 palace |
| `purple_star_day_liuyao_tianma` | Purple Star day 天馬 palace |
| `purple_star_day_liuyao_wenchang` | Purple Star day 文昌 palace |
| `purple_star_day_liuyao_wenqu` | Purple Star day 文曲 palace |
| `purple_star_hour_pillar` | Purple Star hour 干支 (empty if precision < HOUR) |
| `purple_star_hour_stem` | Purple Star hour 天干 |
| `purple_star_hour_branch` | Purple Star hour 地支 |
| `purple_star_hour_hualu` | Purple Star hour 化祿 star |
| `purple_star_hour_huaquan` | Purple Star hour 化權 star |
| `purple_star_hour_huake` | Purple Star hour 化科 star |
| `purple_star_hour_huaji` | Purple Star hour 化忌 star |
| `purple_star_hour_liuyao_lusun` | Purple Star hour 祿存 palace |
| `purple_star_hour_liuyao_qingyang` | Purple Star hour 擎羊 palace |
| `purple_star_hour_liuyao_tuoluo` | Purple Star hour 陀羅 palace |
| `purple_star_hour_liuyao_tianma` | Purple Star hour 天馬 palace |
| `purple_star_hour_liuyao_wenchang` | Purple Star hour 文昌 palace |
| `purple_star_hour_liuyao_wenqu` | Purple Star hour 文曲 palace |

When `--system bazi` is used, all `purple_star_*` columns are present but empty, and vice versa.

---

## JSON Output Schema

```json
{
  "generated_at": "2023-10-01T08:00:00+00:00",
  "system": "both",
  "entries": [
    {
      "input_date": "2023-06-15T12",
      "precision": "hour",
      "calendar_input": "solar",
      "solar": {
        "year": 2023,
        "month": 6,
        "day": 15,
        "hour": 12,
        "timezone": null
      },
      "lunar": {
        "year": 2023,
        "month": 4,
        "day": 28,
        "is_leap_month": false
      },
      "bazi": {
        "year":  { "pillar": "癸卯", "stem": "癸", "branch": "卯" },
        "month": { "pillar": "戊午", "stem": "戊", "branch": "午" },
        "day":   { "pillar": "甲辰", "stem": "甲", "branch": "辰" },
        "hour":  { "pillar": "庚午", "stem": "庚", "branch": "午" }
      },
      "purple_star": {
        "year": {
          "pillar": "癸卯", "stem": "癸", "branch": "卯",
          "hualu": "破軍", "huaquan": "巨門", "huake": "太陰", "huaji": "貪狼",
          "liuyao": { "lusun": "子", "qingyang": "丑", "tuoluo": "亥", "tianma": "巳", "wenchang": "未", "wenqu": "未" }
        },
        "month": {
          "pillar": "丁巳", "stem": "丁", "branch": "巳",
          "hualu": "太陰", "huaquan": "天同", "huake": "天機", "huaji": "巨門",
          "liuyao": { "lusun": "午", "qingyang": "未", "tuoluo": "巳", "tianma": "亥", "wenchang": "巳", "wenqu": "酉" }
        },
        "day": {
          "pillar": "甲辰", "stem": "甲", "branch": "辰",
          "hualu": "廉貞", "huaquan": "破軍", "huake": "武曲", "huaji": "太陽",
          "liuyao": { "lusun": "寅", "qingyang": "卯", "tuoluo": "丑", "tianma": "寅", "wenchang": "午", "wenqu": "申" }
        },
        "hour": {
          "pillar": "庚午", "stem": "庚", "branch": "午",
          "hualu": "太陽", "huaquan": "武曲", "huake": "太陰", "huaji": "天同",
          "liuyao": { "lusun": "申", "qingyang": "酉", "tuoluo": "未", "tianma": "申", "wenchang": "辰", "wenqu": "戌" }
        }
      }
    }
  ]
}
```

Notes:
- BaZi pillar objects contain only `pillar`, `stem`, and `branch` — no 四化 or `liuyao`.
- Purple Star pillar objects always contain `hualu`, `huaquan`, `huake`, `huaji`, and `liuyao` when the pillar is non-null.
- Null values appear when a pillar is not available at the given precision.

---

## Plaintext Output Format

The plaintext format (`.txt`) is written entirely in Traditional Chinese, making it immediately readable by practitioners. The `===` header line is kept in mixed notation so that automated tools can reliably locate entry boundaries. Multiple entries are separated by `---`, which renders as a horizontal rule in Markdown-aware chat apps.

### Delimiter conventions

| Symbol | Meaning |
|--------|---------|
| `：` (full-width colon) | Separates every label from its value |
| `→` | Maps a transformation / star name to its target |
| `、` (ideographic comma) | Separates items within the same group |
| `  ` (two spaces) | Separates independent key-value pairs on one line |

### Example output

```
=== 2023-06-15T12 | 陽曆 | 流時 ===
陽曆：2023-06-15  時：12
農曆：2023-04-28

八字：
  年柱：癸卯  天干：癸  地支：卯
  月柱：戊午  天干：戊  地支：午
  日柱：甲辰  天干：甲  地支：辰
  時柱：庚午  天干：庚  地支：午

紫微斗數：
  年柱：癸卯  天干：癸  地支：卯
    四化：化祿→破軍、化權→巨門、化科→太陰、化忌→貪狼
    流曜：祿存→子、擎羊→丑、陀羅→亥、天馬→巳、文昌→未、文曲→未
  月柱：丁巳  天干：丁  地支：巳
    四化：化祿→太陰、化權→天同、化科→天機、化忌→巨門
    流曜：祿存→午、擎羊→未、陀羅→巳、天馬→亥、文昌→巳、文曲→酉
  日柱：甲辰  天干：甲  地支：辰
    四化：化祿→廉貞、化權→破軍、化科→武曲、化忌→太陽
    流曜：祿存→寅、擎羊→卯、陀羅→丑、天馬→寅、文昌→午、文曲→申
  時柱：庚午  天干：庚  地支：午
    四化：化祿→太陽、化權→武曲、化科→太陰、化忌→天同
    流曜：祿存→申、擎羊→酉、陀羅→未、天馬→申、文昌→辰、文曲→戌
```

Notes:
- Header precision uses the standard flowing-pillar terms: annual (流年), monthly (流月), daily (流日), hourly (流時).
- Solar calendar is labelled `陽曆`; lunar calendar input is labelled `農曆` in the header.
- Only pillars available at the given precision are printed; lower-precision pillars are omitted.
- 八字 pillars show 天干 and 地支 only — no 四化 or 流曜.
- 紫微斗數 pillars include 四化 and 流曜 on separate indented lines.
- When only one system is active (`--system bazi` or `--system purple-star`), the other section is omitted.
- Lunar input dates are prefixed with `L:` in the header.
- Leap months are marked `（閏月）` in the `農曆：` line.
- The file is UTF-8 encoded (no BOM).

To write from Python:

```python
from flowpillars.output import write_plaintext
write_plaintext(results, "output.txt")
```

---

## Notes on BaZi vs Purple Star Pillar Differences

### Year Pillar

| System | Changes at | Example: 1 Jan 2024 | Example: 10 Feb 2024 |
|--------|-----------|---------------------|----------------------|
| **八字** | 立春 (~4 Feb) | 癸卯 | 甲辰 |
| **紫微斗數** | 農曆正月初一 | 癸卯 | 甲辰 |

In 2024 these happen to agree because 立春 (4 Feb) preceded Chinese New Year (10 Feb). In years where they differ, dates between the two events will show different year pillars.

### Month Pillar

**八字** derives the month pillar from solar terms (節氣). The boundary between months falls precisely at the moment the Sun enters a new 15° arc of the ecliptic, calculated by `lunar-python`.

**紫微斗數** (Purple Star) uses the **五虎遁年起月法** formula:

1. Look up the year stem index, compute `year_stem_index % 5`
2. Look up the base stem for 寅月 (lunar month 1): `MONTH_STEM_BASES = [丙, 戊, 庚, 壬, 甲]`
3. Month N stem: `(base_stem_index + N - 1) % 10`
4. Month N branch: `(N + 1) % 12` (寅月→寅, 卯月→卯, …, 子月→子, 丑月→丑)

This means the Purple Star month pillar is derived from the **lunar month number**, not solar terms, and will generally differ from the BaZi month pillar.

### Day and Hour Pillars

Day and hour pillars are **identical** in both systems. The day pillar follows the unbroken sexagenary day cycle tracked since ancient times. The hour pillar is determined by the **五鼠遁日起時法** formula, which `lunar-python` handles internally.

---

## Running Tests

```bash
pytest tests/ -v
```

Reference output files are generated in `tests/test_outputs/` during the test run. These files are gitignored (only the `.gitkeep` marker is committed).
