🧩 코칭 엔진 모듈 명세 · ②/⑦

🌉 ② 추천엔진 (재료·궁합·체인)

오늘 무슨 식재료·메뉴를 권할지 고르는 결정론 엔진 — 결핍군 풀·콜드스타트 시드·youa 급식랭킹·구체 메뉴(popularDishesFor)로 LLM 괴식·환각을 차단

← 문서 허브 · 통합 아키텍처 지도 · 코칭 엔진 모듈 명세
🗺️ 이 문서는 통합 아키텍처 지도의 모듈 ②를 코드 단위로 펼친 명세입니다 — 비개발자 설명 → 구현 → 아키텍처 시각화 → 작동 예시 → 남은 문제. 정본 기준: lib/coachRecos.ts + app/api/cron/coach/route.ts 실측(2026-06-21). 이 모듈은 런타임에 추천을 고르는 쪽 — 그 재료가 되는 식재료 지식그래프·식단 인입·SQL·야간 자가학습은 추천 데이터 엔진(통합 정본) 참고(warmIngredientFreqFromSql·(기관,월) 멱등 집계는 거기 소관).

비개발자용 — 이 모듈이 무엇을 왜 하나

이 모듈은 코칭 편지가 부모에게 "오늘은 이걸 먹여보세요"라고 권할 때, AI가 머릿속으로 음식을 상상하지 못하게 막고, 우리가 미리 만들어 둔 '음식 관계도 표'에서만 답을 꺼내오게 하는 안전장치입니다. AI한테 자유롭게 조리법을 시키면 '미역국에 당근 갈아넣기'처럼 아무도 안 먹는 괴식을 그럴듯하게 지어내기 때문입니다. 그래서 우리는 추상 조리법 대신 또래가 실제로 가장 자주 먹는 '음식 이름'만 권합니다 — 조합은 그 음식 안에 이미 검증돼 있으니까요(예: '순두부찌개'를 권하면 두부 조합은 그 안에 있음).

작동은 음식 인맥 지도라고 생각하면 쉽습니다. 식재료마다 두 종류의 친구가 등록돼 있습니다. 하나는 사촌(맛·식감이 닮아 갈아탈 수 있는 친구 — 고구마·단호박·감자)이고, 다른 하나는 궁합(실제 급식 레시피에서 같이 자주 쓰인 짝꿍 — 가다랑어·김치)입니다. 아이가 잘 먹는 음식에서 출발해 → 사촌을 따라가면 → 부족한 영양 식재료까지 자연스럽게 다리(푸드체이닝)를 놓습니다.

핵심 전략은 5단계 우선순위입니다. 아이가 (1) 이미 잘 먹는 식재료를 한 번 더 → (2) 잘 먹는 것에 궁합 좋은 걸 곁들이기 → (3) 잘 먹는 것의 사촌이 부족한 군에 있으면 그걸로 다리 놓기 → (4) 또래 인기 음식으로 도전 → (5) 그냥 그 식재료. 위로 갈수록 성공 확률이 높고, 다섯 단계 모두 실데이터 표에 근거가 있어 괴식이 0건입니다.

