AI용 분할본 · 지도 3단계(화면) · 이 페이지 전에 읽을 페이지: PART 0 사용법·AI 시작 지침, 부록 A 스펙·코드 받기(A-2) · 코드 파일은 여기 없다 — 코드 자리의 줄을 따른다(폴더에 책 파일이 하나도 없는 처음이면 부록 A-2 의 curl 한 줄부터)
핵심 규칙 — AI 는 어느 페이지에서든 이것을 지킨다
node scripts/get-book-code.mjs 가 책 그대로 만든다(첫 실행은 부록 A-2 의 curl 한 줄). AI용 분할본의 코드 자리는 " 경로" 한 줄이다 — 그 파일을 cat 으로 읽는다. 고쳐도 되는 파일은 PART 0 [AI 시작 지침] 5-b 의 목록뿐이다.node scripts/verify-book-code.mjs 를 실행해 "불일치 0개" 출력을 그대로 보인다.이 앱의 "와" 포인트가 펼쳐지는 장입니다. 화면은 다섯 가지입니다 — 5-1 만세력(사주), 5-3 자미두수 명반, 5-4 나의 별자리(네이탈), 5-5 타로 뽑기, 5-6 꿈해몽. 통합 앱은 다섯 화면을 탭으로 묶고, 탭마다 그 주제의 AI 가 상담합니다(app/page.tsx · 6-2). 모든 화면은 먼저 5-0 의 공통 헬퍼로 프록시를 부릅니다.
한 번 받은 결과는 DB 에 저장해 다시 씁니다 — 그 뒤 호출 비용 0원: 만세력(정체성 포함)·자미두수 명반·궁합·네이탈·꿈해몽·오늘의 사주 같은 계산형과 평생 사주·readings옵션·추가 층(layers) 같은 풀이형은 같은 입력이면 결과가 바뀌지 않습니다. 한 번 받은 결과를 앱 DB 에 저장해 다시 쓰면 그 뒤 호출 비용은 0원입니다 — 오늘의 사주는 날짜별로, 궁합은 두 사람 조합별로 저장합니다. 다시 불러야 하는 때는 사용자가 생년월일(출생 정보)을 고쳤을 때와lang이 바뀌었을 때뿐입니다. 이 책의 프록시는 만세력·궁합·자미두수 명반·네이탈·꿈해몽을 이미 이렇게 합니다 — 같은 회원이 같은 입력을 보내면 HRUM 을 부르지 않고 저장본을 돌려주고, 과금도 하루 한도 차감도 없습니다(lib/api-cache.ts, 7-2). 출생 정보를 고치면 입력이 달라지므로 저절로 새로 한 번 부릅니다. 자미두수 명반은 지금 대한·올해 유년이 기준일에 따라 달라서 이달 1일을 기준일로 보내므로, 달이 바뀌면 한 번 새로 부릅니다. 반대로 AI 사주 리딩(reading·reading/memory)과 자유 채팅(chat/completions)은 대화형이라 매번 새로 생성되니 저장·재사용 대상이 아닙니다.
lib/api.ts — 모든 화면이 쓰는 프록시 호출 헬퍼 (5-0 — 화면보다 먼저)코드 자리: cat lib/api.ts · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
[프롬프트]
온보딩에서 받은 생년월일시로 만세력 화면을 만들어 주세요.
데이터: POST /api/float/manseryeok (이미 만든 프록시 경유)
요청 body: manseryeokBody(profile) + requestId = { year, month, day, hour?, minute?, timezone: "Asia/Seoul", gender, requestId }
※ timezone 은 만세력에만 있는 필드다. 상담(reading·reading/memory)에 이 객체를 그대로 재사용하면 FLOAT가 400("property timezone should not exist")을 돌려준다 — 상담은 lib/api.ts 의 readingBody() 로 따로 만든다
※ requestId 는 lib/api.ts 의 newRequestId() 로 이 조회 1회에 한 번만 만들고, 재시도 시 같은 값을 다시 보낸다 (없으면 프록시가 400). 아래 📄 lib/api.ts(get-book-code 가 만든 파일)의 callProxy 로 호출
※ 시간 모름이면 hour·minute 키를 아예 빼고 보내 주세요(12:00을 앱에서 채워 보내지 말 것 (정오 대체 없음))
응답은 { success, data } 형태이고 실제 데이터는 data 안에 있어요.
화면 요구사항 (아래 📄 4파일(get-book-code 가 만든 파일)을 그대로 쓰고, 결정서의 "만세력 표시 깊이"에 맞춰 쓴다 — 특정 폴더 구조를 강제하지 않으니 기존 프로젝트의 lib/·components/ 자리에 맞춰 넣는다):
1. API 응답은 화면에서 직접 읽지 않는다. lib/manseryeok.normalize.ts 의 normalizeManseryeok(raw) 로 먼저 정규화하고, 그 결과(ManseryeokView)만 components/ManseryeokDetail.tsx 에 넘긴다. 응답의 특정 필드가 항상 있다고 가정하지 말 것 — 없는 값은 null/[] 이고, 화면은 그 섹션을 생략하거나 "계산 정보 없음 / 해당 없음" 한 줄만 보여준다. 값을 지어내지 않는다
2. 요약(항상 표시): 4기둥 큰 한자(세로) + 한글 + 오행색 테두리, 오행 분포 바, 일간 한 줄 요약, 현재 대운. 디자인 토큰(색·간격)은 앱 것을 그대로 쓴다 — 카드를 무더기로 늘어놓지 말 것
3. 상세(펼쳐보기, <details>): 원국 각 주의 한글·한자·오행·천간/지지 십신·지장간·12운성·납음·주요/보조 신살(표), 용신/조후/억부·통관·병약, 신강약 result/level/score/breakdown/통근, 격국 상세(핵심·직업·성격·과제), 합충형해(천간합·천간충·반합투간·합작용·지지관계), 대운 전체(현재 강조), 세운, 공망(년주/일주 기준 발현), 천을귀인, 월령, 천간론, 허자, 오행 변수 — 응답에 실제로 있는 만큼만. 결정서가 "간단 요약"이면 상세 섹션을 렌더하지 않는다
4. 시간을 모르는 사용자는 hour·minute 키 자체를 요청에서 뺀다(12:00 을 앱이 대신 채워 보내지 말 것). 응답은 hour 가 null, birthHour.known=false, birthHour.basis="three_pillars" 이다. 용신·신강약·격국·세운·일간요약은 빠지지 않고 년·월·일 3주(6글자) 기준 값으로 온다(birthHour.excludedFields 는 항상 빈 배열 — 시간 미상 판별은 birthHour.known 으로만). 화면은 시주 자리에 "출생시간을 알려주면 시주까지 볼 수 있어요" 안내, 분석 섹션은 그대로 그리고 요약에 "출생시간을 모르면 시주를 뺀 년·월·일 6글자로 계산해요" 한 줄. 시주 추정값·정오 참고값을 만들지 않는다
5. 접근성·반응형: h2(만세력) → h3(섹션) → h4(하위) 위계, 표에는 caption·th scope, 펼침은 키보드로 열림. 390px 폭에서 기둥은 2열로 접히고 표만 내부 가로 스크롤, 페이지 본문의 가로 넘침 0. 데스크톱에서는 기둥 4개 한 줄
6. 타입: any 금지. lib/manseryeok.types.ts 의 명시 타입만 쓴다
완료 기준 (전부 실제로 실행해 증명):
- npx vitest run tests/manseryeok.test.tsx 통과 (시간 있음 / 시간 미상 / 중첩 불완전 픽스처 6개)
- 1990-03-15 08:30 male 로 실제 호출 → 요약 + 상세 섹션 7개가 펼쳐지고, 원국 상세 표의 십신·지장간·12운성·납음·신살, 용신·신강약 score·격국 상세, 합충, 대운 전체(현재 강조), 세운, 공망·천을귀인, 허자·오행 변수 값이 응답 JSON 과 일치(개발자도구 Network 의 data 와 눈으로 대조)
- 시간 모름(hour 생략)으로 호출 → 응답 wonkuk.hour === null, birthHour.known === false. 시주 자리에 안내 카드, 용신·신강약·격국·세운은 3주 기준 값으로 표시되고 "출생시간을 모르면 시주를 뺀 년·월·일 6글자로 계산해요" 안내가 보임, 화면 어디에도 undefined/null/NaN 텍스트 없음
- 390px(모바일)·1280px(데스크톱)에서 document.documentElement.scrollWidth === clientWidth (가로 넘침 0), 표는 내부에서만 가로 스크롤, 상세 섹션이 키보드 Enter 로 열림
- 스크린샷 4장: 시간 있음 요약 / 시간 있음 상세 펼침 / 시간 미상 / 390px
이 앱의 주제 — lib/app-topics.ts 의 TOPICS 가 이 앱에 있는 화면입니다(통합판: 사주·자미두수·점성술·타로·꿈해몽 다섯 가지). 프록시는 이 목록에 있는 주제의 경로만 열고(나머지는 404), 상담도 이 주제의 AI 에게만 묻습니다 — 앱이 보여 주지 않는 기능으로 여러분 크레딧이 나가지 않게 하는 문입니다(7-2). 이 파일은 손으로 고치지 않습니다(verify-book-code 가 책과 대조).
lib/app-topics.ts — 이 앱의 주제 (프록시·상담이 여는 범위)코드 자리: cat lib/app-topics.ts · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
components/Fortune.module.css — 화면 공통 모양 (globals.css 토큰만)코드 자리: cat components/Fortune.module.css · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
만세력(사주 원국)은 이 앱의 "와" 포인트입니다. HRUM 만세력 API는 4기둥만 주는 게 아니라 십성·지장간·운성·신살·대운·세운·신강약·격국까지 30개 가까운 항목을 계산해서 줍니다. 여러분은 뭘 보여줄지 고르기만 하면 됩니다.
lib/manseryeok.types.ts — 화면용 타입 (any 없음)코드 자리: cat lib/manseryeok.types.ts · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
lib/manseryeok.normalize.ts — API 경계 정규화 (중첩까지 가드)코드 자리: cat lib/manseryeok.normalize.ts · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
components/ManseryeokDetail.tsx — 요약 + 펼쳐보기코드 자리: cat components/ManseryeokDetail.tsx · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
components/ManseryeokDetail.module.css — 코드 자리: cat components/ManseryeokDetail.module.css · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
tests/manseryeok.test.tsx — 픽스처 3종코드 자리: cat tests/manseryeok.test.tsx · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
왜 이렇게까지 하나: 실제 초보자 완주에서 "4기둥 한자 + 일간 한 줄"만 그린 데모가 나왔습니다. 만세력 응답 약 27개 영역 중 2개만 쓴 셈이라 유료 API를 사놓고 대부분을 버린 구현입니다. 반대로 샘플 응답의 필드가 항상 있다고 믿고 data.strength.breakdown.season처럼 바로 읽으면, 일부 필드가 비거나 깨진 응답에서 화면이 죽습니다(2026-09-16 전까지는 시간 미상 응답에서 strength 자체가 빠졌습니다). 그래서 정규화 → 있는 만큼만 렌더가 규칙입니다.
| 위젯 | 쓰는 데이터 | 프롬프트 한 줄 |
|---|---|---|
| 현재 대운 | data.daeun[] (isCurrent) | "대운 타임라인을 가로 스크롤로, isCurrent 구간은 강조" |
| 세운(올해 운) | data.seun[] (grade) | "올해·내년 세운 카드를 길흉 grade 색상과 함께" |
| 신강약 게이지 | data.strength | "신강약을 게이지로, result와 score 표시" |
| 격국 카드 | data.gyeokguk | "격국 이름과 summary를 배지 스타일로" |
| 신살 태그 | wonkuk.*.majorSinsal | "기둥별 신살을 작은 태그 칩으로" |
팁: 5-1의 ManseryeokDetail 이 이미 이 표의 데이터를 상세 섹션에 담고 있습니다. 여기 위젯은 "요약 영역에 하나 더 띄우고 싶을 때"(예: 현재 대운 타임라인)만 고르세요 — 다 넣으면 복잡해집니다. 기본판 + 위젯 2개가 앱스토어 감성.
자미두수(紫微斗數)는 태어난 해·달·날·시로 12궁에 별을 앉혀 읽는 동양 점성술입니다. HRUM 자미두수 API(POST /ziwei-chart)는 12궁(궁 이름·천간·지지·대한 나이), 14주성과 보좌성(밝기·생년사화 포함), 오행국, 명궁·신궁 위치, 생년사화, 기준일의 대한·유년까지 계산해서 줍니다. 같은 입력이면 언제나 같은 명반입니다. 화면은 받은 값을 판에 앉히기만 하면 됩니다.
태어난 시각이 꼭 필요합니다: 자미두수는 시각 없이 명반을 세울 수 없습니다 —hour를 빼고 부르면 HRUM 이 400birth_time_required로 답합니다(과금 없음). 이 책의 화면은 원국에 시각이 없으면 아예 부르지 않고 "태어난 시각을 넣어 주세요" 안내만 보여 줍니다. 요금은 ₩50/호출이고, 프록시가 회원·입력별로 저장해 두어 같은 원국을 다시 열면 0원입니다(lib/api-cache.ts). 지금 대한·올해 유년은 기준일(referenceDate)에 따라 달라지므로 화면은 이달 1일을 기준일로 보냅니다 — 달이 바뀌면 한 번 새로 계산합니다. 풀이 문단 옵션(readings, ₩20 추가)은 이 책에서 쓰지 않습니다 — 풀이는 상담(6-2)에서 받습니다.
[프롬프트]
자미두수 명반 화면을 만들어 주세요. 아래 코드 파일 세 개(lib/ziwei.ts · components/ZiweiChart.tsx · tests/ziwei.test.tsx)와 components/Fortune.module.css 는 get-book-code 가 이미 만들었다 — 고쳐 쓰지 말고 그대로 쓴다. 화면 파일(app/page.tsx)에는 <ZiweiChart profile={원국} /> 와 상담 <Chat profile={원국} topic={{ only: 'ziwei' }} /> 를 놓는다.
데이터: POST /api/float/ziwei-chart (이미 만든 프록시 경유 — lib/api.ts 의 callProxy, requestId 필수)
요청 body: ziweiBody(원국) = { year, month, day, hour, minute, gender, referenceDate: "이달 1일" } — hour 가 없으면 ziweiBody 가 null 을 돌려주고 화면은 부르지 않는다
응답 data: { chart: { solarDate, lunarDate, yearGanzhi, fiveElementsClass, soulPalaceBranch, bodyPalaceBranch, palaces[12], mutagens[], currentDecadal, yearly, yearBasisNote }, notes: { lateZiNotice, approximateTimeNote }, clock, referenceDate, lang }
palaces[] 하나 = { key(ming·siblings·…), label(명궁 …), branch{key,label}, stem{key,label}, isBodyPalace, majorStars[], minorStars[], decadal{from,to,isCurrent} }
별 하나 = { key, label, brightness{key,label}|null, mutagen{key,label}|null }
화면 요구사항 (components/ZiweiChart.tsx 가 이미 지키는 것 — 디자인을 바꿀 때 깨지 말 것):
1. 응답은 lib/ziwei.ts 의 normalizeZiwei(raw) 로 먼저 정규화하고 그 결과(ZiweiView)만 그린다. 없는 값은 빼고, 값을 지어내지 않는다
2. 넓은 화면은 전통 4×4 판(사·오·미·신 / 진·유 / 묘·술 / 인·축·자·해, 가운데 2×2 = 요약), 좁은 화면(640px 미만)은 12궁 목록
3. 궁마다 궁 이름·천간지지·주성(밝기·사화)·보조성·대한 나이. 명궁·신궁·지금 대한·올해 유년은 배지로 표시
4. 가운데 요약: 오행국 · 생년 간지 · 양력/음력 · 명궁/신궁 지지 · 생년사화 4개 · 지금 대한 · 올해 유년. notes 의 안내 문구는 판 아래 그대로
5. 시각이 없는 원국: 명반 대신 안내 한 줄(부르지 않는다). 상담도 띄우지 않는다 — 자미두수 상담은 시각이 있어야 한다
완료 기준 (전부 실제로 실행해 증명):
- npx vitest run tests/ziwei.test.tsx 통과
- 1990-05-15 14:30 여성으로 실제 호출 → 12궁이 모두 보이고, 명궁 주성·생년사화·지금 대한이 응답 JSON(개발자도구 Network 의 data)과 같다
- 시각 모름 원국 → Network 에 ziwei-chart 호출이 없고 안내 한 줄만 보인다. 화면 어디에도 undefined·null·NaN 이 없다
- 390px·1280px 에서 document.documentElement.scrollWidth === clientWidth (가로 넘침 0)
- 스크린샷 3장: 1280px 판 / 390px 목록 / 시각 모름 안내
lib/ziwei.ts — 명반 요청 body·화면용 타입·API 경계 정규화코드 자리: cat lib/ziwei.ts · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
components/ZiweiChart.tsx — 명반 화면 (넓으면 4×4 판, 좁으면 목록)코드 자리: cat components/ZiweiChart.tsx · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
tests/ziwei.test.tsx — 시각 없음·전체·불완전 응답코드 자리: cat tests/ziwei.test.tsx · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
서양 점성술의 네이탈 차트는 태어난 순간 하늘에 있던 천체의 자리입니다. HRUM 점성술 토탈 API(POST /astrology)는 한 번 호출로 태양·달·수성·금성·화성·목성·토성·천왕성·해왕성·명왕성 10개 천체의 별자리·도수·하우스, 상승궁(ASC)·중천(MC), 애스펙트·품위, 그리고 HRUM 이 미리 써 둔 풀이 문단(readings)을 돌려줍니다. 절기가 아니라 서양 점성술 표준인 트로피컬 황도 기준입니다. 같은 입력이면 언제나 같은 차트입니다.
상승궁은 시각과 장소가 둘 다 있어야 나옵니다: 출생 시각(birthTime+birthTimePrecision: "exact")과 출생지 좌표(birthLatitude·birthLongitude)가 모두 있어야 상승궁·중천과 하우스가 계산되고, 하나라도 없으면angles: null과 그 이유anglesOmitted(time_unknown|coordinates_missing)가 옵니다 — HRUM 은 상승궁을 짐작해 채우지 않습니다. 시각을 모르면 정오 기준으로 계산합니다(그날 달의 별자리가 바뀌면moonSignUncertain: true). 하우스 체계는 HRUM 이 기본값을 정하지 않아(houseSystems를 빼면 4종 전부) 이 책은 플라시두스 하나(["placidus"], lib/natal.ts 의 HOUSE_SYSTEM)를 고릅니다. 이 책의 화면은 원국의 생년월일시를 쓰고, 출생지는 국내 8개 도시 중에서 고르게 합니다(lib/natal.ts 의 CITIES — 도시는 한 줄씩 더할 수 있습니다. 시각은 한국 시간(UTC+9, HRUM 기본값)으로 보냅니다). 별자리 이름은lang(기본 ko) 언어로 오고, 이 책은 한국어 그대로 씁니다. 요금은 ₩50/호출(풀이 문단 포함 — 추가 요금 없음)이고, 프록시가 회원·입력별로 저장해 두어 같은 입력이면 0원입니다. 풀이 문단은 미리 써 둔 문단이 있는 키에만 붙습니다(즉석으로 지어내지 않음 — 천왕성·해왕성·명왕성이 낀 각은 문단이 없습니다). 문단을 못 읽은 때도 차트는 그대로 오고readingsUnavailable: true입니다. 내 질문에 맞춘 풀이는 상담(6-2)에서 받습니다.
[프롬프트]
나의 별자리(네이탈 차트) 화면을 만들어 주세요. 아래 코드 파일 세 개(lib/natal.ts · components/NatalChart.tsx · tests/natal.test.tsx)와 components/Fortune.module.css 는 get-book-code 가 이미 만들었다 — 고쳐 쓰지 말고 그대로 쓴다. 화면 파일(app/page.tsx)에는 <NatalChart profile={원국} /> 와 상담 <Chat profile={원국} topic={{ only: 'astro' }} /> 를 놓는다.
데이터: POST /api/float/astrology (점성술 토탈, 프록시 경유 — callProxy, requestId 필수)
요청 body: natalBody(원국, 도시) = { birthDate: "YYYY-MM-DD", houseSystems: ["placidus"], birthTime?: "HH:mm", birthTimePrecision?: "exact", birthLatitude?, birthLongitude? }
시각을 모르면 birthDate·houseSystems 만 보낸다. 좌표는 시각과 도시가 둘 다 있을 때만 넣는다(좌표만으로는 상승궁이 나오지 않는다)
응답 data: { natal: { bodies[10], angles|null, anglesOmitted, houses, aspects, dignities, features, moonSignUncertain }, readings: { dignities[], aspects[] }, readingsUnavailable, costPoints: 50 }
천체 하나 = { id(Sun~Pluto), sign, signName(한글 별자리), degreeInSign(0~30), house: { placidus: 1~12 }, retrograde, dignities }
angles = { ascSign, mcSign, … } — 각각 { sign, signName, degreeInSign } · 풀이 문단 하나 = { key, text, lang }
화면 요구사항 (components/NatalChart.tsx 가 이미 지키는 것):
1. 응답은 lib/natal.ts 의 normalizeNatal(raw) 로 먼저 정규화하고 그 결과(NatalView)만 그린다
2. 맨 위 한 줄: 태양·달·상승궁 별자리. 아래 표: 천체·별자리·도수·하우스 — 10개 천체 + 상승궁·중천(있을 때) (표에는 caption·th scope)
3. 상승궁이 없으면 이유를 사람 말로: 시각이 없으면 "태어난 시각을 넣으면 상승궁도 볼 수 있어요", 시각은 있는데 도시를 안 골랐으면 "태어난 도시를 고르면". anglesOmitted(영문)은 화면에 그대로 내지 않는다
4. 도시 고르기는 시각이 있는 원국에서만 보이고, 고른 도시는 이 브라우저에 기억한다
5. 표 아래 "풀이": readings 의 문단(품위 → 애스펙트 순) 목록. readingsUnavailable 이면 "불러오지 못했어요", 문단이 없으면 상담으로 안내
완료 기준 (전부 실제로 실행해 증명):
- npx vitest run tests/natal.test.tsx 통과
- 1990-05-15 12:00 + 서울로 실제 호출 → 10개 천체·상승궁·중천·풀이 문단이 응답 JSON(Network 의 data)과 같다
- 시각 모름 원국 → 상승궁 없이 안내 문구, 화면 어디에도 undefined·null·NaN 이 없다
- 390px·1280px 가로 넘침 0 (표만 내부 가로 스크롤)
lib/natal.ts — 차트 요청 body·출생 도시·API 경계 정규화코드 자리: cat lib/natal.ts · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
components/NatalChart.tsx — 별자리 화면 (요약 한 줄 + 천체 표 + 풀이 문단)코드 자리: cat components/NatalChart.tsx · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
tests/natal.test.tsx — 시각·도시·상승궁 없음·풀이 없음·깨진 응답코드 자리: cat tests/natal.test.tsx · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
더 해 보기 — 지금의 하늘과 두 사람의 별자리: 같은/astrology에 블록을 붙이면 같은 ₩50 한 번으로 더 받습니다.timing: { orbDegrees, at?, progressions?, yearLength }를 보내면 그 시점(at, 생략하면 지금) 하늘의 트랜싯과 2차 진행이 출생 천체와 맺는 각이timing에 옵니다(orbDegrees0.5~10 필수, 진행을 켜면yearLengthtropical|julian 필수 — 원전이 정하지 않은 값이라 직접 고릅니다).partner: { 상대의 출생 입력 }을 보내면 두 사람 천체 사이 각이synastry에 옵니다. 붙이려면 프록시(app/api/float/[...path]/route.ts)의astrology허용 필드에 넣고houseSystems처럼 모양을 다시 만들어 넘기세요 — 브라우저 본문을 그대로 넘기지 않는 규칙은 같습니다.timing은 시점마다 답이 달라지므로at을 꼭 넣어 저장 열쇠에 시점이 들어가게 하세요. 이 책의 화면은 출생 차트만 그립니다.
HRUM 타로 API(POST /tarot)는 78장 덱(메이저 22 · 마이너 56)에서 겹치지 않게 카드를 뽑아, 카드마다 정·역방향, 키워드, 뜻, 카드 이미지 주소를 돌려줍니다. AI 해석은 들어 있지 않습니다 — 뜻은 카드에 정해진 문장 그대로이고, 해석은 상담(6-2)에서 받습니다. 장수(count 1~10)나 스프레드(spread) 중 하나로 뽑으며, 이 책의 화면은 스프레드 다섯 가지로 고르게 합니다.
| spread | 이름 | 장수 · 자리 |
|---|---|---|
single | 오늘의 한 장 | 1장 |
past-present-future | 과거·현재·미래 | 3장 — 과거·현재·미래 |
choice | 선택 | 3장 — 현재 상황·장애물·결과 |
celtic-condensed | 켈틱 축약 | 5장 |
future-prediction | 미래 예측 | 5장 |
출생 정보가 필요 없습니다: 타로는 생년월일 없이 뽑습니다. 요금은 호출 1번에 고정 요금 하나입니다 — 1장이든 5장이든 같고, 금액은 콘솔 요금표의 계산형 표에 있으며 응답의costPoints가 이번 차감액입니다. 뽑을 때마다 새 카드라 저장·재사용하지 않습니다.seed를 보내면 같은 seed·같은 스프레드는 언제나 같은 카드가 나옵니다 — 이 책은 뽑을 때마다 새 seed 를 만들고, 상담이 그 seed 로 같은 카드를 다시 뽑아 그 카드로 풀이합니다(6-2). 카드 이미지(imageUrl·cardBackUrl)는 주소에 버전이 들어 있어 바뀌지 않는 WebP(가로 600px)이고, 역방향 카드는 이미지를 180도 돌려 보여 줍니다(이미지는 정방향 하나뿐입니다). 그림은 1909 Rider-Waite(Pamela Colman Smith, 퍼블릭 도메인) 원판 스캔이며, 파일별 출처는 출처 원장에 있습니다.
[프롬프트]
타로 뽑기 화면을 만들어 주세요. 아래 코드 파일 세 개(lib/tarot.ts · components/TarotDraw.tsx · tests/tarot.test.tsx)와 components/Fortune.module.css 는 get-book-code 가 이미 만들었다 — 고쳐 쓰지 말고 그대로 쓴다. 화면 파일(app/page.tsx)에는 <TarotDraw onDrawn={setTopic} /> 를 놓고, 한 번 뽑은 뒤에만 상담 <Chat key={JSON.stringify(topic)} profile={null} topic={topic} /> 을 띄운다(topic = { only: 'tarot', tarot: { spread, seed } } — 뽑을 때마다 새 상담 화면).
데이터: POST /api/float/tarot (프록시 경유 — callProxy, requestId 필수)
요청 body: tarotBody(spread, seed, 질문?) = { spread, seed, question? } — seed 는 뽑을 때마다 새로(newRequestId()), 질문은 적었을 때만(500자까지)
응답 data: { lang, count, spread: { type, name, description, positions[] } | null, reversedAllowed, seed, cards[], cardBackUrl, question, costPoints }
카드 하나 = { position(1부터), positionLabel(스프레드 자리 이름|null), id, name, nameEn, arcana, reversed, keywords[], meaning, imageUrl }
화면 요구사항 (components/TarotDraw.tsx 가 이미 지키는 것):
1. 스프레드 버튼 다섯 개(장수 표시) + 질문 입력(선택) + "카드 뽑기". 뽑는 동안 버튼 비활성화(두 번 눌러 두 번 과금되지 않게)
2. 응답은 lib/tarot.ts 의 normalizeTarot(raw) 로 먼저 정규화한다. 이미지 주소는 https 만 쓴다(그 밖의 주소는 버린다)
3. 카드마다 자리 이름·카드 이름·역방향 여부·키워드·뜻. 역방향 카드 이미지는 180도 돌린다. 이미지에는 카드 이름이 든 alt
4. 뽑은 뒤 onDrawn({ only: 'tarot', tarot: { spread, seed } }) — 상담은 이 seed 로 같은 카드를 읽는다
완료 기준 (전부 실제로 실행해 증명):
- npx vitest run tests/tarot.test.tsx 통과
- 과거·현재·미래로 실제 호출 → 카드 3장이 자리 이름과 함께 보이고, 역방향 카드는 이미지가 뒤집혀 있다. 이름·뜻이 응답 JSON 과 같다
- 뽑은 뒤 상담에 "이 카드들로 봐 주세요" → 답변이 화면의 카드 이름을 말한다(같은 seed 라 같은 카드)
- 390px·1280px 가로 넘침 0
lib/tarot.ts — 스프레드·요청 body·API 경계 정규화코드 자리: cat lib/tarot.ts · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
components/TarotDraw.tsx — 타로 뽑기 화면 (스프레드·질문·카드)코드 자리: cat components/TarotDraw.tsx · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
tests/tarot.test.tsx — 전체·깨진 응답·https 아닌 이미지 주소코드 자리: cat tests/tarot.test.tsx · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
HRUM 꿈해몽 API(POST /dream)는 사용자가 적은 꿈 이야기(1,000자까지)에서 꿈 상징 낱말을 찾아, 상징 낱말(keywords, 항목마다 기본 낱말 하나)과 점수를 꿈에서 두드러진 순으로 돌려줍니다(최대 40개, score 0~1). 낱말은 HRUM 자체 꿈 상징 어휘집(상징 1,166개)에서 찾습니다 — 어휘집에는 낱말만 있고 뜻이나 길흉 판단은 없습니다(mode: glossary · none). AI 해석도, 풀이 문장도 들어 있지 않습니다 — 화면은 찾은 상징을 보여 주고, 상징의 뜻은 상담(6-2)에서 AI 가 풀어 줍니다.
출생 정보가 필요 없습니다: 꿈 이야기(dream, 1,000자까지)만 있으면 됩니다. 꿈에서 느낀 감정(feeling, 300자까지)은 선택입니다. 상징 어휘집이 한국어 전용이라 응답 언어는 한국어뿐입니다. 요금은 ₩30/호출이고, 찾은 항목이 하나 이상일 때만 과금됩니다 — 결과가 비면(items: []) 0원입니다. 같은 꿈 글을 다시 보내면 프록시가 저장본으로 답해 0원입니다.
[프롬프트]
꿈해몽 화면을 만들어 주세요. 아래 코드 파일 세 개(lib/dream.ts · components/DreamSearch.tsx · tests/dream.test.tsx)와 components/Fortune.module.css 는 get-book-code 가 이미 만들었다 — 고쳐 쓰지 말고 그대로 쓴다. 화면 파일(app/page.tsx)에는 <DreamSearch onSearched={setTopic} /> 를 놓고, 한 번 찾은 뒤에만 상담 <Chat key={JSON.stringify(topic)} profile={null} topic={topic} /> 을 띄운다(topic = { only: 'dream', dream: 꿈 이야기 }).
데이터: POST /api/float/dream (프록시 경유 — callProxy, requestId 필수)
요청 body: dreamBody(꿈, 감정?) = { dream, feeling? } — 앞뒤 공백을 지우고 1,000자·300자까지
응답 data: { lang: "ko", mode, items: [{ keywords: 상징 낱말[], score }], llm?: { status, report } }
화면 요구사항 (components/DreamSearch.tsx 가 이미 지키는 것):
1. 꿈 입력(필수, 1,000자) + 감정 입력(선택, 300자) + "해몽 찾기". 찾는 동안 버튼 비활성화
2. 응답은 lib/dream.ts 의 normalizeDream(raw) 로 먼저 정규화한다
3. 찾은 상징 낱말을 칩으로 한 번씩 보이고, "관련 상징" 목록(낱말 · 관련도 %)은 관련도가 가장 높은 항목을 강조해 처음 8개만 보인다. 나머지는 "더 보기"
4. 하나도 못 찾으면 오류가 아니라 "꿈에서 상징을 찾지 못했어요" 한 줄(요금 0원). 풀이 문장은 응답에 llm.report(status ok)가 있을 때만 보이고, 없으면 "상징이 무슨 뜻인지는 아래 상담에서 AI 에게 물어보세요" 안내 한 줄
5. 찾은 뒤 onSearched({ only: 'dream', dream }) — 상담은 같은 꿈 이야기로 꿈해몽 AI 에게 묻는다
완료 기준 (전부 실제로 실행해 증명):
- npx vitest run tests/dream.test.tsx 통과
- "큰 뱀이 집 안으로 들어와 물을 마셨어"로 실제 호출 → 화면의 상징 낱말·관련도가 응답 JSON 의 keywords·score 와 같다
- 아무 뜻 없는 글자로 호출 → 안내 한 줄, 콘솔 사용량에 ₩0
- 390px·1280px 가로 넘침 0
lib/dream.ts — 요청 body·API 경계 정규화코드 자리: cat lib/dream.ts · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
components/DreamSearch.tsx — 꿈해몽 화면 (입력·결과·더 보기)코드 자리: cat components/DreamSearch.tsx · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
tests/dream.test.tsx — 찾음·못 찾음·깨진 응답코드 자리: cat tests/dream.test.tsx · 없거나 verify 가 "다름"이면 node scripts/get-book-code.mjs · 손으로 고치지 않는다 · 원문(사람용 — AI 는 열지 않는다)
AI용 분할본 · 지도 3단계(화면) · 이 페이지 전에 읽을 페이지: PART 0 사용법·AI 시작 지침, 부록 A 스펙·코드 받기(A-2) · 코드 파일은 여기 없다 — 코드 자리의 줄을 따른다(폴더에 책 파일이 하나도 없는 처음이면 부록 A-2 의 curl 한 줄부터)