⚙️ ENGINE TECHNICAL ARCHITECTURE · 코드 단위 정독본

코칭 엔진 기술 아키텍처 해부도
전 모듈을 코드 단위로 시각화

매일 새벽, 엔진이 한 아이의 식사 기록을 읽고 부모에게 코칭 편지 한 통을 짓기까지 — 실제 코드를 정독해 소프트웨어 공학 시각화 기법 15종으로 펼쳤습니다. 토대(2축 음식 네트워크·영양 신호) 위에서 결정론이 후보를 계산🧠 두뇌(Sonnet)가 고르고 검수✍️ 손(Haiku)이 작문합니다. 함수명 대신 한글 모듈명으로, 이 문서 하나로 엔진의 기술 구성을 알 수 있게.

📚 문서 허브 › ⚙️ 엔진·데이터 명세 › 기술 아키텍처 해부도  ·  갱신 2026-06-14 · 코드 기준 라이브
1

한눈 요약 — 엔진 계기판

Executive Dashboard

엔진은 토대 위 3단으로 짓습니다 — ① 결정론이 2축 음식 네트워크·영양 신호로 후보(시나리오·추천 풀)를 깔고, ② 🧠 두뇌(Sonnet, ?brain 카나리아)가 시나리오를 고르고 음식 추천을 검수하고, ③ ✍️ 손(Haiku)이 작문합니다. "계획·판단은 코드와 두뇌가, 글만 손이" 원칙 위에 섭니다.

37
엔진 모듈
lib + API·크론
13
시나리오
트리거·우선순위·금기
835
테스트
prebuild 게이트 · 괴식·수렴·품질 0
626·192
음식 네트워크 엣지·노드
pair 451 · bridge 175
5
추천 풀
영양 거울 기반 · 일일 회전
CELLS≥8
괴식 매트릭스 게이트
떡+달걀·미역국+당근 차단
2
크론
KST 02시 코칭 · 03:30 자가진단
3~7 + 1
편지당 LLM 콜
손(Haiku) + 두뇌(Sonnet, ?brain)
📖 이 문서 읽는 법 — ②③④로 "엔진이 어떤 부품으로 짜여 있나(구조)"를 보고, ⑤⑥⑦로 "하루에 어떻게 작동하나(행위)"를, ⑧⑨로 "데이터가 어디서 흘러 어디 쌓이나"를, ⑬으로 "어떤 실수를 막도록 설계됐나"를, ⑭⑮로 "규모·비용·현재 상태"를, ⑯으로 "추천을 무슨 근거로 하나"를 봅니다. 다이어그램마다 우상단에 사용한 소프트웨어 공학 기법명을 적었습니다.
2

시스템 맥락 — 엔진은 누구와 대화하나

C4 Lv.1 · System Context

엔진은 스스로 깨지 않습니다. Vercel Cron이 매일 새벽 깨우면, Supabase에서 식사 기록을 읽고, 필요할 때만 Claude에게 글을 맡긴 뒤, 결과를 다시 Supabase에 넣어 둡니다. 부모는 아침에 이미 만들어진 편지를 읽기만 합니다.

🧠 코칭 엔진 37 모듈 · 결정론 계획 + 작문 결정론 후보 · 두뇌 선택 · 손 작문 ⏰ Vercel Cron 매일 KST 02시·03:30 트리거 🤖 Claude Haiku 작문·Sonnet 종합 🗄 Supabase 끼니·편지·진도·닻 저장 👨‍👩‍👧 부모 끼니 입력 · 편지·질문 읽기 ① 깨운다(GET) ③ 글 요청(필요시) 편지/검증 ④ 편지·진도 저장 ② 기록 읽기 끼니 입력→DB ⑤ 아침에 편지 읽기

★ 핵심 설계 = 비동기 사전 생성. 부모가 앱을 여는 순간 LLM을 호출하지 않습니다 — 새벽에 미리 만들어 둔 편지를 read만 합니다(온디맨드 API는 폴백). 그래서 아침 화면은 즉시 뜨고, LLM 비용·지연은 새벽 배치에 격리됩니다.

3

계층 지도 — 9개 층, 37개 부품

Layered Architecture · 모듈 추상화

위에서 아래로: 깨어남(진입점)지휘(오케스트레이션)전략(주간)·전술(일간)🧠 두뇌 선택·검수사실·추천데이터·영양 토대검증 하네스지원. 칩 하나가 모듈 하나입니다(파일명 병기).

v2 전용 두뇌·카나리아 공용
⏰ 진입점 · 스케줄링언제 깨어나 누구를 처리할지
새벽 코칭 크론공용cron/coach · 826줄 · 메인 루프 야간 자가진단 크론공용cron/coach-selfcheck · 반복 자동탐지 온디맨드 편지 APIv2api/coach · 클라 폴백 오늘의 질문 API공용api/coach/question 크론 스케줄 선언공용vercel.json
🧭 오케스트레이션 · 작문계획을 확정하고 글을 짓는 본부
코칭 본부공용coach.ts · 818줄 · planFor·composeLetter·가드·검증·퇴고
🗓 주간 작전 (전략층)한 주의 방향을 의사처럼 종합
주간 작전 사령부공용coachWeekly · 376줄 · 닻·레버·아크·채근캡(Sonnet)
🎯 일간 전술오늘 무엇을·어떤 각도로
일간 전개 결정기도먼트coachDaily · decideDailyV3 — v3 전용·라이브 미가동 상황 카탈로그v2coachScenarios · 13 시나리오 만성질환 식이 가이드공용coachChronic
🧠 두뇌 선택·검수 (?brain)LLM이 시나리오 고르고 추천 검수
행동 선택 두뇌카나리아coachBrain · pickActionByBrain(Sonnet) 추천 식재료 풀·회전공용coachRecos · buildIngredientPool(5) · cookedName 괴식 조합 매트릭스공용comboMatrix · CELLS_MIN 8 레거시 v3 조립도먼트assembleLetter·letterBlocks·curriculum — 라이브 미가동 식단 예시 조립기공용composePlan
📋 사실 · 추천코드가 사실을 박제, LLM은 인용만
사실 원장 컴파일러공용coachFacts · 300줄 · 추세 라벨·금지어·식단 거울 추천 근거 빌더공용coachRecos · 사촌·인기음식·노출 타깃 기간 지표·주차 키공용progress · 롤업 재노출 적기 계산기공용reexposure
🍱 데이터 그래프 · 영양 (토대)엔진이 딛고 선 자체 데이터 자산
영양 신호 계산기공용nutrition · 561줄 · 신호등·식품군·시계열 음식 궁합·사촌 그래프공용foodGraph · 198노드 549엣지 궁합 폴백공용affinity · 정적→캐시→LLM 키트 음식×식재료 매트릭스공용kitGuide · 32×183 조리법 가이드공용cookingMatrix · 한식·cuisine 제철 달력공용season 성장 기준공용growth-reference · BMI·WHO 상류 데이터 공급공용menuMap·learnedMenus·remapMenus·lexicon·spicy
🔬 검증 하네스라이브 전에 가짜 가정으로 14일을 돌려본다
리플레이 하네스공용replayRunner · 가상 30가정×14일 리플레이 지표·게이트공용replayMetrics · prebuild 컷오버 게이트 품질 자가진단 스냅샷공용evalSnapshot 테스트 스위트공용tests/ · 220개 · 7파일
🔧 지원 · 유틸시각·발신 등 횡단 관심사
알림톡 발신(도먼트)공용sens · NCP SENS KST 날짜 유틸공용date
4

