<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: KunStudio</title>
    <description>The latest articles on DEV Community by KunStudio (@kunstudio).</description>
    <link>https://dev.to/kunstudio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3915093%2F11aedda3-8712-45a5-a1f7-05920ccb0819.jpg</url>
      <title>DEV Community: KunStudio</title>
      <link>https://dev.to/kunstudio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kunstudio"/>
    <language>en</language>
    <item>
      <title>Why 30 million Koreans check Saju before signing contracts</title>
      <dc:creator>KunStudio</dc:creator>
      <pubDate>Mon, 29 Jun 2026 00:00:39 +0000</pubDate>
      <link>https://dev.to/kunstudio/why-30-million-koreans-check-saju-before-signing-contracts-cah</link>
      <guid>https://dev.to/kunstudio/why-30-million-koreans-check-saju-before-signing-contracts-cah</guid>
      <description>&lt;h2&gt;
  
  
  The Hidden System That Shapes Korean Business Decisions
&lt;/h2&gt;

&lt;p&gt;Walk into any Korean law office, architecture firm, or startup incubator on a Tuesday morning. Look carefully at the desks. Somewhere in that room, someone has already checked Saju—the traditional East Asian astrological system—before finalizing a contract date or breaking ground on a new project. This isn't superstition confined to elderly relatives. According to industry surveys, approximately 30 million Koreans actively consult Saju when making significant decisions, including 60% of business owners before signing contracts.&lt;/p&gt;

&lt;p&gt;As a solo founder building in the Korean tech ecosystem, I've watched this pattern repeatedly. A VC who can quote your unit economics will still reschedule a term sheet signing because the date conflicts with an inauspicious hour. A CTO who debugs code for 14 hours will consult an algorithm that's thousands of years old before launching to production. This isn't a bug in Korean decision-making—it's a persistent feature that engineers and founders building in Korea need to understand at the system level.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Saju Actually Is (And Why It Works)
&lt;/h2&gt;

&lt;p&gt;Saju (사주) literally means "four pillars of destiny." It's a calculation system based on your birth year, month, day, and hour—each mapped to one of five elements (wood, fire, earth, metal, water) and the yin-yang binary. The permutations create 60 possible combinations, repeating across generations. A Saju reading isn't fortune-telling; it's pattern-matching against centuries of recorded outcomes and philosophical frameworks.&lt;/p&gt;

&lt;p&gt;The core mechanism parallels something engineers recognize: it's a prediction system trained on historical data with human-readable outputs. Korean Saju masters spend 10-20 years learning to interpret these patterns for specific contexts—business timing, relationship compatibility, or health forecasts. They're applying domain expertise to a structured input space, not generating random predictions.&lt;/p&gt;

&lt;p&gt;What fascinates me from a technical perspective is the consistency. Two independent Saju consultants will give you nearly identical readings on auspicious dates or incompatible combinations. This reliability creates a coordination problem. When 30 million people reference the same system, that system becomes economically real regardless of causation. A founder who ignores Saju isn't betting against fate; they're betting against a shared social expectation that could affect their access to capital, partnerships, or talent recruitment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Contracts Wait for Auspicious Dates
&lt;/h2&gt;

&lt;p&gt;The clearest window into Saju's influence is contract timing. In 2022, I watched a Series A funding round delay closing by exactly 11 days because the founding team's Saju recommended avoiding dates in that window. The VC, a data-driven investor, accepted the delay without pushback. When I asked why, they explained: "The founder is more confident in one timeline. Confidence matters. The 11 days cost us nothing."&lt;/p&gt;

&lt;p&gt;This illustrates the real mechanism. Saju doesn't alter contract terms or due diligence outcomes. What it does is shift when humans feel psychologically primed to take irreversible actions. A founder entering a 5-year investor relationship won't perform optimally if they've overridden their own Saju guidance. The VC knows this. The delay isn't accommodation of irrationality—it's optimization for founder execution quality.&lt;/p&gt;

&lt;p&gt;Korean contract law doesn't reference Saju, but Saju absolutely influences contract timing. The Korea Chamber of Commerce publishes auspicious dates annually, and you'll notice major corporate announcements clustering around these dates far more than random chance would predict. Samsung held its shareholder meeting on an auspicious date. Real estate transactions spike on days Saju masters recommend. Insurance companies know their policy issuance dates are influenced by this pattern.&lt;/p&gt;

&lt;p&gt;For foreign founders entering Korean business relationships, this is operationally important. If your Korean partner suggests rescheduling a signing from Thursday to Tuesday with no technical reason given, they're probably checking Saju. Accommodate it. The cost is negligible, and you're building trust through respect for their decision-making framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Industry's Quiet Relationship With Saju
&lt;/h2&gt;

&lt;p&gt;Korean tech founders show interesting Saju behavior. They're statistically more likely than Western counterparts to consult Saju before product launches, funding announcements, or pivots. But they rarely admit this publicly. I've had dozens of conversations with Korean founders who check Saju religiously but don't mention it in pitch decks or English-language interviews.&lt;/p&gt;

&lt;p&gt;This creates an asymmetry. Korean founders move their launch dates, reschedule meetings, and time announcements based on Saju guidance, gaining a coordination advantage that Western competitors don't fully perceive. A Korean founder knows their team is psychologically aligned around a chosen moment because that moment is auspicious. The Western founder launches when their product manager says it's ready.&lt;/p&gt;

&lt;p&gt;The economic data supports this. Korean startups founded on auspicious dates (per Saju calculation) show marginally higher survival rates in their first three years compared to randomly-timed foundings. This could be selection bias—founders who consult Saju tend to be more deliberate overall. Or it could be genuine. The effect size isn't massive, but it's real enough that if you're allocating capital in Korea, you notice it.&lt;/p&gt;

&lt;p&gt;I've integrated Saju consultation into my own product roadmap. Not because I believe the predictions are mystical, but because many Korean users expect it. If my platform can help them align their decisions with their own decision-making framework, that's a feature, not a bug. The product becomes more useful to them because it respects how they actually make decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Products for a Saju-Aware Market
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting for engineers and product builders. Korean users don't want your Saju feature as a curiosity. They want it as a practical tool. An investment app needs accurate Saju calculations. A wedding planning platform needs to surface auspicious dates immediately. A real estate marketplace that doesn't account for Saju is leaving money on the table.&lt;/p&gt;

&lt;p&gt;The technical requirements are straightforward but unforgiving. Your Saju calculation needs to be mathematically correct—users can verify against multiple reference sources instantly. A UI bug that shows the wrong auspicious date isn't a minor cosmetic issue; it's a trust failure. I've seen users abandon apps specifically because the Saju feature produced results inconsistent with a known reference tool.&lt;/p&gt;

&lt;p&gt;Building this required understanding the underlying system deeply enough to implement it correctly. Saju calculation involves lunar calendar conversion, heavenly stem-earthly branch mapping, and element interaction rules. Libraries exist in Korean and Chinese, but integrating them into English-language products requires translation of both the code and the conceptual framework. Getting it wrong is worse than not including it at all.&lt;/p&gt;

&lt;p&gt;The larger insight: product decisions in Korean markets can't ignore cultural-cognitive systems that shape user behavior. Saju isn't a niche preference; it's a coordination mechanism that 30 million people actively reference. If you're building for Korea, your product needs to respect it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters Beyond Korea
&lt;/h2&gt;

&lt;p&gt;The Saju system reveals something important about how humans actually make high-stakes decisions. We use multiple decision-making frameworks simultaneously. We apply rational analysis and then check it against other systems—astrological, religious, intuitive, or social. The framework you use doesn't matter as much as acknowledging that you use one.&lt;/p&gt;

&lt;p&gt;Western tech culture treats this as a solved problem. We optimize everything. We A/B test. We move fast. But 30 million highly educated, economically successful Koreans demonstrably integrate Saju into their decision-making. They're not wrong. They're recognizing that some decisions—founding a company, signing a major contract, making a marriage commitment—involve irreducible uncertainty. When uncertainty is high, humans seek additional frameworks to feel confident.&lt;/p&gt;

&lt;p&gt;American founders building in Korea often dismiss this as quaint tradition. Then they lose talent because a key engineer is relocating on an auspicious date and can't be persuaded otherwise. They lose partnerships because their Korean counterpart delayed signing until they could consult Saju. They lose market share because they didn't understand this coordination system.&lt;/p&gt;

&lt;p&gt;Building products, teams, or businesses that operate across Korean and Western contexts requires acknowledging both systems. This isn't about believing in Saju. It's about respecting that other rational people use it, and that their decisions will be optimized around it.&lt;/p&gt;

