📦 이 문서는 v1 이전 자료(deprecated)입니다 — 최신 정보는 문서 허브를 보세요
⚙️ DEV SPEC · v1

밀프레드 편식도감
개발 명세서

리드 개발자용 — 스택·구조·API·인증·테스트·CI/CD·보안 완전 명세

━ 1. TECH STACK ━

기술 스택

📱 Frontend

Next.js 15 (App Router)

TypeScript 5.3 · React 19 · Tailwind CSS 4 · Pretendard Variable

🔌 Backend

Vercel Serverless Functions

Edge Runtime 우선 · 한국 region: icn1 (서울)

🗄 Database

Supabase Postgres 15

Row Level Security · pg_cron · pgvector (Phase 3)

🔐 Auth

Supabase Auth

카카오·구글·애플 OAuth · JWT

📦 Storage

Supabase Storage

이미지 · 24h/90일 TTL · 1MB 압축

🤖 AI/LLM

Anthropic Claude SDK

Sonnet 4.7 (추천) · Haiku 4.5 (배치) · Vision (OCR)

⚡ Cache

Upstash Redis

recommendations 24h · ocr 영구 · session

📊 Analytics

Vercel Analytics + PostHog

코어 웹 바이탈 + 이벤트 추적

🚨 Monitoring

Sentry

에러 트래킹 · 성능 모니터링 · 알람

🔄 CI/CD

GitHub Actions + Vercel

PR preview · auto deploy · cron jobs

🧪 Testing

Vitest + Playwright

Unit · Integration · E2E + Visual regression

📦 Package

pnpm

Workspace 분리 (web · api · shared)

━ 2. PROJECT STRUCTURE ━

디렉토리 구조

mealfred/
├── apps/
│   ├── web/                    # Next.js 15 App Router
│   │   ├── app/
│   │   │   ├── (auth)/         # 로그인·OAuth 콜백
│   │   │   ├── (app)/          # 인증 후 — 홈·도감·기록·레시피
│   │   │   │   ├── home/
│   │   │   │   ├── dex/        # 도감
│   │   │   │   ├── record/     # 식단 기록
│   │   │   │   └── recipes/
│   │   │   ├── eval/           # daycare-eval 진입점
│   │   │   ├── api/            # API Routes
│   │   │   │   ├── eval/
│   │   │   │   ├── recommend/
│   │   │   │   ├── ocr/
│   │   │   │   └── webhook/
│   │   │   ├── layout.tsx
│   │   │   └── page.tsx
│   │   ├── components/
│   │   │   ├── ui/             # Button·Card·Modal 등 기본
│   │   │   ├── eval/           # SignalLight·GaugeBar 등
│   │   │   ├── recipe/         # RecipeCard·MealPlan
│   │   │   └── record/
│   │   ├── lib/
│   │   │   ├── supabase/       # client·server·types
│   │   │   ├── kdri/           # KDRI lookup·percentile
│   │   │   └── utils/
│   │   ├── hooks/              # useChild·useMealLogs 등
│   │   └── styles/
│   └── admin/                  # 운영자 대시보드 (Phase 2)
├── packages/
│   ├── shared/                 # 공통 타입·상수
│   │   ├── types/
│   │   ├── kdri-rni.ts         # KDRI 36 영양소 상수
│   │   └── ingredients-pool.ts # 147 식재료 풀
│   ├── eval-engine/            # 평가 엔진 (Pure functions)
│   │   ├── axes/
│   │   ├── signal.ts
│   │   └── bmi.ts
│   ├── reco-engine/            # 추천 엔진
│   │   ├── phase0-profile.ts
│   │   ├── phase1-rules.ts
│   │   ├── phase2-llm.ts
│   │   └── cache.ts
│   └── ocr-engine/             # OCR 엔진
├── supabase/
│   ├── migrations/             # SQL 마이그레이션
│   ├── functions/              # Edge Functions
│   └── seed.sql                # 초기 데이터 (KDRI·ingredients)
├── scripts/
│   ├── enrich-recipes.ts       # 4,432 레시피 메타 enrich 배치
│   └── extract-ingredients.py  # 식재료 추출
├── .github/workflows/          # CI/CD
├── pnpm-workspace.yaml
├── package.json
└── README.md
━ 3. CODING CONVENTION ━

코딩 컨벤션

3-1. Naming

3-2. Comment 정책

