DEV Community

HYUN SOO LEE
HYUN SOO LEE

Posted on

Building an Automated Saju (Korean Bazi) Content Pipeline: Vision Extraction, Channel Formatting, and QA at Scale

The Problem: Manual Saju Content Doesn't Scale

Korean Saju (四柱, Four Pillars of Destiny) content is exploding across YouTube, blogs, and short-form video. The demand is real. The bottleneck is equally real: every piece of content requires a human practitioner to read a manse (萬歲曆, traditional calendar chart), extract structured data — Day Pillar (日柱), Ten Gods (十星), 12-phase cycle (十二運星), auspicious and inauspicious spirits (神殺) — and then rewrite that data into channel-appropriate copy.

For a single subject like a 2026 annual reading, a practitioner might produce:

  • A long-form YouTube script (~2,500 words)
  • A blog post (~1,500 words)
  • A short-form vertical video script (~300 words)
  • A dev-facing technical breakdown (this article)

Doing that manually for every content subject, every year, every channel, is a full-time job. This post documents the automation pipeline we built at Runartree to solve it.


What the Pipeline Actually Does

The pipeline takes two inputs:

  1. Screenshot images — manse calendar renders, input confirmation screens, and long-form result pages exported from our Saju web app
  2. A structured content brief — subject name, trigger date, topic, channel target, word count, required content blocks

It outputs channel-formatted markdown or script copy, validated against a QA checklist before it ever reaches a human editor.

Here is the high-level flow:

[Screenshot images] → [Claude Vision extraction] → [Structured JSON]

[Prompt assembly] → [Claude generation] → [Raw markdown]

[QA checks] → [Channel formatter] → [Final output]


Stage 1: Vision Extraction

The hardest part of the pipeline is not generation — it is reliable structured extraction from manse calendar screenshots.

A typical manse image (see 02_manse_calendar_complete.png in our test set) contains:

  • Four pillars arranged in columns: 時柱 (Hour), 日柱 (Day), 月柱 (Month), 年柱 (Year)
  • Each pillar has: Heavenly Stem (天干) hanja glyph, Earthly Branch (地支) hanja glyph, Ten God label in Korean hangul, 12-phase label in Korean hangul
  • Auspicious/inauspicious spirit tags rendered as colored badge chips below each pillar
  • A separate column for the current Grand Luck Cycle (大運) and Annual Luck (歲運)

The extraction prompt we use is structured as a two-pass system.

Pass 1 — Raw OCR with position anchoring:

You are a manse calendar OCR engine.
For each of the six columns (Hour, Day, Month, Year, Grand Luck, Annual Luck):
Extract: heavenly stem hanja, earthly branch hanja,
heavenly stem ten-god label (Korean, verbatim),
earthly branch ten-god label (Korean, verbatim),
12-phase label (Korean, verbatim),
all spirit/神殺 badge labels (Korean, verbatim, with column position).
Return as JSON. Do not translate or interpret. Copy text exactly as rendered.

Pass 2 — Verification cross-check:

We re-send the image with the Pass 1 JSON and ask Claude to flag any field where the extracted hanja does not match the visible glyph. This catches common OCR errors like 壬/王 confusion or 己/已 confusion — both of which appear in real manse images.

For the test subject in this pipeline run (양력 1996년 1월 16일, 여성, 모름시), the verified extraction produced:

  • 年柱: 乙亥 — 上官(상관) / 比肩(비견)
  • 月柱: 己丑 — 正官(정관) / 正官(정관)
  • 日柱: 壬子 — [日主] / 劫財(겁재)
  • 時柱: 丙午 — 偏財(편재) / 正財(정재)
  • Grand Luck (大運): 壬辰 — 比肩(비견) / 偏官(편관)
  • Annual Luck 2026 (歲運): 丙午 — 偏財(편재) / 正財(정재)

Extracted 神殺 included: 天醫星(천의성) on Day Branch, 羊刃殺(양인살) on multiple pillars, 鬼門關殺(귀문관살) on Month Branch and Hour Branch, 桃花殺(도화살) on Day Branch, 帝旺(제왕) phase on Day Branch.