두뇌 2파이프 — 결정론 후보 → 두뇌 선택·검수 → 손 작문

Container Diagram · 2-Stage Pipeline

엔진은 v2(LLM 작문) 기반 위에 LLM 두뇌를 얹은 2파이프 구조입니다. ① 결정론이 토대(2축 네트워크·영양 신호)로 '오늘 쓸 수 있는 것'(시나리오·추천 식재료 풀)을 후보로 깔고 → ② 🧠 두뇌(Sonnet)가 그중 하나를 고르고 음식 추천을 켤지 끌지 검수(편지는 안 씀)하고 → ③ ✍️ 손(Haiku)이 영양 평가·어제 식단·음식 추천을 엮어 작문합니다. 두뇌가 꺼지거나(?brain 미설정·실패) 후보가 없으면 결정론 산출로 폴백 — 발행은 언제나 보장됩니다.

🧮 ① 결정론 후보 계산

코드 · LLM 0콜 · 순수 함수
1
영양 신호 + 2축 네트워크 — 적신호·집 결핍군 + pair(궁합)·bridge(사촌)·식판 공통출현코드
2
selectScenario — 시나리오 13개 트리거 발동 → priority 정렬 → 최근 2일 제외코드
3
planFor + planFromWeekly — 타깃·무브 결정론 회전·시그니처 dedup·주간 닻으로 채근 캡코드
4
buildIngredientPool — 영양 거울로 추천 풀 5개(supply/challenge/mixed) → 일일 회전코드
5
comboMatrix — '잘 먹는 음식+결핍 식재료' 괴식 점수화(미역국+당근·떡+달걀 차단)가드

🧠 ② 두뇌 선택·검수 + ✍️ ③ 손 작문

두뇌=Sonnet(?brain 카나리아·일1콜) · 손=Haiku(편지당 3~7콜)
1
🧠 buildBrainContext — 후보 시나리오·최근 3주 닻·영양 거울·추천 후보를 두뇌에 제시Sonnet
2
🧠 pickActionByBrainscenarioId·useFood·approvedRecs만 반환(편지 안 씀·후보 밖 금지)Sonnet
3
🧮 recoIng 회전 — 풀에서 최근 추천 안 한 식재료부터 회전(콩 연속 반복 방지)코드
4
✍️ composeLetter — 영양 평가·어제 식단·음식 추천(cookedName=찐 단호박) 엮어 작문Haiku
5
✍️ 가드 3종 결정론 금지어 → 어휘 유사도 → 의미 검증자 → 재생성 → 퇴고·발행가드

왜 두뇌인가 — 결정론만으로는 "오늘 진짜 문제가 음식이 아니라 환경인데도 음식 추천을 우겨넣는" 판단 오류를 못 막습니다. 그래서 두뇌가 useFood로 음식 추천 본문을 통째로 켜거나 끄고, 환경이 핵심인 날엔 음식을 뺍니다. 두뇌는 고르고 검수만, 글은 손이 — 생성자(손)와 판단자(두뇌)를 분리해, 비싼 머리(Sonnet)는 하루 1콜·주 1콜만 쓰고 작문은 싼 손(Haiku)이 맡습니다. 상태기계·영양·사실·간식·추천·괴식 매트릭스·검증 하네스는 전부 결정론 순수 함수(LLM 0콜)라 테스트·리플레이가 쉽습니다. (과거 v3 블록 조립·하이브리드 Letter B 비교는 플래그 뒤 도먼트로만 잔존 — 전 자녀 라이브 경로는 위 2파이프.)

5

시퀀스 — 하루치 편지가 만들어지는 56단계

Sequence / Swimlane Diagram

새벽 코칭 크론이 한 번 깨어나 한 자녀의 편지·질문을 짓기까지. 레인(누가)·종류(무엇을)·갈래(공통/결정론/두뇌·손)로 색을 입혔습니다. 7개 국면으로 묶었습니다.

