목적
이 엔진은 무엇을 위한 것인가
한 줄: 2~6세 아이가 거부감 없이 먹는 음식의 '가짓수'를 한 발씩 넓히도록 — 양이 아니라 다양성을 키운다.
그러려면 ① 추천이 "지어낸 조합"이 아니라 실측 데이터로 검증된 것이어야 하고, ② 아이의 실제 선호·결핍에 맞아야 하며, ③ 영유아 안전(질식·알레르겐·저염·괴식)을 절대 어기지 않아야 한다.
- ① 데이터 인입 — 전국 어린이집·유치원 식단표(3경로)를 모아 "급식에 뭐가 자주 나오나"의 모집단 기준을 만든다.
- ② 식재료 지식 그래프 — 식재료를 곁들임(pair)·사촌(bridge)으로 잇고, 도감 등급(2축)·영양을 붙인다.
- ③ 메뉴→식재료 분해 — 식단표의 '음식 이름'을 표준 '식재료'로 결정론 분해(환각·괴식 0).
- ④ 추천·푸드체이닝 — 잘 먹는 것 → 곁들임 → 사촌 → 새 조리법으로 다리를 놓아 편지에 안전한 후보만 넘긴다.
- ⑤ 저장·자가학습 — SQL을 단일 진실로, 데이터가 쌓일수록 추천이 현실에 맞게 야간 자가보정(복리).
핵심 설계 원칙: 판단(괴식이냐·등재냐)은 빌드타임 1곳에서 사람·증거가 정하고, 런타임/편지는 검증된 것을 믿고 결정론 필터(연령·알레르기)만 건다. "수치가 LLM을 이기는 게 아니라, 수치가 LLM을 증거로 무장시킨다."
1
전체 흐름 — 식단 1장이 추천이 되기까지
3경로로 들어온 식단표가 식재료 지식이 되고, 그게 추천·편지로 나가는 e2e. 외부 OCR/LLM·우리 모듈·DB를 색으로 구분.
범례 외부 OCR외부 LLMDB·SQL
우리 모듈
code
입력식단표 3경로 → ocr_logs 큐(멱등)
① zip 관리자(센터 식단 묶음) · ② daycare-eval 업로드 · ③ 부모 기록탭. 모두
(기관,월) 멱등키로 중복 제거 — 같은 기관-월 재입력해도 한 번만.▼
S1OCR 전사 (사진/PDF → 글자)Naver CLOVA OCR V2Storage
app/api/ocr — 표 인식(실패 시 일반 OCR 폴백). PDF는 pdftoppm 래스터화. 📦
ocr_logs(감사): ocr_text·model·tokens▼
S2메뉴 추출·이름 교정Claude Sonnet 4.6 · Vision
OCR 텍스트+이미지 →
items[{date,slot,menu}]. 오인식 교정(돈까스→돈가스)·알레르기기호 제거.▼
S3메뉴 → 식재료 분해 (결정론 6단계)DeepSeek/Haiku 미해결만
0 learned 캐시→1 dict 2062→1b 부분→2 스캔+boost→meatFix → 미스만 3 LLM. 표준명(lexicon)·CANON_VOCAB 검증(양념 제외·환각 차단). 📦
learned_menus 캐시(멱등·다음부터 무료)▼
S4(기관,월) dedup 집계 → 지식SQL
기관-월 1벌 단위로: 등장률(youa-freq baseline) + 공출현 쌍(같은 끼니) + 도감 2축 등급.
📦
institution_menus·ingredient_edges(pair/bridge)·ingredients(도감)▼
S5추천·푸드체이닝 → 편지DeepSeek 작문만
graphSource(SQL) → strongPairsOf·verifiedCousinsOf·safeGarnishOf → 5전략(liked·pair·chain·dish·plain) → 안전 필터 → 편지(재료=결정론, 문장=LLM).▼ 새벽 크론(자가학습 복리) ▼
🌙야간 통합 배치
ocr_logs 큐 정제→learned_menus · 식재료 backfill · 신규 식재료→enrich_queue(영양) · 공출현→
ingredient_edges 갱신 · warm(SQL→메모리). 데이터 쌓일수록 추천 자가보정.2
5개 축 (옛 문서들의 통합)
옛 6개 문서가 다루던 내용을 5축으로 합쳤다. 각 축 제목 옆 = 현재 상태(라이브/부분/계획).
A. 데이터 인입 부분 ← menu-ingest
전국 식단표 3경로 → (기관,월) 멱등 → 모집단 빈도(youa-freq) baseline
- 3경로: zip(센터)·daycare-eval(
institution_menusUNIQUE upsert)·부모 기록탭(children.institution_id+place=daycare로 환원) - 빈도 2층 분리: baseline(기관-월 등장률
youa-freq.json= 도감 등급·코칭 우선순위 — 2026-06-21 실측 OCR 식단 MAX 병합: 단호박 1.4→65.7%·애호박·배추김치 게이트 통과) / observed(ingredient-freq.json= 랭킹·풀 순서 — 7종 degraded → 162종 실측 식단 재구축build-ingredient-freq.py --src sikdan) - 경로1 zip = 사진/PDF면 OCR 경로(parse_and_aggregate는 xlsx 전용). 폐지된 식약처 영유아 API를 이 OCR 적재가 대체.
B. 식재료 지식 그래프 라이브 ← 음식추천네트워크
식재료 노드 + pair(곁들임/궁합)·bridge(사촌/대체)·도감 2축 등급
- pair 940 · bridge 175(무방향). lift=c·N/(na·nb)로 우연 보정 → grade strong/medium/weak. tray(NEIS 식판 공출현) 축 병합.
strongPairsOf(약신호 차단)·verifiedCousinsOf(검증 사촌)·garnishPairsOf(tray 제외 곁들임).- 도감 2축: 영유아 등장률 ⊕ 초등 elem_count → 결합등급 MAX → 코칭 노출 우선순위. 계획 TIER2 자동 bridge(영양 코사인·감각 7축).
C. 메뉴→식재료 분해·유지 라이브 ← 코퍼스/도감 스킬
음식 이름을 표준 식재료로 결정론 분해 + 사전/도감 유지보수
- 결정론 6단계(
0 dict→1 룰→1b 부분→2 스캔+boost→meatFix) → 미스만 LLM. 대부분 메뉴 무료·즉시. - 크론=적용 / 스킬=개선: 새벽 크론은 사전대로 분해·백필,
/mealfred-food-mapping스킬은 사전·도감 자체를 개선(DB 쓰기 0·정적 JSON+코드 커밋). - 안전 스크린 우회 금지: 도감 자동 등재해도 영유아 안전(질식·보툴리누스·고나트륨 한방·비타민A 과잉)은 절대 통과 못 함. 한방 재료는 매핑에서 무시(인삼닭곰탕→[닭고기,무,대파]).
D. 추천 — 이 데이터를 *쓰는* 쪽 → 별도 모듈(B)
위 지식그래프·도감·등장률을 런타임에 소비해 "오늘 무엇을 권할지" 고르는 건 추천 모듈 소관. 이 데이터 엔진은 그 재료만 공급.
- 요약: 잘 먹는 것 → 곁들임(pair) → 사촌(bridge) → 새 조리법으로 한 발씩(다양성↑). 5전략·괴식게이트·콜드스타트·안심톤.
- 상세 구현·로드맵(2축 확장·온도축·neophobia 곡선)은 → 추천 모듈 ② (정본)로 분리. (책임 분리: 데이터=여기 / 런타임 추천=거기)
E. 저장·전환·자가학습 부분 ← 추천 SQL 핸드오프
SQL 단일 진실 + graphSource 격리 + 야간 복리 학습
- 7테이블(ingredients 허브 ⊃ ingredient_edges·dish_ingredient_stats·dogam·meal_logs·learned_menus·nong_foods). 3층 캐시(L1 SQL→L2 스냅샷 JSON→L3 인메모리+폴백).
graphSource.ts한 겹으로 JSON↔SQL 격리 → 소비 함수 byte 무변경. 라이브 크론이 항상 SQL warm(플래그 졸업).- 미구현 야간 SQL→JSON export job(스냅샷 자동 갱신) · 야간 공출현 학습 write(증분 강화) — 로직 검증 완료(아래 예시).
3
현재 개발현황 (2026-06-21)
1115
ingredient_edges(SQL=JSON)
175
검증 사촌(bridge)
2062
menu-dict 메뉴
679
도감 식재료
13.4k
learned_menus 캐시
| 항목 | 상태 | 메모 |
|---|---|---|
| 식재료 그래프 pair/bridge·lift·tray·garnish 분리 | 라이브 | strongPairsOf/garnishPairsOf 등 적용 |
| SQL ingredient_edges ↔ JSON 동기화 | 완료 | 782→1115(strong 276→517, tray 복구)·값 100% 일치 |
COACH_GRAPH_SQL 플래그 졸업(항상 warm) | 코드 완료·배포 대기 | route.ts 타 작업과 얽혀 합류 대기(prod는 JSON 폴백으로 안전) |
| 메뉴 분해(결정론 6단+LLM)·도감 스킬 | 라이브 | 커버리지 ~83%(실측), 미스만 DeepSeek |
| 추천 5전략·괴식 게이트·안심 톤 | 라이브 | tray/매운/단↔짠 교차괴식 차단 |
| 데이터 인입 3경로 통합·(기관,월) 멱등 | 부분 | OCR 배치 가동 중·institution_menu_items 아직 0행 |
| 야간 공출현 학습 write·SQL→JSON export job | 미구현 | 집계 로직은 검증(아래 예시)·크론 배선 남음 |
| 푸드체이닝 2축 확장·neophobia·학습루프 | 계획 | 5전략·진척5단계는 라이브, 조리법/온도축은 설계 |
| TIER2 자동 bridge(영양 코사인·감각 7축) | 계획 | 현재 bridge 175 수기 시드 |
4
예시 데이터 사례
① 실데이터 — 광진구 어린이집 10월 식단표 (OCR→분해→집계)
S1·S2 (CLOVA+Sonnet):
S3 분해: 닭살채소볶음밥→[닭고기·당근·양파·쌀] · 연근조림→[연근] · 배추김치→[배추·김치]
S4 집계(143개 기관-월·멱등): 등장률 TOP = 양파·소고기·두부·미역 100% · 단호박 65.7%(표준식단 1.4%였던 것 복권) · 공출현(같은 끼니) =
멱등 검증: 같은 데이터 재투입 → 신규 0·전량 중복 스킵·누적 불변(중복 카운팅 0).
1(수) 오전간식: 떠먹는요구르트, 파프리카스틱 / 점심: 닭살채소볶음밥, 연근조림, 자른토마토, 배추김치 / 오후간식: 단호박샐러드S3 분해: 닭살채소볶음밥→[닭고기·당근·양파·쌀] · 연근조림→[연근] · 배추김치→[배추·김치]
S4 집계(143개 기관-월·멱등): 등장률 TOP = 양파·소고기·두부·미역 100% · 단호박 65.7%(표준식단 1.4%였던 것 복권) · 공출현(같은 끼니) =
멥쌀+파 ×140 · 김치+배추 ×133 · 멸치+파 ×129 → ingredient-freq.json 162종·youa-freq.json 병합으로 라이브멱등 검증: 같은 데이터 재투입 → 신규 0·전량 중복 스킵·누적 불변(중복 카운팅 0).
※ 데이터 쌓일수록(현재 OCR 배치 가동 중·155개 식단표) 등장률이 차등화 → 폐지된 식약처 영유아 API 대체. 분해 미스(돈가스·오므라이스 등)는 야간 DeepSeek.
② 판정 엑셀 — 사람이 증거 보고 결정(빌드타임 1곳)
| a ↔ b | 종류 | lift | 증거 | 안전 | 판정 |
|---|---|---|---|---|---|
| 소고기 ↔ 시금치 | pair | 1.8 | 시금치무침+불고기 식판 3건 | O | 승인 |
| 떡 ↔ 달걀 | pair | 0.72 | 같은 끼니 거의 없음(우연 이하) | 음성lift | 거부 |
| 견과 ↔ 요거트 | bridge | — | flavor 유사 | 질식·알레르겐 | 조건부(만3+·잘게) |
| 두부 ↔ 근대 | bridge | — | 부드러운 식감·철분 결핍 동급 | O | 승인 |
안전플래그(질식·8대알레르겐·음성lift·꿀만1세)는 코드 자동 계산 = 판단 대상 아님. 나머지만 사람 판정.
③ 분해 4단 폴백
- '계란찜' → learned 캐시 → menu-dict 일치 → substring 최장일치 → LLM(Haiku)
- '인삼닭곰탕' → [닭고기·무·대파] (인삼=한방 안전스크린 차단)
- '함초삼겹' → [돼지고기] (함초=고나트륨 차단)
④ 추천·괴식 차단
- 감자 잘 먹음 → 단호박(사촌·비타민A 갭) + 찐 감자(조리법 전이)
- 미역국+당근 = 괴식 자동 제외(scoreCombo<임계)
- 두부 타깃 + 김치 → "김치 곁들임" 차단(매운/tray 제외)
5
안전·불변선 (절대 어기지 않는 것)
하드 차단(코드 자동) — 질식위험·8대 알레르겐·음성 lift·꿀(만1세). 판단 대상 아님, 무조건 제외.
괴식 0 — 검증 테이블 안의 조합만 추천. 공출현 0 조합은 메뉴로 존재 안 함. 단↔짠(과일·우유 ↔ 생선·콩)·매운/김치류 곁들임 차단.
도감 안전스크린 우회 금지 — 자동 등재해도 보툴리누스·고나트륨 한방·비타민A 과잉은 통과 못 함. 위험군 보류·보고.
날조·과장 금지 — "5→20.5" 영구 금지. 서열·등수·% 부모 노출 금지(안심 톤만). 근거 없으면 안 넣음.
불변 계약 — 소비 함수 시그니처·RawEdge 필드명·PAIR_MIN_STRENGTH=2·괴식 가드·골든 테스트는 불변. graphSource 한 겹으로만 JSON↔SQL 전환.
저작권·경계 — Satter sDOR 무수정만, SOS표 복제 금지. 점검 신호 ≠ 진단(위험 신호 시 소아과 게이트). 푸드브릿지 ≠ 푸드체이닝.