사주할매 제작기 (1) 만세력 엔진 고르기부터 심사 제출까지
만세력은 코드로 계산하고 긴 풀이는 AI가 할매 목소리로 쓰는 사주 앱. 9월 23일 밤 빈 레포에서 25일 심사 제출까지.
사주할매라는 사주 앱을 만들어 Google Play에 내놓았다. 만세력(사주 여덟 글자, 대운, 오늘의 일진)은 코드로 계산하고, 긴 풀이 글은 AI가 할매 목소리로 쓴다. 9월 23일 밤 11시에 빈 레포를 만들었다. 25일 오후 2시에 심사를 넣었다.
계산은 LLM에 맡기지 않기로 했다
첫 커밋에 스펙과 함께 들어간 게 이거였다. 간지, 십성, 합충, 대운 같은 값은 결정적인 코드로 뽑고, LLM에는 계산이 끝난 결과만 넘긴다. LLM에 계산까지 맡기면 틀린 팔자를 그럴듯하게 풀이할 것 같았다.
그러면 계산을 무엇으로 하느냐가 남는다. 직접 만세력을 짜는 대신 공개된 라이브러리를 후보로 놓고, 라이브러리와 상관없는 정답을 따로 만들어 대조하기로 했다. 정답은 이렇게 뽑았다.
| 항목 | 정답 출처 |
|---|---|
| 절입 시각 (년주·월주) | NASA JPL DE440s 천체력 + Skyfield, 태양 황경이 15°의 배수를 지나는 순간 |
| 일주 | 율리우스 적일(JDN) 공식 |
| 시주 | 동경 127.5° 평균태양시, 서울 진태양시 |
| 벽시계 ↔ UTC | IANA tzdata Asia/Seoul (1954~61년 UTC+8:30, 서머타임 시기 포함) |
| 음력 | KASI 음력 테이블 |
케이스는 1912~2050년 모든 절입 시각의 앞뒤 ±2분·±10분, 자시 경계, 시 경계 ±3분, 1900~2050년 윤달, 대운 시작 시점 같은 것들로 채웠더니 9,980건이 됐다.
후보 셋 중 @fullstackfamily/manseryeok은 절기 시각이 2020~2030년치만 있어서 빠졌고, lunar-python은 중국 음력 기준이라 빠졌다. 남은 manseryeok 2.0.0(TypeScript, MIT)에 진태양시 옵션을 항상 켜서 돌리면 9,980건이 전부 정답과 맞았다. 이 라이브러리를 그대로 쓴다. 앱 화면에는 시간 보정, 자시, 서머타임처럼 결과를 바꾸는 기준을 늘 적어 둔다.
서버에 DB를 두지 않았다
같은 날 밤에 저장 구조도 정했다. 우리 쪽 DB는 두지 않고, 서버는 요청을 받아 계산하거나 풀이를 만들고 끝나는 함수만 둔다. 생년월일시와 풀이는 사용자 기기에만 저장한다. 만세력을 계산할 때 서버를 거치지만 남기지 않고, 풀이를 만들 때 AI에는 계산된 사주 정보만 보낸다. 이름과 생년월일시 원문은 보내지 않는다. 개인정보 보관 의무와 고정비를 피하려는 선택이었다. 대신 풀이 복원은 같은 OS 백업(iCloud, Android 자동 백업) 안에서만 된다.
결제도 이 구조에 맞췄다. 구독과 광고 없이, 긴 풀이를 코인으로 한 편씩 산다. 코인 잔액은 RevenueCat이 들고 있다. 그런데 RevenueCat에는 거래를 조회하는 API가 없어서, 먼저 차감하고 풀이가 실패하면 환불하는 흐름은 확인할 방법이 없었다. 그래서 풀이 생성과 검증이 다 끝난 뒤에 차감한다. 실패하면 차감 자체가 일어나지 않는다.
만 14세에서 만 18세로
처음에는 만 14세 이상으로 정했다. 법정대리인 동의 절차를 피하려는 선이었다. 다음 날 조사 세션이 Google Cloud 서비스 약관 원문을 다시 읽다가, 18세 미만이 쓸 가능성이 있는 서비스에 쓰지 말라는 조항이 Vertex AI에도 걸려 있는 걸 찾았다. Gemini API에만 있는 조항인 줄 알고 Vertex AI를 골랐던 건데, 헛짚은 거였다. 이용 연령을 만 18세 이상으로 올렸다.
이름: 낙관, 사주할매
처음 정한 이름은 낙관이었다. 디자인에 붉은 낙관 하나를 찍는 방향이 먼저 정해져 있었고, 도장 낙관(落款)과 낙관(樂觀) 두 뜻이 다 걸렸다. 그날로 사주할매로 바꿨다. 앱 아이콘은 붉은 바탕에 흰 命 한 글자다.
이름을 바꿀 때만 해도 풀이 말투는 합쇼체 그대로 두고 할매 캐릭터는 넣지 않기로 했었다.
풀이 말투를 할매로
풀이 샘플을 읽어 보니 한자와 용어가 어렵고 도인 말처럼 뜬구름이었다. 20~30대가 읽을 글이 아니었다. 샘플을 확인해 보니 문장이 70자대였고, 문단마다 용어가 3~5개 들어 있었고, 생활 장면은 하나도 없었다. 해요체로 바꾸고 문단을 결론, 쉬운 이유, 생활 장면, 할 일 순서로 쓰게 했다.
그래도 읽히지 않았다. 샘플을 읽고 클로드한테 한 말은 "재미가 없다", "글이 작고 빽빽해 논문 같다", "그래서 결론이 뭐야"였다. 같은 원국, 같은 섹션으로 말투 후보 넷을 실제 LLM에 뽑게 했다. 지금 해요체, 할매 반말 이야기체, 할매 존댓말, 결론 먼저 짧게. 할매 반말이 이야기로 읽혔다. 비유가 늘면서 뜬구름 표현이 새는 경향도 같이 보였다.
페르소나를 정해야 이랬다저랬다 안 할 것 같아서, 말투를 고치기 전에 할매가 누구인지부터 문서로 정했다. 일흔 남짓, 사십 년 넘게 동네 사람들 사주를 봐 준 사람, 점·굿·부적과는 선을 긋고 글자 보고 말하는 사람. 스스로를 할매라고 부르고, 결론부터 말하고, 겁을 주지 않는다. 쓰는 어미와 안 쓰는 어미도 표로 정했다. 풀이 분량은 절반으로 줄였다.
지금 풀이는 이런 식으로 나온다.
예시 프로필(1985년 1월 10일생) 평생 풀이의 첫 두 문단이다.
어디 보자, 할매가 보니까 태어난 날의 중심 글자가 논밭처럼 푸근한 己(기)로 들어찼어. 무엇이든 품어서 싹을 틔우는 흙이라 주변 사람을 따뜻하게 챙기는 마음씨가 남다르지. 묵묵히 제 몫을 해내며 살림과 일터를 차근차근 가꾸는 재주가 참 좋단다. (근거: 일간 己)
태어난 달의 바탕인 월지에 丑(축)을 두어 계절의 힘을 든든하게 얻었구먼(득령). 차가운 땅속에서도 봄을 기다리듯 묵묵히 버티는 힘이 있어 쉽게 무너지지 않거든. (근거: 득령 · 월지 비견)
풀이 문장은 AI(Google Cloud Vertex AI의 Gemini)가 이 할매 목소리로 쓴다. 할매가 말하는 곳은 풀이와 오늘의 운세, 카드 같은 읽을거리이고, 결제·약관·오류·개인정보 안내는 앱이 해요체로 말한다.
문단마다 글자를 붙인다
풀이 문단마다 내 사주의 어느 글자를 보고 한 말인지 붙는다. 누르면 원국표에서 그 글자가 켜진다. 사주에 없는 글자를 댄 문단은 서버가 걸러 낸다.