🆕 이번 세션 추가 — '뭘 좋아하는지 모를 때'의 콜드스타트
가장 큰 보강입니다. 아린처럼 부모가 '완식했어요'를 거의 안 찍어서 아이가 뭘 좋아하는지 확신이 없을 때(완식 신호 2개 미만), 예전엔 추천이 '두부 디폴트'로 무너졌습니다(콩류군 첫 대표가 두부라서). 이제는 ① 신호계량화(①신호선택기 모듈)가 '한 번 완식한 걸 좋아한다고 착각'하지 않게 거르고, ② 콜드스타트 시드 사다리가 출발점을 '집에서 자주 차려진 음식'(Tier2) → 그것도 빈약하면 '영유아 급식에 흔히 오르는 재료'(Tier3 youa 큐레이션)로 보강합니다. 또 추천 음식에 '또래 급식에도 자주 오르는 익숙한 재료'라는 안심 한 마디를 붙이되, 서열·등수·%는 절대 노출하지 않고(이사님 원칙), 흔하지 않은 재료엔 그 절을 아예 빼서 '0회를 흔함으로 위장'하지 않습니다.
🧒 운영 예시(아린·5세) — 아린(콩류·비타민A채소 결핍·완식 신호 빈약). [입력] 영양 거울이 '비타민A채소 red·콩류 yellow'를 보내고, 집 끼니에서 '자주 차려진 것=소고기·계란·감자·미역'을 집계해 넘김(단 '완식 확신'은 0개 → 콜드스타트). [처리] 콜드스타트 시드가 servedTop을 앵커로 삼아 주간 풀 5개를 짜고, 일일은 그 풀을 날짜로 돌려가며 추천(같은 식재료 연속 금지). 주간계획(⑧)이 정한 '오늘의 슬롯 음식'(단호박찜 등)이 있으면 그것을 본문 음식으로 강제하고, 추천 사실 블록은 슬롯과 안 싸우게 사촌 줄을 끕니다(F-18). [결과] "단호박찜 같은 것도 가끔 식탁에 올려보세요"처럼 전부 실데이터 근거 음식만 등장. '미역국에 당근' 같은 괴식은 약신호·미검증이라 자동 차단.

구현된 내용 — 코드에 실제로 있는 것

라이브 배선됨정본: lib/coachRecos.ts호출자: app/api/cron/coach/route.ts

이 모듈의 라이브 산출물은 단 두 함수입니다 — buildRecoFacts(추천 사실 블록 text) + buildIngredientPool(주간 5개 풀). 나머지(popularDishesFor·coldStartSeed·youaRankOf·safeGarnishOf·cookedName·stapleDisplay)는 이 둘과 주간계획(⑧)이 호출하는 결정론 헬퍼입니다. pickFoodReco·weeklyExposureTarget은 존재하지만 라이브 본문 경로에서는 호출되지 않습니다(거울/하위호환 잔존).

핵심 함수 (coachRecos.ts 실측 file:line)