DB 읽기/쓰기 결정론 계산 LLM 콜 가드 분기 발행 · 행 배경: 결정론 두뇌·손
#레인행동종류
국면 Ⅰ · 깨어남 · 인증 (1–6)
1⏰ Vercel Cron매일 KST 02시 트리거 GET /api/cron/coach — 부모가 아침에 열기 전 미리 생성발행
2크론 라우트인증(CRON_SECRET) · QA 오버라이드 ?child ?force ?date 허용가드
3크론 라우트API 키 확인 · today 확정 — 편지는 '어제까지' 확정 데이터로만 평가가드
4🗄 DBcron_runs INSERT(running) — 실행 로그 시작DB쓰기
5상류 공급야간 미매핑 보강 — menus만 있고 식재료 빈 행 백필(영양계산 전)계산
6크론 라우트카테고리맵·또래 인기음식맵 정적 로드(비면 '채소 없음' 단정 금지)읽기
국면 Ⅱ · 자녀 선별 · 라운드로빈 (7–14)
7🗄 DB최근 7일 meal_logs SELECT(어제까지) → 자녀별 그룹읽기
8크론 라우트활성 자녀 = 최근 7일 중 기록일 ≥3일인 자녀분기
9–10🗄 DB최근 30일 기록으로 휴면 일수 계산 → 휴면(2~7일·이력≥3) 자녀 합류읽기
11–13크론 라우트최근 3일 편지로 중복회피 원장 구축(과거 편지만) → '오래된 순' 정렬(라운드로빈)계산
14🗄 DB자녀 메타·오늘 질문·등원·체위 일괄 SELECT(없으면 안전 degrade)읽기
국면 Ⅲ · [자녀 루프] 수집 · 사실 컴파일 (15–26)
15크론 라우트경과 > 50초(시간예산)면 중단 → 남은 자녀는 다음 실행이 이어받음(SIGKILL 회피)가드
16–19상태기계휴면 복귀 분기 — 평일 포함 갭이면 죄책감 없는 복귀 편지 후 continue(주말만 갭은 제외)분기
20크론 라우트끼니 집계 — 식재료/거부/메모/장소, 집·기관 분해(부모 통제 기준 칭찬)계산
21영양 계산기computeSignals/FoodGroups/Timeseries → 적신호·결핍군·시계열(집 끼니 별도)계산
22간식 엔진BMI 밴드 → evaluateSnacks 4축(초가공·간섭·칼로리방향·좋은간식)계산
23사실 원장compileFacts — 추세 라벨(단발/간헐/반복)·금지어(detForbid) 박제계산
24–25재노출 계산기최근 28일로 '거부→수용 전환' 감지 + 재노출 적기 사실(숫자는 코드, LLM은 인용만)계산
26크론 라우트식단 지문(srcHash) 계산 — 식재료·거부·적신호·메모수+today계산
국면 Ⅳ · 재사용 판정 · 주간 닻 (27–32)
27크론 라우트재사용? 직전 편지 지문 동일 또는 오늘 편지 있음 → LLM 스킵·내용·context 보존분기
28–29🗄 DB(생성) 과거 편지 5통·ICFQ 위험 누적·구조화 입력 분포(식감·자율성·환경·시간)읽기
30계획 수립기planFor — 결정론 CoachPlan(프레임·타깃·무브·시그니처)을 작문 전에 확정계산
31주간 사령부주간 닻 로드/종합 — 일요일=차주 닻 Sonnet 종합(QA date 시뮬엔 금지)Sonnet
32🗄 DBweekly_plans upsert(닻) — error 검사, 신규 컬럼 거부 시 레거시로라도 영속DB쓰기
국면 Ⅴ-ⓐ · 두뇌 경로 (33–41) — ?brain 카나리아
33두뇌 게이트?brain 게이트qp.get('brain')==='1'(카나리아)면 두뇌 선택 진입, 아니면 결정론(planFor) 그대로분기
34–35🗄 DB · 추천최근 3주 닻·과거 편지·영양 거울 + 추천 후보(buildIngredientPool 풀 5개) 로드읽기
36🧠 두뇌pickActionByBrain — 후보 시나리오 중 선택 + 음식 추천 검수(scenarioId·useFood·approvedRecs, 편지 안 씀)Sonnet
37🧮 결정론planFor(forceScenarioId) — 두뇌가 고른 시나리오로 잠그고 계획(타깃·무브)은 결정론 산출계산
38–39🧮 추천 회전recoIng 풀에서 최근 추천 안 한 식재료 회전 → useFoodbuildRecoFacts(찐 단호박)로 음식 추천, 아니면 본문 제외코드
40–41✍️ 손두뇌 선택·검수 결과를 composeLetter로 인계 → 손(Haiku) 작문(아래 Ⅴ-ⓑ와 동일 작문·가드)Haiku
국면 Ⅴ-ⓑ · v2 경로 (42–47) — 전 자녀 라이브
42주간 사령부planFromWeekly — 타깃 잠금·채근 캡·행동지연·아크 단계(관측된 실행만 칭찬)계산
43–44간식·추천간식 멘트 게이팅(쿨다운·과일타깃·구조 프레임 제외) + buildRecoFacts 화이트리스트계산
45코칭 본부composeLetter — 계획 주입 → Haiku 작문(intro는 Sonnet) · 데드라인 강등Haiku
46가드가드 3종 — 금지어 재생성 · 의미 검증자 · 어휘 유사도 재생성가드
47가드자동 반복 경보 — 직전 편지 최대 유사도·동일 시그니처 2연속 → issues 기록가드
국면 Ⅵ · 발행 · 질문 (48–52)
48크론 라우트발행 컨텍스트 단일화 — 두뇌 산출 ?? 재사용 보존 ?? 결정론 폴백(어드민 검증용 근거 스냅샷)계산
49🗄 DBcoach_letters UPSERT(child_id, letter_date=오늘)DB쓰기
50알림톡신규 편지면 2줄 미리보기(부모당 1건) — 현재 도먼트발행
51코칭 본부오늘의 질문 — ICFQ 주기 > generateQuestion(주제 로테이션·완식 수렴 방지)Haiku
52🗄 DBdaily_questions UPSERT(child_id, q_date=오늘) [자녀 루프 끝]DB쓰기
국면 Ⅶ · 롤업 · 마감 (53–56)
53–54영양·DB기간 요약 롤업 — 주·월 지표 재계산 → period_summaries 멱등 UPSERT계산
55🗄 DBcron_runs 마감 — 처리·오류·issues(반복경보·검증위반·폴백·닻 장애) ≤30건DB쓰기
56크론 라우트tip-ranking 크론 self-fetch 체이닝 → JSON 응답(Hobby 플랜 2크론 한도 우회)발행
6

상태기계 3종 — 엔진의 의사결정 뇌

UML Statechart · 유한상태기계

엔진은 "오늘 어떤 각도로 코칭할지"를 확률이 아니라 상태 전이·트리거로 결정합니다. ⓐ 시나리오 선택(현재 라이브·selectScenario+두뇌)과 ⓒ 한 주의 가르치는 결(주간 아크)은 라이브, ⓑ 유닛 생애주기(decideDailyV3)는 v3 도먼트입니다.

ⓐ 일간 전개 결정기 decideDailyV3 + advanceProgress

진입진도·goals 로드 관측(observe)저데이터·진도 동결 진도 진화재발 스캔·focus 진화 졸업 celebrate유지 주 통과 전진 advance사다리 +1 심화 deepen신호 있음·진전 중 유지 maintain정체·위안 피벗 pivot목표 교체(주1회) 관측 observe → 편지 조립기로 {unit, step, mode, pivotTo} + 진도 updates + goalsAfter mode → 블록 시퀀스 결정 기록<3일 기록 ≥3일 피벗 캡 소진

전이 조건(코드 그대로): 전진=합격 연속이 holdWeeks 충족(step+1) · 심화=최근 신호 있고 진전 중 · 피벗=정체(무신호 6일+ & 충분히 코칭) 또는 절뚝거림(약신호로 정체 판정엔 안 걸리나 사다리 0진전)이고 주당 피벗 캡(1회) 미소진 · 유지=최종단 후 유지 주, 또는 정체+피벗 불가 → 강등 · maintain 3연속이면 deepen으로 강제 재개(위안만 반복 차단). 우선순위 1순위는 재발 신규(mastered→relapsed) 유닛 재개.

ⓑ 유닛 생애주기 UnitStatus · evolveRow

not_started active progressing maintenance mastered ✓ pivoted relapsed 활성화 신호 최종단 충족 유지 주 통과 정체·절뚝 재발(2주) 붕괴→재코칭

ⓒ 가르치는 아크 5단계 WeeklyArcStage

intro 도입·진단주 1회·Sonnet 승격 how 방법 obstacle 걸림돌 observe 관찰 reinforce 강화관측된 실행만 콕 집어 칭찬 요일 회전 ROT — 진단 재서술 금지 · reinforce 이틀 연속 금지 행동 관측 시 연속 금지→환기
7

스크래치 블록 — 코드를 모르는 분도 읽는 엔진 로직

Block-based(Scratch) Pseudocode

교육용 블록 언어(Scratch)처럼, 엔진의 두 핵심 로직을 색깔 블록으로 풀었습니다. 주황=제어(만약~), 파랑=동작, 초록=판정, 보라=나만의 블록(모듈), 노랑=시작(언제), 빨강=저장.

