DEV Community

HYUN SOO LEE
HYUN SOO LEE

Posted on

Automating Korean Saju Content at Scale: A Python + Claude Vision Pipeline

Automating Korean Saju Content at Scale: A Python + Claude Vision Pipeline

The Hook: Why Saju Is a Surprisingly Hard Automation Problem

Korean Saju (四柱推命) content is exploding — millions of monthly searches, celebrity chart analyses going viral, and a growing English-language audience curious about East Asian astrology. But here is the problem: the raw data lives inside image-rendered Manse-ryeok charts (萬歲曆, the Korean birth-pillar calculator), full of mixed Hanja (漢字), color-coded circles, and positional metadata that a naive OCR pass completely butchers.

We needed a pipeline that could ingest one of these chart screenshots, extract every meaningful field without hallucinating, apply domain-specific validation, and output channel-formatted Markdown — for Dev.to, for newsletters, for short-form social — all without a human editor in the loop. This article documents exactly how we built it, using Park Seo-jun's publicly available birth data (1988-12-16, male) as our canonical test case throughout development.


The Problem in Detail

A typical Manse-ryeok screenshot contains:

  • Four pillars (年柱 · 月柱 · 日柱 · 時柱) each with a Heavenly Stem (天干) and Earthly Branch (地支), rendered as colored circle glyphs
  • Ten-God labels (十星) in Korean Hangul above and below each glyph: 比肩(비견), 劫財(겁재), 食神(식신), 傷官(상관), 偏財(편재), 正財(정재), 偏官(편관), 正官(정관), 偏印(편인), 正印(정인)
  • 12-Stage labels (十二運星): 장생, 목욕, 관대, 건록, 제왕, 쇠, 병, 사, 묘, 절, 태, 양
  • Shen Sha tags (神殺): 도화살, 양인살, 백호살, 화개살, 천을귀인, 천덕귀인, 월덕귀인, 문창귀인 — each with bracketed sub-descriptions
  • Current Grand Luck (大運) and Annual Luck (歲運) columns rendered separately

Standard Tesseract OCR scores roughly 34% field accuracy on these images in our benchmarks. The Hanja glyphs inside colored circles are anti-aliased against dark backgrounds, and positional semantics (which Ten-God belongs to which pillar) are encoded purely by 2-D layout — invisible to a linear text extractor.


Pipeline Architecture

[Image Upload]
     │
     ▼
[Claude Vision — Structured Extraction Prompt]
     │
     ▼
[JSON Schema Validator]
     │
     ▼
[Domain Rule Engine (Bazi Logic Checks)]
     │
     ▼
[Channel Formatter (Dev.to / Newsletter / Short-form)]
     │
     ▼
[QA Gate — Guardrail Checklist]
     │
     ▼
[Output Markdown / HTML / JSON]
Enter fullscreen mode Exit fullscreen mode

Each stage is a discrete Python module. The pipeline is orchestrated by a simple FastAPI endpoint that accepts a multipart image upload and a channel query parameter.


Stage 1 — Claude Vision Extraction

The Prompt Strategy

Naive prompts like "extract the Saju chart" produce inconsistent JSON. After 40+ iterations, our production prompt uses positional anchoring and explicit enumeration constraints:

You are a Manse-ryeok (萬歲曆) chart parser.
The image contains exactly four pillars: 年柱(Year), 月柱(Month), 日柱(Day), 時柱(Hour).
For EACH pillar extract:
  - heavenly_stem: one Hanja character
  - earthly_branch: one Hanja character
  - stem_ten_god: EXACT Korean label as printed (e.g. 정재, 비견, 식신)
  - branch_ten_god: EXACT Korean label as printed
  - twelve_stage: EXACT Korean label as printed
  - shen_sha: array of EXACT Korean labels as printed, with bracketed descriptions verbatim
Also extract: current_grand_luck (天干, 地支, stem_ten_god, branch_ten_god),
annual_luck_2026 (天干, 地支, stem_ten_god, branch_ten_god, shen_sha array).
Return ONLY valid JSON matching the schema below. Do not infer, translate, or normalize any label.
If a field is not visible, return null. Never substitute a similar label.
Enter fullscreen mode Exit fullscreen mode

The critical instruction is "EXACT Korean label as printed." Without it, Claude occasionally normalizes 정재 to 편재 when it "knows" the theoretical Ten-God for that stem combination — which breaks our downstream domain validator and, more importantly, misrepresents the chart author's own computed output.

Extraction Result for Our Test Case

From Park Seo-jun's chart image, Claude Vision returned (condensed):

