← 문서 목록

Functional Specification현행 (As-Built)

Energy Meal 기능명세서 v2
— 사주팔자 · 오행 엔진 고도화

문서 버전
v2.5 (As-Built — 사주 풀이 쉬운 말 개정, §10 개정 이력 참조)
기준일
2026-07-29
이전 버전
기능명세서 v1 — 구버전
변경 범위
추천 엔진 전면 교체(web/lib/saju/ 신설, recommendation.ts 재작성), 결과 화면 풀이 노출(/today). 입력 폼 · 저장 데이터 스키마는 변경 없음
검증 결과
만세력 golden test 14건 · 단위/컴포넌트 47건 · E2E 2건 전부 통과. 일진은 두 독립 라이브러리 교차검증 300건 불일치 0건

1.고도화 배경 및 목표

v1 추천 엔진은 사주 컨셉만 차용한 간이 규칙(출생 시각 · 생월 · 생일 일자 기반)으로, 실제 사주 요소가 없고 사용자별 · 날짜별 차별성이 부족했습니다. v2는 엔진을 만세력 기반 사주팔자 · 오행 분석으로 전면 교체합니다.

v1 한계v2 목표
L-1 사주 요소 부재 (생일 숫자 규칙)사주팔자(년 · 월 · 일 · 시주) 계산 후 오행 분포로 체질 판정
L-2 음력 입력 미변환음력 생년월일을 실제로 양력 변환하여 계산에 반영
L-3 사용자 상태 4종뿐팔자 조합(60갑자 × 8자리)으로 사용자별 오행 분포 차별화
L-4 날짜와 무관한 추천오늘의 일진(日辰) 오행을 결합해 매일 달라지는 추천 구현
L-5 이미지별 고정 이유 문구일간 · 부족 오행 · 일진 기반 동적 사주 풀이 노출

2.신규 추천 파이프라인

① 달력 정규화음력 입력 시
양력 변환
→
② 사주팔자년 · 월 · 일 · 시주
(시간 모름 → 6자)
→
③ 오행 분석분포 집계
일간 · 부족 오행
→
④ 일진 결합오늘의 간지
오행 가중
→
⑤ 추천 · 풀이오행 스코어링
풀이 문구 생성

시간대 필터와 이미지 다양성 재정렬(v1 §5.2, §5.4)은 검증된 로직이므로 그대로 유지하고, 상태 추론과 스코어링 단계만 교체합니다.

3.기능 명세

FR-501음력 → 양력 변환

FR-502사주팔자 계산

FR-503오행 분포 및 부족 오행 판정

FR-504오늘의 일진

FR-505메뉴 오행 태깅

FR-506오행 기반 스코어링 (확정)

시간대 후보군의 각 메뉴에 아래 가점을 합산해 정렬합니다.

가점조건비고
+5메뉴 오행이 나의 부족 오행과 일치주 가점 — 아래 가점을 모두 합쳐도 넘지 못하게 설계
+2메뉴 오행이 오늘의 일진이 생(生)하는 오행일진 가점 — 같은 사용자도 날마다 순위가 바뀌는 요인
+1 / +1메뉴 온도 · 무게가 체질의 반대 속성v1의 주 가점(+4/+3)을 보조 축으로 강등
+1 (10% 확률)부족 오행 비일치 메뉴의 랜덤 허용치v1과 동일한 탐색 다양성 장치
−100최근 14일 안에 이미 보여준 메뉴재추천 방지 — 가점 총합보다 크게 잡아 "안 본 메뉴"가 항상 앞
−3최근 2일 안에 보여준 계열(baseMenu)요거트 → 요거트 같은 계열 반복만 완화

FR-507사주 풀이 노출

오행칩 표기(자연물)부족해지기 쉬운 것 (문장)음식 수식 (§5 매핑 기준, 첫 버전)
목 (木)나무(木)활력상큼한
화 (火)불(火)온기뜨끈한
토 (土)흙(土)든든함구수한
금 (金)쇠(金)집중력담백한
수 (水)물(水)여유깊은 맛의
일간 (나의 기운)칩 표기성향 수식 (현재 문장에는 쓰지 않음 · 칩 · 확장용)
갑목 (甲木)큰 나무곧게 뻗은 큰 나무처럼 우직한
을목 (乙木)풀꽃바람에 흔들려도 꺾이지 않는 풀꽃처럼 유연한
병화 (丙火)태양한낮의 태양처럼 환하고 활기찬
정화 (丁火)촛불어둠을 밝히는 촛불처럼 따뜻하고 섬세한
무토 (戊土)큰 산큰 산처럼 묵직하고 든든한
기토 (己土)기름진 땅씨앗을 품는 땅처럼 포용력 있는
경금 (庚金)무쇠무쇠처럼 강직한
신금 (辛金)보석잘 다듬은 보석처럼 예리하고 섬세한
임수 (壬水)넓은 바다넓은 바다처럼 깊고 유연한
계수 (癸水)빗물조용히 스미는 빗물처럼 맑고 지혜로운