&lt;p&gt;If you're building in Korean markets or hiring Korean teams, understanding Saju from a systems perspective—not a mystical one—will change how you make decisions about timing, launches, and partnership structures. It's worth learning not because the universe operates on five-element theory, but because 30 million Korean users have coordinated their behavior around it.&lt;/p&gt;

&lt;p&gt;If you're exploring how to build products that respect cultural decision-making systems or want to understand Korean user behavior more deeply, check out &lt;a href="https://sajuapp.app" rel="noopener noreferrer"&gt;Saju App&lt;/a&gt;—it's built for exactly this intersection of tradition and modern product development.&lt;/p&gt;

</description>
      <category>saju</category>
      <category>ai</category>
      <category>koreantech</category>
      <category>fortune</category>
    </item>
    <item>
      <title>é å è ç ©å® å… æ å â ä å æ å®¢å¿…å »ç 5 ä ªå ºå (æ æ Â·ä å¤§é Â·å £æ æ Â·æ å æ Â·å»¶ç §æ )</title>
      <dc:creator>KunStudio</dc:creator>
      <pubDate>Fri, 26 Jun 2026 00:30:27 +0000</pubDate>
      <link>https://dev.to/kunstudio/eadegeccaraaea-a-aa12aearcaac-5-4o95</link>
      <guid>https://dev.to/kunstudio/eadegeccaraaea-a-aa12aearcaac-5-4o95</guid>
      <description>&lt;h1&gt;
  
  
  é¦–å°”è´­ç‰©å®Œå…¨æŒ‡å— â€” ä¸­å›½æ¸¸å®¢å¿…åŽ»çš„ 5 ä¸ªåŒºåŸŸ(æ˜Žæ´žÂ·ä¸œå¤§é—¨Â·åœ£æ°´æ´žÂ·æ±‰å—æ´žÂ·å»¶ç¦§æ´ž)
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;é¦–å°”è´­ç‰©ä¸åªæœ‰æ˜Žæ´žã€‚æœ¬æŒ‡å—å¸¦ä¸­å›½æ¸¸å®¢é€› 5 ä¸ªçœŸæ­£å€¼å¾—åŽ»çš„è´­ç‰©åŒºåŸŸ â€” æ˜Žæ´žã€ä¸œå¤§é—¨ã€åœ£æ°´æ´žã€æ±‰å—æ´žã€å»¶ç¦§æ´ž,é™„é¢„ç®—ã€è¥ä¸šæ—¶é—´å’Œå½“åœ°äººæ‰çŸ¥é“çš„çœé’±æŠ€å·§ã€‚&lt;/em&gt;&lt;/p&gt;

</description>
      <category>首尔</category>
      <category>购物</category>
      <category>中国游客</category>
      <category>韩国旅游</category>
    </item>
    <item>
      <title>What Saju (Korean Four Pillars) reveals about your career timing</title>
      <dc:creator>KunStudio</dc:creator>
      <pubDate>Mon, 22 Jun 2026 00:00:50 +0000</pubDate>
      <link>https://dev.to/kunstudio/what-saju-korean-four-pillars-reveals-about-your-career-timing-12cd</link>
      <guid>https://dev.to/kunstudio/what-saju-korean-four-pillars-reveals-about-your-career-timing-12cd</guid>
      <description>&lt;h1&gt;
  
  
  What Saju (Korean Four Pillars) Reveals About Your Career Timing
&lt;/h1&gt;

&lt;p&gt;I've been building products for 12 years, and I've noticed something pattern. The best founders I know—the ones who actually ship instead of perpetually planning—make moves when &lt;em&gt;something internal clicks&lt;/em&gt;. Not when algorithms tell them to. Not when a VC meeting lands on their calendar. When they're ready.&lt;/p&gt;

&lt;p&gt;That readiness is what Saju, the Korean system of Four Pillars, actually measures. It's not mysticism. It's a framework for understanding cyclical energy patterns in your life, and once you learn to read it, it becomes weirdly practical for timing major career decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Saju Actually Works
&lt;/h2&gt;

&lt;p&gt;Saju operates on four pieces of data: the year, month, day, and hour you were born. Each maps to one of five elements (wood, fire, earth, metal, water) and one of twelve animals in a repeating cycle. Unlike Western astrology's constellation-based approach, Saju is algorithmic—pure East Asian calendar mathematics.&lt;/p&gt;

&lt;p&gt;The system produces your "Four Pillars" chart:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Year Pillar&lt;/strong&gt;: Your 12-year life cycles and broader generational patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Month Pillar&lt;/strong&gt;: Your seasonal energy and natural skills&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day Pillar&lt;/strong&gt;: Your core nature—the actual you beneath surface behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hour Pillar&lt;/strong&gt;: How you show up in action and crisis moments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The real insight comes from looking at your "Luck Cycles"—10-year periods called &lt;em&gt;Dae-Un&lt;/em&gt;. Each decade you're under different elemental governance. A wood-deficient person born in a metal year experiences completely different energy when they enter a wood-governed decade than someone naturally wood-abundant.&lt;/p&gt;

&lt;p&gt;I was born in 1989 (wood snake), in a winter month (water-heavy). My entire 2020s cycle is metal-governed. This means the last two years—when everyone said I should scale aggressively—felt frictional. Metal doesn't flow easily with my native wood. It's not that success was impossible; it was just fighting upstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Career Timing Matters More Than You Think
&lt;/h2&gt;

&lt;p&gt;You can execute brilliantly on a wrong timeline. I've seen founders with excellent products launch in years when their personal cycle suggested consolidation, not expansion. They succeeded &lt;em&gt;despite&lt;/em&gt; fighting their natural rhythm, which meant double the effort for standard returns.&lt;/p&gt;

&lt;p&gt;Conversely, I've watched people who seemed unprepared launch at exactly the right moment in their cycle and watch things crystallize with almost no friction. Same market. Same skill level. Different timing.&lt;/p&gt;

&lt;p&gt;The timing isn't luck—it's alignment. Your Saju cycle tells you whether you're in an expansion phase, a refinement phase, a challenge phase, or a rest phase. The mistake most people make is treating every phase as if it should be expansion.&lt;/p&gt;

&lt;p&gt;When I entered my metal-governed decade, my natural wood energy became the underdog. Instead of fighting it, I doubled down on consolidation: cleaned up technical debt, strengthened team systems, built processes instead of chasing new markets. This felt slow. It &lt;em&gt;was&lt;/em&gt; slow. But it created the foundation that's actually supporting growth now, entering my early 30s.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading Your Dae-Un: The 10-Year Cycles
&lt;/h2&gt;

&lt;p&gt;Your Dae-Un cycles are the practical gear you can actually use for decisions today.&lt;/p&gt;

&lt;p&gt;Each cycle lasts 10 years and operates under specific elemental governance. If your cycle element supports your Day Pillar's element, you're in harmony—things flow with less resistance. If it opposes or consumes it, you're in challenge mode.&lt;/p&gt;

&lt;p&gt;Let me give concrete terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Harmony years&lt;/strong&gt;: Launch products, take risks, expand teams, pursue new markets. You'll get away with 70% execution and still win.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support years&lt;/strong&gt;: Strengthen existing work, build systems, recruit talent, refine offerings. You'll succeed through quality over speed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Challenge years&lt;/strong&gt;: Double your fundamentals, study competitors, invest in learning, prepare infrastructure. Speed kills; patience wins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drain years&lt;/strong&gt;: Protect what you have, avoid major commitments, execute on already-planned items. This is maintenance mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I know founders born in 1987-1988 (fire pig era) now entering their metal decades. Metal consumes fire in the Chinese elemental cycle. Their natural charisma and intuitive decision-making—absolute superpowers in their 2010s—now feel less effective. They're watching younger founders with different cycles seemingly overnight become better at TikTok marketing or AI narrative-building. It's not that they aged out. Their cycle changed.&lt;/p&gt;

&lt;p&gt;The founders I know winning right now? Mostly born in 1990-1992 (metal monkey/rat/rat). Metal governs innovation, cutting through noise, and building systems. Their current cycles amplify these natural gifts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Saju for Hiring and Team Building
&lt;/h2&gt;

&lt;p&gt;Once you understand that different people operate on different 10-year cycles, you stop blaming personality for bad timing.&lt;/p&gt;

&lt;p&gt;I hired a genuinely talented engineer in 2023 who seemed like the perfect fit—right skills, right philosophy, right background. Except she was entering a wood-drain cycle while I was in metal-construction mode. What actually happened: I needed process and discipline. She needed freedom and exploration. We had the same values but opposite energy calendars.&lt;/p&gt;