🅰 새벽 크론의 하루 (스크립트)
🌅 매일 새벽 KST 02시가 되면
최근 7일 끼니를 모은다
기록일이 3일 이상인 아이들에 대해 (오래된 순으로) 반복
만약 50초 예산 초과 라면
⏸ 멈추고 남은 아이는 다음 새벽에
영양 신호·간식·사실 카드를 계산한다
📋 사실 원장을 컴파일한다 (추세 라벨·금지어)
만약 식단 지문이 어제와 같다 라면
어제 편지를 그대로 재사용(LLM 안 부름)
아니고 만약 ?brain 카나리아 아이 라면
🧠 후보 시나리오·추천 중 두뇌가 고른다 (Sonnet)
🔎 음식 추천을 검수한다 (useFood — 환경 날엔 끔)
아니라면 (결정론)
🧭 시나리오·계획을 확정한다 (코드)
✍️ 손(Haiku)이 영양 평가·어제 식단·음식 추천을 엮어 쓴다 → 가드 3종 통과까지 다시 쓴다
💾 편지·질문을 저장한다 (오늘 날짜)
기간 요약을 굴리고 실행 로그를 마감한다
🅱 "오늘 무엇을 가르칠까" 결정 블록 (decideDailyV3 · v3 도먼트 — 라이브 미가동)
🎯 오늘의 전개를 결정한다 (진도·답변 받기)
만약 기록 3일 미만 라면
🧊 진도 동결 · "관측"으로 쉬어간다
재발한 유닛·focus 유닛을 진화시킨다
만약 졸업했다 → 🎉 축하
아니고 사다리 한 칸 올랐다 → 👣 전진
아니고 신호 있고 진전 중 → 🔎 심화
아니고 정체·절뚝 & 피벗 가능 → 🔀 목표 피벗
아니라면 → 🌙 유지/관측
📤 {유닛·단계·모드}를 돌려준다

★ "만약~아니고~" 사다리가 곧 F-02 결정표입니다. 가장 급한 것(재발)부터 가장 한가한 것(관측)까지 우선순위가 고정돼 있어, 같은 데이터면 누가 돌려도 같은 결정이 나옵니다(결정론).

8

데이터 흐름도 — 한 끼니가 편지가 되기까지

Data Flow Diagram (DFD)

모든 것의 원천은 부모가 찍은 끼니 기록 하나. 그것이 여러 처리기를 거쳐 편지·질문·진도·요약 네 개의 저장소로 흩어집니다. 사각=데이터 저장소, 둥근=처리기.

🍽 meal_logs끼니 기록(원천)
▼   상류 매핑(메뉴→식재료 정규화)
영양 신호 계산기신호등·식품군·시계열
사실 원장 컴파일러추세 라벨·금지어·거울
진도 상태기계12 유닛 전이
간식·추천·재노출4축·사촌·적기
▼   계획 확정(planFor / decideDailyV3) + 주간 닻
v2 작문기Haiku + 가드 3종
✍️ 손 작문composeLetter · 가드 3종
▼   발행(upsert)
💌 coach_letters편지(+상태 스냅샷)
❓ daily_questions오늘의 질문
📈 curriculum_progress12 유닛 진도
📊 period_summaries주·월 롤업
▲   환류(피드백 루프)
👍 letter_feedback부모 1탭 평가
질문 답변 → 진도 신호 · 편지 평가 → 자가진단 · 진도/닻 → 다음 날 결정. 출력이 다음 입력이 되는 닫힌 루프.

방향성 — 진단·사실은 항상 왼쪽(데이터)→오른쪽(편지)으로만 흐릅니다. 편지(LLM 산출)가 사실을 거꾸로 만들지 못하게, 모든 숫자·라벨은 처리기(코드)가 확정하고 작문기는 인용만 합니다. 이 단방향성이 환각 차단의 뼈대입니다.

9

데이터 모델 — 12개 테이블, children 허브

Entity-Relationship Diagram

children가 허브이고 모든 코칭 테이블이 child_id로 매답니다. 부모는 coach_letters·daily_questions만 읽고, 엔진 내부 상태(weekly_plans·curriculum_progress)는 RLS로 비노출(서버만 접근)입니다.

👶 childrenPK id
자녀 마스터 · 모든 child_id의 허브이자 코칭 입력 프로필
  • nickname·age_band·sex 별명·연령대·성별
  • chronic_conditions 만성질환→식이 가이드
  • height/weight·daycare 체위·등원
  • excluded_ingredients 집에있어요 제외
🍽 meal_logschild+date+slot
끼니 기록(상류 입력) · 엔진 모든 사실·진도·영양의 원천
  • ingredients 정규화 식재료(핵심)
  • refused·ate_well 거부·잘먹음
  • texture·autonomy·environment 씹기·자율·환경 메타
  • place·meal_time 집/기관·끼니시각
💌 coach_letterschild+date
편지 원장(최종 산출물) · context에 엔진 내부 상태 통째 박힘
  • letter·oneliner 본문·한줄
  • source_hash 식단 지문(재사용 키)
  • context jsonb: plan·decision·blocks·weekly·verify
📅 weekly_planschild+week
주간 닻(작전층) · 일요일 Sonnet 종합 · 부모 비노출
  • mission_target·target_pool 초점 타깃(주 불변)
  • budget·ledger 채근 캡·소비 추적
  • goals 주간 목표 포트폴리오 2~3
  • teaching_arc·impression 아크·의사 소견
📈 curriculum_progresschild+unit
수업 진도 영속(주간 goals 후보·도먼트) · 부모 비노출 · 자녀당 ≤12행
  • unit_id 12종 CHECK
  • status·step 상태기계·사다리 단
  • evidence 신호 카운터(jsonb)
  • last_signal_at·relapse_count 정체·재발
❓ daily_questionschild+date
오늘의 질문 · 답변이 다음 날 진도 신호로 환류
  • topic icfq·unit-probe·로테이션
  • chips·answer 객관식·부모 답
  • context unitProbe 적립 키
📊 period_summarieschild+type+key
병원 차트형 기간 요약(주·월) · 어드민 시계열
  • metrics variety·refusalPct·reds·bmiPct…
👍 letter_feedbackchild+date uniq
편지 1탭 피드백(자가발전) · 반복 자동탐지 입력
  • rating up · down · repeat
📏 growth_logschild+date uniq
체위 시계열 · BMI-for-age 백분위 입력
  • height_cm·weight_kg 성장·간식 칼로리 방향
🩺 cron_runsPK id
크론 실행 로그 · 카나리아 모니터(어드민)의 근거
  • status·meta issues·alerts·reused·skipped
🔗 children 1:N meal_logs · coach_letters · daily_questions · weekly_plans · curriculum_progress · growth_logs · period_summaries · letter_feedback
🔗 weekly_plans → curriculum_progress (논리적) — 주간 goals[].unit_id 포트폴리오가 진도 유닛을 가리킴(jsonb 참조)
🔗 weekly_plans 1:N coach_letters (논리적 닻) — 주간 plan 1행을 그 주 일간 편지들이 read해 phase 결정·context.weekly로 기록
🔗 coach_letters 1:1 letter_feedback (논리적) — 같은 child_id+date로 그날 편지를 평가
10

수업 커리큘럼 — 주간 작전층이 고르는 12 유닛

Domain Model · Capability Map

유닛 하나가 "편식 해결의 한 과목"입니다. 각 유닛은 레버(어떤 지렛대)·최소 주차·2단 사다리(과제)·졸업 기준·관측 질문을 코드로 선언합니다(선언이 곧 명세). 색=레버.