함수 · 타입역할 · 입출력
buildRecoFacts
coachRecos.ts:276
추천 사실 블록 단일 소스. 입력 {likedIngredients, target?, targetIngredient?, freqMap?, suppressCousins?} → 출력 RecoFacts{target, cousins[], lines[], text}. (a) 타깃 라인 = targetIngredient(주간 풀 일일회전 식재료) 우선, 없으면 target 그룹 첫 대표 → 그 식재료의 인기 음식 + safeGarnishOf로 거른 '잘 먹는 것 곁들임' + youa 안심절. (b) 잘 먹는 것 → 검증 사촌·궁합. ⭐F-18 신규: suppressCousins=true면 (b) 전체 생략(라인 303 args.suppressCousins ? [] : liked) — 슬롯이 음식 타깃을 정한 날 사촌(감자→두부)이 슬롯과 경쟁해 본문이 두부로 회귀하던 근원 차단
buildIngredientPool
coachRecos.ts:237
주간 추천 식재료 5개 풀. 입력 {signals, likedIngredients, freqMap?, max?} → 출력 IngredientPool{pool[], mode, reason}. 끼니 6군(MEAL_GROUPS·과일/유제품 제외) 중 결핍 상위 3군만(흩뿌림 방지). mode 결정: red≥2 → supply(결핍 대표 라운드로빈) / red=0 && nonGreen≤1 → challenge(cousinsOfLiked 푸드체이닝 + 도전) / 그 외 → mixed. reason 예: "supply(red 2·결핍군 3)". ⭐2026-06-21 풀-기아 봉합: 콜드스타트(사촌 0)+단일 결핍군이면 mixed가 군당 1개만 담아 풀이 두부 1개로 굶던 것을 결핍군 대표 라운드로빈으로 백필 → 아린형 풀 1→4종(주간계획·일일 회전 다양성).
popularDishesFor
coachRecos.ts:80
식재료 → '가장 많이 쓰이는 실존 음식' 최대 2개. STAPLE_FORMS(밀·쌀)는 빵·면·떡·밥으로, FRUIT는 빈 배열(간식채널). freqMap(ingredient-recipes) freq≥4 우선 → dishesForIngredient(kit-matrix count>0) 폴백. ⭐2026-06-21 DISH_ALIAS(계란→달걀): 표준명 '계란'이 코퍼스 '달걀' 표기라 dishes 통째 누수하던 것 봉합(표시는 계란팟국·계란장조림으로 정규화). cleanDishName(NEIS 접두태그·'&곁들임'·용량괄호 제거)·inappropriateDish(매운·짠지·견과·튀김 차단)로 정제. ⚠️ 날짜seed·노출이력 입력 없음 → 같은 식재료=항상 같은 top-2
coldStartSeed :218 · YOUA_TOP :177⭐신규 콜드스타트 시드 사다리. 입력 servedIngredients[] → Tier2(자주 차려진 집 식재료) → Tier3(YOUA_TOP=youa-freq.json 등장률 내림차순·식품군 인식만). 과일(간식채널)·중복 제외, max 6. 확신 liked가 빈약할 때 추천 앵커가 두부 디폴트로 안 무너지게 보강
youaRankOf :207 · youaReassuranceFor :213⭐신규 급식 출현 랭킹. YOUA_RANK(youa-freq 등장률→동률안전 순위+상위N%)를 ING_YOUA_ALIAS(요거트→요구르트·콩→콩(대두)·현미→멥쌀 등)로 별칭 해소 후 조회. 미수록=null(0을 꼴등으로 위장 금지). youaReassuranceFor는 등장률 ≥YOUA_COMMON_PCT(50)일 때만 '또래 급식에도 자주 오르는 익숙한 재료' 반환 — 서열·등수·% 노출 0(이사님 원칙). 연어는 의도적 미매핑(고가·수입이라 급식 희소 → 흔함 위장 금지)
safeGarnishOf :165곁들임 전용 안전 궁합. garnishPairsOf(tray 제외)에서 ①매운/김치류(KIMCHI_EXTRA) ②날곡물(STAPLE_FORMS) ③교차괴식(garnishConflict: 단과일/우유류 ↔ 생선·해산물/콩·두부)을 한 번 더 거름. buildRecoFacts (a)/(b) 곁들임이 이걸 씀
cookedName :60 · stapleDisplay :38안전 조리표기. COOK_ADJ(찐 단호박·삶은 달걀·구운 고등어·익힌 당근)로 생물 오인 방지, 주식 곡물은 먹는 형태(밥·빵·면·떡). 두부·치즈 등 가공식품은 접두 없음
groupOfIngredient :144 · GROUP_INGREDIENTS :21식재료↔식품군 역색인 + 결핍군→대표 식재료 사전(콩류→두부·검은콩·콩·콩나물). ING_GROUP 멤버 외(치킨·삼겹살 등 운반체명)는 null
pickFoodReco :103 · weeklyExposureTarget :128liked→pair→chain→dish→plain 5단계 via 태그(거울용)·주간 노출 타깃. 현 라이브 본문 경로 미호출(잔존). via 회전 아이디어의 원형

route.ts 배선 (실측 file:line)

