← 문서 목록

Functional Specification현행 (As-Built)

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

문서 버전
v2.4 (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사주 풀이 노출

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 대상도 바뀜
⑤ 풀이 생성 "신금(辛金) 기운의 당신은 오늘 목(木)·토(土) 기운이 부족한 하루예요. 부족한 목(木) 기운을 치킨 샐러드로 채워 …"

시간을 모르는 경우 시주(壬辰)를 제외한 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의 시간대 판정 · 이미지 다양성 · 메뉴 데이터 규모 테스트는 그대로 유지
컴포넌트 결과 화면 풀이 노출 — 일간 · 부족 오행별 문구 렌더링, 시간 모름 케이스
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시 저녁 → 아침으로 변경(자정에 날짜 시드가 바뀌므로)