🍎 food 음식🪑 environment 환경🙌 autonomy 자율성👅 texture 질감🔀 mixed 복합
압박 내려놓기
pressure-off · W1
mixed
① "한 입만"·재촉 멈추기
② 압박 없는 식탁 분위기
🎓 압박 메모 0일·부정 태그 ≤20%
공복 리듬
hunger-rhythm · W1
environment
① 끼니 30분 전 간식 멈추기
② 식사·간식 2~3시간 간격
🎓 끼니 전 간식 0·간식 과다일 ≤2
식탁 무대
table-stage · W1
environment
① 하루 한 끼 화면 끄고 식탁
② 주 5끼+ 같은 자리·시간
🎓 식탁 식사 비율 ≥60%
새 음식 조금씩 노출
exposure-savings · W2
food
① 도전 음식 말없이 식탁에(격일)
② 티스푼 맛보기 초대
🎓 누적 노출 8회+ (적금)
배부름 존중
fullness-respect · W2
mixed
① 완식 강요 멈추기
② 30분쯤 부담 없이 정리
🎓 강요 0·30분 초과율 ≤30%
부모가 메뉴판
parent-model · W2
mixed
① 같은 음식 곁에서 맛있게
② 가족 저녁 주 5회
🎓 가족 저녁 5일+/주
달콤한 협상 끊기
no-bargain · W2
mixed
① "먹으면 ~줄게" 멈추기
② 디저트 지위 중립화
🎓 거래 메모 0일
식탁의 말
table-talk · W3
mixed
① 금지어 3종 끊기
② 객체 중심 질문으로
🎓 객체 대화 비율 ≥70%
감각·질감 트랙
sensory-texture · W3
texture
① 한 끼 한 단계 위 질감
② 일반식 비중 올리기
🎓 핑거푸드·일반식 안착
확장 트랙(음식 다리)
food-bridge · W3
food
① 잘 먹는 음식의 사촌 1종
② 사슬 다음 칸(찍어먹기)
🎓 주당 신규 1종+ 안착
자율성·참여 트랙
autonomy-part · W3
autonomy
① 둘 중 고르기·스스로 떠먹기
② 셀프 서빙·상차림 역할
🎓 자기주도 비율 ≥50%
연계·리듬 트랙
link-rhythm · W3
food
① 기관 거부 식재료 집에서 재노출
② 격일 리듬으로 이어가기
🎓 기관→집 재노출 주 2회+

코어 8 → 확장 4pressure-off → hunger-rhythm → table-stage → exposure-savings → fullness-respect → parent-model → no-bargain → table-talk가 기초 순서(W1~W3 가능), 나머지 4종(감각·다리·자율성·연계)은 신호 발동 시 선택됩니다. 주간 사령부가 신호 강도(trigger 점수)로 후보를 올리고, 포트폴리오 2~3개로 묶어 한 주의 goals를 정합니다. 이 12 유닛은 학부모용 실라버스(coaching-syllabus.html)와 1:1로 정합 — 엔진이 실제로 가르치는 것만 적었습니다.

11

작문 = LLM(손) + 가드 — 블록 조립은 폐기

Generate-then-Verify

현재 라이브 작문은 ✍️ 손(composeLetter, Haiku)이 LLM으로 직접 쓰고, 결정론 금지어 → 어휘 유사도 → 의미 검증자(별도 LLM 1콜) 3종 가드로 사후 검열·재생성합니다(§4·§11 방어 가드). "글맛은 LLM, 안전은 코드 가드" 원칙입니다.

폐기된 접근 — 한때 "글을 쓰지 않고 검수된 블록을 집합 차감으로 조립(LLM 0콜)"하는 v3 경로를 실험했습니다(반복을 구조적으로 차단하려는 의도). 하지만 조립식은 따뜻함·다양성·구체성을 잃어(아린 A/B 실증에서 LLM 작문에 완패) 폐기했습니다. 관련 코드(assembleLetter·letterBlocks·decideDailyV3)는 카나리아 플래그 뒤 도먼트로만 남아 있고 전 자녀 라이브에선 가동되지 않습니다. 다양성은 조립이 아니라 두뇌의 시나리오 회전 + 추천 풀 일일 회전 + 유사도 가드로 확보합니다.

12

의존 그래프 — 누가 누구를 부르나

Dependency Graph · Call Hierarchy

새벽 코칭 크론이 모든 것의 지휘자입니다. 아래로 갈수록 순수해집니다 — 잎(leaf) 모듈(영양·그래프·날짜)은 DB·LLM·시계를 모르는 순수 함수라 테스트·리플레이가 쉽습니다.

⏰ 새벽 코칭 크론 (지휘자)
coachWeekly · coachDaily · assembleLetter · curriculum · coach · coachFacts · coachRecos · nutrition · snack · progress · reexposure · growth-reference · remapMenus · sens + DB 9개 테이블
🧭 코칭 본부 (coach.ts)
coachScenarios · coachFacts · coachRecos · coachChronic · snack · coach-tips · Claude SDK. v2 작문·가드의 중심.
🧠 두뇌 (coachBrain) (?brain 카나리아)
coachScenarios · coachRecos · coachFacts · Claude(Sonnet). 결정론 후보를 받아 시나리오 선택 + 음식 추천 검수(useFood)만 — 편지는 안 씀.
🎯 일간 결정기 (coachDaily)
curriculumUnits · curriculum · coachWeekly · coach(icfq). 순수 함수.
🗓 주간 사령부 (coachWeekly)
curriculumUnits · curriculum · coachScenarios · Claude(Sonnet). 닻·레버·아크.
🍃 잎 모듈 (순수·무의존)
nutrition · foodGraph · cookingMatrix · season · growth-reference · progress · reexposure · date · curriculumUnits — DB·LLM·시계 0. 리플레이·테스트의 토대.
🔬 검증 하네스 (replayRunner)
coachDaily · assembleLetter · curriculum · curriculumUnits만 호출(실 LLM·DB 0) → replayMetrics 게이트.

의존 방향이 단방향(상위→하위)이고 잎이 순수해서, 전체 파이프라인(결정론 후보 → 두뇌 선택·검수 → 손 작문)을 실제 DB·LLM 없이 인메모리로 14일 돌려볼 수 있습니다. "라이브에 내보내기 전에 미래를 시뮬레이션한다"가 가능한 이유.

13

방어 가드 38종 — 실패에서 자란 면역계

Defense-in-Depth · Incident → Guard 매핑

가드 하나하나가 실제 사고의 흉터입니다. 5겹 방어로 묶었습니다 — 결정론(정규식) → 의미(LLM 검증자) → 구조(집합 차감) → 운영(시간·영속) → 안전(아동 보호). 프롬프트로 호소해선 못 막은 것을 코드 가드 + 리플레이로 잡았습니다.

🔵 결정론 가드 정규식·재생성

거부값 통째 드롭
부모 메모('배아파서 조금만먹음 카레')를 음식명으로 오인 추출해 거짓 '거부→수용 전환' 날조하는 것 차단. NOISE_RE 매치 시 통째 null.
사고: 카레 유령
결정론 안전·괴식 가드
환각 시점·없는 증상·섞기 금지 시나리오의 섞기·김치 괴식·튀김 권유·'N가지' 칭찬·해조류↔생선 혼동을 정규식 묶음으로 잡아 최대 2회 재생성.
사고: 치즈·김치 괴식
점심 결식 단정 금지
점심이 급식으로 기록돼 있는데 '점심을 거른다' 단정하는 것 차단. 점심 커버리지 사실 1순위 주입 + 단정형 정규식 동적 금지.
사고: 뷔페 과일반화
단발 이벤트 메아리 차단
최근 2일 메모에 없는 stale 이벤트 단어('뷔페/외식')를 forbidParts에 넣어 동적 금지(detForbid).
사고: 뷔페 과일반화
편지 유사도 재생성
최근 편지와 너무 비슷하거나(전체 0.45) 도입 첫 25자 동일(0.40)이면 무브 밀어 재생성. '같은 도입+다른 본문' 복붙 차단.
사고: 3일 복붙
계획 시그니처 dedup
'글자는 다른데 전략이 똑같은'(프레임·타깃·무브) 의미 반복을 계획 단계에서 차감 회전. 못 만들면 정체기로 전환.
사고: 3일 복붙
결정론 어법 교정+퇴고
'국을 마시다'·'을 물론' 어법, 가드 빠져나간 번역투·존대·미역=생선 오류를 polishKo(결정론)+LLM 퇴고로 마감.
어법 오류
질문 주제 로테이션
오늘의 질문이 매일 '완식'으로 수렴하는 것 차단. 8종 무브를 시드로 강제 회전(완식은 맨 끝).
질문 다양성