FR-508최근 추천 이력 (재추천 방지)

FR-509알림 진입 시 본 끼니 교체

4.사주 계산 규칙 상세

기둥계산 규칙비고
년주 (年柱) 60갑자 순환. 해의 경계는 1월 1일이 아닌 입춘(立春) 기준 입춘 이전 출생은 전년도 간지 적용
월주 (月柱) 월의 경계는 절기(節氣) 기준 (입춘 · 경칩 · 청명 …). 월간은 년간에 따른 규칙(오호둔)으로 결정 절기 시각 데이터 필요 — 라이브러리 제공 여부가 선정 기준
일주 (日柱) 기준일로부터의 경과일수로 60갑자 순환 계산 (순수 산술) 오늘의 일진(FR-504)도 동일 규칙 사용
시주 (時柱) 십이시 구간으로 시지 결정, 시간은 일간에 따른 규칙(오서둔)으로 결정 진태양시 −30분 보정 후 판정. 시간 모름 시 생략
경계 케이스 정책 — 야자시/조자시 구분은 1차 범위에서 적용하지 않고 23:00~00:59를 자시로 일괄 처리합니다(현행 입력 UI의 십이시 구간과 일치). 입춘 당일 출생은 절입 시각 기준으로 판정합니다.

4.1 계산 예시 — 팔자 조합에서 추천까지

1996-08-22(양력) 진시(07:00–08:59) 출생 사용자의 실제 계산 흐름입니다.

단계결과
① 사주팔자 년주 병자(丙子) · 월주 병신(丙申) · 일주 신묘(辛卯) · 시주 임진(壬辰)
② 오행 분포 (8자) 천간 丙화 丙화 辛금 壬수 + 지지 子수 申금 卯목 辰토 → 목 1 · 화 2 · 토 1 · 금 2 · 수 2
③ 일간 · 부족 오행 나의 기운 = 일간 신금(辛金) · 부족 오행 = 최소 개수 동률인 목(木) · 토(土)
④ 스코어링 (예: 갑진일) 목 메뉴(샐러드 · 과일 · 요거트)와 토 메뉴(죽 · 덮밥 · 정식)에 +5. 일진 갑진(甲辰)은 목 기운 → 목이 생하는 화 메뉴(떡볶이 · 볶음류)에 +2. 다음 날 일진이 바뀌면 +2 대상도 바뀜
⑤ 풀이 생성 "활력이 부족해지기 쉬운 당신, 오늘 점심은 상큼한 치킨 샐러드 어때요?" (v2.5 쉬운 말 개정 후 — 뼈대는 메뉴마다 다름. 이전: "신금(辛金) 기운의 당신은 오늘 목(木)·토(土) 기운이 부족한 하루예요. …")

시간을 모르는 경우 시주(壬辰)를 제외한 6자 분포로 같은 흐름을 적용합니다.

5.오행 · 음식 매핑 기준

메뉴 오행 태깅(FR-505)은 전통 오행 식품 분류(맛 · 색 · 성질)를 기준으로 하되, 메뉴의 주 재료와 조리 방식으로 판단합니다.

오행맛 · 색대표 분류해당 메뉴 예시 (현 DB 기준)
목 (木)신맛 · 녹색채소 · 과일 · 산미 있는 음식샐러드류, 과일 · 요거트류, 아보카도 토스트
화 (火)쓴맛 · 붉은색맵고 뜨거운 음식 · 불 조리떡볶이, 짬뽕, 볶음류, 튀김류, 김치 · 부대찌개, 매운 라멘
토 (土)단맛 · 노란색곡물 · 구수함 · 부드러운 음식죽류, 덮밥 · 정식류, 짜장면, 된장찌개 · 청국장
금 (金)매운맛 · 흰색흰 살 재료 · 담백함 · 맑은 면삼계탕, 닭죽, 유부우동 · 칼국수 · 잔치국수, 모둠 · 새우초밥
수 (水)짠맛 · 검은색해산물 · 짠 국물 · 차가운 음식물냉면, 메밀소바, 국밥류, 해산물 포케 · 연어초밥, 전복죽

상생 관계(일진 가점 판정용): 목→화→토→금→수→목. 전체 메뉴의 확정 태깅은 web/lib/menu-data.ts의 시드 테이블(MENU_SEEDS)에 기록되어 있습니다.