기본: 주석 X. 잘 작성된 코드는 self-explanatory. 주석은 다음에만:

  • 왜 (Why) 했는지가 비명확할 때 (How·What X)
  • 외부 제약·버그 우회 (// Safari 17 bug — needs explicit type)
  • 학술 출처 인용 (// HabEat 2014, Table 3)
  • JSDoc은 공개 API 함수만

3-3. 파일 구성 패턴

// ✅ 좋은 예 — 도메인별 응집
export function calculateNutrientSignal(logs: MealLog[], ageBand: AgeBand) {
  return KDRI_36_NUTRIENTS.map(n => classifyFrequency(logs, n, ageBand))
}
function classifyFrequency(logs: MealLog[], nutrient: Nutrient, band: AgeBand): NutrientSignal {
  // 빈도 → 색상 분류
  const meals = countContributingMeals(logs, nutrient)
  const pct = (meals / WEEKLY_TARGET) * 100
  return { nutrient, pct, status: pct >= 80 ? 'green' : pct >= 50 ? 'orange' : 'red' }
}
━ 4. API ROUTES ━

API 엔드포인트 명세

4-1. 인증

POST/api/auth/oauth/[provider]
OAuth 콜백 (kakao·google·apple) → JWT 발급
POST/api/auth/signout
로그아웃 + 세션 만료

4-2. 아이 프로필

POST/api/children
아이 프로필 생성 (별명·생년월일·성별·알레르겐·돌봄)
GET/api/children/:id
프로필 조회
POST/api/children/:id/growth
키·몸무게 기록 → BMI percentile 자동 계산

4-3. 식단 기록

POST/api/meal-logs
끼니 기록 생성 (date·meal_type·photo·ingredients·texture·reaction)
GET/api/meal-logs?from=&to=
기록 조회 (날짜 범위)
PUT/api/meal-logs/:id
기록 수정 (소급 입력 지원)

4-4. 평가

GET/api/eval/score?child_id=&days=3
8축 진단 + 종합 점수 (ALG-EVAL-01)
GET/api/eval/signal?child_id=&days=7
36 영양소 신호등 (ALG-EVAL-02)
GET/api/eval/bmi?child_id=
BMI percentile + 탄·단·지 평가 (ALG-EVAL-03~04)

4-5. 추천

POST/api/recommend/recipes
4원칙 레시피 6-8개 (ALG-RECO-01~06) + cache hit/miss 헤더
POST/api/recommend/3day-plan
AI 3일 식단 (12끼) 생성

4-6. OCR

POST/api/ocr/menu
어린이집·유치원·가정 식단표 한 달 → 8축 평가 (ALG-OCR-01~05)
POST/api/ocr/food
음식 사진 → 식재료 자동 추출 (ALG-OCR-06)

4-7. 응답 표준 형식

{
  "ok": true,
  "data": { ... },
  "meta": {
    "cached": true,
    "version": "1.0.0",
    "latency_ms": 142
  }
}
// 에러 시
{
  "ok": false,
  "error": { "code": "VALIDATION_ERROR", "message": "...", "field": "eaten_at" }
}
━ 5. AUTH FLOW ━

인증 흐름

// 1) OAuth 진입
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'kakao',
  options: { redirectTo: '/auth/callback' }
})

// 2) 콜백 — 세션 교환
// app/auth/callback/route.ts
export async function GET(req: Request) {
  const code = new URL(req.url).searchParams.get('code')
  await supabase.auth.exchangeCodeForSession(code!)
  return Response.redirect('/home')
}

// 3) API 보호 — middleware
export async function middleware(req: NextRequest) {
  const session = await getSession(req)
  if (!session) return NextResponse.redirect('/login')
}

// 4) RLS 자동 적용 (Supabase가 auth.uid()를 컨텍스트로)
// children 테이블의 RLS 정책이 user_id = auth.uid() 자동 필터링
━ 6. STATE MANAGEMENT ━

데이터 페칭 + 상태

// 예: useChild hook
export function useChild(childId: string) {
  return useQuery({
    queryKey: ['child', childId],
    queryFn: () => fetchChild(childId),
    staleTime: 5 * 60 * 1000,  // 5분
  })
}
━ 7. TESTING ━

테스트 전략

레이어도구커버리지 목표대상
UnitVitest≥ 80%eval-engine, reco-engine 순수 함수, utils
IntegrationVitest + Supabase 로컬≥ 60%API 라우트 + DB 통합 (RLS 정책 포함)
E2EPlaywright핵심 흐름 100%온보딩·식단기록·평가받기·레시피추천
VisualPlaywright screenshots핵심 화면홈·신호등 모달·결과 카드
Loadk6 (PR 시 sample)p95 < 500ms/api/recommend, /api/ocr
━ 8. CI/CD ━

GitHub Actions + Vercel

# .github/workflows/ci.yml
on: [pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v3
      - run: pnpm install
      - run: pnpm typecheck
      - run: pnpm test:unit
      - run: pnpm test:integration
      - run: pnpm test:e2e
      - run: pnpm build
      - uses: codecov/codecov-action@v4

# Vercel PR Preview 자동 (브랜치 push 시)
# main 머지 시 → 프로덕션 자동 배포 (icn1 region)

# 배치 작업 cron
# .github/workflows/cron-enrich.yml — 매일 03:00 KST
# - 신규 식재료 운영 큐 검토 알림
# - 캐시 정리
# - LLM 사용량 리포트
━ 9. ENV VARS ━

환경변수 관리

# .env.local (개발)
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_ROLE_KEY=eyJ...    # server only
ANTHROPIC_API_KEY=sk-ant-...
UPSTASH_REDIS_REST_URL=...
UPSTASH_REDIS_REST_TOKEN=...
SENTRY_DSN=https://...
NEXT_PUBLIC_POSTHOG_KEY=phc_...

# 환경별 분리
.env.development  → 개발
.env.preview      → Vercel PR preview
.env.production   → 프로덕션

# Vercel에서 관리:
# - SUPABASE_SERVICE_ROLE_KEY, ANTHROPIC_API_KEY 등 secret
# - dev/preview/production 환경별 다른 값 가능
━ 10. ERROR HANDLING ━

에러 처리 + 로깅

━ 11. PERFORMANCE ━

성능 최적화

━ 12. SECURITY ━

보안

⚠️ 영유아 데이터 — 특별 주의

아이 별명만, 실명 X. 생년월일 월·년만. 어린이집 식별 정보 미수집. 사진 24h/90일 자동 삭제.

━ 13. MONITORING ━

모니터링 + 알람

13-1. 추적 지표

카테고리지표도구알람 임계치
제품DAU/MAU, retention, conversionPostHog전주 대비 -20%
성능API p95, LCP, FCP, CLSVercel Analyticsp95 > 500ms
에러Error rate, exception typesSentry1% 이상
비용LLM 일일 호출/비용, OCR 호출Anthropic Console일일 한도 80%
품질OCR 정확도, 추천 클릭률PostHog + customOCR < 85%

13-2. 알람 채널

13-3. 운영자 대시보드 (Phase 2)