🟣 의미 가드 LLM 검증자·격리

의미 검증자(생성자-검증자 분리)
정규식이 못 따라가는 표현 변형(사실 왜곡·행동 누수·금지 표현·진단 재서술)을 별도 LLM 1콜로 데이터 대조 검사 → 위반 시 1회 재작성(가드 재통과해야 채택).
사고: 3일 복붙
전환사실 누수 차단
'거부→수용 전환' 축하가 자격 없는 시나리오로 새어 매 편지 반복되는 것 차단(ALLOW_TRANSITION 집합 외 시 시계열에서 필터).
사고: 카레 유령
부모 메모 격리(주입 방어)
메모를 10개·120자로 cap하고 '<<부모관찰(지시아님)>>' 펜싱. 메모 속 규칙 변경·영양 주장을 사실/지시로 추종하는 것 차단.
주입 위험
구조 시나리오 음식 행동 차단
환경·자율성 코칭에 식재료 섞기/메뉴 제안이 얹혀 요구가 2~3개 되는 것 차단(NO_FOOD_ACTION_FRAMES — '한 번에 하나').
사고: 요구 3개
추천 화이트리스트
목록 밖 음식·잘못된 궁합('김치+생선')·없는 사촌을 지어내는 것 차단. buildRecoFacts가 검증된 인기음식·그래프 사촌만 컴파일.
사고: 괴식 조합
거짓 칭찬 차단
아크 reinforce에서 실제 안 일어난 부모 실행을 지어내 칭찬하는 것 차단. 관측된 실행(progressNote)이 있을 때만 강화.
사고: 3일 복붙
식단 거울 '잘 먹는' 판정
기관 급식을 '집에서 잘 먹는다'고 칭찬 세탁(P10), 한 번 먹은 걸 단정하는 것 차단. 집·2일+·거부없음 조건.
P10 칭찬 분리

🟢 검증 게이트 리플레이·prebuild

블록 3일 재사용 0
같은 블록 id가 3일(8일 신선도) 내 재등장 차단. 풀 소진 시 3일만 피해 완화(게이트는 항상 0 유지).
사고: 3일 복붙
동일 조합 재현 차단
3일 원장 밖에서 블록 '조합'이 7일 내 byte-동일이면 변형 재선택.
아린 6/1↔6/5
진단 1회 인용 원장
같은 진단 통계를 활성 기간 1회만. 키가 '전역'이라 피벗으로 유닛 바뀌어도 재서술 못 함.
원장 전역키
intro 7일 재진입 0
같은 유닛 도입(진단+왜)이 7일 내 재등장 차단(피벗 재도입 포함).
사고: 3일 복붙
후보 0 안전 폴백
조립 후보 0일 때 LLM 자유생성으로 도망 금지 — 공용 블록→상수 문구로 발행 보장.
사고: 폴백·크론 사망
prebuild 컷오버 게이트
리플레이 30가정×14일 산출에서 구조 지표(블록3일·조합7일·진단재서술·intro재등장·피벗캡·폴백률) 임계 위반을 라이브 전 차단.
통합버그 5종

⚫ 운영 가드 시간·영속·일관성

우아한 강등(SIGKILL 좀비 방지)
재생성·검증이 60초를 넘기면 현재 본문 발행(발행>완벽). 크론 50초 예산 가드가 남은 자녀 이월.
사고: 크론 사망
닻 영속 검사
커리큘럼 컬럼 미적용으로 weekly_plans 행이 조용히 거부돼 매일 Sonnet 재종합·ledger 사망하는 것 차단. upsert error 검사+레거시 재시도.
크론 사망·원장 전역키
QA date 시뮬 라이브 보호
과거 날짜 재실행(?date)이 라이브 주 닻·ledger를 파괴하거나 차주 닻 오종합하는 것 차단(일요일 종합 금지).
원장 전역키
재사용 시 context 전체 보존
편지 재사용 시 weekly를 null로 덮어 다음 날 firstOfWeek 오판→같은 주 intro 중복하는 것 차단.
사고: 3일 복붙
오늘 데이터 동결 차단
당일 입력이 발행 편지를 바꾸거나, 식단 같다고 어제로 동결되는 것 차단. 어제까지 평가 + srcHash에 today.
편지 동결
야간 자가진단
'편지 겹친다'를 사람이 매번 제보하던 한계 탈피 — 최근 10일 편지의 유사도(0.45)·거울 누락(80%)·1탭 피드백을 매일 LLM 0콜로 자동 탐지.
6/5 제보
주말 갭 ≠ 휴면
평일 기록만 가정한 휴면 회유가 주말 공백에 일·월마다 오발송되는 것 차단(gapHasWeekday).
주말 오발송

🔴 안전 가드 아동 보호

간식 질식 안전 표시 보존
작고 단단한 간식(방울토마토·포도·생당근·견과)을 통째로 권하는 질식 위험 차단. '(반으로 잘라)·(익혀)·(잘게)' 조리 표시 보존.
질식 적대검수
간식 체중 단어 금지
유아 체중 민감 — '체중·살·다이어트·BMI' 노출 차단. stance를 방향(encourage/maintain/reduce)으로만.
간식 적대검수
과일↔끼니 분리
과일을 밥·반찬에 곁들이는 부자연 차단 — 과일은 간식 채널로 따로(fruitMealPairing 정규식).
과일 페어링
휴면 복귀 죄책감 차단
복귀 편지가 '왜 안 하세요·아이 굶어요' 류 압박 주는 것 차단. 과거 잘한 점+'한 끼만 다시' 초대.
리텐션 설계
시급 예외 3종만 진단 재인용 허용
ICFQ 적신호·질식 메모·3일+ 전량거부일 때만 원장 우회 + 전문가 안내. 그 외 진단 재서술 금지.
ARFID 안전
집 끼니 기준 평가
기관 급식 덕을 부모 칭찬으로 돌리지 않게, 적신호·칭찬을 집(place≠daycare) 기준으로 별도 계산.
P10 칭찬 분리

⏱ 사고 연대기 — 가드가 자란 자리

엔진의 강건함은 설계가 아니라 사고 → 진단 → 가드 → 리플레이 박제의 반복에서 왔습니다.