6.데이터 스키마 변경

대상변경내용
UserData (localStorage) 변경 없음 생년월일 · 시간 · 성별 · 양음력 그대로 사용 → 기존 사용자 마이그레이션 불필요
UserState 교체 { temperature, weight, timeSlot } → { dayMaster(일간), elementCounts(오행 분포), deficientElements(부족 오행), todayElement(일진 오행), timeSlot } + 보조 축으로 temperature · weight 유지
MenuRecommendation 필드 추가 element: "wood" | "fire" | "earth" | "metal" | "water" 추가 (기존 필드 유지)
신규 모듈 추가 lib/saju/calendar.ts(변환 · 절기) · pillars.ts(사주팔자) · elements.ts(오행 분석) · interpretation.ts(풀이 생성)

7.기술 결정 사항

결정선택근거
계산 위치 클라이언트 유지 사주 계산은 밀리초 수준의 결정적 연산으로, 서버리스 · localStorage 구조를 바꿀 이유가 없음
만세력 라이브러리 확정: lunar-typescript + korean-lunar-calendar lunar-typescript가 사주팔자 · 절기(입춘 경계 포함) 계산, korean-lunar-calendar가 한국 기준 음력→양력 변환 담당. 선정 전 검증: 년주 앵커 4건 · 입춘 경계 2건 · 시주 규칙(오서둔) · 일진 300건 교차검증(두 라이브러리 독립 계산, 불일치 0건) 통과
진태양시 보정 미적용으로 변경 (Draft: −30분 적용) 입력 UI가 분 단위 시각이 아닌 십이시 구간 선택이므로, 사용자가 고른 십이시가 곧 시지(時支)가 됨. 구간 시작 시각에 −30분을 적용하면 오히려 선택한 십이시와 다른 시지로 계산되는 오류가 생겨 미적용으로 확정
일주 경계 (자시) 23시 출생 = 당일 일주 + 자시 야자시 구분 없음(§4 정책). 검증에서 23:30 출생이 당일 일주를 유지함을 확인
스코어링 가중치 (확정) 부족 오행 +5 · 일진 상생 +2 · 온도/무게 반대 각 +1 · 랜덤 허용치 10% +1 부족 오행 가점이 항상 우세하도록 설계 (비일치 메뉴의 이론상 최대 +5 동점 시 부족 오행 일치가 타이브레이크 우선)
체질 판정 방식 부족 오행 보완 휴리스틱 정통 억부용신은 복잡도가 과도. 메뉴 추천 도메인에서는 "부족한 기운을 채우는 음식" 서사가 직관적이고 풀이로 설명하기 쉬움
지장간 1차 미적용 (본기만) 분포 정밀도보다 규칙 단순성 우선. 필요 시 v2.x에서 확장

8.구현 단계 계획

단계작업산출물
Step 1 만세력 라이브러리 검증 · 선정 + 사주팔자 계산 코어 구현 lib/saju/calendar.ts · pillars.ts + 만세력 대조 golden test
Step 2 오행 분포 · 부족 오행 · 오늘의 일진 계산 lib/saju/elements.ts + 단위 테스트
Step 3 메뉴 120종 오행 태깅 menu-data.ts element 필드 + 태깅 검수표(§5 갱신)
Step 4 추천 엔진 교체 (오행 스코어링, 기존 인터페이스 유지) lib/recommendation.ts 재작성 + 테스트 개편
Step 5 풀이 생성기 + 결과 화면 풀이 노출 lib/saju/interpretation.ts + /today UI 반영
Step 6 테스트 정비 · E2E 갱신 · 문서 As-Built 반영 전체 테스트 그린 + 기능명세서 v2 확정판
최대 리스크는 Step 1의 사주 계산 정확도입니다. 계산이 틀리면 이후 단계 전체가 무의미하므로, 한국 만세력 서비스와의 대조 테스트를 통과한 뒤에만 Step 2로 진행합니다.

9.테스트 계획

계층내용
Golden test (핵심) 알려진 생년월일시 케이스의 사주팔자를 한국 만세력과 대조. 케이스 구성: 일반 케이스 · 음력 입력 · 입춘 경계(전후일) · 자시 출생(진태양시 보정 경계) · 시간 모름
단위 오행 분포 집계 · 부족 오행 판정(동률 포함) · 일진 계산 · 상생 관계 가점 · 스코어링 정렬
회귀 유지 v1의 시간대 판정 · 이미지 다양성 · 메뉴 데이터 규모 테스트는 그대로 유지
컴포넌트 결과 화면 풀이 노출 — 부족 보완 · 일진 · 균형 케이스별 문장, 사주 용어 미노출, 메뉴별 뼈대 다양성(10개 중 5종 이상) · 위치 회전(연속 5개 모두 다름) · 60자 이내, 시간 모름 케이스
E2E 기존 온보딩 → 결과 흐름 유지 + 풀이 섹션 노출 확인 추가

