🔄 DATA PIPELINE · ERD · SYSTEM ARCHITECTURE

식단 데이터 파이프라인 · ERD
시스템 아키텍처

원천(공공 식단·학부모 식단표) → 수집 → 메뉴→식재료 분해(+웹 그라운딩 추정) → DB → 식재료·음식·궁합 네트워크 → 코칭 편지까지. 어디서 무엇이 가공·전처리되고 LLM이 어디 3곳에만 개입하는지, 데이터가 어떤 테이블·관계(ERD)로 들어가고 편지가 그걸 어떻게 끌어다 쓰는지 — 실제 코드 단위로 그린 단일 진실.

현행 (라이브 코드) LLM 개입 지점 제안 (신규 설계)
← 문서 허브 · 엔진·데이터 명세 · 2026-06-14 · 연관 데이터 강화 아키텍처
1

원천 데이터 — 어디서 긁어오나 (합법 3층)

유치원/어린이집 식단은 국가 단일 포털이 없다. NEIS는 학교만, 시설 메타는 정보공개포털, 메뉴는 분산. 그래서 합법성으로 3층으로 나눈다.

2

데이터 파이프라인 — 원천에서 네트워크까지

메뉴 문자열 → 표준 식재료로 분해되는 흐름. 0~2차는 전부 결정론(환각 0), LLM은 미지 메뉴·OCR에만. 제안 = 사용자 요청한 웹 식재료 추정 + 검수 게이트.

S1수집결정론 (OCR만 LLM)
NEIS keyless 전수(pull-neis-full.py)·식약처 표준식단 API·학부모 식단표 이미지. 이미지는 CLOVA OCR(표 셀 인식) → ocr_logs 로깅.
S1b식단표 OCR 구조화LLM · Sonnet 4-6 Vision
표 구조·날짜 배치가 제각각이라 Vision이 메뉴명·날짜 매핑만 구조화. is_menu 판정(비식단 거절)·전수 로깅. 추출 메뉴는 S2~S4로 다시 결정론 검증.
S2정규화 (렉시콘)결정론
표기 변형 통일 — 쎠세지→소시지·달걀→계란·백미→쌀. lib/lexicon.ts(77개)·도감 표준명 일관. 캐논누수 9,992메뉴 감사 튜닝.
S3메뉴→식재료 분해 (4단 폴백)0~2차 결정론
우선순위 care override › learned › dict › rule › scan › llm. (0)learned_menus 학습사전 → (1)menu-dict.json 정확일치+룰 → (2)scanIngredients substring 최장일치(양념·물·기름 EXCLUDE). 여기서 대부분 무료 히트, 환각 0.
S3-LLM미지 메뉴 LLM 추정LLM · Haiku 4.5
0~2차 전부 []인 처음 보는 메뉴만 호출. 3단 환각 차단: 프롬프트 명시금지('확실치 않으면 []'·환각 금지) → canon 정규화 → CANON_VOCAB 어휘검증(도감 밖 식재료 자동 제외). 빈 결과는 저장 거부.
S3-web미지 메뉴 웹 그라운딩 추정제안 · 사용자 요청LLM + 웹서치
"메뉴명을 구글링해 안에 든 식재료를 추정" — LLM 지식만 쓰는 S3-LLM을 보강. 미지 메뉴를 웹서치 → 실제 레시피 페이지에서 식재료 후보 추출 → CANON_VOCAB 필터 → 검수 게이트(아래 S3.5)로. 지식 환각보다 실제 레시피에 그라운딩돼 정확↑.
S3.5엑셀 검수 게이트제안사람(Claude) 판정
웹/LLM 추정 결과를 증거(출처 URL·동시출현·안전플래그) 펼친 엑셀로 → 내가 터미널에서 승인/거부/수정 → 통과분만 사전에 박음. 질식·알레르겐은 코드가 자동 차단(안전 바닥). 상세 →
S4통합 보정결정론
boostFromName(메뉴명에 숨은 곡물 강제: 밥/죽→쌀·튀김→밀 튀김옷)·meatFix(불고기+닭 명시→닭고기)·dedupCanon.
S5코퍼스 씨딩 + 야간 백필대부분 결정론
빌드: NEIS/레시피→learned_menus 초기화. 런타임: 2시 크론ingredients 빈 행을 learned→rule→scan→LLM(예산가드 max 8콜·6초) 순으로 채움. 부모 교정 행은 보존.
S6동시출현 네트워크 + 키트 매트릭스통계 (일부 LLM 채점)
pair=레시피 동시출현 lift=c·N/(na·nb)(떡+달걀 0.72=weak 차단)·tray=식판 공동출현(upgrade-only)·bridge=사촌 수기 → food-graph.json. 음식×식재료 = kit-dish-matrix.json.
3