카레 유령
부모 거부 메모를 음식으로 오인 → 거짓 '전환' 날조
자유텍스트를 엔진 신호로 쓸 때 토큰 추출이 거짓 세탁. 해법: cleanRefusal 통째 드롭 + 전환사실 시나리오 게이팅.
뷔페·점심 거름
주말 하루 메모를 '리듬·패턴'으로 LLM이 과일반화
프롬프트 호소로는 못 막음(실증). 해법: 사실 카드에 추세 라벨(단발/간헐/반복) + 점심 커버리지 1순위 + detForbid 동적 금지.
3일 복붙 편지
도입·일화·마감이 3일 내내 겹침(사람 제보)
전체 유사도만 봐서 '같은 도입+다른 본문' 못 잡음. 해법: 도입부 전용 유사도(0.40) + 시그니처 dedup + 의미 검증자 + 추천 풀 일일 회전 + 야간 자가진단(제보 탈피).
크론 사망(SIGKILL)
재생성 콜이 60초 초과 → 좀비 → 무편지·폴백 연쇄
해법: composeLetter 데드라인 강등 + 크론 50초 예산 가드(남은 자녀 이월) + cron_runs 정상 마감.
원장 전역키 / 닻 미저장
유닛 스코프 키라 피벗하면 같은 사실 재서술 · 컬럼 미적용으로 닻 조용히 거부→매일 재종합
리플레이가 적발. 해법: 사실 인용 키 '전역'화 + 주간 리셋 + upsert error 검사·레거시 재시도 + QA date 일요일 종합 금지.
14

지표 · 모델 사용 지도

Software Metrics · Cost Map

모듈별 규모와 LLM 모델 사용처. 결정론 토대(LLM 0콜) 위에서 손(Haiku)·두뇌·주간 닻(Sonnet)만 모델을 씁니다.

모듈 규모(LOC) — 상위

새벽 코칭 크론
826
코칭 본부 coach.ts
818
영양 신호 계산기
561
상류 데이터 공급(5파일)
524
12 수업 유닛 정의
417
편지 조립기(도먼트)
401
주간 작전 사령부
376
사실 원장 컴파일러
300
간식 평가 엔진
252
일간 전개 결정기(도먼트)
232
진도 상태기계(도먼트)
221
테스트 스위트(7파일)
1,763 / 220개

LLM 모델 사용 지도 — "비싼 머리는 두 곳만"

⚡ Claude Haiku 4.5
손 — 기본 작문
✍️ 손(composeLetter) 편지 작문 · LLM 퇴고 · 의미 검증자 · 온보딩·휴면 편지 · 오늘의 질문 · 궁합 LLM 폴백. 편지당 3~7콜.
🧠 Claude Sonnet 4.6
머리 — 닻·두뇌·승격
주간 닻 종합 — 일요일 의사식 회진(자녀당 ~₩630/월). ② 두뇌 시나리오 선택·추천 검수(?brain 카나리아, 일 1콜). ③ 주 첫 진단(intro) 편지 승격(주 1회).
🟢 LLM 없음
결정론 토대
시나리오·계획·영양/사실/간식/추천 풀·괴식 매트릭스·2축 네트워크 계산 · 리플레이 하네스 · 야간 자가진단 — 전부 결정론 순수 함수(LLM 0콜)라 테스트·리플레이가 쉬움.
198·549
궁합 그래프
노드·엣지(궁합 362·사촌 187)
51
영양 보석 큐레이션
must-eat.json
32×183
키트 음식×식재료
정성 채점 매트릭스
13
v2 시나리오
트리거·무브·금기
15

범위 · 한계 · 현재 상태

Honest Status · 정직 진단

이 문서는 2026-06-14 코드 기준 실재하는 것만 그렸습니다. 추측·로드맵은 배제했습니다.

✅ 지금 라이브 (전 자녀)

  • 토대(2축 음식 네트워크·영양 신호) → 결정론 후보(planFor 시나리오·추천 풀 5개) → ✍️ 손(composeLetter Haiku) 작문 + 가드 3종이 모든 활성 자녀의 매일 편지·질문 생성
  • 주간 작전층(Sonnet 닻·레버·아크·채근 캡)·사실 원장·간식 4축·추천 화이트리스트·괴식 매트릭스·휴면 회유 가동
  • 온보딩(<3일) — 입력 칭찬 + 가벼운 영양 평가 + 안내 + 팁(결핍 단정·추천 없음)으로 신규 가입 침묵 갭 메움
  • 음식 추천 풀 일일 회전(콩 연속 반복 방지) + 조리법 접두(cookedName=찐 단호박)로 즉시 실행 가능한 추천
  • 야간 자가진단 크론(반복·중복·거울 누락 모니터) + 검증 하네스(괴식 0·수렴 0·품질 0) prebuild 게이트, 835 테스트

🔬 카나리아 / 대기

  • 🧠 두뇌(?brain=1) — 결정론이 올린 시나리오·추천 후보를 Sonnet이 고르고 검수(scenarioId·useFood·approvedRecs). 아린 카나리아 가동, 실패 시 결정론 폴백
  • 두뇌는 편지를 쓰지 않습니다 — 선택·검수만 결정론 산출에 얹고, 작문은 항상 손(Haiku, 생성자-검증자 분리)
  • 모니터 통과 시 전 자녀 확대(env). 두뇌 OFF여도 결정론 경로로 동일하게 발행
  • 폐기: v3 블록 조립(assembleLetter·12 수업·decideDailyV3)·하이브리드 Letter B 비교 — 플래그 뒤 도먼트로만 잔존, 라이브 미가동

관련 정본 문서 — 무엇을·언제(시나리오·간식) · 어떻게 정하고 만드나(plan 엔진) · 일·주·월 케이던스(주간 케이던스) · 학부모용(실라버스) · 피치용(엔진 다이어그램). 이 문서는 그 모두를 코드 단위로 합친 기술 해부도입니다.

16

음식 추천 2축 네트워크 — "실제 식단으로 판단한다"

Bipartite Evidence Graph · 동시출현 추론

추천의 질문은 하나입니다: "이 음식·식재료가 정말 같이 먹는 조합인가?" 엔진은 이걸 의견이 아니라 실제 식단 데이터의 공통출현으로 판정합니다. 단일 카운트는 흔한 식재료(밥·달걀)를 우연히 '강함'으로 둔갑시키므로, 우리는 서로 다른 두 데이터에서 각자 lift(우연 보정)로 등급화하고, 둘 다 받쳐줄 때만 추천에 씁니다. 그래서 떡+달걀처럼 LLM은 "괜찮다" 했지만 두 축 모두 약신호인 조합이 자동으로 차단됩니다.

451
pair 궁합 엣지
레시피 362 + 식판 89
175
bridge 사촌 엣지
수기 고신뢰 verified
192
노드(식재료)
도감과 1:1
1,612식판
NEIS 전국초등
keyless · 같은 끼니 단위
35 / 89
식판 승급 / 신규
upgrade-only · 과일 교차채널 116 차단
0.72
떡+달걀 lift
우연 이하 → 차단
📖 한 줄 요지 — 추천 후보는 ① 같은 레시피 한 그릇 안에 함께 들어가는가(B_레시피DB ~4,700), ② 같은 식판(한 끼)에 함께 차려지는가(NEIS 1,612식판), 두 증거축으로 검증합니다. 어느 축이든 lift로 우연을 빼고 등급(strong/medium/weak)을 매긴 뒤, 추천 경로는 strong만 통과시킵니다. 미수록 조합은 '통과'가 아니라 금지(보수적 기본값)입니다.

