일진 오행은 스코어링 보조 가점과 풀이 문구에 반영되어, 같은 사용자라도 날짜에 따라 추천과 풀이가 달라집니다.
결과 화면 하단 문구 "내일은 또 다른 기운이 찾아올 거예요"가 실제 동작과 일치하게 됩니다.
FR-505메뉴 오행 태깅
메뉴 전체(현행 253종 — 안주 전용 포함. 한때 1,000종까지 늘렸다가 v3.0.20에서 실제로 사 먹을 수 있는 메뉴 위주로 재정비)에 element(오행) 속성을 부여합니다. 분류 기준은 §5의 매핑표를 따릅니다.
기존 온도 · 무게 속성은 삭제하지 않고 보조 가중치 축으로 유지합니다.
FR-506오행 기반 스코어링 (확정)
시간대 후보군의 각 메뉴에 아래 가점을 합산해 정렬합니다.
가점
조건
비고
+5
메뉴 오행이 나의 부족 오행과 일치
주 가점 — 아래 가점을 모두 합쳐도 넘지 못하게 설계
+2
메뉴 오행이 오늘의 일진이 생(生)하는 오행
일진 가점 — 같은 사용자도 날마다 순위가 바뀌는 요인
+1 / +1
메뉴 온도 · 무게가 체질의 반대 속성
v1의 주 가점(+4/+3)을 보조 축으로 강등
+1 (10% 확률)
부족 오행 비일치 메뉴의 랜덤 허용치
v1과 동일한 탐색 다양성 장치
−100
최근 14일 안에 이미 보여준 메뉴
재추천 방지 — 가점 총합보다 크게 잡아 "안 본 메뉴"가 항상 앞
−3
최근 2일 안에 보여준 계열(baseMenu)
요거트 → 요거트 같은 계열 반복만 완화
동점 시 부족 오행 일치 메뉴 우선 → 날짜 시드 기반 랜덤 → 원본 데이터 순서로 정렬합니다. 랜덤 타이브레이크가 없으면 같은 조건의 날마다 동점 무리의 맨 앞 메뉴가 늘 1순위로 고정됩니다.
비일치 메뉴의 이론상 최대 점수는 +5(1+2+1+1)로, 부족 오행 일치 메뉴(최소 +5)와 동점이 되어도 타이브레이크에서 밀립니다.
이미지 다양성 재정렬은 v1 로직을 그대로 재사용합니다.
FR-507사주 풀이 노출
결과 화면의 추천 이유 영역을 이미지별 고정 문구에서 사주 기반 동적 풀이로 교체합니다.
풀이 구성: 나의 기운(일간 오행) + 부족 오행 + 오늘의 일진 + 추천 근거.
예: "목(木) 기운의 당신은 오늘 수(水) 기운이 부족한 하루예요. 시원한 국물의 물냉면으로 균형을 채워보세요."
문구는 일간 오행(5) × 부족 오행(5) × 시간대(3) 조합의 템플릿으로 생성합니다.
내 정보 요약 카드의 십이시 표기(자시~해시)는 유지하며, 일간 정보(예: "갑목(甲木)의 하루")를 추가 노출할 수 있습니다.
FR-508최근 추천 이력 (재추천 방지)
끼니 알림을 보고 들어왔을 때 어제와 같은 메뉴가 떠 있으면 열어볼 이유가 없습니다. 화면에 실제로 띄운 메뉴를 이력으로 남기고, 다음 날 스코어링에서 감점합니다.
저장 위치는 localStorage의 energy-meal:recent-menus — { date, slot, name, baseMenu } 배열이며 최대 150건까지 보관하고 넘치면 오래된 것부터 버립니다.
기록 시점: ① 화면 진입 시 세 끼니의 현재 추천(탭을 열지 않아도 기록) ② "다시 추천"으로 갈아탄 메뉴.
오늘 날짜 이력은 감점 대상에서 제외합니다. 포함하면 새로고침할 때마다 방금 본 메뉴가 밀려나 하루 고정 원칙(§FR-506 시드)이 깨집니다.
필터가 아닌 감점이므로 이력이 후보군을 다 덮어도 추천이 비지 않습니다. 끼니별 후보가 194~409종이라 14일 창(하루 3건 + 다시 추천분)에는 여유가 있습니다.
FR-509알림 진입 시 본 끼니 교체
추천은 하루 고정(§FR-506 시드)이라, 아침에 점심 · 저녁 탭까지 미리 열어본 사용자는 점심 알림을 눌러도 이미 본 메뉴를 다시 보게 됩니다. 알림을 눌러 들어온 진입에 한해 그 끼니를 다음 후보로 갈아 끼웁니다.
"봤다"의 기준(v2.4 개정): 끼니 탭을 직접 누르면 그 즉시, 화면을 열 때 펴진 첫 탭도 즉시 본 것으로 기록합니다. 당초(v2.3)는 탭이 1.5초 이상 떠 있어야 했는데, 메뉴만 빠르게 훑고 넘기는 사용 습관에서는 기록이 남지 않아 알림으로 들어와도 이미 본 메뉴가 그대로 떴습니다(사용자 피드백). 1.5초 체류 기준은 자동으로 펴진 탭(백그라운드 복귀로 끼니가 넘어간 경우, 화면을 켜둔 채 자정을 넘겨 다시 만든 경우)에만 남깁니다 — 알림 진입 직전의 자동 전환까지 본 것으로 세면 정작 처음 보는 메뉴가 교체됩니다. 저장 위치는 energy-meal:viewed-slots — { date, slots }이며 날짜가 바뀌면 비워집니다.
열어보지 않은 끼니는 그대로 둡니다. 아침 · 점심만 봤다면 저녁 알림은 그날의 원래 추천을 보여줍니다.
진입 경로는 네이티브 셸이 붙이는 쿼리 /today?entry=meal-reminder&slot=<끼니>&n=<토큰>입니다. 토큰(알림 식별자 + 발생 시각)은 energy-meal:handled-entry에 남겨, 같은 알림으로 두 번 들어와도 연달아 교체되지 않게 합니다. 처리 후 쿼리는 주소창에서 지웁니다.
교체했을 때는 "오늘 이미 본 OO 메뉴라 새로 골랐어요" 안내와 "이전 메뉴 보기"를 함께 띄웁니다. 아침에 본 메뉴로 이미 마음을 정한 경우를 되돌릴 수 있어야 합니다.
그냥 앱을 다시 열거나 백그라운드에서 복귀한 경우에는 교체하지 않습니다. 메뉴가 바뀌는 계기는 알림 진입뿐입니다. 다만 백그라운드에 있는 동안 끼니가 넘어갔다면(아침 → 점심) 복귀 시 지금 끼니 탭으로 옮겨 줍니다.
결과 화면의 첫 탭은 지금 끼니입니다 — 0~10시 아침 · 11~16시 점심 · 17~23시 저녁(추천 엔진의 시간대 구분과 같음). 당초 0~4시는 저녁 탭이었으나, 날짜 시드가 자정에 바뀌어 새 날의 메뉴가 뜨므로 자정 직후 "오늘 메뉴"를 보러 온 사용자가 지난 저녁이 아닌 다음 날 저녁부터 보게 됐습니다 — v2.4에서 아침 탭으로 변경(사용자 결정).
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
사주 계산은 밀리초 수준의 결정적 연산으로, 서버리스 · 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시 저녁 → 아침으로 변경(자정에 날짜 시드가 바뀌므로)