10 Heavenly Stems, 12 Earthly Branches --- Translating Saju into TypeScript

 


When a developer first tries to express Four Pillars of Destiny (Saju) in code, the very first question is: what data structure should represent these Chinese characters? Translating the Yin-Yang and Five Elements system embedded in 22 characters into TypeScript types demanded deeper design thinking than I expected.

22 Characters as the Starting Point

Everything in Saju begins with 22 characters. Ten Heavenly Stems (Cheongan, 天干) and twelve Earthly Branches (Jiji, 地支).

The Stems are: Gap (甲), Eul (乙), Byeong (丙), Jeong (丁), Mu (戊), Gi (己), Gyeong (庚), Sin (辛), Im (壬), Gye (癸). The Branches are: Ja (子), Chuk (丑), In (寅), Myo (卯), Jin (辰), Sa (巳), O (午), Mi (未), Sin (申), Yu (酉), Sul (戌), Hae (亥).

Each character carries assigned Yin-Yang polarity and Element. Gap is Yang Wood, Eul is Yin Wood, Byeong is Yang Fire, Jeong is Yin Fire. The ten Stems map two to each of the Five Elements --- Wood, Fire, Earth, Metal, Water --- one Yang and one Yin. The Branches follow the same pattern.

These assignments were established thousands of years ago, and they do not change. Gap will not suddenly become Yin Wood tomorrow. This immutability became the key design driver for the TypeScript representation.

Guaranteeing Immutability with as const

TypeScript has an as const keyword that locks values into read-only literal types. This feature turned out to be a perfect match for the data characteristics of Saju.

The foundational data of Saju is "a mapping that never changes." Gap is Yang Wood. Eul is Yin Wood. Ja is Yang Water. Chuk is Yin Earth. These are reference data. They will not change at runtime, and they must not.

Using as const guarantees this immutability at the type level. If someone accidentally writes code that modifies a mapping, the compiler catches it. "Data established thousands of years ago still holds today" --- this domain characteristic is now enforced by the code's type system.

This was AI's suggestion. After reading the domain model document, Claude recommended: "This data is immutable, so as const is a good fit." The moment I heard it, I felt domain characteristics and technical choices clicking into place. It was the first case where AI leveraged domain knowledge and programming knowledge simultaneously to propose a design decision.

Designing the Stem Types

How to represent the 10 Heavenly Stems? The simplest approach is a string array. But TypeScript's type system lets you build something far safer.

Each Stem has attributes: Chinese character, Korean reading, Element, and Yin-Yang polarity. The relationships between these attributes are fixed. The character Gap (甲) is always read "gap," and it is always Yang Wood. Expressing these constraints as types means invalid combinations are caught at compile time.

If someone accidentally assigns Fire as Gap's element, it is a type error. Even if code review misses it, the compiler will not. Because every subsequent calculation depends on these 22 characters and their attribute mappings being correct, this level of safety is not overkill.

Designing this type structure with AI was efficient. I described the domain: "These are the Stem attributes, and these are the constraints." AI proposed how to express those constraints using TypeScript's type system. It was as if a domain expert and a TypeScript expert were collaborating in a single conversation.

The Additional Complexity of Branches

Branches are more complex than Stems. Each of the 12 characters has an Element and Yin-Yang polarity, just like Stems. But Branches carry one additional concept that Stems do not: Hidden Stems (Jijanggan, 地藏干).

Hidden Stems are literally "Heavenly Stems concealed inside Earthly Branches." Each Branch hides 1 to 3 Stems. For example, In (寅) conceals Gap (甲), Byeong (丙), and Mu (戊). Sa (巳) conceals Byeong (丙), Gyeong (庚), and Mu (戊).

Why does this matter? In Saju analysis, looking only at the visible Stems and Branches gives an incomplete picture. Factoring in the Stems hidden within each Branch enables deeper analysis. Hidden Stems map to interpretive concepts like "concealed tendencies" and "unrevealed potential."

In code terms, this means Branch types must contain references to Stem types. It is not simple attribute mapping but a structural relationship between types. Branches "containing" Stems. Expressing this relationship cleanly in the type system was the second design challenge.

First School Divergence: Hidden Stems of Ja (子)

While implementing Hidden Stems, I encountered the project's first real-world school divergence. The Hidden Stems of the Branch Ja (子).

One school says Ja contains both Im (壬) and Gye (癸). Another school says only Gye. This was the first concrete instance of the "domain with multiple right answers" discussed in Part 2.

It matters because Hidden Stems feed into Ten Gods analysis. Whether Im exists inside Ja or not changes the Ten Gods relationships derived from that Branch. The depth and direction of interpretation can shift.

The design principle from Part 2 --- "sensible defaults + flexible structure" --- was applied here for the first time. The default includes both Im and Gye, but a configuration option allows switching to the alternative. The code structure itself accommodates school-based differences.

Representing the 60 Stem-Branch Cycle