두 증거축 — 무엇이 다른가

축 ① 레시피 동시출현 (한 음식 안)

B_레시피DB ~4,700 · gen-food-graph.py
1
메인 식재료 추출 — 양념 블로클리스트 제외 + 양 8g↑·15%↑ 게이트로 그릇의 '주연'만코드
2
동시출현 카운트 c — 같은 레시피에 함께 쓰인 횟수(MIN_CO≥4, 노드별 top-14)코드
3
lift = c·N / (na·nb) — base-rate 보정(1.0=우연, >1=실제 연관)코드
4
등급 — strong(s≥2 & lift≥1.2) · medium(≥1.0) · weak(나머지)등급
5
잡는 신호 = 요리 안에서 같이 조리되는 궁합(된장+두부, 미역+소고기)

축 ② NEIS 식판 공통출현 (같은 끼니)

전국초등 1,612식판 · gen-neis-tray-cooccur.py
1
식판=한 끼(날짜+학교) 메뉴 리스트 → 도감 식재료로 분해(향신료 제외·최장일치, 환각 0)코드
2
식판별 식재료 집합 → 같은 식판에 오른 쌍 카운트 c코드
3
lift = c·N / (na·nb) — 식판 총수 N으로 우연 보정코드
4
등급 — strong(lift≥1.2 & c≥15) · medium(≥1.0 & c≥8) · weak. 저빈도 우연 폭주 차단(고등어\|살구 c6 lift14 제거)등급
5
잡는 신호 = 같은 끼니에 차려지는 조합(메뉴명에 안 적힌 잡채 속 당근까지)

★ 두 축은 다른 것을 본다 — 레시피축은 '한 그릇 안에 섞이는가', 식판축은 '같은 상에 같이 오르는가'. 둘 다 lift로 우연을 빼지만, 식판축은 메뉴명에 안 적히는 숨은 채소(잡채·볶음 속 당근)와 '같이 차려짐'이라는 부모 직관을 보강합니다.

세 번째 관계, bridge(사촌). 대체 사촌(명태↔대구, 고구마↔단호박)은 같은 레시피·식판에 거의 함께 안 나오므로 동시출현으로는 안 잡힙니다. 그래서 맛·식감·색이 닮은 고신뢰 수기 시드 175쌍(전부 verified:true·동일 식품군 근접)으로 따로 둡니다. 푸드체이닝 'chain' 추천(잘 먹는 것 → 닮은 새 음식)은 verifiedCousinsOf로 이것만 씁니다.

병합 규칙 — upgrade-only(신뢰는 올리기만)

식판축을 레시피축 그래프에 합칠 때 강등은 금지합니다. 메뉴명에 안 적힌 채소 누락이 '안 어울림'을 뜻하진 않기 때문 — 빠진 근거는 약점 증거가 아닙니다. 그래서 식판 strong 쌍은 ① 기존 엣지면 등급을 승급만(37쌍), ② 그래프에 없으면 새 pair 엣지로 추가(106쌍, src:'tray'·basis="같은 끼니 식단 N회").

식판 등급그래프에 이미 있나행동결과
strong있음 · 더 낮은 등급GRANK[tray] > GRANK[기존]grade=tray (strength<2면 2로)승급 35
strong있음 · 같거나 높은 등급e.tray만 기록(메타) · 등급 유지보존
strong없음새 pair 엣지 추가(strength 3·src:'tray')신규 89
strong · 과일 한쪽무관교차채널 차단 — 과일은 간식채널(끼니 곁들임 아님)이라 같은 끼니 디저트 동시출현을 궁합으로 안 봄차단 116
medium / weak무관병합하지 않음(strong만 승격·신규)차단
강등 경로 자체가 없음(누락=약점 증거 아님)금지

추천 게이트 — 조합이 통과하는 경로 comboMatrix.scoreCombo

최종적으로 '잘 먹는 음식 + 결핍 식재료' 조합을 띄울 때, 4단 폴백을 위에서부터 따집니다. 핵심은 borderline LLM 점수(2)는 실제 레시피 동시출현(cells)이 ≥8일 때만 인정한다는 게이트 — 떡+달걀(score 2·cells 4)을 여기서 한 번 더 거릅니다.

순위source판정 규칙예시
1matrixLLM 정성채점 0~3. 단 score 2 & cells < 8 → 1로 강등(차단), score 3·실증된 2는 그대로볶음밥+당근=3 통과 · 미역국+당근=1 차단 · 떡+달걀=2·cells4→1 차단
2pairstrongPairsOf — grade='strong'만(약신호 s=1 일괄 차단). 강도를 0~3 클램프된장+두부 등 강한 궁합
3cells레시피 동시출현 count>0이지만 임계 미만 = 약신호 → score 1(보수적 금지)드물게만 같이 쓰인 조합
4none어디에도 없음 → score 0. 미수록은 '통과'가 아니라 '금지'근거 없는 임의 조합

통과 임계는 기본 2(isComboOk) — score 1(미역국+당근·떡+달걀)은 막고 2·3만 통과. 약신호 차단 임계 PAIR_MIN_STRENGTH=2는 그래프(strongPairsOf)와 매트릭스(CELLS_MIN=8) 양쪽에서 통일됩니다 — 모든 소비자(coachRecos·coachMaterials·comboMatrix·comboGuard)가 같은 헬퍼로 일원화.

사례 추적 — 왜 '떡+달걀'이 차단되나(두 축 모두 약신호)

축 ① 레시피cells 4 · lift 0.72
축 ② 식판strong 미달(c·lift 부족)
LLM 정성채점score 2(borderline)
▼ scoreCombo
cells 4 < CELLS_MIN 8borderline 2 → 1 강등
lift 0.72 < 1.0우연 이하 → strong 아님
▼ isComboOk(임계 2)
최종 score 1 < 2 → 추천에서 제외두 증거축 어느 쪽도 받쳐주지 않음

★ 핵심 — LLM 한 사람의 "괜찮을 것 같다"는 판단(score 2)이 아니라 실제로 같이 먹는 데이터가 최종 결정권을 갖습니다. 떡+달걀은 레시피에서도(lift 0.72) 식판에서도(strong 미달) 약신호라, 두 축의 교차 검증으로 자동 탈락합니다.

✍️ 작문 단계 곁들임 가드 Write-time Guard

검증 안 된 조합은 본문에서 섞지 않는다
serializeMaterials의 곁들임 규칙 줄 — 검증된(strong·게이트 통과) 조합이 없으면 작문 단계에서 식재료를 임의로 섞지 못하게 막습니다. 그래프가 통과시킨 조합만 글에 등장.
2축 검증 ↔ 작문 일관성

설계 한 줄 — 추천은 "이 음식 좋아요"라는 의견이 아니라, 두 데이터(레시피·식판)에서 우연을 뺀 뒤에도 함께 등장하는가라는 증거 질문입니다. 등급화·upgrade-only 병합·교차 게이트·작문 가드가 한 줄로 꿰여, 부모가 보는 문장 속 곁들임은 모두 실제 식단으로 검증된 것만 남습니다.