10.개정 이력

버전일자내용
v2.0 Draft 2026-07-29 사주팔자 · 오행 엔진 고도화 설계
v2.0 2026-07-29 엔진 구현 완료 — 라이브러리 · 가중치 · 진태양시 결정 확정 (As-Built)
v2.1 2026-07-29 콘텐츠 확장 — 사용자 피드백("메뉴가 한정적이고 이미지가 똑같다") 대응. 메뉴 120종 → 232종(분식 · 중식 · 일식 · 양식 · 아시안 추가, 아침 50 / 점심 95 / 저녁 94), 공유 이미지 14종 → 요리별 개별 이미지 196종(AI 푸드 포토그래피로 일괄 생성, 톤 통일). 카드 배경 팔레트는 각 이미지의 평균 색상에서 자동 추출(scripts/build-menu-image-map.mjs). 메뉴 데이터는 시드 테이블 구조로 개편, 모든 이미지 키의 에셋 존재를 테스트로 강제
v2.2 2026-08-17 재추천 방지 — 사용자 피드백("알림 보고 들어와도 어제와 같은 메뉴") 대응. 최근 추천 이력(FR-508) 신설 및 스코어링 감점 추가, 동점 정렬에 날짜 시드 랜덤 타이브레이크 도입(FR-506)
v2.3 2026-08-22 알림 진입 시 본 끼니 교체(FR-509) 신설 — 사용자 피드백 ("오전에 점심 · 저녁까지 다 봤는데 점심 알림에 같은 메뉴가 뜬다") 대응. 끼니 탭 열람 기록(1.5초 체류 기준)을 남겨, 알림을 눌러 들어온 진입에 한해 이미 본 끼니만 다음 후보로 교체하고 "이전 메뉴 보기"로 되돌릴 수 있게 함. 백그라운드 복귀 시 끼니가 넘어갔으면 현재 끼니 탭으로 이동
v2.4 2026-09-08 알림 진입 교체(FR-509) 개정 — 사용자 피드백("빠르게 슥 넘기며 보는데 알림에 이미 본 메뉴가 그대로 뜬다") 대응. 열람 기준을 1.5초 체류에서 탭을 직접 누르면 즉시 · 열 때 펴진 첫 탭도 즉시로 바꾸고(자동 전환된 탭만 체류 기준 유지), 결과 화면 첫 탭 규칙을 0~4시 저녁 → 아침으로 변경(자정에 날짜 시드가 바뀌므로)
v2.5 2026-09-28 사주 풀이(FR-507) 쉬운 말 개정 — 사용자 피드백("임수가 뭔지, 화 기운이 부족해서 뭘 어쩌라는 건지 사주를 모르면 알 수 없다"). 1차로 용어를 괄호로 뒤에 두는 방식("촛불처럼 따뜻한 정화(丁火)의 당신은 타고난 다섯 기운(오행) 중 …")을 시도했으나 "그 내용 자체가 이해가 안 된다"는 재피드백으로 같은 날 철회. 최종: 풀이 문장에서 일간 · 오행 · 일진 용어와 "다섯 기운" 개념을 모두 빼고 ① 당신은 어떤 타입(자연물 비유) ② 무엇이 부족해지기 쉬운지(활력 · 온기 · 든든함 · 집중력 · 여유) ③ 그래서 이 끼니엔 이 메뉴 — 3단으로 씀. 부족 오행은 "오늘 부족한 하루"가 아니라 타고난 경향으로. 사주 용어는 정보 칩에만("나의 기운 · 촛불 정화(丁火)" · "오늘의 기운 · 나무(木) · 갑진일"). 이어서 "다시 추천하기를 눌러도 메뉴 이름만 바뀐다"는 피드백으로 문장 조각을 메뉴 id 해시로 골라 조합했으나 "여전히 비슷하고 너무 길다"는 재피드백 → 최종 타입 문장 삭제 · 한두 문장(60자 이내) · 메뉴마다 문장 뼈대 자체가 다르게(부족 보완 5종 · 일진 3종 · 균형 3종, 사용자 선택 ②, 같은 메뉴는 늘 같은 문구). 안주 페어링 코멘트 · 칩 · 술 카드(v3 FR-605)도 같은 원칙 적용. 어휘표 추가