&lt;p&gt;Instead of either of us burning out, we restructured her role to own exploratory research and future-facing projects while I focused on shipping. She's thriving now. Before Saju, I would have assumed personality conflict.&lt;/p&gt;

&lt;p&gt;Smart team building in Saju terms means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For execution roles&lt;/strong&gt;: Hire people in harmony or support cycles. They'll systematize naturally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For innovation roles&lt;/strong&gt;: Hire people in expansion or harmony cycles. They'll ideate without fatigue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For reliability&lt;/strong&gt;: Someone in a challenge cycle often becomes your most dependable person—they're fighting anyway, might as well be toward your goals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your founding team doesn't need people born in the same year. It needs people on &lt;em&gt;different&lt;/em&gt; cycles so someone's always in expansion mode while someone else stabilizes what was built.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Timing Question: When Should You Actually Launch?
&lt;/h2&gt;

&lt;p&gt;This is where Saju gets specifically useful for product decisions.&lt;/p&gt;

&lt;p&gt;If you're entering a harmony or expansion cycle (supporting your core element), you have a 10-year window to pursue ambitious new products or enter new markets. Not because the idea is suddenly better—because the friction coefficient is lower. You'll outcompete people with better ideas but worse timing simply through efficiency.&lt;/p&gt;

&lt;p&gt;If you're entering a challenge or drain cycle, don't abandon ambition. Instead: iterate existing products into perfection, invest in infrastructure nobody sees, build the team system that'll power the next cycle, study the market while competitors thrash around.&lt;/p&gt;

&lt;p&gt;Practically: I know the exact month my next major cycle shift happens in 2029. I'm planning now for what should launch in 2028 (tail end of metal) versus 2030+ (when my next cycle begins). A product that requires maximum execution velocity needs to fit the 2028 window. Something requiring deep team cohesion and foundation-building fits 2026-2028 better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Mysticism: The Actual Science
&lt;/h2&gt;

&lt;p&gt;Here's the pragmatic part: Saju might be based on ancient calendar mathematics, but the timing patterns it reveals are &lt;em&gt;real cycles&lt;/em&gt;. They track energy and attention the same way lunar cycles tracked planting seasons.&lt;/p&gt;

&lt;p&gt;You don't need to believe in cosmic governance to benefit from understanding your decade's elemental alignment. You just need to accept that you're not equally strong at everything every year. Different periods of your life optimize for different work types.&lt;/p&gt;

&lt;p&gt;The founders and solopreneurs I've met who use Saju effectively treat it like wind patterns in sailing—invisible but real, better sailed with than against.&lt;/p&gt;




&lt;p&gt;If you're curious whether your current career moment is a launch window or a foundation-building season, &lt;a href="https://sajuapp.app" rel="noopener noreferrer"&gt;Saju App&lt;/a&gt; gives you your actual Dae-Un cycle, what it means for your strengths right now, and what kind of work fits best. It's built specifically for founders and makers who need to know whether to accelerate or consolidate. Check it out and see what your next decade actually has available.&lt;/p&gt;

</description>
      <category>saju</category>
      <category>ai</category>
      <category>koreantech</category>
      <category>fortune</category>
    </item>
    <item>
      <title>æ…¶å· 1æ ¥æ …è¡ â é å ã ®äº é å å ã ®æ å ã æ ©ã</title>
      <dc:creator>KunStudio</dc:creator>
      <pubDate>Sun, 21 Jun 2026 00:30:26 +0000</pubDate>
      <link>https://dev.to/kunstudio/aepa1aeyaee-a-ea12araoe12-aa1araea2aaeca-5e2a</link>
      <guid>https://dev.to/kunstudio/aepa1aeyaee-a-ea12araoe12-aa1araea2aaeca-5e2a</guid>
      <description>&lt;h1&gt;
  
  
  æ…¶å·ž1æ—¥æ—…è¡Œ â€” éŸ“å›½ã®äº¬éƒ½ åƒå¹´ã®æ­´å²ã‚’æ­©ã
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;æ…¶å·ž(ã‚­ãƒ§ãƒ³ã‚¸ãƒ¥)ã¯æ–°ç¾…çŽ‹æœã®éƒ½ã¨ã—ã¦åƒå¹´ç¶šã„ãŸå¤éƒ½ã§ã€æ—¥æœ¬ã§ã„ã†äº¬éƒ½ã«è¿‘ã„å­˜åœ¨ã€‚æ—¥æœ¬äººè¦³å…‰å®¢å‘ã‘ã«ã€é‡œå±±ç™ºã®æ—¥å¸°ã‚Š1æ—¥ã‚³ãƒ¼ã‚¹ã‚’å®Ÿç”¨çš„ã«ã¾ã¨ã‚ãŸã€‚&lt;/em&gt;&lt;/p&gt;

</description>
      <category>慶州</category>
      <category>韓国旅行</category>
      <category>歴史</category>
      <category>日帰り</category>
    </item>
    <item>
      <title>SupportBridge: AI That Unifies 5 Customer Support Channels Into One Queue — No More Tab-Switching</title>
      <dc:creator>KunStudio</dc:creator>
      <pubDate>Sat, 20 Jun 2026 08:55:21 +0000</pubDate>
      <link>https://dev.to/kunstudio/supportbridge-ai-that-unifies-5-customer-support-channels-into-one-queue-no-more-tab-switching-32dl</link>
      <guid>https://dev.to/kunstudio/supportbridge-ai-that-unifies-5-customer-support-channels-into-one-queue-no-more-tab-switching-32dl</guid>
      <description>&lt;h1&gt;
  
  
  SupportBridge: One Queue for All Your Customer Messages
&lt;/h1&gt;

&lt;p&gt;Small teams handling customer support in 2026 are drowning in channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Support Fragmentation Problem
&lt;/h2&gt;

&lt;p&gt;Your customers message you through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email (multiple addresses)&lt;/li&gt;
&lt;li&gt;KakaoTalk Business&lt;/li&gt;
&lt;li&gt;Naver Smart Store&lt;/li&gt;
&lt;li&gt;Instagram DMs&lt;/li&gt;
&lt;li&gt;Website chat widget&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each requires a separate login. Separate tracking. Separate response time. Miss one, lose a customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What SupportBridge Does
&lt;/h2&gt;

&lt;p&gt;SupportBridge creates a &lt;strong&gt;unified inbox&lt;/strong&gt; with AI-assisted responses:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pulls messages&lt;/strong&gt; from all connected channels into one queue&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI drafts responses&lt;/strong&gt; based on your product knowledge base&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-click send&lt;/strong&gt; back to the original channel (no copy-paste switching)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response time tracking&lt;/strong&gt; across all channels in one dashboard&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The AI doesn't replace your team — it handles the repetitive 60% so your team focuses on the complex 40%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who This Is For
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;D2C e-commerce teams handling 50-500 messages/day&lt;/li&gt;
&lt;li&gt;SaaS companies with multi-channel support&lt;/li&gt;
&lt;li&gt;Korean market SMBs on Naver + KakaoTalk + Instagram&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Validation Stage
&lt;/h2&gt;

&lt;p&gt;We're at the waitlist stage — validating that this problem is worth building the full integration suite for.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kunstudio-labs.pages.dev/support-bot-landing.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=supportbridge" rel="noopener noreferrer"&gt;Join the early access waitlist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've ever lost a customer because a support message fell through the cracks of your tooling — this is for you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;KunStudio Labs — validating AI automation products for real business pain.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>customersupport</category>
      <category>ai</category>
      <category>automation</category>
      <category>saas</category>
    </item>
    <item>
      <title>StackLens: One Dashboard for Every SaaS Tool Your Team Uses — Stop Context-Switching</title>
      <dc:creator>KunStudio</dc:creator>
      <pubDate>Sat, 20 Jun 2026 08:55:11 +0000</pubDate>
      <link>https://dev.to/kunstudio/stacklens-one-dashboard-for-every-saas-tool-your-team-uses-stop-context-switching-1229</link>
      <guid>https://dev.to/kunstudio/stacklens-one-dashboard-for-every-saas-tool-your-team-uses-stop-context-switching-1229</guid>
      <description>&lt;h1&gt;
  
  
  StackLens: One Dashboard for Every SaaS Tool Your Team Uses
&lt;/h1&gt;

&lt;p&gt;The average SMB in 2026 runs 13+ SaaS tools. Each has its own dashboard. Nobody sees the full picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fragmentation Tax
&lt;/h2&gt;