Year Pillar (年柱): 戊(무) / 辰(진) · stem: 正財(정재) · branch: 正財(정재) · stage: 관대 · shen_sha: [양인살, 백호살, 화개살]

Month Pillar (月柱): 甲(갑) / 子(자) · stem: 劫財(겁재) · branch: 偏印(편인) · stage: 병 · shen_sha: [천을귀인, 천덕귀인]

Day Pillar (日柱 — self): 乙(을) / 巳(사) · stem: 比肩(비견) · branch: 傷官(상관) · stage: 목욕 · shen_sha: [천덕귀인]

Hour Pillar (時柱): 壬(임) / 午(오) · stem: 正印(정인) · branch: 食神(식신) · stage: 장생 · shen_sha: [문창귀인, 월덕귀인, 도화살]

Current Grand Luck (大運 — age 30): 戊(무) / 辰(진) · stem: 正財(정재) · branch: 正財(정재) · stage: 관대

2026 Annual Luck (歲運): 丙(병) / 午(오) · stem: 傷官(상관) · branch: 食神(식신) · shen_sha: [없음 extracted from image column]


Stage 2 — JSON Schema Validation

We use jsonschema with a strict schema. Every stem_ten_god and branch_ten_god must match an allowlist of exactly 10 Korean strings. Any extraction returning "편재" when the image clearly shows "정재" fails validation and triggers a re-prompt with the specific field flagged.

TEN_GOD_ALLOWLIST = [
    "비견", "겁재", "식신", "상관",
    "편재", "정재", "편관", "정관",
    "편인", "정인"
]
Enter fullscreen mode Exit fullscreen mode

Rejection rate on first pass: ~12% of images. After one re-prompt with field-level feedback: ~2%. We log all failures for fine-tuning a future dedicated vision model.


Stage 3 — Domain Rule Engine

This is where Bazi domain knowledge becomes code. The rule engine runs logical consistency checks:

Rule R-01 — Day Master Identity: The Day Pillar stem Ten-God must always be 比肩(비견) or null. If Claude returns anything else, reject.

