Building an Automated Saju (四柱) Content Generation Pipeline with Claude Vision + Python
The Hook: Why Saju Is a Surprisingly Hard Automation Problem
A single Saju birth chart image contains six layers of structured data — Heavenly Stems (天干), Earthly Branches (地支), Ten Gods (十星), 12-Phase Cycle (十二運星), Auxiliary Stars (神殺), and decade/annual luck cycles — all rendered as mixed Hanja glyphs, Korean Hangul labels, and color-coded SVG circles inside a single raster PNG.
OCR alone fails. GPT-4o hallucinates the wrong Ten God when a glyph is small. And if your pipeline misreads one field — say, labeling a Day Pillar (日柱) Heavenly Stem as Direct Wealth (正財) instead of Friend (比肩) — every downstream paragraph is semantically broken and legally risky.
This article documents the full pipeline I built to solve that: image ingestion → structured extraction → multi-pass QA → channel-specific Markdown rendering → automated CTA injection. Saju is the content domain; the architecture generalizes to any glyph-heavy structured document.
Problem Statement
The client brief: generate ~1,500-word editorial articles about public figures' Saju charts, one per trigger event (birthday, comeback, award), across four channels (Dev.to, Naver Blog, Instagram, Threads), each with different tone and format requirements.
Manual production cost: ~90 min per article × 4 channels = 6 hours per subject.
Target: under 4 minutes total, with a human QA gate before publish.
The hard constraint: zero tolerance for field-swap errors. If the chart says the Month Pillar (月柱) Earthly Branch carries a Seven Killings (偏官) star, the output must say Seven Killings — not Direct Officer (正官). Readers in this niche notice immediately, and corrections damage trust faster than silence.
Pipeline Architecture
[Image Upload]
│
▼
[Stage 1: Claude Vision — Raw Extraction]
│ structured JSON (all 6 layers)
▼
[Stage 2: Python Validator — Schema + Logic Checks]
│ validated ChartObject
▼
[Stage 3: Prompt Builder — Channel × Trigger × Subject]
│ assembled system+user prompt
▼
[Stage 4: Claude Sonnet — Article Draft]
│ raw Markdown
▼
[Stage 5: QA Agent — Guardrail Checks]
│ flagged diff or PASS
▼
[Stage 6: Formatter — Channel Renderer]
│ final Markdown / HTML / caption
▼
[Stage 7: CTA Injector — runartree.com links]
│
▼
[Human Review Gate → Publish]
Each stage is a discrete Python function. Stages 1, 3, and 4 hit the Anthropic API. Stages 2, 5, 6, and 7 are pure Python with no external calls.
Stage 1: Claude Vision Extraction
The extraction prompt is the most critical piece of engineering in the entire system. It must be field-by-field, positional, and verbatim.
Key design decisions:
1. Positional anchoring. The prompt names each pillar by its visual column label: Year Pillar (年柱), Month Pillar (月柱), Day Pillar (日柱), Hour Pillar (時柱). This prevents the model from reordering columns when the image layout shifts between chart providers.
2. Verbatim instruction. The prompt explicitly says: "Return the Ten God label exactly as printed in Korean Hangul. If the image shows 겁재, return 겁재. Do not translate, normalize, or infer." This single instruction eliminated ~80% of label-swap errors in testing.
3. Separate passes for stars. Auxiliary stars (神殺) — including Empty Void (空亡), Heavenly Noble (天乙貴人), Goat Blade (羊刃殺), Peach Blossom (桃花殺), Travel Horse (驛馬殺) — are extracted in a second Vision call focused only on the small-text annotation rows. Combining them with the main extraction pass degraded accuracy on both.
Sample extraction schema (simplified):
chart_schema = {
"subject": {"name": str, "gender": str, "solar_date": str},
"pillars": {
"year": {"stem": str, "branch": str, "stem_god": str, "branch_god": str, "phase": str, "stars": list},
"month": {"stem": str, "branch": str, "stem_god": str, "branch_god": str, "phase": str, "stars": list},
"day": {"stem": str, "branch": str, "stem_god": str, "branch_god": str, "phase": str, "stars": list},
"hour": {"stem": str, "branch": str, "stem_god": str, "branch_god": str, "phase": str, "stars": list},
},
"annual_luck": {"year": int, "stem": str, "branch": str, "stem_god": str},
"decade_luck": {"age": int, "stem": str, "branch": str, "stem_god": str, "branch_god": str, "phase": str, "stars": list}
}
Stage 2: Python Validator
The validator runs three check classes before any article generation begins.
Structural checks: All required keys present. Stem and branch values are members of the canonical 10-stem / 12-branch sets. Ten God values are members of the 10-god set. Phase values are members of the 12-phase set.
Logic checks: The Day Pillar stem determines the entire Ten God mapping. The validator recomputes expected Ten God values from the Day Stem and compares against extracted values. Any mismatch triggers a REEXTRACT flag, which sends the image back to Stage 1 with a targeted correction prompt.
Star plausibility checks: Certain stars only appear on certain branches. For example, Empty Void (空亡) pairs are deterministic given the Day Pillar stem-branch combination. If the extracted Empty Void branch contradicts the computed pair, the validator flags it.
In production, the reextraction loop runs a maximum of two times before escalating to human review. About 6% of images require one reextraction; under 1% escalate.
Stage 3: Prompt Builder
The prompt builder assembles a system prompt and user prompt from three inputs: the validated ChartObject, the channel config, and the trigger event.
Channel config controls tone register, length target, format rules, and forbidden patterns. Dev.to config specifies: technical/analytical tone, 1,400–1,600 words, Markdown headers, code blocks permitted, no fortune-telling framing, no first-person predictions.
Trigger event controls which chart elements receive editorial emphasis. A birthday trigger emphasizes the Day Pillar (日柱) and the annual luck cycle intersection. A comeback trigger emphasizes the Hour Pillar (時柱) creative stars and the decade luck phase.
Subject injection inserts the validated chart data as a structured block inside the user prompt, not the system prompt. This keeps the system prompt stable across subjects, which improves caching efficiency and reduces token variance.
The prompt includes explicit guardrails injected as numbered constraints:
- Do not assert any outcome with certainty language (100%, definitely, absolutely, certainly).
- Do not speculate on romantic relationships.
- All Ten God names must match the extracted labels exactly — if the chart shows Friend (比肩), write Friend (比肩), not Robber (劫財).
- End every article with the standard disclaimer line.
Stage 4: Draft Generation
Claude Sonnet generates the draft in a single call. Temperature 0.7 for analytical channels (Dev.to, Naver expert), 0.9 for social channels (Instagram, Threads).
The nine required content blocks are specified in the prompt as a numbered list with word-count targets per block. The model generally respects these when the blocks are explicit and the total target is within a comfortable range.
One consistent failure mode: the model occasionally collapses the infographic block into prose when the chart data is complex. The fix was adding an explicit format example for that block in the prompt, showing the exact Markdown table structure expected.
Stage 5: QA Agent
The QA agent runs five automated checks on the draft:
Field echo check: Every extracted Ten God label must appear in the draft at least once with its Hanja in parentheses. Missing fields are flagged.
Forbidden phrase scan: Regex scan for certainty language, prediction language, and gossip-adjacent phrases. Any match blocks publish.
Pillar consistency check: The article must not assign a star to a pillar that the chart data does not support. This is done via entity extraction on the draft followed by lookup against the ChartObject.
Length check: Word count within ±10% of channel target.
Disclaimer presence check: Final paragraph must contain the standard disclaimer string.
Pass rate in production: ~91% on first draft. Most failures are field echo misses or length violations, both of which trigger a targeted revision call rather than a full regeneration.
Stage 6: Channel Formatter + Stage 7: CTA Injector
The formatter applies channel-specific transformations: frontmatter injection for Dev.to, <div> wrapping for Naver, caption truncation for Instagram. No content changes — purely structural.
The CTA injector appends a standardized block pointing to runartree.com with UTM parameters encoding the subject, trigger, and channel. This is injected after human review approval, not before, so the reviewer sees clean content.
To make the pipeline concrete, here is the structured data the system extracted from the sample chart, rendered as a reference table:
| Pillar | Stem (天干) | Branch (地支) | Stem God | Branch God | Phase | Key Stars |
|---|---|---|---|---|---|---|
| Year (年柱) | 癸 | 酉 | Seven Killings (偏官) ★ | Indirect Wealth (偏財) | Longevity (長生) | Heavenly Noble (天乙貴人), Literary Star (文昌貴人) |
| Month (月柱) | 丁 | 巳 | Friend (比肩) ★ | Robber (劫財) | Emperor (帝旺) | Empty Void (空亡) |
| Day (日柱) | 丁 | 酉 | Friend (比肩) ★ | Indirect Wealth (偏財) | Longevity (長生) | Heavenly Noble (天乙貴人) |
| Hour (時柱) | 丙 | 午 | Robber (劫財) | Friend (比肩) | — | Goat Blade (羊刃殺), Peach Blossom (桃花殺) |
| 2026 Annual | 丙 | 午 | Robber (劫財) | — | — | — |
| Decade Luck | 庚 | 申 | Direct Wealth (正財) | Direct Wealth (正財) | Bath (沐浴) | Monthly Virtue Noble (月德貴人) |
Observation the pipeline flags for editorial emphasis: The 2026 annual luck stem-branch 丙午 is identical to the Hour Pillar (時柱) stem-branch. This stem-branch echo between annual luck and a natal pillar is a structurally notable pattern — the pipeline tags it as a PILLAR_ECHO event and routes it to the "reversal" content block. As Zi Ping Zhen Quan (子平真詮, He Lun-Qing, Ch. 14) notes, when the annual stem reinforces a natal pillar's energy rather than introducing new qi, the year tends to intensify existing patterns rather than open new directions — amplification, not transformation.
Reversal: The Star Nobody Talks About
The most editorially interesting extraction from this chart is not the prominent Heavenly Noble (天乙貴人) appearing on both Year and Day Pillars — that gets covered in every Saju article about this subject.
The structural surprise is the Empty Void (空亡) on the Month Pillar (月柱) branch 巳. The Month Pillar governs the prime working years and external social performance. An Empty Void here does not indicate absence of achievement — it indicates that conventional metrics of "success" may feel hollow or misaligned to the subject even when externally visible. The pipeline flags this as a REVERSAL_CANDIDATE because it contradicts the otherwise strong Emperor (帝旺) phase on the same branch. High-phase energy + Empty Void on the same branch is a rare structural tension that generates genuine editorial nuance.
This is the kind of insight that a purely template-based system misses. The QA agent is specifically designed to surface these contradictions rather than smooth them over.
Lessons Learned
Verbatim extraction is non-negotiable. Any normalization step between Vision extraction and article generation introduces drift. Keep raw labels raw until the article draft itself.
Two-pass extraction beats one-pass. Main chart data and auxiliary stars in separate Vision calls. The quality improvement justifies the added latency and cost.
Logic validation catches what Vision misses. The Ten God recomputation check from Day Stem catches ~80% of extraction errors that pass the structural schema check.
Channel config as code, not prompt text. Storing channel rules in Python dataclasses rather than prompt strings makes them testable, versionable, and auditable.
Human review is a feature, not a bug. The pipeline's value is reducing the reviewer's cognitive load from 90 minutes to 5 minutes — not eliminating review entirely.
Summary
- Claude Vision + two-pass extraction + Python schema validation is a reliable stack for glyph-heavy structured document ingestion.
- Verbatim field extraction with logic-layer verification eliminates the most damaging error class in this domain.
- Channel-specific prompt configs and post-generation QA agents make multi-channel scaling tractable without proportional quality degradation.
Try the Live Chart Tool
The chart extraction and content pipeline described here powers the tools at runartree.com. If you are building similar document-to-content pipelines and want to see the output format live, the site runs the full stack in production.
This article describes a technical content automation pipeline. All chart interpretations generated by the system are analytical pattern observations based on classical Chinese metaphysical frameworks and do not constitute predictions, guarantees, or professional advice of any kind.
Project link
This article is based on an automated content workflow for a Korean Saju platform.
- Website: https://runartree.com?utm_source=devto&utm_medium=article&utm_campaign=saju_automation
- Stack: Python, Claude Vision, channel-specific formatting, content QA
- Domain: Korean Saju / Bazi content automation
The key lesson is simple: generation alone is not enough. A useful publishing pipeline also needs formatting, QA, tracking links, and channel-specific editorial rules.
Bazi interpretation. Not medical, legal, or investment advice.




Top comments (0)