위치무엇을 하나
route.ts:368-374⭐콜드스타트 게이트. quantifyPreferences(rs)(①신호선택기) → confidentLiked(state==='liked'만) · dislikedIng(확실 거부). coldStart = confidentLiked.length < 2이면 likedSeed = [...confidentLiked, ...coldStartSeed(servedTop, 6)], 아니면 likedIng(빈도순). dislikedIng는 앵커에서 제외(거부한 걸 또 권하지 않기)
route.ts:842-848buildIngredientPool({signals: computeGroupSignals(homeDays||byDay), likedIngredients: likedSeed, freqMap})recoPoolArr·recoMode. 그 다음 풀을 '오늘 타깃군(또는 집 부족군)'에 정렬하고 recentRecoIng[cid].slice(0,5)에 없는 첫 식재료로 일일 회전recoIng(같은 식재료 연속 추천 방지)
route.ts:851-853defMature && planDetailCtx면 주간계획(⑧) pickPlanSlot(...)이 우선 → recoIng = slot.ingredient, recoMode = slot.track(콩류 두부 도돌이표 차단·구체 dish 회전). dedup에 recentIngredients 전달
route.ts:864-867⭐F-18 슬롯본문봉합. slotDish=슬롯이 정한 구체 dish(단호박찜)·slotFood=슬롯 cookedName. bridgeFacts = _noFood ? '' : buildRecoFacts({likedIngredients: likedSeed, target: _recoTarget, targetIngredient: recoIng, freqMap, suppressCousins: !!slotFood}).text
route.ts:760-764두뇌(coachBrain) 경로: recoCand = buildRecoFacts({likedIngredients: likedSeed, target: precomputed.plan?.target, freqMap}).textbrainCtx.recoCandidates(시나리오·useFood 판단 재료)
route.ts:316·326·371servedHomeFreq(집·수용도 무관 자주 차려진 식재료) 집계 → servedTop = coldStartSeed Tier2 앵커
route.ts:960context 저장: recoIng, recoPool: recoPoolArr, recoMode + planSlot{reason·weeklyEstFreq·level·via·track}(:956) — 어드민·다음날 회전이력
📎 어드민 추천 근거(A) — 인접 모듈⑧ 소관이나 본 엔진이 공급PlanSlot.reason/weeklyEstFreq/level(coachWeekly.ts:57-68)은 주간계획 모듈의 타입이지만, 그 dishes본 엔진의 popularDishesFor가 채우고 reason 문구('결핍군 콩류 보급(주 0.8회·red)'·'잘 먹는 두부의 검증 사촌')의 식재료·사촌 판정은 groupOfIngredient/cookedName이 만듭니다. 어드민은 이 reason을 직접 표시합니다.
📎 휴면 코드 정직 표기 — 과거 문서가 라이브처럼 서술한 selectDailyMaterials·buildValidatedCombos·comboMatrix·comboGuard·rankIngredients·rotateRecommendation·deriveLikedIngredients(coachMaterials.ts)는 현재 라이브 추천 경로에서 호출되지 않습니다(테스트만 참조·dormant). 라이브 괴식 차단은 safeGarnishOf + inappropriateDish 정규식 + 그래프 strong/verified 게이트에만 의존합니다(combo matrix 미배선).

설계 아키텍처 — 데이터 파이프라인 그림

입력 테이블 → 가공 함수 → 출력 컨텍스트키. 실선=라이브 호출, 붉은 점선=설계됐으나 미배선(휴면).

┌───────────────────── 입력 (route.ts 집계 · 정적 JSON) ─────────────────────┐
│ meal_logs (집 끼니·place≠daycare)        lib/youa-freq.json   lib/food-graph.json │
│   ├ servedHomeFreq → servedTop          (급식 등장률%)     (bridge 사촌·pair 궁합) │
│   ├ favIngFreq    → likedIng            ingredient-recipes  lib/kit-dish-matrix    │
│   └ acceptance_level/ate_well           (freqMap freq≥4)   (count>0 폴백)        │
└──────────┬──────────────────────────────┬───────────────────┬──────────────────┘
           │                              │                   │
           ▼ (①신호계량화)                 ▼                   ▼
  quantifyPreferences ───► confidentLiked   YOUA_RANK/YOUA_TOP   verifiedCousinsOf
           │  (state==='liked'만)          │  youaRankOf        garnishPairsOf
           │  dislikedIng (거부 제외)        │  youaReassuranceFor       │
           ▼                              ▼                   ▼
  coldStart = confidentLiked < 2 ?     ┌──── safeGarnishOf ◄── garnishConflict
    coldStartSeed(servedTop)  ───┐     │     (매운/날곡물/교차괴식 제거)
  : likedIng                   ─┴► likedSeed
           │
           ▼  signals = computeGroupSignals(homeDays)
  ┌──────────── buildIngredientPool(signals, likedSeed, freqMap) ────────────┐
  │  결핍 상위3군 · mode = supply / challenge / mixed                          │
  └──────────────────────────┬───────────────────────────────────────────────┘
                             ▼  recoPoolArr (5개)  ─ 타깃군 정렬 ─ recentRecoIng dedup
                    recoIng (일일 회전)
                             │
              ┌──────────────┴───────────────┐
              ▼ (defMature & planDetail)      ▼ (폴백)
   ⑧ pickPlanSlot → slot.ingredient     recoIng 그대로
   slotDish / slotFood / track / reason
              │
              ▼  suppressCousins = !!slotFood (F-18)
  ┌──── buildRecoFacts(likedSeed, target=_recoTarget, targetIngredient=recoIng) ────┐
  │  (a) 타깃 라인: popularDishesFor + safeGarnishOf 곁들임 + youa 안심절          │
  │  (b) 잘먹는것→사촌·궁합   ← suppressCousins면 생략(F-18)                       │
  └──────────────────────────────────┬───────────────────────────────────────────┘
                                     ▼  .text
  ┌──────────────── 출력 CoachContext 키 ────────────────┐
  │ bridgeFacts (✍️손=작문 재료)   recoCandidates (🧠뇌)   │
  │ slotFood·slotDish·slotTrack    recoIng·recoPool·recoMode │
  │ planSlot.reason/level/weeklyEstFreq (어드민 근거 A)      │
  └─────────────────────────────────────────────────────────┘

  ┌╌╌╌ 휴면(미배선) ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐
  ┊ coachMaterials: selectDailyMaterials · buildValidatedCombos ┊
  ┊ comboMatrix.scoreCombo · comboGuard ── 라이브 호출 없음     ┊
  └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘

