한 줄 요약
"우리 아이가 잘 먹는 베이스에
학술 근거로 도전 식재료를 자연스럽게 — 매번 다른 방식으로"
밀프레드 편식도감 추천은 6 데이터 소스를 11 학술 이론에 따라 분석하고, 4 핵심 원칙으로 4,432 레시피에서 6-8개를 골라 "왜 우리 아이에게?" 자연어 이유와 함께 제공합니다. 추천은 매번 다릅니다 — 같은 메뉴는 4주 내 반복하지 않습니다.
1. 참조 데이터 — 6 소스
레시피 추천은 다음 6가지 데이터를 종합 분석합니다.
| 필드 | 활용 |
|---|---|
| 식재료 (ingredients) | 노출 빈도·먹은 빈도·accept_rate 계산 |
| 반응 (reaction) | 잘먹음/또달라 = 선호 · 거부/남김 = 도전 식재료 |
| 식감 단계 (texture_level) | 현재 식감 분포 → 점진 단계 ↑ 결정 |
| 자율성 (autonomy) | Satter DOR · 스스로 비율 추적 |
| 식사 환경 (place·duration) | 20분 이내·정해진 장소 권장 체크 |
| 동적 질문 답변 (dimension_data) | 거부 반응·환경·새 식재료 등 차원별 시계열 |
| 필드 | 예시 |
|---|---|
| ingredient_stats | {시금치: {노출 22, 먹음 8, 36%, 마지막 3일 전}} |
| base_stats | {계란찜: 43회, 죽: 32회, 부침개: 15회} (선호 베이스 TOP 5) |
| texture_distribution | {죽 65%, 매시 25%, 다진 7%, 핑거 3%} |
| nutrient_status (KDRI 36) | {철: red, 콜린: red, 비D: red, ...} |
| recent_menus | 최근 21끼 메뉴명 (중복 회피용) |
| 필드 | 활용 |
|---|---|
| age_months / age_band | 연령별 권장 (식감·자율성·KDRI band) |
| sex | KDC 성장도표 lookup |
| allergens | 알레르겐 위배 레시피 자동 제외 (안전 필수) |
| care_type | 어린이집·유치원·가정 → 추천 톤 조정 |
| BMI percentile | 탄·단·지 권장 양 조정 |
| 필드 | 활용 |
|---|---|
| base_type (15분류) | 죽·계란찜·부침개·국·볶음·찜·구이·무침·생식·면·밥·간식·찌개·전·튀김 |
| texture_level | 미음/페이스트/매시/다진/큐브/스틱/통째 7단계 |
| main_protein_type | 붉은고기·흰고기·생선·계란·콩 (메인 반찬 판단용 — §4 참조) |
| ingredients[].form | 즙/다진/매시/큐브/스틱/통째/페이스트/가루 — concealment 판정 |
| nutri_total (36) | 레시피 단위 영양 합계 — 부족 영양 보강 매칭 |
| nutrient_highlights | RNI 30%+ 기여 영양소 (예: ['철', '엽산']) |
| concealment | {식재료: high/mid/low} 가림 정도 |
| cook_minutes·difficulty | 현실성 (간식·간단·복잡) |
| 필드 | 활용 |
|---|---|
| category / subcategory | 16 카테고리 분류 (잎채소·뿌리·생선·계란 등) |
| species_id | 시금치 vs 케일 vs 상추 = 다른 종 (다양성 2-Layer) |
| nutri_per_100g | 농진청 국가표준식품성분표 36 영양소 |
| allergens | 식약처 17대 + 추가 |
| seasonality | 제철 월 (계절성 점수) |
| processing_level (NOVA) | 1=자연식 / 2=가공 / 3=초가공 / 4=울트라가공 |
| 필드 | 활용 |
|---|---|
| kdri_rni | 연령별 RNI/AI/UL — 영양 충족률 계산 |
| who_mdd_families | 8 식품군 매핑 |
| our_subcategories_14 | WHO MDD 세분화 (Layer 2) |
| habeat_texture_table | 연령별 권장 식감 분포 |
| satter_dor | 자율성·식사 환경 기준 |
2. 적용하는 학술 이론 — 11종
모든 추천 결정은 다음 11개 학술 이론에 근거합니다. 임의 룰 X.
2-1. 다양성 평가 (5종)
2-2. 식습관 형성 이론 (4종)
2-3. 영양·식감 이론 (2종)
3. 추천 4 원칙 + 사용 데이터
⭐ 선호 베이스 + 도전 식재료
잘먹는 베이스(계란찜·죽·부침개)에 거부 식재료를 자연스럽게 — 거부감 ↓
학술 근거: Food Chaining (Toomey 2007) + Repeated Exposure (Cooke 2011)
🥗 잘먹지만 오랜만 → 원물 노출
accept_rate ≥ 0.7 AND 30일+ 안 만났고 항상 가린 형태(즙·다진)였던 식재료를 통째·스틱·큐브로 식감 단계 ↑
학술 근거: HabEat (식감 점진 단계) + Satter DOR (자율성·핑거푸드)
🥄 극도 거부 → 형태 안 보이게
accept_rate < 30% AND exposure ≥ 5회인 식재료를 즙·페이스트로 — 노출 누적 우선
학술 근거: Toomey SOS Approach (단계 4-5) + Cooke 12-30회 반복 노출
💊 부족 영양 보강
KDRI 신호등 빨강 영양소 (철·콜린·비타민D·EPA+DHA 등)를 잘먹는 베이스 + 영양 highlight 매칭으로 자연 보강
학술 근거: KDRI 2025 (보건복지부) + 농진청 식품성분표
3-1. 4 원칙 가중합 랭킹
final_score = ( 0.30 × base_preference_score + # 잘먹는 베이스 (Food Chaining) 0.20 × concealment_score + # 가림 정도 (Toomey SOS) 0.30 × nutrient_boost_score + # KDRI 부족 영양 0.10 × novelty_score + # 최근 21끼 미등장 (반복 회피) 0.10 × age_appropriateness_score # 연령 fit (HabEat) )
4. 고단백 메인 반찬 판단 로직 (NEW)
🚨 문제 — "고기 있다 = 고단백" 단순 체크의 한계
예시: "소고기 미역국"은 메인이 미역국 (수프). 소고기는 5g 정도 들어간 육수용 — 실제 고단백 메인 X.
vs "갈비찜": 메인이 소고기 (50g+). 고단백 메인 ✓
단순 ingredients.contains('소고기') 체크로는 두 경우를 구분 못함. 메인 반찬 판단 알고리즘 필요.
4-1. recipes.main_protein_type 필드 (NEW)
레시피 enrich 단계에서 LLM으로 결정:
type MainProteinType = | 'red_meat' // 소·돼지·양 (메인, ≥30g) | 'white_meat' // 닭·오리 (메인, ≥30g) | 'fish' // 생선 (메인, ≥30g) | 'seafood' // 새우·오징어·조개 (메인) | 'egg' // 계란 (메인, ≥1개) | 'soy' // 콩·두부 (메인 단백질 ≥40g) | 'mixed' // 2종 이상 메인 (혼합) | 'aux_protein' // 보조 단백 (소고기 미역국·멸치육수) | 'no_protein'; // 단백질 미포함 (밥·면 위주)
4-2. 판단 알고리즘 (LLM 분류 + 검증)
async function classifyMainProtein(recipe: Recipe): Promise<MainProteinType> { // 1차: 룰 기반 — 단백질 식재료 g 기준 const proteinIngs = recipe.ingredients.filter(i => PROTEIN_INGREDIENTS.has(i.name)); if (proteinIngs.length === 0) return 'no_protein'; // 단백질 식재료의 분량 합 (메인 판정) const mainProtein = proteinIngs.filter(i => i.amount_g >= PROTEIN_MAIN_THRESHOLD[i.type]); if (mainProtein.length === 0) return 'aux_protein'; // 보조만 // 메인 단백 분류 if (mainProtein.length > 1) return 'mixed'; const main = mainProtein[0]; return PROTEIN_TYPE_MAP[main.type]; } // 임계값 (메인 판정 기준) const PROTEIN_MAIN_THRESHOLD: Record<string, number> = { 'red_meat': 30, // 소고기·돼지고기 ≥30g 'white_meat': 30, 'fish': 30, 'seafood': 20, 'egg': 50, // 달걀 1개 ≈ 50g 'soy': 40, // 두부 1/4모 ≈ 40g }; // 검증 사례 // 소고기 미역국: 소고기 5g → aux_protein // 갈비찜: 갈비 100g → red_meat // 닭볶음탕: 닭 80g → white_meat // 두부찜: 두부 100g → soy
4-3. 고단백 메인 다양성 추적
주간 메인 반찬 단백 유형 다양성:
function mainProteinVariety(logs: MealLog[], days = 7): MainProteinReport { const mainTypes = logs .map(l => l.recipe?.main_protein_type) .filter(t => t && t !== 'no_protein' && t !== 'aux_protein'); const uniqueTypes = new Set(mainTypes); return { totalMainMeals: mainTypes.length, uniqueTypes: uniqueTypes.size, distribution: countBy(mainTypes), // 권장: 주 7끼 메인 중 4종 이상 (red·white·fish·soy 최소) status: uniqueTypes.size >= 4 ? 'green' : uniqueTypes.size >= 3 ? 'orange' : 'red' }; }
| 이번 주 메인 반찬 | uniqueTypes | 등급 | narrative |
|---|---|---|---|
| 매일 닭볶음탕·닭조림·닭구이만 (7끼) | 1 (white_meat) | 🔴 red | "메인 단백질 변화 없음 — 소고기·생선·두부 도전 권장" |
| 닭 3회 + 소고기 2회 (5끼 메인) | 2 | 🟠 orange | "생선·콩 메인 1회씩 추가하면 균형" |
| 닭 2회 + 소고기 1 + 생선 2 + 두부 1 + 계란 1 | 5 | 🟢 green | "메인 단백질 다양성 우수" |
5. 다양성 2-Layer 시스템
5-1. Layer 1 — WHO MDD (Pass/Fail)
8 식품군 중 N개 등장 여부. 최소 통과 기준.
| MDD 점수 | 등급 cutoff |
|---|---|
| ≤ 4/8 | D (Fail) |
| 5-6/8 | C (Pass) |
| 7-8/8 | B 이상 진입 가능 |
5-2. Layer 2 — 우리 14 sub-카테고리 + 종 다양성
| # | 대분류 | sub-카테고리 | 주간 권장 종 | 예시 |
|---|---|---|---|---|
| 1 | 곡물 | 정제곡물 | - | 백미·흰밀가루 |
| 2 | 통곡물·잡곡 ⭐ | 2종+ | 현미·보리·귀리·메밀·찰미·기장 | |
| 3 | 콩 | 콩제품 | 2종+ | 두부·콩나물·연두부·콩가루 |
| 4 | 콩알류·렌틸 | 1종+ | 검은콩·강낭콩·렌틸·병아리콩·팥 | |
| 5 | 유제품 | 우유·요거트·치즈 | 2종+ | 우유·요거트·치즈·버터 |
| 6 | 고기·생선 | 붉은 고기 | 1종+ | 소·돼지·양 |
| 7 | 흰 고기·가금류 | 1종+ | 닭·오리 | |
| 8 | 생선·해산물 ⭐ | 2종+ | 고등어·연어·새우·오징어·미역 | |
| 9 | 계란 | 계란류 | 1종+ | 달걀·메추리알 |
| 10 | 진한 채소 | 진한 잎채소 ⭐ | 3종+ | 시금치·근대·아욱·청경채·케일·상추 |
| 11 | 진한 색 채소 ⭐ | 3종+ | 당근·단호박·파프리카·토마토·비트 | |
| 12 | 기타 채소 | 기타 채소 | 3종+ | 무·오이·양배추·콩나물·숙주·버섯 |
| 13 | 과일 | 과일 | 3종+ | 사과·딸기·바나나·블루베리·키위 |
| 14 | 견과·씨 | 견과·씨앗 | 1종+ | 호두·들깨·참깨·아몬드 |
⭐ 핵심 4 카테고리는 종 다양성 필수 (Lachat DSR + HabEat). 시금치만 5번 = 잎채소 통과지만 ⭐ 미충족 → A 등급 안 됨.
5-3. 등급 cutoff (Layer 1 + 2 종합)
function finalDiversityGrade(layer1: number, layer2: VarietyMetrics): Grade { if (layer1 <= 4) return 'D'; // MDD fail if (layer1 <= 6) return 'C'; // MDD pass only if (layer1 === 8 && layer2.subcatCount >= 10) { const criticalPass = CRITICAL_4.every(c => layer2.speciesPerSubcat[c] >= MIN_SPECIES[c]); if (criticalPass) { if (layer2.totalUniqueSpecies >= 25 && layer2.newSpeciesThisWeek >= 2) return 'S'; return 'A'; } } return 'B'; }
5-4. 카테고리 내 다양성 narrative 예시
// 시금치만 매번 등장 시 { subcategory: '진한 잎채소', species_count: 1, recommendation: '시금치만 5번 — 근대·아욱·청경채로 잎채소 다양화 권장', recipes_to_suggest: ['근대된장국', '아욱죽', '청경채 무침'] }
6. 반복 회피 (시계열)
같은 메뉴를 4주 내 반복하지 않음 (HabEat 권고). 자동 제외 로직:
function noveltyScore(recipe: Recipe, recentMenus: string[]): number { // 최근 21끼(7일) 메뉴 vs 추천 후보 if (recentMenus.includes(recipe.name)) return 0; // 완전 동일 if (similarBaseSubstr(recipe.name, recentMenus, 0.8)) return 0.3; // 유사 (찜닭 vs 닭볶음탕) return 1.0; // 신규 } // 베이스 반복도 추가 체크 function baseRepetitionWarning(profile: ChildProfile): Warning | null { const top = profile.recentBases[0]; // 가장 많이 등장한 베이스 if (top.count >= 5) { // 한 주에 5회 이상 같은 베이스 return { type: 'base_repetition', message: `${top.baseType}이 한 주 ${top.count}번 — 베이스 로테이션 권장`, suggestion: rotateSuggestion(top.baseType) // 죽 → 부침개·국·찜 등 }; } return null; }
7. 최종 출력 — 6-8 레시피 + 자연어 이유
30일] D2[📊 프로파일
집계] D3[📏 아이 정보
BMI·알레르겐] D4[📚 레시피 풀
4,432종] D5[🥕 식재료 마스터
1000+] D6[📖 학술 기준
KDRI·WHO·HabEat] PHASE0[Phase 0
프로파일 집계] PHASE1A[원칙 1
선호+도전] PHASE1B[원칙 2
오랜만 원물] PHASE1C[원칙 3
형태 가림] PHASE1D[원칙 4
영양 보강] RANK[가중합 랭킹
+ 반복 회피] LLM[LLM 정제
Claude Sonnet 4.7] OUT[📤 6-8 레시피
+ 자연어 이유] D1 --> PHASE0 D2 --> PHASE0 D3 --> PHASE0 PHASE0 --> PHASE1A & PHASE1B & PHASE1C & PHASE1D D4 --> PHASE1A & PHASE1B & PHASE1C & PHASE1D D5 --> PHASE1A & PHASE1B & PHASE1C & PHASE1D D6 --> PHASE1D PHASE1A & PHASE1B & PHASE1C & PHASE1D --> RANK RANK --> LLM LLM --> OUT classDef data fill:#E3F2FD,stroke:#3498DB,color:#1F2D3D classDef phase fill:#E8F5E9,stroke:#16A085,color:#1F2D3D classDef llm fill:#F3E5F5,stroke:#9C27B0,color:#1F2D3D classDef out fill:#FFF8E1,stroke:#F9A825,color:#1F2D3D class D1,D2,D3,D4,D5,D6 data class PHASE0,PHASE1A,PHASE1B,PHASE1C,PHASE1D,RANK phase class LLM llm class OUT out
7-1. 출력 예시 (JSON)
{
"recommendations": [
{
"principleId": 1,
"recipeName": "시금치 즙 계란찜",
"reasoning": "우리 아이가 32회 좋아한 계란찜 베이스에 시금치 5g 즙으로 추가했어요.
시금치는 22번 노출 중 8번만 먹은 식재료라 거부 단계가 정상입니다.
학술 연구에 따르면 평균 12-30번 노출 후 수용된다고 합니다.
즙으로 만들면 색·향이 거의 안 보여 부담 0.",
"factors": {
"basePreference": 0.95, "concealment": 1.0,
"nutrientBoost": 0.6, "novelty": 1.0,
"ageFit": 1.0
},
"academicSources": ["Food Chaining 2007", "Cooke 2011"]
},
...
],
"narrativeSummary": "이번 주 핵심: 시금치·콜린·EPA+DHA 3가지 부족 — 위 4 레시피로 자연 보강",
"warnings": [
{ "type": "base_repetition", "message": "죽이 한 주 5번 — 부침개·국으로 로테이션" }
]
}
9. 제철 식재료 기준 (한국 12개월)
제철 식재료 = 영양가 ↑ · 가격 ↓ · 농약 ↓ · 환경 ↓. 농진청 + 해양수산부 권고 기반 12개월 마스터.
9-1. 한국 12개월 제철 식재료 마스터
| 월 | 제철 채소·과일 | 제철 수산물 | 하이라이트 |
|---|---|---|---|
| 1월 | 시금치·당근·우엉·연근·미나리·콩나물·배추·무·귤·딸기 | 굴·새조개·아귀·꽃게·과메기 | 겨울 시금치·굴 |
| 2월 | 시금치·미나리·달래·냉이·연근·딸기·한라봉 | 굴·미더덕·황태·과메기·꼬막 | 겨울 끝물 + 봄나물 시작 |
| 3월 | 봄나물(달래·냉이·쑥·취나물)·시금치·딸기·앵두 | 주꾸미·꼬막·도다리·바지락·소라 | 봄나물 본격 |
| 4월 | 봄나물·근대·아욱·상추·딸기·앵두·미나리 | 주꾸미·도다리·꼬막·소라·바지락 | 잎채소 다양 |
| 5월 | 죽순·아스파라거스·근대·완두콩·딸기·체리·매실 | 멸치·갑오징어·새우·해삼·다슬기 | 죽순·새우 최성기 🌟 |
| 6월 | 매실·복숭아·살구·자두·블루베리·옥수수·오이·가지·호박 | 농어·갈치·전복·붕장어 | 여름 시작 |
| 7월 | 옥수수·오이·가지·애호박·복숭아·자두·수박·참외 | 민어·갈치·장어·전복 | 여름 채소 절정 |
| 8월 | 옥수수·가지·오이·토마토·수박·참외·복숭아·자두·포도 | 전복·장어·갈치·민어 | 과일 풍성 |
| 9월 | 가지·고구마·호박·사과·배·포도·대추·밤 | 전어·꽃게·갈치·고등어·새우 | 전어·꽃게 절정 🍂 |
| 10월 | 고구마·단호박·무·당근·시금치·사과·배·홍시·석류 | 꽃게·고등어·전어·삼치·갈치 | 가을 영양 풍부 |
| 11월 | 시금치·무·배추·생강·연근·우엉·당근·귤·사과·배 | 고등어·갈치·삼치·전어·과메기 | 김장 채소 |
| 12월 | 시금치·무·배추·당근·우엉·연근·미나리·귤·사과 | 굴·꼬막·과메기·아귀·복어 | 겨울 영양 회복 |
9-2. 평가 알고리즘 — 제철 비율 점수
const SEASONAL_TABLE: Record<number, string[]> = { 1: ['시금치', '당근', '우엉', '연근', '굴', '꽃게', ...], 2: [...], ... }; function seasonalScore(logs: MealLog[], currentMonth: number): SeasonalResult { const seasonalIngs = new Set(SEASONAL_TABLE[currentMonth]); const allUsed = logs.flatMap(l => l.ingredients.map(i => i.name)); const uniqueUsed = new Set(allUsed); const seasonalUsed = [...uniqueUsed].filter(i => seasonalIngs.has(i)); const ratio = seasonalUsed.length / uniqueUsed.size; return { pct: ratio * 100, seasonalCount: seasonalUsed.length, totalUnique: uniqueUsed.size, matched: seasonalUsed, missing: [...seasonalIngs].filter(i => !uniqueUsed.has(i)).slice(0, 5), status: ratio >= 0.30 ? 'green' : ratio >= 0.15 ? 'orange' : 'red', recommendation: buildSeasonalRecommendation(seasonalUsed, currentMonth) }; }
9-3. 임계치
| 제철 비율 | 등급 | narrative |
|---|---|---|
| ≥ 30% | 🟢 green | "제철 식재료 잘 챙기고 있어요" |
| 15-30% | 🟠 orange | "이번 달 죽순·새우 같은 제철 식재료 1-2종 추가 권장" |
| < 15% | 🔴 red | "제철 식재료 거의 없음 — 가격·영양·환경 모두 ↓" |
9-4. 레시피 추천 가산점
// 4 원칙 final_score에 0.05 가산 final_score += seasonal_bonus(recipe, current_month); function seasonalBonus(recipe, currentMonth) { const seasonalIngs = SEASONAL_TABLE[currentMonth]; const recipeIngs = recipe.ingredients.map(i => i.name); const matchCount = recipeIngs.filter(i => seasonalIngs.includes(i)).length; return matchCount >= 2 ? 0.05 : matchCount === 1 ? 0.03 : 0; }
9-5. 데모 적용
- 홈 진단 카드에 8번째 축 "제철" 추가 (현재 7축 → 8축)
- 도감 식재료 클릭 시 "🌸 지금 제철 (5월)" 뱃지
- 레시피 카드에 "🌸 제철 보너스" 라벨
- 식단표 평가 7축 中 "계절성·국산"이 이미 있음 — 정밀화
10. 조리 스타일 다양성 (한식 외 다채로움)
14 sub-카테고리는 식재료 단위. 그러나 매번 한식만이면 식습관 폭이 좁아짐. HabEat·EU 코호트 연구는 cuisine variety가 식습관 형성에 결정적이라고 보고.
10-1. 6 cuisine 카테고리 (제안)
| # | Cuisine | 대표 메뉴 | 핵심 영양 |
|---|---|---|---|
| 1 | 한식 (기본) | 밥·국·찌개·반찬·김치·전·찜·무침 | 발효식품·미네랄·식이섬유 |
| 2 | 양식 | 파스타·스튜·샐러드·오믈렛·리조또·수프 | 유제품·올리브유·치즈 |
| 3 | 중식 | 볶음밥·짜장·만두·딤섬·당면·동파육 | 다양한 채소·콩·견과 |
| 4 | 일식 | 덮밥·우동·소바·돈가스·계란말이·미소국 | 해산물·다시·간단 조리 |
| 5 | 동남아·아시아 | 쌀국수·카레·팟타이·나시고렝·반미 | 향신료·코코넛·향초 |
| 6 | 지중해·중동 | 후무스·팔라펠·쿠스쿠스·타불레 | 올리브유·통곡물·콩 |
10-2. 평가 알고리즘
function cuisineDiversity(logs: MealLog[]): CuisineResult { const cuisineUsed = new Set<CuisineType>(); for (const log of logs) { const cuisine = classifyCuisine(log.recipe); // LLM enrich cuisineUsed.add(cuisine); } const uniqueCount = cuisineUsed.size; return { uniqueCount, distribution: countBy(logs.map(l => l.recipe.cuisine)), status: uniqueCount >= 3 ? 'green' : uniqueCount >= 2 ? 'orange' : 'red', recommendation: uniqueCount < 2 ? '매번 한식만 — 양식·일식 메뉴 1-2회 시도' : null }; }
10-3. 임계치 (주간 기준)
| 주간 cuisine 수 | 등급 | narrative |
|---|---|---|
| 3종+ | 🟢 green | "조리 스타일 다양 — 식습관 폭 ↑" |
| 2종 | 🟠 orange | "1종 더 시도 권장 (양식 파스타·일식 덮밥 등)" |
| 1종 (한식 only) | 🔴 red | "매번 한식 — 양식·중식·일식 한 끼씩 도전" |
10-4. 데모 적용
- 7축 → 8축 또는 9축 진단에 "조리 스타일" 추가
- 레시피 탭에 cuisine 필터 (한식·양식·중식·일식·동남아)
- 도감에 "이번 주 cuisine: 한식 6 / 양식 1" 시각화
11. LLM 입력 전처리 (자연어·사진 → DB 매칭)
엄마들은 비결정적 입력 (사진·자연어 메모·퀵노트)을 사용. 우리는 LLM으로 정확하게 매칭해야 영양 평가가 정확해짐.
11-1. 입력 유형 4가지
| 입력 | 예시 | 매칭 대상 |
|---|---|---|
| 📸 사진 | 음식 사진 1장 | ingredients 마스터 1,070종 |
| 🏷 해시태그 | "시금치", "당근", "ㅂㄹㅋㄹ" | INGREDIENT_POOL 147종 (정확) |
| 📝 자연어 메모 | "시금치 거부했는데 김 가루로 덮으니 잘 먹음" | 식재료(시금치·김) + 반응(거부→잘먹음) 추출 |
| 🔘 퀵노트 chip | "한 입도 안 먹음", "친구따라 먹음" | CEBQ 코드·동적 질문 차원 매핑 |
11-2. ALG-PREP-01 — 사진 → 식재료 추출
이미 ALG-OCR-06 (engines-deep)에 명세. 신뢰도 ≥ 0.7만 자동 채택 + 사용자 수정 UI.
11-3. ALG-PREP-02 — 자연어 메모 → 구조화 데이터 (NEW)
const MEMO_EXTRACT_PROMPT = `엄마가 자유 텍스트로 쓴 식사 메모에서 다음을 추출하세요. # 입력 예시 "시금치 거부했는데 김 가루로 덮으니 잘 먹음. 오늘 아침에는 안 먹다가 점심에는 또 달라고 했어요." # 출력 JSON { "ingredients_mentioned": [ {"name": "시금치", "reaction": "rejected", "context": "초기 거부"}, {"name": "김", "reaction": "well_eaten", "context": "위장 식재료로 활용"} ], "behaviors": [ {"type": "rejection", "context": "초기"}, {"type": "conceal_success", "context": "다른 식재료로 가림"}, {"type": "time_difference", "details": "아침 vs 점심 다른 반응"} ], "parent_emotion": "neutral_with_relief", // 안도·기쁨·좌절·중립 "cebq_signals": ["food_responsiveness", "food_fussiness_partial"] }\`; async function extractFromMemo(memo: string): Promise<MemoExtraction> { if (memo.length < 5) return null; const response = await anthropic.messages.create({ model: 'claude-haiku-4-5', // 분류 작업이라 Haiku로 충분 max_tokens: 800, messages: [{ role: 'user', content: MEMO_EXTRACT_PROMPT + '\n\n# 입력\n' + memo }], }); const extracted = parseJSON(response.content[0].text); // 매칭 검증: ingredients_mentioned가 INGREDIENT_POOL에 있는지 for (const ing of extracted.ingredients_mentioned) { ing.matched_id = await fuzzyMatchIngredient(ing.name); } return extracted; }
11-4. ALG-PREP-03 — 해시태그 정규화 (이미 구현)
// 한글 초성 추출 + fuzzy match (care.html 이미 구현) function normalizeIngredientInput(raw: string): string | null { const trimmed = raw.trim().toLowerCase(); // 1. 정확 매칭 const exact = INGREDIENT_POOL.find(i => i.name === trimmed); if (exact) return exact.id; // 2. 초성 매칭 (ㅂㄹㅋㄹ → 브로콜리) if (/^[ㄱ-ㅎ]+$/.test(trimmed)) { return INGREDIENT_POOL.find(i => getChosung(i.name).startsWith(trimmed))?.id; } // 3. 부분 매칭 return INGREDIENT_POOL.find(i => i.name.startsWith(trimmed))?.id; }
11-5. ALG-PREP-04 — 퀵노트 chip → CEBQ 매핑 (NEW)
const QUICK_NOTE_TO_CEBQ: Record<string, CEBQDimension[]> = { '한 입도 안 먹음': ['food_fussiness', 'satiety_responsiveness'], '골라냄': ['food_fussiness'], '향만 맡음': ['food_fussiness_early_stage'], '평소보다 많이': ['food_responsiveness', 'enjoyment_of_food'], '또 달라': ['food_responsiveness'], '친구 따라 먹음': ['social_facilitation'], 'TV 보며': ['environment_distraction'], '스스로 다': ['autonomy_high'], ... }; function mapQuickNoteToCEBQ(quickNote: string): CEBQDimension[] { return QUICK_NOTE_TO_CEBQ[quickNote] || []; }
11-6. 전체 전처리 파이프라인
async function preprocessMealInput(input: RawMealInput): Promise<StructuredMealLog> { const log: StructuredMealLog = { ...input }; // 1. 사진 → 식재료 추출 (있으면) if (input.photo) { const photoIngs = await extractFromPhoto(input.photo); // ALG-PREP-01 log.ingredients = [...(log.ingredients || []), ...photoIngs]; } // 2. 해시태그 정규화 (이미 입력 시 매칭됨) log.ingredients = log.ingredients.map(normalizeIngredientInput); // 3. 자연어 메모 분석 (있으면) if (input.note && input.note.length > 10) { const memoData = await extractFromMemo(input.note); // ALG-PREP-02 log.memo_extraction = memoData; // 메모에서 추가 발견된 식재료 병합 (중복 제외) log.ingredients = dedup([...log.ingredients, ...memoData.ingredients_mentioned.map(i => i.matched_id)]); } // 4. 퀵노트 → CEBQ 매핑 log.cebq_signals = (input.quickNotes || []).flatMap(mapQuickNoteToCEBQ); // 5. 신뢰도 메타 log.preprocessing_confidence = computeOverallConfidence(log); log.requires_user_confirm = log.preprocessing_confidence < 0.75; return log; }
11-7. 사용자 수정 인터페이스
신뢰도 < 0.75일 경우 부모에게 확인 받음:
- "AI가 인식한 식재료가 맞나요?" — 5개 중 수정·삭제·추가 가능
- "메모에서 '시금치 거부'로 이해했어요 — 맞나요?" — Y/N
- 수정 시 ML 학습 데이터로 누적 (다음 OCR 정확도 ↑)
8. 학술 출처 풀 리스트
국제 기준 (Diversity)
- WHO. (2017). Indicators for assessing infant and young child feeding practices: definitions and measurement methods. Geneva: World Health Organization.
- Lachat C, et al. (2018). Dietary species richness as a measure of food biodiversity and nutritional quality of diets. PNAS, 115(1), 127-132.
- USDA. (2020). Healthy Eating Index-2020 (HEI-2020). Center for Nutrition Policy and Promotion.
- Hatløy A, et al. (1998). Food variety, socioeconomic status and nutritional status in urban and rural areas in Koutiala (Mali). Public Health Nutrition, 1(3), 205-211.
- Monteiro CA, et al. (2019). Ultra-processed foods, diet quality, and health using the NOVA classification system. FAO.
식습관 형성 이론
- Fraker C, Fishbein M, Cox S, Walbert L. (2007). Food Chaining: The Proven 6-Step Plan to Stop Picky Eating. Da Capo Lifelong Books.
- Toomey KA, Ross E. (2017). The SOS Approach to Feeding. SOS Approach to Feeding Conference.
- Cooke L. (2011). The importance of exposure for healthy eating in childhood. Journal of Human Nutrition and Dietetics, 20(4), 294-301.
- Birch LL, Marlin DW. (1982). I don't like it; I never tried it: Effects of exposure on two-year-old children's food preferences. Appetite, 3(4), 353-360.
- Satter E. (2007). Eating Competence: Definition and Evidence for the Satter Eating Competence Model. JNEB, 39(5 Suppl), S142-S153.
식감·코호트 연구
- Schwartz C, et al. (2014). HabEat Project — Determining factors and critical periods of food habit formation. EU FP7.
- Northstone K, et al. (2001). Are dietary patterns stable throughout early and mid-childhood? ALSPAC cohort.
한국 기준
- 보건복지부·한국영양학회. (2025). 2025 한국인 영양소 섭취기준 (KDRI). 1·2·3권 + 국문/영문 요약본 + 3차 정오표.
- 농촌진흥청. (2024). 국가표준식품성분표 v10.0.
- 질병관리청. (2017). 소아청소년 표준성장도표 (LMS 파라미터).
- 식품의약품안전처. 식품 알레르기 17대 표시 의무.
영유아 특화
- Solid Starts. (2024). 100+ First Foods. solidstarts.com.
- Rapley G. (2008). Baby-Led Weaning. Vermilion.
- NHS. (2024). Your baby's first solid foods. UK National Health Service.