ERD — 식재료 · 음식 · 음식↔식재료 · 식재료↔식재료

식재료(INGREDIENT)를 중심 허브로, 음식이 식재료를 담고(contains), 식재료끼리 궁합/사촌(pair·bridge)으로 잇고, 아이가 끼니에서 먹는다(eats). 운영 테이블(파랑·SQL)과 지식 네트워크(초록/주황·빌드 JSON)가 만난다.

1:N 1:N 1:N produces eats N:M contains N:M pair·bridge·tray (N:M self) archetype×식재료 N:M reads INSTITUTIONS 기관 31,819 · PK id CHILDREN PK id FK institution_id age·만성질환·알레르기 MEAL_LOGS FK child_id · log_date·slot menus[] · ingredients[] refused·place·ate_well source(home/daycare_menu) OCR_LOGS image·ocr_text·is_menu → meal_logs 생성 COACH_LETTERS FK child_id · letter_date letter·oneliner·context source_hash·model MENU (음식) learned_menus·menu-dict · PK menu_key INGREDIENT (식재료) 도감 · PK nm (표준명) grade·category·💎 nutrient·season·cuisine freqMap(또래 인기음식) 제안: 식감·색·알레르겐· 질식·연령 메타 KIT_DISH_MATRIX 음식 archetype × 식재료 cells{archetype:{식재료:count}} FOOD_ GRAPH edges a·b·kind lift·grade
운영 데이터 (런타임 SQL) 지식 — 음식·식재료 (빌드 JSON) 네트워크 — 궁합·매트릭스 ↠ 점선 = 생성/읽기 · ⪪ 까마귀발 = N(다)
MENU (음식) learned_menus · SQL
  • PKmenu_key 정규화 메뉴명
  • ingredients[] 표준 식재료 배열
  • processed · source(learned/dict/rule/scan/llm)
  • ≈9,992개 · 분해 결과 영구 캐시 · learn_menu RPC 멱등
INGREDIENT (식재료) 도감 · build
  • PKnm 표준명(192/도감 204)
  • grade·category·💎 · nutrient · season · cuisine
  • freqMap 또래 인기음식(ingredient-recipes.json)
  • 제안 식감·색·알레르겐·질식·연령 메타
FOOD_GRAPH (식재료↔식재료) build JSON
  • edges[] = {a, b}
  • kind pair(궁합)·bridge(사촌)·tray(식판)
  • lift·grade·strength·basis
  • self-relationship N:M · 추천 네트워크
KIT_DISH_MATRIX (음식↔식재료) build JSON
  • dishes[] {key, em, n}
  • cells {archetype:{식재료:count}}
  • 음식 형태별 메인 식재료 빈도 행렬
  • '당근→가장 많이 쓰는 음식 2개' 근거
MEAL_LOGS (끼니 로그) SQL
  • FKchild_id · log_date · slot
  • menus[](원문) → ingredients[](분해)
  • refused·place(home/daycare)·ate_well·texture
  • source home / daycare_menu(OCR)