One critical guard in the extraction prompt: verbatim label preservation. If the image renders 正財 on the Hour Branch heavenly stem, the JSON must say 正財 — not 偏財, not "wealth star." Downstream generation prompts are built on this JSON, and a single mislabeled Ten God corrupts the entire interpretation chain.


Stage 2: Structured JSON Schema

After extraction, we normalize into a typed schema:

@dataclass
class Pillar:
position: str # "year" | "month" | "day" | "hour"
stem_hanja: str # e.g. "壬"
branch_hanja: str # e.g. "子"
stem_ten_god: str # verbatim Korean label
branch_ten_god: str # verbatim Korean label
twelve_phase: str # verbatim Korean label
spirits: list[str] # verbatim Korean badge labels

@dataclass
class ManseChart:
subject_name: str
gender: str
solar_date: str
lunar_date: str
pillars: list[Pillar] # year, month, day, hour
grand_luck: Pillar
annual_luck: Pillar
body_strength: str # "신강" | "신약"
element_ratio: dict # {"水": 40, "火": 25, "土": 25, ...}

This schema is serialized to JSON and passed directly into generation prompts. No free-text interpretation at this stage. The schema enforces that generation prompts receive only what the image actually contained.


Stage 3: Prompt Strategy for Long-Form Generation

Long-form Saju content has a specific structure that readers expect. We encode this as a required block manifest in the generation prompt:

Required blocks (in order):

  1. Hook — 3 lines, reference one specific chart element
  2. One-line conclusion — Day Pillar + dominant Ten God + 2026 Annual Luck
  3. Bazi principles — cite at least 3 elements: stems, branches, Ten Gods, spirits
  4. Classical reference — one line from 滴天髓/子平真詮/窮通寶鑑/淵海子平 with source
  5. Modern interpretation — connect Annual Luck and Grand Luck to subject's domain
  6. Infographic placeholder — [INFO_GRAPHIC] tag
  7. Reversal insight — one unexpected interaction (spirit, combination, clash)
  8. Three-line summary
  9. CTA — runartree.com

The generation prompt also carries a guardrail block:

Guardrails:

  • No speculation beyond the extracted chart
  • No absolute certainty language (100%, definitely, certainly, guaranteed)
  • No gossip or personal life assertions
  • Disclaimer line required at end
  • Use English + hanja notation for all technical terms

This guardrail block is checked post-generation by a separate QA pass (Stage 5).


Stage 4: Channel Formatting

The same structured JSON and the same generation output get reformatted per channel. The formatter is a simple Python class with per-channel render methods:

class ChannelFormatter:
def render_devto(self, content: GeneratedContent) -> str:
# YAML frontmatter + dev.to markdown
# Technical framing: pipeline, not fortune-telling
...

def render_blog(self, content: GeneratedContent) -> str:
    # Korean-language long-form
    # Dalbitnaemu tone guidelines applied
    ...

def render_shorts_script(self, content: GeneratedContent) -> str:
    # 300-word vertical video script
    # Hook in first 3 seconds, single CTA
    ...
Enter fullscreen mode Exit fullscreen mode

Channel-specific rules are encoded as formatter config, not baked into the generation prompt. This means the same generation output can be reformatted without re-running the expensive Vision + generation steps.

For dev.to specifically, the formatter:

  • Strips all Korean hangul from body text
  • Converts Ten God labels to English + hanja format: Direct Wealth(正財), Indirect Wealth(偏財), etc.
  • Ensures frontmatter tags include bazi, kpop, saju, korea
  • Inserts the [INFO_GRAPHIC] block as a placeholder comment for the design team

Stage 5: QA Checks

Before any output reaches a human editor, it runs through an automated QA checklist:

Check Method
Required blocks present Regex scan for block headers
Ten God labels match extracted JSON String diff against schema
Guardrail violations Claude classification pass
Word count within ±10% of target len(content.split())
Hanja notation format consistent Regex: [A-Za-z]+\([^\)]+\)
No Korean hangul in English-channel output Unicode range check \uAC00-\uD7A3
CTA present String search for runartree.com
Disclaimer present String search for disclaimer pattern