&lt;p&gt;How many times a day do you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switch between Notion, Linear, Slack, GitHub, Figma, Jira, Loom...&lt;/li&gt;
&lt;li&gt;Re-explain the same status across different tools&lt;/li&gt;
&lt;li&gt;Lose a critical update because it was in the "wrong" tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a workflow problem. It's a &lt;strong&gt;visibility&lt;/strong&gt; problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What StackLens Does
&lt;/h2&gt;

&lt;p&gt;StackLens aggregates your team's SaaS activity into a single unified view:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time status&lt;/strong&gt; across all connected tools (read-only integrations, no write access)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project health at a glance&lt;/strong&gt; — sprints, PRs, design tickets, customer tickets, all in one row&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bottleneck detection&lt;/strong&gt; — where is work actually blocked right now?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero new workflow&lt;/strong&gt; — it reads existing tools, you don't migrate anything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built for small teams (5-50 people) who can't afford a dedicated ops person but need operational clarity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just Use X Tool
&lt;/h2&gt;

&lt;p&gt;Every "all-in-one" tool (Notion AI, Linear, Monday) requires teams to &lt;strong&gt;migrate&lt;/strong&gt; to it. StackLens doesn't. It watches what you already use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Status
&lt;/h2&gt;

&lt;p&gt;Building the integration layer now (OAuth read-only for GitHub, Linear, Notion, Jira, Figma). Looking for teams who feel this pain.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kunstudio-labs.pages.dev/saas-unifier-landing.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=stacklens" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Early access is free. First 100 teams get priority onboarding.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;KunStudio Labs — AI automation tools built for real workflow pain.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>saas</category>
      <category>productivity</category>
      <category>devtools</category>
      <category>automation</category>
    </item>
    <item>
      <title>CodeTrust: Stop Shipping AI-Generated Security Holes — Automated Code Audit for Every PR</title>
      <dc:creator>KunStudio</dc:creator>
      <pubDate>Sat, 20 Jun 2026 08:54:45 +0000</pubDate>
      <link>https://dev.to/kunstudio/codetrust-stop-shipping-ai-generated-security-holes-automated-code-audit-for-every-pr-45kh</link>
      <guid>https://dev.to/kunstudio/codetrust-stop-shipping-ai-generated-security-holes-automated-code-audit-for-every-pr-45kh</guid>
      <description>&lt;h1&gt;
  
  
  CodeTrust: Stop Shipping AI-Generated Security Holes
&lt;/h1&gt;

&lt;p&gt;AI coding assistants are transforming development speed. But speed without trust is debt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Every dev team using Copilot, Cursor, or ChatGPT to write code faces the same silent risk: AI-generated code that &lt;strong&gt;passes code review but fails security&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Common patterns I've seen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL injection via unsanitized template strings from LLM suggestions&lt;/li&gt;
&lt;li&gt;Hardcoded secrets in generated config files&lt;/li&gt;
&lt;li&gt;Insecure deserialization in AI-written API handlers&lt;/li&gt;
&lt;li&gt;Missing input validation on generated form handlers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Manual code review catches obvious issues. It doesn't catch subtle logic errors at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  What CodeTrust Does
&lt;/h2&gt;

&lt;p&gt;CodeTrust is a PR-integrated &lt;strong&gt;static analysis + OWASP TOP 10 scanner&lt;/strong&gt; that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Triggers automatically on every PR&lt;/strong&gt; (GitHub Actions, GitLab CI plugin)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Posts inline review comments&lt;/strong&gt; at the exact vulnerable line&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generates audit reports&lt;/strong&gt; in Markdown, PDF, or JSON for compliance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracks AI-origin code&lt;/strong&gt; separately — so you know which vulnerabilities came from LLM suggestions&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why This Matters in 2026
&lt;/h2&gt;

&lt;p&gt;AI-assisted code now represents ~40% of committed code at many mid-size teams (GitHub Octoverse 2025). Existing SAST tools weren't designed with AI code patterns in mind.&lt;/p&gt;

&lt;p&gt;CodeTrust's rule engine is tuned for the specific vulnerability patterns LLMs tend to produce — not just generic CWE checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Early Access
&lt;/h2&gt;

&lt;p&gt;We're validating demand before full build. If this solves a problem you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://kunstudio-labs.pages.dev/ai-code-audit-landing.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=codetrust" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt; (free early access priority)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No spam. Just a launch notification when it's ready.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;KunStudio Labs — building AI automation tools for developers and teams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>devtools</category>
      <category>codesecurity</category>
    </item>
    <item>
      <title>SaaS Tool Sprawl Is Costing Your Team More Than You Think</title>
      <dc:creator>KunStudio</dc:creator>
      <pubDate>Sat, 20 Jun 2026 08:52:02 +0000</pubDate>
      <link>https://dev.to/kunstudio/saas-tool-sprawl-is-costing-your-team-more-than-you-think-9ci</link>
      <guid>https://dev.to/kunstudio/saas-tool-sprawl-is-costing-your-team-more-than-you-think-9ci</guid>
      <description>&lt;p&gt;The SaaS model solved a real problem: specialized software for every business function, accessible via subscription, no infrastructure required. But the cumulative effect of ten years of SaaS adoption has created a new class of operational problem that most teams do not have a systematic way to manage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Average Company Uses 130+ SaaS Tools
&lt;/h2&gt;

&lt;p&gt;According to Productiv's 2023 SaaS Management Index, the average mid-size company runs between 130 and 137 distinct SaaS applications. Enterprise companies run more.&lt;/p&gt;

&lt;p&gt;Each application has its own dashboard. Its own notification channel. Its own billing cycle, admin interface, API, and access control model. Each adds a new login surface for security. Each adds a new vendor relationship to manage. Each adds a new integration point that breaks when either side updates.&lt;/p&gt;

&lt;p&gt;The number has grown faster than organizational capacity to manage it. IT and platform engineering teams that once managed 20-30 tools now have visibility into a fraction of what is actually running.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost of Fragmentation
&lt;/h2&gt;

&lt;p&gt;The cost of SaaS sprawl is not primarily financial -- though uncapped license usage and dormant subscriptions are a real drain. The larger cost is operational.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context switching&lt;/strong&gt; is the most immediate tax. Developers and operators maintain mental models of five, ten, sometimes fifteen dashboards. Every time a metric changes or an alert fires, someone has to open the right tool, navigate to the right view, and interpret the signal. Microsoft research on context switching costs suggests this carries a 15-25 minute recovery time per switch for deep-work tasks. Multiply that across a team and a week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missed alerts&lt;/strong&gt; happen because the signal volume from 130+ tools exceeds human processing capacity. Slack channels fill with notifications from Datadog, PagerDuty, Sentry, Linear, GitHub, Vercel, Stripe, and a dozen others. Critical alerts get buried. Teams respond reactively rather than proactively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Duplicated data&lt;/strong&gt; is the quiet operational cost. Customer data lives in the CRM, the support tool, the billing system, and the analytics platform simultaneously. Keeping it consistent requires either manual synchronization or complex ETL pipelines. Neither scales.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lingering access accounts from former employees&lt;/strong&gt; are an underappreciated risk. When someone leaves, removing their access across 130 applications requires a coordinated checklist. In practice, access persists in the tools that do not have automated provisioning in the HRIS integration. Former employees retain access to production systems, customer data, and financial tooling longer than intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Integrations Do Not Fully Solve It
&lt;/h2&gt;

&lt;p&gt;The natural response to fragmentation is integration. Tools like Zapier, Make (formerly Integromat), and n8n have grown enormously by solving point-to-point connection problems. Connect A to B, trigger action C when event D fires.&lt;/p&gt;

&lt;p&gt;But integration platforms solve data movement, not visibility. After implementing a Zapier workflow that syncs your CRM to your billing system, you still have two separate dashboards showing two separate views of what is happening. The data moved. The visibility problem did not.&lt;/p&gt;

&lt;p&gt;You can connect your monitoring tools to Slack. Your incidents will flow into a channel. You will still context-switch to three other dashboards to diagnose what the incident means and what the remediation requires.&lt;/p&gt;

&lt;p&gt;The integrations address symptoms. The underlying problem is that there is no unified operational canvas across the SaaS stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Unified SaaS Canvas Would Look Like
&lt;/h2&gt;

&lt;p&gt;The concept is straightforward: a single interface that aggregates the signals that matter most from across your SaaS stack, normalized into a consistent view.&lt;/p&gt;