작동 예시 — 아린 케이스 before/after

아린(5세) · 콩류 yellow·비타민A채소 red · 완식 신호 빈약(ate_well===true가 4건이나 전부 다른 식재료 각 1회) · 자주 차려진 것=소고기·계란·감자·미역.

📥 INPUT (route.ts 집계)
{
  "// quantifyPreferences 결과": "①신호선택기",
  "confidentLiked": [],          // 완식 반복+Wilson 통과 0개
  "coldStart": true,             // < 2 → 콜드스타트 발동
  "servedTop": ["소고기","계란","감자","미역"],
  "likedSeed": ["소고기","계란","감자","미역", "...youa Tier3"],
  "signals(homeDays)": [
    {"group":"비타민A채소","level":"red","weeklyEst":0.4},
    {"group":"콩류","level":"yellow","weeklyEst":0.8}
  ],
  "freqMap": { "단호박":[{"name":"단호박찜","freq":7}] },
  "planDetailCtx.slot": { "ingredient":"단호박",
    "cookedName":"찐 단호박", "dishes":["단호박찜"],
    "group":"비타민A채소", "track":"supply",
    "reason":"결핍군 비타민A채소 보급(주 0.4회·red)" }
}
📤 OUTPUT
{
  "recoIng": "단호박",          // 슬롯이 override (line 853)
  "recoMode": "supply",
  "slotFood": "찐 단호박", "slotDish": "단호박찜",
  "// buildRecoFacts(suppressCousins:true)": "F-18",
  "bridgeFacts":
    "[오늘 타깃 비타민A채소] 찐 단호박 (또래 인기 음식: 단호박찜)",
  "// part(b) 사촌 줄 생략": "슬롯과 경쟁 차단",
  "planSlot.reason": "결핍군 비타민A채소 보급(주 0.4회·red)"
}
BEFORE (콜드스타트·F-18 도입 전) vs AFTER
BEFORE — 구 누적기가 '완식 true 4건'을 그대로 liked 4개로 오판 → 콜드스타트 미발동. 슬롯은 단호박인데 추천 사실 블록 part(b)가 '감자 → 사촌 두부'를 동시에 내보내, LLM이 결핍군 대표 두부를 택해 메타=단호박 / 본문=두부 이중장부. 거울줄도 '콩류→두부' 디폴트로 두부 재소환. 결과: 24통 중 두부5·달걀6 복붙.
AFTERconfidentLiked=0이 정확히 콜드스타트를 켜 servedTop 앵커로 풀을 짜고, suppressCousins=!!slotFood가 part(b)를 꺼 사촌 두부 줄이 사라짐. 본문·메타·거울이 모두 '찐 단호박/단호박찜'으로 단일화. 실측: 비-콩류 슬롯 12일 두부누수 0.
📌 한계가 그대로 보이는 지점 — 위 예시에서 popularDishesFor("단호박")은 날짜·노출이력을 입력으로 안 받으므로, 다음에 단호박이 또 슬롯이 되면 '단호박찜'이 100% 복붙됩니다. 같은 음식의 메뉴 회전이 없습니다(⑤ 천장 참조).