The guardrail violation check deserves special mention. We send the generated text back to Claude with a focused classification prompt:

Does this text contain any of the following? Answer YES/NO for each:

  1. Absolute certainty language (must, will definitely, 100%)
  2. Personal life speculation beyond the chart
  3. Anxiety-inducing predictions without mitigation
  4. Missing disclaimer

If any answer is YES, the output is flagged for human review rather than auto-published.


[INFO_GRAPHIC]

Suggested asset: A four-column pillar diagram showing 壬子 Day Pillar at center, 丙午 Annual Luck 2026 on the right, with a red clash arrow between 子 and 午 branches, and a blue reinforcement arrow between 壬 Grand Luck stem and 壬 Day Stem. Labels in English + hanja. Brand colors: Runartree dark navy + gold.


The Reversal: The Hour Pillar Clash Is Already in the Chart

Here is the part that surprises most people who look at this chart for the first time.

The 2026 Annual Luck pillar is 丙午. The Hour Pillar (時柱) of the natal chart is also 丙午 — identical heavenly stem and earthly branch. This means the Annual Luck is not introducing a new energy into the chart: it is doubling an energy that already exists natively.

When 午(오) appears in both the Hour Branch and the Annual Luck simultaneously, the clash between 午 and the Day Branch 子 is not a single external pressure — it is a resonance effect. The chart's own Hour Pillar amplifies the Annual Luck's clash against the Day Branch. In classical terms this is called 伏吟(복음), a "hidden echo," and it tends to intensify both the opportunity signal (Direct Wealth 正財 activating strongly) and the instability signal (子午沖 destabilizing the daily rhythm) beyond what either factor would produce alone.

This is why the QA-flagged interpretation for this chart emphasizes energy management over opportunity chasing. The Direct Wealth(正財) and Indirect Wealth(偏財) signals are genuinely strong — but the structural amplification means the cost of overextension is also amplified.


Lessons Learned

1. Verbatim extraction is non-negotiable. The single most common failure mode in early pipeline versions was the model "correcting" a Ten God label based on its own interpretation rather than copying the image text. A 正財 relabeled as 偏財 produces a fundamentally different interpretation. Solve this with explicit verbatim instructions and a verification pass.

2. Schema before generation. Passing structured JSON to the generation prompt produces dramatically more consistent output than passing image descriptions. The schema enforces boundaries. Free-text descriptions invite hallucination.

3. Channel formatting is cheap; re-generation is expensive. Invest in the formatter layer. A single generation run should feed multiple channels. Re-running Vision + generation because a channel format was wrong is a waste of API budget.

4. QA catches what prompts miss. Even well-crafted generation prompts occasionally produce guardrail violations. The automated QA pass is not optional — it is the last line before human review.

5. The disclaimer is load-bearing. In Saju content, the disclaimer is not boilerplate. It is the structural element that separates a content automation pipeline from a prediction service. Every output, every channel, every time.


Summary

  • Claude Vision + structured JSON extraction is reliable for manse calendar screenshots when verbatim extraction is enforced and a verification pass is included
  • Required block manifests and guardrail blocks in generation prompts produce consistent, channel-appropriate long-form content
  • Per-channel formatters decouple generation from presentation, reducing API costs significantly
  • Automated QA on block presence, Ten God label consistency, guardrail violations, and word count catches the majority of output failures before human review

Try the Pipeline Output

The Saju content automation pipeline described in this article powers the readings at runartree.com. If you are building content automation in a specialized domain — astrology, numerology, TCM, or any knowledge-dense vertical — the extraction-schema-generation-QA pattern transfers directly.


This article describes a content generation pipeline and uses a publicly known public figure's birth data as a technical test case for chart extraction accuracy. All interpretations are generated outputs for pipeline demonstration purposes only. Saju readings are a traditional cultural reference framework and do not constitute predictions, advice, or guaranteed outcomes of any kind.


Project link

This article is based on an automated content workflow for a Korean Saju platform.

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)