OCR_LOGS / CHILDREN / INSTITUTIONS SQL
  • ocr_logs image·ocr_text·is_menu·model → meal_logs
  • children PK id · FK institution_id · age·질환·알레르기
  • institutions 31,819 · type · name_norm
  • affinity_cache 정적 그래프 부족분 LLM 폴백
4

LLM은 딱 3곳에만 — 각자 환각 가드

파이프라인 전체에서 LLM 개입은 (A)미지 메뉴 분해 · (B)식단표 OCR · (C)편지 작문/검증뿐. 나머지는 전부 결정론. 각 지점마다 환각·괴식 차단 가드가 붙는다.

🧩

A. 미지 메뉴 → 식재료 추정 Haiku 4.5

0~2차(learned·dict·scan)가 모두 실패한 처음 보는 메뉴만. 결정론 규칙으로 분해 불가한 신조어·복합 가공식품을 자연어 이해로 추정. 제안: 여기에 웹서치 그라운딩(S3-web)을 더해 실제 레시피로 추정.
🛡️ 3단 차단: 프롬프트 명시금지('확실치 않으면 []'·환각 금지) → canon 정규화 → CANON_VOCAB 어휘검증(도감 밖 식재료 자동 제외) → 빈 배열 저장 거부. + 제안 S3.5 엑셀 검수 게이트.
📷

B. 식단표 OCR 구조화 Sonnet 4-6 Vision

학부모가 찍은 식단표는 표 구조·날짜 배치가 제각각 → CLOVA OCR 텍스트만으론 매핑 불가. Vision이 메뉴명·날짜 구조화 추출만 담당.
🛡️ CLOVA가 1차 텍스트 확보 후 LLM은 구조화만 · is_menu 판정(비식단 거절) · 추출 메뉴는 S2~S4 결정론 분해로 다시 검증 · ocr_logs 전수 로깅.
✉️

C. 편지 작문 + 검증 Haiku / intro만 Sonnet

'재료=코드 결정론·문장=LLM 자유'. 타깃·식재료·조합·근거·무브는 코드가 100% 못박고, LLM은 도입·톤·장면·질문만 작문 → '같은 잔소리' 회피. + proofreadLetter(윤문)·verifyLetter(의미 검증).
🛡️ 4중 게이트: ① letterDeterministicBad(정규식: 괴식·증상·시점) ② letterSimilarity(자카드: 도입≥0.40·전체≥0.45) ③ verifyLetter(LLM 의미: 사실·행동1개·진단재서술) ④ qualityScan(은유·나열·재료밖 음식). serializeMaterials가 화이트리스트 밖 '지어내기 입구' 차단.
5

편지가 DB를 끌어다 쓰는 경로 (1건 생성)

2시 크론이 자녀별로 — 읽고(결정론) → 신호 파생 → 재료 결정론 조립 → LLM 작문 → 4중 검증 → 발행. '재료=결정론, 문장=LLM'이 코드로 구현된 전 과정.

1
데이터 리드 결정론meal_logs 최근 7일(어제까지) + freqMap·food-graph·ingredients-light fetch + coach_letters 3일(중복 회피용).
2
신호 파생 결정론 — likedIngredients(집 2일+ 거부없음)·refusedSet·homeByDate(집/기관 분리)·nutritionSignals(영양 부족 가중).
3
계획 결정론 결정론 — planFor→selectScenario→buildCoachPlan{frame·target·moveKey·signature}. 타깃·무브를 코드가 못박음(어제 'mix'면 오늘 'beside').
4
재료 결정론 결정론 — rankIngredients(시급도·급식빈도·궁합·사촌 4기준)→rotate(3일 무재사용)→validatedCombos(scoreCombo≥1, 미역국+당근 괴식 차단)→buildReasonPhrases.
5
사실·거울·직렬화 결정론 — compileFactCards(단발/간헐/반복)·buildMealMirror(7일 신호등). serializeMaterials가 [타깃·조합·근거]를 LLM 대입 불가 텍스트로 + materialFoods 화이트리스트 확정.
6
작문 LLM 자유 — generateLetter(Haiku, intro만 Sonnet)가 [재료]+[거울]+[부모메모] 위에 도입·톤·장면·질문만 자유 작문(목록 밖 음식 금지·3~4문장·행동 1개) → polish/proofread.
7
4중 검증 3차만 LLM — 정규식(괴식·증상)→자카드(반복)→verifyLetter(LLM 의미: 사실·행동1개)→qualityScan. 위반 시 무브 바꿔 재생성 1~2회.
8
발행 결정론 — 통과(또는 미개선 위반은 '발행>완벽' 우아한 강등) → coach_letters upsert. 다음 아침 부모 앱 노출.
6