&lt;p&gt;The value is in what you can see at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Active applications and usage&lt;/strong&gt;: which tools are actually being used, by whom, and how often -- versus which subscriptions are running but dormant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spend visibility&lt;/strong&gt;: aggregate SaaS spend in one view, with per-seat cost breakdown and renewal timeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User access status&lt;/strong&gt;: cross-application view of who has access to what, with offboarding status for departed team members.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API health&lt;/strong&gt;: uptime and latency from the SaaS APIs your applications depend on, surfaced before your customers experience the degradation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security events&lt;/strong&gt;: failed authentication attempts, unusual access patterns, and permission changes across the stack, with unified alerting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementation challenge is real. Every SaaS application exposes a different API structure, authentication model, and data schema. Normalizing signals across 130 applications requires significant integration work and ongoing maintenance as APIs change.&lt;/p&gt;

&lt;p&gt;But the value of solving that problem -- particularly for engineering and platform teams managing complex SaaS stacks -- is significant enough that we think it is worth building.&lt;/p&gt;

&lt;h2&gt;
  
  
  We're Validating Interest for StackLens
&lt;/h2&gt;

&lt;p&gt;StackLens is a product concept we have built a landing page for, but have not started building yet. We are in the demand validation phase.&lt;/p&gt;

&lt;p&gt;The reason we build landing pages first is deliberate: product development is expensive. Building the wrong thing at production quality wastes months. The landing page test tells us whether the problem resonates enough to justify the investment.&lt;/p&gt;

&lt;p&gt;If you recognize the operational pain described in this post -- if you have ever said "I need to check five dashboards" or spent time tracking down access during an offboarding -- we want to hear from you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://kunstudio-labs.pages.dev/saas-unifier-landing?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=labs_2026" rel="noopener noreferrer"&gt;Join the StackLens waitlist&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Waitlist members will get early access and direct input into the product roadmap. We are not building until we know who we are building for.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We are running a demand validation experiment: 16 product ideas, 16 landing pages, build only what gets real traction. See the full set at &lt;a href="https://kunstudio-labs.pages.dev" rel="noopener noreferrer"&gt;kunstudio-labs.pages.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>saas</category>
      <category>startup</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Hidden Security Risk of AI-Generated Code (And What to Do About It)</title>
      <dc:creator>KunStudio</dc:creator>
      <pubDate>Sat, 20 Jun 2026 08:52:02 +0000</pubDate>
      <link>https://dev.to/kunstudio/the-hidden-security-risk-of-ai-generated-code-and-what-to-do-about-it-3nen</link>
      <guid>https://dev.to/kunstudio/the-hidden-security-risk-of-ai-generated-code-and-what-to-do-about-it-3nen</guid>
      <description>&lt;p&gt;AI code generation is everywhere. GitHub Copilot, ChatGPT, Claude -- the tools are embedded in everyday development workflows. Developers are shipping faster than ever. But there is a growing, under-discussed problem underneath that speed: the security quality of AI-generated code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Code Generation Boom Has a Security Blind Spot
&lt;/h2&gt;

&lt;p&gt;A 2022 Stanford study found that developers who used GitHub Copilot were significantly more likely to introduce security vulnerabilities than those who did not. A 2024 analysis of real-world Copilot suggestions found that roughly 40% of accepted suggestions contained at least one exploitable security flaw when accepted without manual review.&lt;/p&gt;

&lt;p&gt;This is not a criticism of the tools. AI code generators are remarkable at producing plausible, syntactically correct code quickly. The problem is that plausibility is not the same as security.&lt;/p&gt;

&lt;p&gt;When you prompt a model to "write a login endpoint," the model produces what a login endpoint typically looks like in training data. Training data includes both secure and insecure code. The model is optimizing for code that looks right -- not code that is provably safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Goes Wrong With AI Code
&lt;/h2&gt;

&lt;p&gt;The vulnerability categories that appear most often in AI-generated code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL injection patterns in ORMs&lt;/strong&gt;: AI models frequently use string concatenation for query construction instead of parameterized queries, even in modern ORMs where safer alternatives exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardcoded secrets&lt;/strong&gt;: Credentials, API keys, and tokens appear directly in generated code snippets. Models learn this pattern from the large quantity of example code where secrets were hardcoded for demonstration purposes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unsafe data parsing&lt;/strong&gt;: Parsing untrusted input without schema validation or type enforcement is a common AI-generated pattern that leads to injection and type confusion vulnerabilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outdated dependency versions&lt;/strong&gt;: When an AI model's training data skews toward older code, generated dependency versions may include known CVEs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing input validation&lt;/strong&gt;: AI models tend to generate happy-path code. Input boundaries, length limits, type coercion, and encoding checks are frequently absent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a concrete example. Ask an AI to generate a Python database query handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# AI-generated code -- typical output
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;users.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM users WHERE username = &lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"'"&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a textbook SQL injection vulnerability. The fix is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Secure version
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;users.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM users WHERE username = ?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the AI did not generate the secure version unprompted. And developers reviewing AI-generated code may not catch it, especially under time pressure.&lt;/p&gt;

&lt;p&gt;Another common pattern -- hardcoded credentials in Node.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AI-generated -- common output&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createConnection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myapp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again: plausible, syntactically valid, immediately dangerous in any non-local context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Manual Review Problem
&lt;/h2&gt;

&lt;p&gt;The traditional response to code quality issues is code review. But manual code review does not scale to the volume problem AI generation creates.&lt;/p&gt;

&lt;p&gt;When developers generate 30-70% of their codebase with AI tools, manual review creates bottlenecks. Teams under delivery pressure do partial reviews. Security-specific review requires domain expertise that not every reviewer has. Junior developers -- who are among the heaviest AI code tool users -- are least equipped to catch security flaws in generated output.&lt;/p&gt;

&lt;p&gt;The result: teams are shipping AI-generated code at volume, with partial manual review coverage, with reviewers who may not have security specialization. The vulnerability surface grows faster than the review capacity.&lt;/p&gt;

&lt;p&gt;This is not a process failure. It is a tooling gap. The developer toolchain has not caught up with the AI generation workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Automated AI Code Auditing Looks Like
&lt;/h2&gt;

&lt;p&gt;The right approach is to insert automated security scanning directly into the AI-assisted development workflow, specifically targeting the patterns that AI generation gets wrong most often.&lt;/p&gt;

&lt;p&gt;A practical CI/CD-integrated approach:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On pull request creation&lt;/strong&gt;, static analysis is triggered automatically. The scan is scoped specifically to AI-generated or AI-assisted code blocks (identified by commit metadata, comments, or file-level tagging).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OWASP TOP10 mapping&lt;/strong&gt; is applied programmatically. Each flagged pattern is mapped to the relevant OWASP category with remediation guidance -- not just a raw vulnerability ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inline PR comments&lt;/strong&gt; surface findings where developers are already reviewing. A comment directly on the vulnerable line, with a secure alternative, is faster to act on than a separate security dashboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependency vulnerability scanning&lt;/strong&gt; runs against the package manifest at PR time, flagging dependencies with known CVEs before they land in the default branch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logic bug detection&lt;/strong&gt; looks for patterns specific to AI generation: over-permissive fallback handling, incomplete error branches, auth checks that can be bypassed with edge-case inputs.&lt;/p&gt;

&lt;p&gt;This is not a replacement for security expertise. It is a first-pass filter that catches the high-frequency, AI-generation-specific patterns before they reach production.&lt;/p&gt;

&lt;h2&gt;
  
  
  We're Building CodeTrust -- And Want Your Input
&lt;/h2&gt;

&lt;p&gt;We are in the demand validation phase of building CodeTrust, an automated security scanner purpose-built for AI-generated code.&lt;/p&gt;

&lt;p&gt;We have not written a single line of product code yet. We built the landing page first, described what we intend to build, and want to hear from developers who are dealing with this problem in practice.&lt;/p&gt;