검증을 붙이고 나서 풀이가 자꾸 실패했다. 유료 풀이 하나가 섹션 일곱 개로 되어 있고, 섹션 하나가 검증에 떨어지면 풀이 전체가 실패한다. 규칙 하나하나는 가끔 걸리는데 일곱 섹션을 곱하면 자주 걸렸다. 그래서 프롬프트에서 금지한 것만 떨어뜨리고, 결론 줄 길이 같은 나머지 규칙은 기록만 남기도록 바꿨다.
분량 하한도 같은 문제였다. 심사를 넣던 날 오전, 실기기로 받아 본 평생 풀이가 한 섹션의 분량이 하한보다 조금 짧아 실패했다. 하한을 85%에서 75%로 내렸는데, 다음에는 궁합 풀이가 같은 이유로 실패했다. 모델이 그 섹션을 원래 짧게 쓰는 것 같았다. 하한은 실패 사유에서 빼고 기록만 남기되, 기준의 절반도 안 되는 섹션만은 실패로 둔다. 실패한 풀이는 코인이 차감되지 않았다.
프롬프트에 넣은 좋은 예문을 모델이 글자까지 그대로 베껴서, 원국이 다른 사람에게도 같은 결론 문장이 나간 일도 있었다. 지금 프롬프트에는 예문 대신 틀과 나쁜 예만 둔다.
여러 Claude Code 세션이 나눠서 만들었다
코드는 거의 다 Claude Code가 썼다. 세션 하나가 리드를 맡아 스펙과 결정 기록(D1부터 번호를 붙였다)을 관리하고, 앱, 서버, 디자인, 문구, QA, 스토어 작업은 각각 다른 세션이 맡아 PR을 올렸다. 리드 세션이 수용 기준을 다시 돌려 보고 머지했다. 나는 방향을 정하고 실기기로 확인하는 쪽을 맡았다. 이틀 동안 PR 129개가 머지됐다.
처음에는 여러 세션이 한 폴더에서 브랜치를 바꿔 가며 일했다. 그러다 리드 커밋 세 개가 디자인 브랜치로 push됐다. 그 뒤로는 세션마다 자기 git worktree에서만 작업한다.
Android 화면 확인은 에뮬레이터로 하다가 접었다. 검은 화면, GPU 멈춤, 시계 어긋남으로 매번 한 시간씩 걸렸다. 결국 클로드한테 "토큰 낭비하고 시간 1시간씩 잡아먹고 병목"이라며, 느끼지도 못하는 AI에게 화풀이를 했다. 지금은 iOS 시뮬레이터 캡처와 헤드리스 위젯 테스트로 확인하고, Android는 내 Galaxy에 깔린 빌드를 adb screencap으로 찍는다.
올해 들어 만든 다섯 번째 앱이라 수월한 점도 있었다. 다만 AI 에이전트로만 개발한 건 이번이 처음이었고, 그 점에서 의의가 있는 프로젝트였다. Claude Code 세션을 역할별로 나눠서 지휘하니 컨텍스트를 관리하기도 편했고, 처음 아이디어 기획부터 최종 배포까지 일관성 있게, 무난하게 작업할 수 있었던 것 같다.
지금 들어 있는 것
- 무료, 로그인 없이: 만세력 원국표(사주 8자, 십성, 지장간, 12운성, 오행 분포, 합충, 신살, 공망), 대운·세운표, 오늘의 운세와 아침 알림, 용어를 누르면 뜻 한 줄
- 코인: 평생 사주 종합 90코인, 올해 운세 70코인, 궁합 50코인. 코인 팩은 3,300원(30), 9,900원(99), 29,000원(348). 로그인은 코인을 살 때만 구글로
- 공유 카드 3종(내 사주, 오늘의 운세, 풀이 한 문단). 기본값에는 생년월일과 여덟 글자가 들어가지 않는다
- 만 18세 이상만 쓸 수 있다. 풀이는 AI가 쓴 참고용 글이고 의료·법률·투자 판단을 대신하지 않는다


앱은 Flutter, 계산과 풀이는 Firebase 서버 함수, 결제는 RevenueCat이다.
- Google Play: https://play.google.com/store/apps/details?id=com.ruiboss.saju
- 소개 페이지: https://saju.po24lio.com
이제 이 세션 팀에 마케팅, 홍보, 편집자, 콘텐츠 제작자 같은 새 역할 몇을 영입해서, 출시 전부터 출시 이후까지를 맡겨 볼 생각이다. 그동안 제대로 도전해 보지 않았던 영역이다. 좀 두렵기도 했고, 귀찮기도 했고, 잘 모르기도 했다. 이번에는 그 영역을 지휘하면서 실전 경험을 쌓고 배워 보려고 한다.

끝