Rule R-02 — Stem/Branch Polarity Consistency: 正財(정재) must appear on Yang stems conquering Yin elements, 偏財(편재) on same-polarity conquest. We encode the full 天干 conquest table and flag mismatches as warnings (not hard failures, since the chart tool's own logic may differ).

Rule R-03 — 12-Stage Sequence Integrity: The twelve stages follow a fixed cycle per branch. We verify that the extracted stage label is plausible for the given stem/branch pair.

Rule R-04 — Shen Sha Cross-Reference: 도화살(Peach Blossom 桃花殺), 양인살(Goat Blade 羊刃殺), 천을귀인(Heavenly Noble 天乙貴人), 문창귀인(Literary Star), 백호살(White Tiger), 화개살(Canopy Star), 천덕귀인(Heavenly Virtue Noble), 월덕귀인(Monthly Virtue Noble) each have classical branch-based calculation rules. We flag extractions where a Shen Sha appears on a pillar that should not carry it by classical formula — logged as a "chart-tool variance" rather than an error, since different Korean Saju apps use slightly different Shen Sha assignment rules.

For Park Seo-jun's chart: 문창귀인(Literary Star) on the Hour Pillar stem, 도화살(Peach Blossom 桃花殺) on the Hour Pillar branch, and 천을귀인(Heavenly Noble 天乙貴人) on the Month Pillar all passed R-04 cleanly.


Stage 4 — Channel Formatter

Each channel has a FormatterConfig dataclass:

@dataclass
class FormatterConfig:
    channel: str          # "devto" | "newsletter" | "shortform"
    language: str         # "en" | "ko" | "mixed"
    hanja_inline: bool    # include Hanja alongside romanization
    word_target: int
    cta_template: str
    prohibited_phrases: list[str]  # guardrail strings
    certainty_cap: str    # max certainty language allowed
Enter fullscreen mode Exit fullscreen mode

For Dev.to (devto), language="en", hanja_inline=True, word_target=1500, and prohibited_phrases includes ["100%", "definitely", "absolutely", "certainly", "guaranteed"] — enforcing our probabilistic framing guardrail.

The formatter takes the validated JSON and a topic template, then calls a second Claude completion (text only, no vision) with the structured data injected as a system context block. The channel config is serialized into the prompt as explicit constraints.


Stage 5 — QA Gate

Before any output is written to disk or pushed to an API, a QAChecker runs eight assertions:

  1. Word count within ±10% of target
  2. Zero occurrences of prohibited certainty phrases
  3. Zero occurrences of gossip-pattern strings (hardcoded regex for relationship speculation)
  4. All Hanja characters present where hanja_inline=True
  5. CTA URL present exactly once
  6. Disclaimer line present at document end
  7. No code fences in Markdown output (our formatter sometimes leaks them)
  8. Frontmatter block valid YAML

Failure on any assertion routes the output to a human review queue rather than auto-publishing.


[INFO_GRAPHIC] — Pipeline at a Glance

┌─────────────────────────────────────────────────────────┐
│  INPUT: Manse-ryeok chart image (PNG/JPG)               │
│  + channel param + topic template                       │
└────────────────────┬────────────────────────────────────┘
                     │
          ┌──────────▼──────────┐
          │  Claude Vision      │  Structured JSON extraction
          │  Extraction Pass    │  ~88% first-pass accuracy
          └──────────┬──────────┘
                     │ fail → re-prompt (field-level feedback)
          ┌──────────▼──────────┐
          │  JSON Schema        │  Ten-God allowlist, null checks
          │  Validator          │
          └──────────┬──────────┘
                     │
          ┌──────────▼──────────┐
          │  Domain Rule        │  R-01 Day Master, R-02 Polarity,
          │  Engine             │  R-03 12-Stage, R-04 Shen Sha
          └──────────┬──────────┘
                     │ warnings logged, hard fails rejected
          ┌──────────▼──────────┐
          │  Channel Formatter  │  FormatterConfig → Claude text
          │  (text completion)  │  completion with injected JSON
          └──────────┬──────────┘
                     │
          ┌──────────▼──────────┐
          │  QA Gate            │  8 assertions, auto-reject on fail
          └──────────┬──────────┘
                     │
          ┌──────────▼──────────┐
          │  OUTPUT             │  Markdown / HTML / JSON
          │  (or human queue)   │
          └─────────────────────┘
Enter fullscreen mode Exit fullscreen mode

The Reversal: The Shen Sha Consistency Problem

Here is the most interesting engineering discovery from this project. We initially assumed Shen Sha (神殺) labels were deterministic — given a stem and branch, you can calculate them from classical tables, so they should be a reliable validation signal.

They are not. At least not across Korean Saju apps.

When we tested our R-04 validator against 200 chart images from three different Korean Manse-ryeok tools, we found 23% disagreement on Shen Sha assignments for the same birth data. 도화살(Peach Blossom 桃花殺) placement varied the most — some tools assign it by Day Branch, others by Year Branch, others by the strongest pillar branch. 양인살(Goat Blade 羊刃殺) had similar variance.

The implication for content automation: you cannot use Shen Sha as a hard validation rule. You must treat the chart image as the authoritative source and extract verbatim, even when classical tables disagree. Our R-04 now logs variances for analytics but never rejects on Shen Sha mismatch alone. This also reinforced our core prompt instruction: extract exactly what is printed, never infer.


Lessons Learned

1. Positional anchoring beats semantic description in vision prompts. Telling Claude "the Hour Pillar is the leftmost column with a blue header tag" outperforms "find the 時柱." Layout is data.

2. Two-pass is worth the latency. Vision extraction + separate text formatting, rather than one combined prompt, improved consistency significantly. The vision pass focuses on reading; the text pass focuses on writing.

3. Domain allowlists are your best friend. A 10-item Ten-God allowlist catches more errors than any amount of prompt engineering alone.

4. Guardrails must be mechanical, not instructional. Telling the LLM "do not use certainty language" helps but does not guarantee it. The QA Gate's regex check is the actual enforcement mechanism.

5. Verbatim extraction is a product decision. When the chart says 正財(정재), your pipeline must say 正財(정재) — even if your domain model thinks it should be 偏財(편재). The chart tool's output is the contract with the user.


Summary

  • We built a five-stage pipeline (Vision Extraction → Schema Validation → Domain Rules → Channel Formatting → QA Gate) that converts Manse-ryeok chart images into publication-ready content with ~98% field accuracy after re-prompting.
  • The hardest problem is not OCR — it is positional semantics and Shen Sha variance across Korean Saju tools, both of which require domain-specific engineering rather than general vision capability.
  • Verbatim extraction with mechanical QA gates, not instructional guardrails alone, is the architecture that makes this reliable at scale.

Try the live demo and explore the full chart analysis toolkit at runartree.com


This article uses a publicly available birth date for technical demonstration purposes. All Bazi interpretations are probabilistic frameworks based on classical Chinese metaphysical systems and should not be taken as definitive predictions of any individual's life outcomes.


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)