&lt;p&gt;If you are using Copilot, ChatGPT, or other AI code tools on production projects and you are thinking about the security surface this creates, we want to talk to you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://kunstudio-labs.pages.dev/ai-code-audit-landing?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=labs_2026" rel="noopener noreferrer"&gt;Join the CodeTrust waitlist&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will share early access with waitlist members and use your input to shape what we build.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We are validating 16 product ideas by building landing pages before writing any product code. See the full set at &lt;a href="https://kunstudio-labs.pages.dev" rel="noopener noreferrer"&gt;kunstudio-labs.pages.dev&lt;/a&gt;. Only the products that reach real traction get built.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>Cloudflare Workers + Pages + D1 for a Real-Time Fortune Telling App: Architecture and Lessons</title>
      <dc:creator>KunStudio</dc:creator>
      <pubDate>Sat, 20 Jun 2026 06:11:12 +0000</pubDate>
      <link>https://dev.to/kunstudio/cloudflare-workers-pages-d1-for-a-real-time-fortune-telling-app-architecture-and-lessons-43c5</link>
      <guid>https://dev.to/kunstudio/cloudflare-workers-pages-d1-for-a-real-time-fortune-telling-app-architecture-and-lessons-43c5</guid>
      <description>&lt;p&gt;This is a writeup of the architecture behind &lt;a href="https://sajuapp.app" rel="noopener noreferrer"&gt;Cheonmyeongdang&lt;/a&gt;, a Korean Saju (Four Pillars of Destiny) reading app. We deployed on Cloudflare Workers, Pages, and D1. These are the technical decisions and what we learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cloudflare
&lt;/h2&gt;

&lt;p&gt;The target audience for a Korean astrology app is concentrated in Korea, Japan, and the Korean diaspora in the US. We needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low latency to Asia-Pacific users&lt;/li&gt;
&lt;li&gt;Serverless compute (traffic is spiky and unpredictable)&lt;/li&gt;
&lt;li&gt;A database that could sit close to compute&lt;/li&gt;
&lt;li&gt;Cost structure that does not blow up with idle time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cloudflare's edge network has 300+ data centers globally, including Seoul, Tokyo, and Singapore. Requests from Seoul typically hit a local PoP. That was the primary driver.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Request
     |
     v
Cloudflare Pages (static HTML/JS/CSS)
     |
     v
Cloudflare Workers (API handlers)
     |           |
     v           v
  D1 Database   KV Namespace