남은 문제점 · 해야할 일 — 랄프위검 실측 천장

랄프위검(연속성 47점·2026-06-20) 실측 기준, 이 모듈 관점에서 정직하게. 증상 → 근본원인 → 해야할일(owner). 미화·과장 금지.

✅ EARNED (사실인 성과) — ⓐ 괴식 0건은 실측으로 유지(safeGarnishOf 교차괴식·매운·날곡물 + inappropriateDish). ⓑ 콜드스타트 시드·신호계량화로 '완식 true 단발을 liked 오판'하던 두부 디폴트 붕괴를 차단(confidentLiked<2 게이트). ⓒ F-18 suppressCousins로 슬롯-본문 이중장부(메타 단호박/본문 두부) 봉합 — 비-콩류 슬롯 12일 두부누수 0. ⓓ youa 안심절은 서열·% 노출 0·미수록 null로 정직성 유지. 비용 ₩331/자녀·월.
🔴 천장4 (핵심) — 추천 풀 협소: 24통 중 두부5·달걀6이 추천 절반 점유
증상 — 24통 편지 중 추천 음식의 절반이 두부(5)·달걀(6)에 쏠림. 콜드스타트가 Tier2(두부·달걀류) 안에서만 회전.
근본원인coldStartSeed의 Tier3(YOUA_TOP 큐레이션)가 사실상 미확장: servedTop이 채워지면 Tier2에서 max 6이 다 차 Tier3가 잘려나감. 또 GROUP_INGREDIENTS 풀 자체가 협소(콩류=두부·검은콩·콩·콩나물 4종, 그중 그래프·freqMap 커버는 두부에 집중). confidentLiked 확장은 P0-D 신호 입력에 종속(천장1 의존).
해야할일P0-B/C: coldStartSeed Tier2→Tier3 확장(servedTop이 충분해도 Tier3 youa 큐레이션을 일정 비율 강제 혼입), GROUP_INGREDIENTS 결핍군 대표 풀 확장(두부5·달걀6 협소 → 사촌 다양화). owner: 추천엔진
🔴 메뉴 문자열 100% 복붙 — popularDishesFor 결정론 캐싱
증상 — 재방문 시 같은 음식의 메뉴가 100% 복붙(달걀→달걀팟국/달걀장조림, 두부→유부맑은국/순두부찌개). 슬롯 dish도 음식별 top-2 고정.
근본원인popularDishesFor(ing, freqMap)(coachRecos.ts:80)이 날짜seed·노출이력을 입력으로 받지 않음 → freqMap freq 내림차순 top-2를 결정론으로 잘라 캐싱. 같은 식재료면 항상 같은 두 메뉴. slotDish는 dishes[0] 단일 선택(coachWeekly:864).
해야할일popularDishesFor(ing, freqMap, {seed, recentDishes}) 시그니처 확장 → 날짜seed로 freq≥4 후보 중 회전, 노출이력(recentRecoIng처럼 recentDishes 원장)에 있는 메뉴는 후순위. 같은 식재료라도 방문마다 다른 메뉴 노출. owner: 추천엔진
🟠 추천 필드(식재료 단품) ↔ 주간슬롯(메뉴) 이원화
증상recoIng(식재료 단품)과 slotDish/slotFood(메뉴)가 별 경로로 산출돼, 부모가 '오늘의 타깃'을 어느 걸로 봐야 할지 모호. context에 recoIng·recoPoolplanSlot.ingredient·dishes가 병존.
근본원인 — 추천 필드는 buildIngredientPool 회전(route.ts:848)에서, 슬롯은 pickPlanSlot(⑧)에서 나오는 이원 파이프라인. slot이 있으면 recoIng를 덮어쓰지만(853), 슬롯 없는 날엔 식재료 단품만 남아 표현 단위가 들쭉날쭉.
해야할일 — 추천 필드 메뉴화(recoIngpopularDishesFor로 메뉴까지 동반) 또는 슬롯/추천 단일 소스화(buildIngredientPool 출력을 slot 형태로 통일). owner: 추천엔진+⑧주간계획
🟠 confidentLiked 확장 = P0-D 신호 입력에 종속 (천장1 의존)
증상 — 콜드스타트가 거의 항상 켜진 상태로 머묾(아린 confidentLiked=0 지속). 5단계 우선순위의 (1)liked·(2)pair·(3)chain 경로가 사실상 비활성 → 추천이 (4)dish/supply에 편중.
근본원인 — confidentLiked는 명시 완식(acceptance_level≥3 또는 ate_well===true)이 ≥2회 필요한데, 부모가 그 신호를 거의 입력 안 함. child_daily_state(환경신호) 프로브가 라이브 미배선(P0-D)이라 졸업·확신 데이터가 안 쌓임.
해야할일본 모듈만으로 봉합 불가. P0-D child_daily_state 신호 입력(care 5단계 실데이터 축적·table-stage 전진)이 선행돼야 confidentLiked가 채워짐. 본 모듈은 그때 likedSeed가 confidentLiked로 자동 전환되도록 게이트만 유지. owner: ①신호선택기/P0-D (이 모듈은 종속)
🟠 combo 괴식 게이트(comboMatrix/comboGuard) 휴면 — 라이브 미배선
증상 — dish×ingredient 괴식 검증(미역국+당근류)이 라이브에선 정규식 블랙리스트(inappropriateDish)와 그래프 strong/verified 게이트에만 의존. 음식+식재료 '자연스러움' 매트릭스 미검증.
근본원인comboMatrix.scoreCombo/isComboOk·buildValidatedCombos가 coachMaterials(휴면)에만 묶여 있어 라이브 buildRecoFacts/popularDishesFor 어디서도 호출 안 됨. 라이브는 식재료×식재료(safeGarnishOf)만 검증.
해야할일 — 휴면 게이트를 buildRecoFacts 곁들임 산출에 배선(isComboOk(dish, ing) 통과분만), comboMatrix↔comboGuard 중복 단일화. 단 현재 괴식 0건이라 우선순위는 천장4·메뉴회전 뒤(P1). owner: 추천엔진
요약 — 이 모듈의 천장은 '안전하지만 협소'입니다. 괴식·환각·이중장부는 봉합됐으나(EARNED), 추천 다양성이 두부5·달걀6에 묶여 있고 같은 음식이면 메뉴까지 복붙됩니다. 다양성의 두 뿌리는 ⓐ popularDishesFor 날짜seed·노출이력 입력(이 모듈 단독 해결 가능, P0-B/C)과 ⓑ confidentLiked 확장(P0-D 신호 입력 종속, 천장1)입니다. 다음 우선순위 = P0-D 신호 입력부터(대부분 care 5단계 실데이터가 쌓이면 자연 해소).
🌉 푸드체이닝 확장 로드맵 (데이터 엔진에서 이관 — 추천은 여기 소관) — 라이브: 거울 5전략(liked·pair·chain·dish·plain)·supply/challenge 주간 로테이션(영양 심각도 기반). 미구현 확장: ① 2축 확장(현 식재료축 + 조리법축=같은 재료 새 기법, 예 감자→찐감자) ② 온도축(찬↔실온↔데움) ③ neophobia 연령곡선(2세=가까운 사촌만 / 5~6세=먼 도약 허용) ④ 아이별 노출-수용 학습루프. 원칙: 한 번에 한 축만(novelty 과부하 차단)·노출 8~10회 안심(USDA·최대 30회 정상). ⚠️ "5→20.5" 날조 수치 인용 영구 금지(정확: n=10·범위 1~8→8~129). 데이터 재료(그래프·등장률·도감)는 식단 데이터 엔진(정본)이 공급.