When 10 Heavenly Stems meet 12 Earthly Branches, you get the 60 Stem-Branch Cycle (Sexagenary Cycle). Starting from Gap-Ja (甲子) and ending at Gye-Hae (癸亥) --- 60 Stem-Branch pairings. This is the basic unit of the Four Pillars. Each of the four pillars (Year, Month, Day, Hour) is one of these 60 combinations.

The origin of the number 60 is interesting. It is the least common multiple of 10 and 12. Pairing Stems and Branches in order --- Gap-Ja, Eul-Chuk, Byeong-In --- cycles through all possibilities and returns to Gap-Ja at the 60th pairing. That is why a 60th birthday is called "hwangap" (環甲) in Korean --- your birth year's Stem-Branch pair has come full circle.

In code, the question was whether to hardcode 60 string pairs in a static array or dynamically generate them from Stem and Branch combinations. The answer was both. The static array provides fast lookups; the dynamic generation logic serves validation and flexibility. Both are needed.

Representing Yin-Yang and Five Elements as Types

Yin-Yang is straightforward. Yang (陽) and Yin (陰), two values. A union type or enum in TypeScript handles this easily.

The Five Elements are five values: Wood (木), Fire (火), Earth (土), Metal (金), Water (水). That also sounds simple. But the Elements carry generating (producing) and overcoming (controlling) relationships, and these relationships are a core axis of Saju analysis. Wood generates Fire, Fire generates Earth, Earth generates Metal, Metal generates Water, Water generates Wood. For overcoming: Wood overcomes Earth, Earth overcomes Water, Water overcomes Fire, Fire overcomes Metal, Metal overcomes Wood.

How to encode these relationships in the type system is the subject of Part 5. For now, the key point is that Yin-Yang and Elements serve as attributes of Stems and Branches.

How AI "Translated" Domain Knowledge into Code

The most impressive aspect of this phase was watching AI translate domain knowledge into code structures.

I explained the concepts: "Each of the 10 Heavenly Stems has an assigned Yin-Yang and Element, and these never change." AI converted that explanation into TypeScript: as const objects, union types, mapping functions.

In this translation process, AI acted as a bridge between a domain expert's language and a developer's language. "Immutable assignments" became as const. "Yin-Yang distinction" became a union type. "Stems inside Branches" became nested object structures. Each domain concept mapped to its most fitting TypeScript pattern.

This was possible because of the design documents. With the domain model document giving AI a full-system understanding, implementing individual elements maintained consistency with the whole. The "mechanism by which documents improve code quality" emphasized in Part 3 was concretely proven here.

Handling Chinese Characters in Code

There was a practical concern too. Should Chinese characters be used directly in code, or should they be converted to romanized equivalents?

Using 甲乙丙丁 directly as keys could hurt readability. Typing Chinese characters is also inconvenient. On the other hand, romanized forms like "gap," "eul," "byeong" sever the connection to the domain.

The conclusion: use Chinese characters as the primary identifier, but include romanized forms and Korean readings as properties. In practice, code accesses data mainly through array indices or constant references, so you rarely type Chinese characters directly. But the data itself should contain the original characters. In Saju, Chinese characters are not mere labels --- they are the essence of the domain.

Data Table Accuracy

Transferring the 10 Stems, 12 Branches, Hidden Stem assignments, and the 60 Cycle list into code --- accuracy was the top priority. A single wrong character throws off every downstream analysis.

This is where AI's strength shone. Asking Claude to "list all 10 Heavenly Stems with Chinese character, Korean reading, Element, and Yin-Yang in a table" produces an accurate mapping table in one shot. Same for Hidden Stems and the 60 Cycle sequence. Because the foundational data of Saju is a well-established system, AI is particularly strong at generating it accurately.

Verification was still essential. I cross-checked AI-generated data against the design documents and external references. But compared to typing everything manually, the "AI generates, I verify" workflow was dramatically faster and more accurate.

What I Learned Along the Way

First, domain characteristics drive code design. The domain trait of "immutable mappings" directly leads to the technical choice of as const. Good code design flows from deep domain understanding.

Second, AI can leverage domain knowledge and programming knowledge simultaneously. In the role of "translating" a domain expert's descriptions into developer code, AI is remarkably effective.

Third, school-based differences are resolved through flexible code architecture. In a domain with multiple right answers, the correct approach is not hardcoding one answer but building a configurable structure.

Next Up

The 22 characters are now TypeScript types. Next comes coding the "relationships" between those characters. The Five Elements' generating and overcoming cycles, the Ten Gods' 10 classifications, and the sprawling web of Combinations, Clashes, and Punishments. Why I maintained both mapping tables and index arithmetic, and the astonishing experience of AI generating massive data tables accurately in a single pass --- Part 5 continues.

댓글

이 블로그의 인기 게시물

사랑을 직접 올리지 않는 설계

감정을 변수로 옮기다 — 3계층 감정 모델

시작의 충동 — "타로 웹앱을 만들어볼까?"