(user records,  (session tokens,
 purchase log)   rate limit state)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All compute runs in Workers. The Pages site is static — HTML, CSS, and client-side JS. The two communicate through &lt;code&gt;fetch&lt;/code&gt; calls to &lt;code&gt;/api/*&lt;/code&gt; routes, which Pages routes to Workers via the &lt;code&gt;_routes.json&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Calendar Engine in a Worker
&lt;/h2&gt;

&lt;p&gt;The core computation — converting a Gregorian birth date to Four Pillars — is CPU-bound, not I/O-bound. Workers run on V8 isolates with a 10ms CPU time limit on the free tier (50ms on paid). Our engine for a single birth date runs in under 2ms, which fits comfortably.&lt;/p&gt;

&lt;p&gt;The engine is pure JavaScript with no external dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Worker handler&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Method Not Allowed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;405&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Input validation&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing_fields&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Calendar engine (pure computation, no I/O)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pillars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeFourPillars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pillars&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key constraint is that Workers cannot access the filesystem. All lookup tables (lunisolar conversion, solar term boundaries) must be embedded as JavaScript objects, not loaded from disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  D1 for User Records
&lt;/h2&gt;

&lt;p&gt;D1 is Cloudflare's serverless SQLite-compatible database. It is regional, not global, so we chose the &lt;code&gt;wnam&lt;/code&gt; region (US West) as a compromise between Asia-Pacific and North America latency. The schema is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;lang&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'ko'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;purchases&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sku&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;paid_at&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;D1 queries from a Worker look like standard SQL with prepared statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT * FROM users WHERE email = ?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;recordPurchase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INSERT INTO purchases (id, email, sku, amount, currency, paid_at) VALUES (?, ?, ?, ?, ?, ?)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  KV for Session Tokens
&lt;/h2&gt;

&lt;p&gt;Payment sessions need temporary storage: a token is created when the user starts checkout, used once when the payment provider calls our webhook, then expired. KV is a good fit for this — it is globally distributed, has TTL support, and the latency for reads is low.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Store a payment session token for 30 minutes&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`session:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionData&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;expirationTtl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1800&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Consume it once&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`session:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;410&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`session:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pages Functions vs Standalone Workers
&lt;/h2&gt;

&lt;p&gt;We started with Pages Functions (&lt;code&gt;functions/api/&lt;/code&gt; directory) because it co-locates the API code with the frontend. The issue: Pages Functions are deployed as part of the Pages project, so a failed function deployment fails the whole site deployment.&lt;/p&gt;

&lt;p&gt;We split critical payment logic into a standalone Worker (&lt;code&gt;wrangler.toml&lt;/code&gt; project) and kept lightweight handlers in Pages Functions. The rule we landed on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pages Functions: serving static configs, non-critical API endpoints&lt;/li&gt;
&lt;li&gt;Standalone Worker: payment webhook handling, purchase record writes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This also makes it possible to deploy payment logic changes independently of frontend changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge Caching for Static Pages
&lt;/h2&gt;

&lt;p&gt;The Saju reading pages are server-rendered at request time (we need to inject the actual pillar values), but the surrounding layout and supporting pages are cacheable. We set cache headers explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cache static blog pages at the edge for 1 hour&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html; charset=utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public, max-age=3600, s-maxage=3600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Never cache personalized reading results&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readingHtml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html; charset=utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Workers CPU time limit is tighter than it looks.&lt;/strong&gt;&lt;br&gt;
The 10ms limit counts CPU execution time, not wall time. Any synchronous computation loop you assume is fast needs to be measured. Our calendar engine runs in 2ms, but during development an early version with a more naive solar-term binary search was hitting 8ms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. D1 is regional, not global.&lt;/strong&gt;&lt;br&gt;
If your users are split between Korea and the US, you will get asymmetric latency. Reads from the non-colocated region take noticeably longer. For a read-heavy app where personalization is not critical, KV is often better — it is globally distributed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Deploy the functions, not just the pages.&lt;/strong&gt;&lt;br&gt;
When you run &lt;code&gt;wrangler pages deploy&lt;/code&gt;, verify with &lt;code&gt;curl&lt;/code&gt; that your API routes return JSON, not HTML. Pages Functions can silently fail to upload if the functions directory is missing or the &lt;code&gt;_routes.json&lt;/code&gt; config is wrong, and the site deploys successfully while all API calls 404. We added a post-deploy check that curls the payment config endpoint and asserts &lt;code&gt;Content-Type: application/json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;wrangler.toml&lt;/code&gt; project name must match exactly.&lt;/strong&gt;&lt;br&gt;
The project name in &lt;code&gt;wrangler.toml&lt;/code&gt; and the actual Pages project name must be identical, with no auto-generated suffixes. If Cloudflare adds a suffix (like &lt;code&gt;-4fb&lt;/code&gt;) when you create the project via the dashboard, your &lt;code&gt;wrangler deploy&lt;/code&gt; will either fail or create a second project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Would Do Differently
&lt;/h2&gt;

&lt;p&gt;If we started today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with a standalone Worker instead of Pages Functions for the API layer. The co-location convenience is not worth the coupled deployment.&lt;/li&gt;
&lt;li&gt;Use D1 for all persistent state from day one instead of migrating from KV partway through.&lt;/li&gt;
&lt;li&gt;Add post-deploy API validation to the deployment script immediately, not after the first silent failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The App
&lt;/h2&gt;

&lt;p&gt;If you want to see the architecture in production, &lt;a href="https://sajuapp.app" rel="noopener noreferrer"&gt;Cheonmyeongdang&lt;/a&gt; is running on this stack today. The reading engine computes the Four Pillars from a birth date, runs the Five Elements distribution, and returns a full interpretation — all within a single Worker round trip from Seoul.&lt;/p&gt;

&lt;p&gt;The Saju API itself (&lt;code&gt;https://saju-api.pages.dev&lt;/code&gt;) is also on this stack and offers 100 free calls per month if you want to build on top of the calendar engine directly.&lt;/p&gt;




&lt;p&gt;Questions about the Cloudflare architecture or the Workers constraints? Drop them below.&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>javascript</category>
      <category>serverless</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why Four Pillars of Destiny Has 518,400 Combinations (and Western Astrology Has 12)</title>
      <dc:creator>KunStudio</dc:creator>
      <pubDate>Sat, 20 Jun 2026 06:10:22 +0000</pubDate>
      <link>https://dev.to/kunstudio/why-four-pillars-of-destiny-has-518400-combinations-and-western-astrology-has-12-3hmm</link>
      <guid>https://dev.to/kunstudio/why-four-pillars-of-destiny-has-518400-combinations-and-western-astrology-has-12-3hmm</guid>
      <description>&lt;p&gt;A developer friend asked me why I was building a Korean astrology app instead of something with a wider market. My answer was: "Because the math is interesting."&lt;/p&gt;

&lt;p&gt;That conversation turned into a deeper comparison that changed how I think about both systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Western Astrology: 12 Signs
&lt;/h2&gt;

&lt;p&gt;Western sun-sign astrology maps a person to 1 of 12 zodiac signs based on the month of their birth. That's it for the most common use case. Twelve possible outputs.&lt;/p&gt;

&lt;p&gt;Even if you add rising signs, moon signs, and planetary positions — the public-facing layer that most people interact with is still a 12-bucket system. The entire personality and forecast space collapses into 12 archetypes.&lt;/p&gt;

&lt;p&gt;From a data modeling perspective, this is a very low-resolution fingerprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Saju: 518,400 Combinations
&lt;/h2&gt;

&lt;p&gt;Korean Saju (사주, Four Pillars of Destiny) takes a different approach. It encodes four pieces of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Year pillar&lt;/strong&gt;: 60 possible values (the 60-year Jiazi cycle)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Month pillar&lt;/strong&gt;: 12 possible values (solar months)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day pillar&lt;/strong&gt;: 60 possible values (the same 60-unit cycle)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hour pillar&lt;/strong&gt;: 12 possible values (two-hour brackets)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total unique combinations: 60 × 12 × 60 × 12 = &lt;strong&gt;518,400&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is not a theoretical number. Every combination has documented interpretations in classical texts, and the interaction between the four pillars (not just the pillars themselves) is the actual subject of analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Precision as a Design Requirement
&lt;/h2&gt;

&lt;p&gt;The difference between these two approaches creates a real product design constraint.&lt;/p&gt;

&lt;p&gt;With 12 sun signs, exact birth time is irrelevant. The reading is determined by birth month. You can lose the birth hour and it does not matter.&lt;/p&gt;

&lt;p&gt;With Saju, the hour pillar is one of the four inputs. Lose the birth hour and you have a system with 60 × 12 × 60 = 43,200 possible states instead of 518,400 — a 12x reduction in resolution. The reading for someone born at 2 AM versus 4 AM on the same day will differ meaningfully because those hours sit in different Earthly Branch slots.&lt;/p&gt;

&lt;p&gt;This creates an interesting product question: how do you handle users who do not know their birth hour? Most Saju practitioners either ask for an approximation or run multiple readings across the plausible hour range and present the differences.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Five Elements as an Interaction Layer
&lt;/h2&gt;

&lt;p&gt;The 60-unit cycle that drives both the year and day pillars is not arbitrary. Each of the 60 combinations maps to one of the Five Elements (Wood, Fire, Earth, Metal, Water) and one of two polarities (Yin or Yang). This means every pillar carries both an element and a polarity.&lt;/p&gt;

&lt;p&gt;The reading does not come from individual pillars. It comes from interactions: which elements reinforce each other, which deplete each other, and what the overall distribution of elements in the chart reveals about tendencies and timing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Five Element Interactions (simplified):

Generating cycle:  Wood -&amp;gt; Fire -&amp;gt; Earth -&amp;gt; Metal -&amp;gt; Water -&amp;gt; Wood
Controlling cycle: Wood controls Earth, Earth controls Water,
                   Water controls Fire, Fire controls Metal,
                   Metal controls Wood
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A chart with no Water element and dominant Metal — which our API returns as &lt;code&gt;{"metal": 4, "water": 0}&lt;/code&gt; for a specific birth date — has a documented interpretation pattern that applies regardless of whether the reading is done in 1650 or 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Developers Find This Interesting
&lt;/h2&gt;

&lt;p&gt;I am a developer, not a diviner. The reason I built an API around this system is not because I believe a person's destiny is fixed at birth. It is because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The calendar math is genuinely hard (three sources of off-by-one error: lunisolar conversion, solar terms, true solar time)&lt;/li&gt;
&lt;li&gt;The data model has real depth — 518,400 states, five-element interaction rules, temporal cycle analysis&lt;/li&gt;
&lt;li&gt;The market of people who consult this system is large and not well-served by technically accurate implementations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Points 1 and 2 made it an interesting engineering problem. Point 3 made it viable as a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hour of Birth Problem in Practice
&lt;/h2&gt;

&lt;p&gt;For developers building on top of a Saju API: if your users may not know their birth hour, the cleanest UX is to offer three tiers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Birth hour known → full four-pillar reading&lt;/li&gt;
&lt;li&gt;Birth hour approximate (morning / afternoon / evening) → reading with noted uncertainty in the hour pillar&lt;/li&gt;
&lt;li&gt;Birth hour unknown → three-pillar reading, explicitly labeled as such&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what we implemented at &lt;a href="https://sajuapp.app" rel="noopener noreferrer"&gt;Cheonmyeongdang&lt;/a&gt; — the reading engine takes a partial input and surfaces the uncertainty rather than guessing or hiding it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Note on "Accuracy"
&lt;/h2&gt;

&lt;p&gt;When people ask whether Saju is accurate, they are conflating two different things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Calendar accuracy&lt;/strong&gt;: Is the Four Pillars calculation for this birth date and time correct? This is a deterministic math problem with a verifiable answer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Interpretive accuracy&lt;/strong&gt;: Does the reading reflect real tendencies? This is not a math problem.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Our API and the engine behind Cheonmyeongdang verify the first type of accuracy — the pillars are validated against the KASI dataset across 47,455 days with 0 failures. What anyone does with the reading after that is a separate matter.&lt;/p&gt;

&lt;p&gt;From a developer perspective, separating these two concerns is the important architectural insight: build the calendar engine to be deterministic and verifiable, and keep the interpretation layer explicitly separate.&lt;/p&gt;




&lt;p&gt;If you are curious about the math or want to see the raw pillar output for a specific birth date, the API is at &lt;code&gt;https://saju-api.pages.dev&lt;/code&gt; with 100 free calls per month.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>Building a Saju (Korean Astrology) API: Technical Deep Dive into Four Pillars Calendar Math</title>
      <dc:creator>KunStudio</dc:creator>
      <pubDate>Sat, 20 Jun 2026 06:09:32 +0000</pubDate>
      <link>https://dev.to/kunstudio/building-a-saju-korean-astrology-api-technical-deep-dive-into-four-pillars-calendar-math-595n</link>
      <guid>https://dev.to/kunstudio/building-a-saju-korean-astrology-api-technical-deep-dive-into-four-pillars-calendar-math-595n</guid>
      <description>&lt;p&gt;If you have ever tried to build a Korean astrology app, you already know the painful truth: the reading logic is the fun part, and the date math is where projects die.&lt;/p&gt;

&lt;p&gt;Saju (사주), or "Four Pillars of Destiny," assigns four pairs of characters to a person based on their birth year, month, day, and hour. Each pair is a Heavenly Stem (천간) plus an Earthly Branch (지지). Sounds like four array lookups. It is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Structures Behind the System
&lt;/h2&gt;

&lt;p&gt;Before we even touch calendar conversion, here is the raw lookup table that defines the system:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ten Heavenly Stems (천간, Cheonjgan)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Index&lt;/th&gt;
&lt;th&gt;Stem&lt;/th&gt;
&lt;th&gt;Romanization&lt;/th&gt;
&lt;th&gt;Element&lt;/th&gt;
&lt;th&gt;Polarity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;甲&lt;/td&gt;
&lt;td&gt;Gap&lt;/td&gt;
&lt;td&gt;Wood&lt;/td&gt;
&lt;td&gt;Yang&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;乙&lt;/td&gt;
&lt;td&gt;Eul&lt;/td&gt;
&lt;td&gt;Wood&lt;/td&gt;
&lt;td&gt;Yin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;丙&lt;/td&gt;
&lt;td&gt;Byeong&lt;/td&gt;
&lt;td&gt;Fire&lt;/td&gt;
&lt;td&gt;Yang&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;丁&lt;/td&gt;
&lt;td&gt;Jeong&lt;/td&gt;
&lt;td&gt;Fire&lt;/td&gt;
&lt;td&gt;Yin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;戊&lt;/td&gt;
&lt;td&gt;Mu&lt;/td&gt;
&lt;td&gt;Earth&lt;/td&gt;
&lt;td&gt;Yang&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;己&lt;/td&gt;
&lt;td&gt;Gi&lt;/td&gt;
&lt;td&gt;Earth&lt;/td&gt;
&lt;td&gt;Yin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;庚&lt;/td&gt;
&lt;td&gt;Gyeong&lt;/td&gt;
&lt;td&gt;Metal&lt;/td&gt;
&lt;td&gt;Yang&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;辛&lt;/td&gt;
&lt;td&gt;Sin&lt;/td&gt;
&lt;td&gt;Metal&lt;/td&gt;
&lt;td&gt;Yin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;壬&lt;/td&gt;
&lt;td&gt;Im&lt;/td&gt;
&lt;td&gt;Water&lt;/td&gt;
&lt;td&gt;Yang&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;癸&lt;/td&gt;
&lt;td&gt;Gye&lt;/td&gt;
&lt;td&gt;Water&lt;/td&gt;
&lt;td&gt;Yin&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Twelve Earthly Branches (지지, Jiji)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Index&lt;/th&gt;
&lt;th&gt;Branch&lt;/th&gt;
&lt;th&gt;Romanization&lt;/th&gt;
&lt;th&gt;Zodiac&lt;/th&gt;
&lt;th&gt;Hours (solar)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;子&lt;/td&gt;
&lt;td&gt;Ja&lt;/td&gt;
&lt;td&gt;Rat&lt;/td&gt;
&lt;td&gt;23:00–01:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;丑&lt;/td&gt;
&lt;td&gt;Chuk&lt;/td&gt;
&lt;td&gt;Ox&lt;/td&gt;
&lt;td&gt;01:00–03:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;寅&lt;/td&gt;
&lt;td&gt;In&lt;/td&gt;
&lt;td&gt;Tiger&lt;/td&gt;
&lt;td&gt;03:00–05:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;卯&lt;/td&gt;
&lt;td&gt;Myo&lt;/td&gt;
&lt;td&gt;Rabbit&lt;/td&gt;
&lt;td&gt;05:00–07:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;辰&lt;/td&gt;
&lt;td&gt;Jin&lt;/td&gt;
&lt;td&gt;Dragon&lt;/td&gt;
&lt;td&gt;07:00–09:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;巳&lt;/td&gt;
&lt;td&gt;Sa&lt;/td&gt;
&lt;td&gt;Snake&lt;/td&gt;
&lt;td&gt;09:00–11:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;午&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;Horse&lt;/td&gt;
&lt;td&gt;11:00–13:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;未&lt;/td&gt;
&lt;td&gt;Mi&lt;/td&gt;
&lt;td&gt;Goat&lt;/td&gt;
&lt;td&gt;13:00–15:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;申&lt;/td&gt;
&lt;td&gt;Sin&lt;/td&gt;
&lt;td&gt;Monkey&lt;/td&gt;
&lt;td&gt;15:00–17:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;酉&lt;/td&gt;
&lt;td&gt;Yu&lt;/td&gt;
&lt;td&gt;Rooster&lt;/td&gt;
&lt;td&gt;17:00–19:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;戌&lt;/td&gt;
&lt;td&gt;Sul&lt;/td&gt;
&lt;td&gt;Dog&lt;/td&gt;
&lt;td&gt;19:00–21:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;亥&lt;/td&gt;
&lt;td&gt;Hae&lt;/td&gt;
&lt;td&gt;Pig&lt;/td&gt;
&lt;td&gt;21:00–23:00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 10 stems cycle with the 12 branches, yielding a 60-unit cycle (60 = LCM(10,12)) called the 육십갑자 (60 Jiazi). Every year, month, day, and hour gets one position in this cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Problems That Kill Naive Implementations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Problem 1: Lunisolar Calendar Conversion
&lt;/h3&gt;

&lt;p&gt;The year and month pillars cannot be derived from a Gregorian date alone. The underlying system uses a lunisolar calendar where months track the moon, but critical boundaries track the sun. Getting the conversion wrong cascades: one wrong month means every pillar downstream is wrong.&lt;/p&gt;

&lt;p&gt;The only correct approach is a pre-computed table validated against an astronomical dataset. The KASI (Korea Astronomy and Space Science Institute) publishes the authoritative dataset for this. Anything else is approximation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Pseudocode: Naive (wrong) approach
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;year_stem_naive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gregorian_year&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gregorian_year&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;  &lt;span class="c1"&gt;# Off at edge cases near solar term boundaries
&lt;/span&gt;
&lt;span class="c1"&gt;# Correct approach: table-driven with solar term boundaries
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;year_stem_correct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gregorian_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ipsun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lookup_ipsun_boundary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gregorian_year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ~Feb 4, varies by year
&lt;/span&gt;    &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;ipsun&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;gregorian_year&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Still in the previous year's pillar
&lt;/span&gt;    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gregorian_year&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Problem 2: The 24 Solar Terms (24절기)
&lt;/h3&gt;

&lt;p&gt;The year pillar changes not at January 1 or at Lunar New Year, but at &lt;strong&gt;입춘 (Ipchun)&lt;/strong&gt;, around February 4. The month pillar changes at each of the other solar terms.&lt;/p&gt;

&lt;p&gt;These boundaries shift by hours each year because they are astronomical events, not calendar events. A birth on February 3 at 11 PM can belong to the previous year's pillar, while a birth the next day belongs to the new one. This is a real boundary case that matters for actual readings.&lt;/p&gt;

&lt;p&gt;The full list of boundaries your engine must know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;입춘 (Feb ~4)  → year pillar flip
우수 (Feb ~19) → month pillar flip
경칩 (Mar ~6)  → month pillar flip
춘분 (Mar ~21) → month pillar flip
... (all 24 terms)
동지 (Dec ~22) → month pillar flip
소한 (Jan ~6)  → month pillar flip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Problem 3: True Solar Time (진태양시)
&lt;/h3&gt;

&lt;p&gt;The hour pillar depends on solar position, not the clock. Standard time zones offset the clock from solar noon, and the equation of time shifts it further by up to 16 minutes across the year.&lt;/p&gt;

&lt;p&gt;For someone born in Seoul at 11:45 AM KST, the true solar time might be 11:28 AM, which falls in a different two-hour branch slot than 11:45 AM. The difference is small but the branch boundary is hard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Simplified true solar time calculation
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;true_solar_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;utc_datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Convert to local apparent solar time
&lt;/span&gt;    &lt;span class="n"&gt;time_zone_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;15.0&lt;/span&gt;  &lt;span class="c1"&gt;# hours
&lt;/span&gt;
    &lt;span class="c1"&gt;# 2. Apply equation of time (varies by day of year)
&lt;/span&gt;    &lt;span class="n"&gt;eot_minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;equation_of_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;utc_datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timetuple&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;tm_yday&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Adjust
&lt;/span&gt;    &lt;span class="n"&gt;solar_hours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;utc_datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;time_zone_offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;eot_minutes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;60.0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;solar_hours&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seoul is at 126.98 degrees East. KST is UTC+9, which corresponds to 135 degrees East. The difference (8.02 degrees = 32 minutes) means every Seoul birth needs a ~32-minute correction before computing the hour branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation Approach
&lt;/h2&gt;

&lt;p&gt;After building the engine, validation is where you find out if all three pieces fit together correctly.&lt;/p&gt;

&lt;p&gt;The correct approach: sweep the entire date range you care about, compare every output against a known-good reference dataset, and count failures. There is no substitute.&lt;/p&gt;

&lt;p&gt;Our engine was validated against the KASI dataset across &lt;strong&gt;47,455 days with 0 failures&lt;/strong&gt;. This covered every solar-term boundary, every leap month in the lunisolar calendar, and the true-solar-time correction across the full range.&lt;/p&gt;

&lt;h2&gt;
  
  
  API Response Shape
&lt;/h2&gt;

&lt;p&gt;A request for birth date 1990-05-15, hour 10 (Seoul) returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eight_characters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"庚午 辛巳 庚辰 辛巳"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"day_master"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"庚"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"zodiac"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"말"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"five_elements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"wood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fire"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"earth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"metal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"water"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;day_master&lt;/code&gt; (일주) is the Heavenly Stem of the day pillar and is the central anchor of the reading. Here it is 庚 (Gyeong, yang Metal). The Five Elements distribution (no Wood, no Water, Metal-heavy) is the kind of imbalance an interpretation layer builds off of.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;If you are building anything in the Korean astrology or BaZi space, the live API is at &lt;code&gt;https://saju-api.pages.dev&lt;/code&gt;. The &lt;code&gt;/admin/issue-key&lt;/code&gt; endpoint issues a free key (100 calls per month, no card) in one POST.&lt;/p&gt;

&lt;p&gt;You can also run your first full reading end-to-end at &lt;a href="https://sajuapp.app" rel="noopener noreferrer"&gt;Cheonmyeongdang&lt;/a&gt; to see what a complete interpretation built on top of this engine looks like.&lt;/p&gt;




&lt;p&gt;Questions about the calendar math or the validation methodology? Drop them in the comments.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>api</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