시스템 아키텍처 — 8층

원천부터 앱까지 8개 층. 핵심 분담 = 2시 크론은 런타임 백필(적용) · /mealfred-food-mapping 스킬은 빌드 산출물 개선. learned_menus는 양쪽 공용 단일 진실.

L1

원천 Sources

NEIS 급식 API(학교만) · 식약처 영유아 식단 API(15118061/65) · CCFSM 표준식단 · 학부모 식단표 이미지. 합법 3층: 완전합법→조건부→금지(알림장·카페).

L2

수집 Ingestion

pull-neis-*.py(keyless 전수)·import-nong-foods.py·ocr/route.ts(CLOVA+Sonnet Vision)·seed-institutions.py. OCR만 LLM, 나머지 결정론.

L3

가공 Normalize·Decompose

lexicon(표준명)·createMapper(4단 폴백 dict→rule→scan→LLM)·boostFromName/meatFix·2시 크론 백필. 0~2차 결정론(환각 0)·3차만 Haiku(3단 차단).

L4

저장 Store (이중)

런타임 SQL: learned_menus·meal_logs·ocr_logs·affinity_cache·coach_letters·institutions·nong_foods. 빌드 JSON: menu-dict·food-graph·kit-dish-matrix·ingredient-recipes·corpus-stats·lexicon.

L5

검수 게이트 Curation·Safety

CANON_VOCAB 어휘검증·빈배열 저장거부·food-graph lift 게이트·kit-matrix cells·도감 영유아 안전스크린(질식·보툴리누스). 제안: 엑셀 검수 게이트(사람 판정).

L6

추천 네트워크 Recommendation

coachRecos(strongPairsOf·verifiedCousinsOf)·coachMaterials(4기준 랭킹·3일 회전·validatedCombos)·food-graph·kit-matrix·affinity_cache 폴백. '잘 먹는 음식에 결핍 섞기'를 검증된 동시출현으로.

L7

편지 Coaching Letter

재료=결정론(plan·materials·facts·mirror·serialize) · 문장=LLM(generate·proofread). 4중 게이트(정규식→자카드→LLM 의미→품질). S7 우아한 강등(발행>완벽).

L8

앱·운영 App·Ops

care(식단 입력·OCR·달력)·홈 코칭 스레드(3일 FOMO)·도감·마을·/admin(funnel·compare·모더레이션)·Vercel 크론(coach 2시 외 통합 배치)·예산 가드·CRON_SECRET.

🎯 한 줄 요약

원천(합법축 = NEIS + 식약처 영유아 API + 학부모 OCR) → 메뉴를 0~2차 결정론으로 분해(환각 0), 미지 메뉴만 웹 그라운딩+LLM 추정 → 엑셀 검수 게이트learned_menus(음식↔식재료)·food-graph(식재료↔식재료)·kit-matrix(음식×식재료)에 저장 → 편지는 이 네트워크를 읽어 재료를 결정론으로 조립하고 문장만 LLM이 자유 작문, 4중 게이트로 괴식·반복·환각을 막고 발행. LLM은 미지분해·OCR·작문 3곳에만.