Skip to content

feat(cody): adapter reconcile 체크리스트 (mock → CodyMatchService S2) #632

Description

@thxforall

목적

F1 단일 adapter(lib/api/adapters/cody-match.ts)의 mock → real 교체 절차를 문서화하고, 구현 전에 schema/mapping 불일치를 검증하는 체크리스트. 본 이슈는 구현이 아니라 F1 확정·F2-F6 진행 중 발견된 gap을 정리 하고, ② analyze 확장(6/15)·④⑤ CodyMatchService S2(6/30) trigger 시 협의 없이 진행하지 않을 교체 규칙을 명시한다.


범위

  • 대상: packages/web/lib/api/adapters/cody-match.ts (F1이 유일하게 생성하는 단일 adapter)
  • 검증: mock fixture ↔ cody-match-stage2 §2·§7.1 schema 일치성, 타입 mirror 누락, 매핑 규칙 간극
  • trigger:
    • ②분석 화면(F3): analyze endpoint 호출 시 CodyAnalysis + AnalysisQuality 반환 계약 재확인
    • ④⑤추천 화면(F5·F6): CodyMatchService S2 → ④Recommended 레이어 호출 시 ShopperPreference → preference_variant 매핑 검증
  • 생성물: gap 문서 1개(체크리스트 형태) + 별도 구현 없음

화면 & 의존 지점

② Analysis (F3) ─ analyze endpoint 호출
              ├─ Input: raw image
              └─ Output: CodyAnalysis {style, items, style_formula}
                       + AnalysisQuality (FE mock state — backend 없음)
                       
④ Recommended (F5) ─ CodyMatchService.match() 호출 (6/30)
              ├─ Input: CodyAnalysis + ShopperPreference
              └─ Output: CodyMatchResult {per_role:[CandidateSKU]}
              
⑤ Item Detail (F6) ─ catalog_items.affiliate_url 조회 (join)
              └─ Output: CandidateSKU + affiliate_url (catalog_items 출처)

Acceptance Criteria

1. TS 타입 mirror — cody-match-stage2 §2.2 전 필드 누락 검증

  • CandidateSKU fixture가 다음 전부 포함:
    • sku_signature_hash (PK)
    • brand, product, color, category ('top'|'bottom'|'footwear'|'outerwear'|'accessory')
    • price_krw, primary_image_url, product_url, source_domain
    • retailer_scope ('kr'|'global')
    • confidence (0.0-1.0, per-SKU 매칭 점수)
    • reason (토큰 매칭 설명)
    • relaxation_level ('S1'|'S2'|'S3'|'S4')
    • image_verification ('og_verified'|'og_missing'|'hyperbrowser_fallback')
  • affiliate_url은 CandidateSKU에 없음 — catalog_items(§7.1) 소속. ⑤에서 join으로 hydrate 확인
  • CodyMatchResult fixture:
    • raw_post_id, preference_variant, matched_at
    • per_role: Record<string, CandidateSKU[]> (role key는 카테고리 dedup)
    • coverage: Record<string, number> (per-role 후보 개수)
    • relaxation_summary: Record<string, string> (S3/S4 사유)
    • cost_usd, cache_hit

2. CodyAnalysis 타입 — mock vs stage2 §2.1 계약

  • mock fixture의 style_formula가 다음 포함:
    • must_keep: string[] (core identity tokens)
    • replaceable: string[] (swappable axes)
    • core_combination: string (optional 확장, mvp-scope §3엔 없으나 §2.1 소비)
    • substitute_axis: string[] (optional 확장, stage2만 정의)
  • mock fixture에 confidence 필드 없음 (cody_analysis schema 불포함, §2.1)
    • → AnalysisQuality는 FE-only mock state (분석 품질 배지용, client-side fixture)

3. ShopperPreference 매핑 — 4가지 gap 검증

3a. Budget UI bucket → preference_variant tier 매핑

  • UI 입력(wireframes ③): 20만 / 50만 / 무제한
    • → Backend tiers(§2.1): budget-100k / budget-500k / budget-1M / authentic
    • 매핑 결정사항 명시:
      • 50만 UI ↔ budget-500k
      • 무제한 UI ↔ authentic ✓ (단, retailer_scope='both' 수반 — gap 3d 참조)
      • 20만 UI ↔ ?
        • 가설 A: budget_krw=200_000 + custom:<hash> (§7.2 allows custom)
        • 가설 B: snap to nearest(budget-100k)
        • 결정 필요 — 미정 시 체크박스 미적용(⚠️ flag)
    • budget-100k, budget-1M은 UI에서 직접 도달 불가 — 문서화: custom preference 또는 future phase로 명시
    • 매핑 문서 출처: cody-match-stage2 §2.1 §4.5 (budget soft guide) + wireframes ③

3b. Fit (클라이언트 전용) — ShopperPreference에서 제외 확인

  • mock ShopperPreference fit 필드 없음 (§2.1 명시)
  • UI의 핏 선택(오버/슬림/무관, wireframes ③) ↔ ClientOnlyFitPreference 타입
    • backend에 송신하지 않음, client-side only
    • FE mock 분기: codyStore에 fitPreference: 'over'|'slim'|'any' 별도 상태
    • real S2 호출 전: fitPreference는 post-filter에만 사용(백엔드 contract 외)
  • CONTEXT.md "Client-only Fit Preference" 정의 재확인

3c. core_combination / substitute_axis — 선택적 확장 AC

  • mvp-scope §3 fixture: must_keep / replaceable만 (core_combination/substitute_axis 없음)
    • → stage2 §2.1에서 core_combination + substitute_axis 추가 소비
  • mock adapter의 CodyAnalysis.
    • core_combination optional (null 허용, ② 분석 v1에서 미생성)
    • substitute_axis optional (null 허용, ② 분석 확장 시 생성)
  • 재현 규칙: F7 체크만, 구현은 F3(② analyze endpoint 확장) 담당

3d. 쇼핑몰 체크 → retailer_scope + preferred_retailers 매핑

  • UI 체크(wireframes ③): 무신사 / 29CM / W컨셉 / 전체(모두 체크)
    • → Backend ShopperPreference(§2.1):
      • retailer_scope: 'kr'|'global'|'both'
      • preferred_retailers: string[] (soft hint, slugs per §4.3: musinsa/29cm/wconcept)
    • 매핑 규칙:
      • 무신사만 체크 → retailer_scope='kr', preferred_retailers=['musinsa']
      • 무신사+29CM → retailer_scope='kr', preferred_retailers=['musinsa','29cm']
      • 무신사+29CM+W컨셉 → retailer_scope='kr', preferred_retailers=['musinsa','29cm','wconcept']
      • 전체(모두 미체크) → retailer_scope='kr' (default, preferred_retailers=[])
      • global은 UI에서 직접 도달 불가 — 오직 authentic(무제한) 선택 시에만 retailer_scope='both' 플립
    • 관찰: 무신사 only 요청은 ④④에서 "무신사 only 버전" 별도 카드로 표시 (wireframes ④) — rank 시 preferred_retailers match 우선 (§6.2)
  • 출처: cody-match-stage2 §2.1 §4.3 §6.2

4. 단일 adapter 격리 — F1 import seam 검증

  • packages/web/lib/api/adapters/cody-match.ts 만 존재
    • packages/web/lib/components/cody/* 화면들이 이 파일에서만 import
  • 금지된 파일 없음:
    • codyAnalysisAdapter.ts
    • recommended/adapter.ts
    • useCodyMatch.ts
    • CodyEngineAdapter
    • codyStore.ts 내 직접 adapter 로직 (store는 타입만, seam은 adapter)
  • F3/F5/F6 컴포넌트: "import { ... } from 'lib/api/adapters/cody-match'"만 사용

5. mock fixture — full §2.2 형태 & spike→production deltas

  • mock CodyMatchResult 예제가 complete per_role set 포함:
    • top, bottom, footwear 최소 (outerwear/accessory는 optional)
    • 각 role마다 ≥1 CandidateSKU (full 필드)
  • mock CandidateSKU.confidence: 0.7 이상(S1 threshold, §4.5)
  • spike→prod delta 명시 (§4.5):
    • spike: budget hard cap in prompt → production: soft guide + post-filter
    • spike: all role same prompt → production: role × axis token weighting
    • spike: no cache → production: match_request_hash 24h TTL, sku_signature_hash forever
    • spike: confidence 0.7 hardcoded → production: per-tier (S1=0.7, S2=0.6, S3=0.5, S4=0.5)
  • 도구: cody-match-stage2 Appendix A spike script mapping 재확인

6. 실패상태 소유권 — 각 화면이 자기 gap 소유 확인

  • ②Analysis(F3): low-confidence AnalysisQuality 배지 표시 (구현 AC 불포함, F7 검증만)
  • ④Recommended(F5):
    • 예산초과 처리 (①→③ 완화 유도)
    • 빈결과(coverage 0)
    • 품절 auto-swap + 유사도 재표기
    • 👎 재랭킹
    • → 위 4가지가 mock fixture에서 검증 가능한지 확인
  • ⑤Item Detail(F6):
    • affiliate_url이 catalog_items(§7.1) 출처임 명시
    • 링크 끊김 처리(fallback 후보)
    • 미가입 + 저장 시도(gated modal)
  • F8(통합QA)는 위 실패상태 8종이 F2-F6에 빠짐없이 구현됐는지 cross-cutting 검증 (F7은 존재 확인만, 구현 아님)

7. VTON 분리 — zero import 검증

  • packages/web/app/cody/* 코드 grep:
    • import.*vton / VtonModal / vtonStore
    • import.*api/v1/vton
    • → 모두 없음 확인 (smoke test)
  • 출처: cody-match-stage2 §9.2 / wireframes / CONTEXT.md "Virtual Try-On" 분리

8. 경로 표준 — CANONICAL SPEC 路 정확성

  • 모든 신규 화면 (app/cody/, lib/components/cody/*)
  • adapter 단일: lib/api/adapters/cody-match.ts + cody-match-mock.ts
  • 금지 경로 없음: /recreate, app/request, app/(request), app/(consumer), app/cody/screens, app/cody/detail

9. Contract 출처 명시 & 의존성 맵

  • Input 계약: cody-match-stage2 §2.1
    • CodyAnalysis (cody_analysis schema, 타입 mirror)
    • ShopperPreference (신설, 4가지 필드)
  • Output 계약: cody-match-stage2 §2.2
    • CodyMatchResult, CandidateSKU (full 필드)
    • catalog_items.affiliate_url은 §7.1 (join 후 hydrate)
  • Preference variant 정의: §2.1 (4-tier enum)
  • Spike→prod delta: §4.5 (6가지 항목)
  • Ranking 규칙: §6.2 (sort key)

10. 페르소나 & 제약사항

  • persona owner = raf, 확정 6/11 (CANONICAL SPEC)
    • F7 AC에서는 제외 (구현 AC가 아니므로)
    • 단, 의존성 명시: persona 확정 전까지는 wireframes ③의 Primary(Aspirational) 기준만
  • 제약:
    • fit은 backend contract에 절대 들어가지 않음
    • affiliate_url은 CandidateSKU가 아니라 catalog_items 조회
    • 20만 UI bucket은 명시적 매핑 결정 필요 (open question flag)

핵심 파일

파일 역할 상태
packages/web/lib/api/adapters/cody-match.ts 단일 adapter (타입+entry fn) F1이 생성
packages/web/lib/api/adapters/cody-match-mock.ts mock fixture F1이 생성
packages/web/lib/stores/codyStore.ts flow store (ShopperPreference state) F2 생성
packages/web/app/cody/page.tsx ① Upload F2 생성
packages/web/app/cody/analysis/page.tsx ② Analysis F3 생성
packages/web/app/cody/conditions/page.tsx ③ Conditions F4 생성
packages/web/app/cody/recommended/page.tsx ④ Recommended F5 생성
packages/web/app/cody/item/[skuHash]/page.tsx ⑤ Item Detail F6 생성

mock·real 경계 (contract 출처)

Mock fixture 기준 (codyStore mock)

// cody-match-mock.ts — F1이 생성, 전체 계약 반영
type CodyAnalysis = {
  style: { mood, season, color_palette, silhouette };
  items: [{ category, color, fit, role, importance }]; // §2.1 소비
  style_formula: {
    must_keep: string[];          // mvp-scope §3
    replaceable: string[];        // mvp-scope §3
    core_combination: string?;    // stage2 §2.1 (optional, F3 확장)
    substitute_axis: string?[];   // stage2 §2.1 (optional, F3 확장)
  };
};

// ② analyze endpoint → CodyAnalysis + AnalysisQuality (FE mock only)
type AnalysisQuality = {
  confidence: number; // 분석 품질 배지(0.2-1.0)
  // backend cody_analysis에 없음 — FE mock state
};

type ShopperPreference = {
  budget_krw: number;              // 0 = unbounded
  retailer_scope: 'kr'|'global'|'both';
  country: string;
  gender: 'male'|'female'|'unisex';
  excluded_brands: string[];
  preferred_retailers: string[];   // soft hint
  // fit은 없음 — ClientOnlyFitPreference로 별도 관리
};

type CandidateSKU = {
  sku_signature_hash: string;
  brand: string;
  product: string;
  color: string|null;
  category: 'top'|'bottom'|'footwear'|'outerwear'|'accessory';
  price_krw: number;
  primary_image_url: string;
  product_url: string;
  source_domain: string;
  retailer_scope: 'kr'|'global';
  confidence: number;              // 0.0-1.0, 매칭 점수
  reason: string;
  relaxation_level: 'S1'|'S2'|'S3'|'S4';
  image_verification: 'og_verified'|'og_missing'|'hyperbrowser_fallback';
  // affiliate_url은 없음 — catalog_items(§7.1)에서 join
};

type CodyMatchResult = {
  raw_post_id: UUID;
  preference_variant: string;      // "budget-500k"|"authentic"|"custom:..."
  matched_at: Date;
  per_role: Record<string, CandidateSKU[]>;
  coverage: Record<string, number>;
  relaxation_summary: Record<string, string>;
  cost_usd: number;
  cache_hit: boolean;
};

Real gateway (trigger: 6/15 ②분석 확장, 6/30 ④⑤ S2 go-live)

  • ②분석: analyze endpoint 응답 = CodyAnalysis + AnalysisQuality (backend → FE, quality는 FE mock 유지)

    • trigger: cody-engine API 문서 확인 시점
    • gap: core_combination / substitute_axis 실제 생성 여부 (F3 구현 단계 협의)
  • ④⑤추천: CodyMatchService.match(preference_variant) 호출

    • trigger: ai-server CodyMatchService gRPC 준비 (cody-match-stage2 §9.1 scheduler)
    • boundary: ShopperPreference 매핑(3a-3d) 결정 후 → real entry point
    • affiliate_url: catalog_items 테이블 join (§7.1) — ④④에서 다음 tier 카드 조회 시 포함

의존성

  • F1: cody-match.ts (단일 adapter) — F7의 검증 대상
  • F2: codyStore (ShopperPreference 상태) — adapter input 제공
  • F3: ② analyze endpoint 확장(core_combination/substitute_axis, AnalysisQuality)
  • F5: ④ CodyMatchService S2 호출 (preference_variant 매핑)
  • F6: ⑤ affiliate_url hydrate (catalog_items join)
  • F8: 통합 실패상태 검증 (F2-F6의 gap들)

제약·주의

구현 금지

  • F7은 구현 이슈가 아니다. 모든 AC는 "~를 검증한다", "~가 있다", "~를 확인한다" 스타일 (existence check, not creation).
  • adapter 파일 생성/수정 불가 (F1 담당)
  • 화면 컴포넌트 수정 불가 (F2-F6 담당)

Adapter seam 규칙

  • cody-match.ts: types + entry function 한 파일에만
  • F3/F5/F6: 이 파일에서만 import (useCodyMatch hook, CodyEngineAdapter 등 금지)

버킷 gap 미처리

  • 20만 UI bucket의 매핑이 미정:
    • custom preference(budget_krw=200_000 + hash) 선택 vs snap-to-tier 선택
    • 결정 전까지 checkbox 미사용 권장 (모호성 회피)
    • 이 체크의 통과 기준: "매핑 규칙 문서화" (결정값 자체는 불필요)

Persona owner = raf / 6/11

  • 본 F7의 구현 AC에는 persona 미포함
  • 단, wireframes ③의 Primary(Aspirational) 기준만으로 진행 (확정 후 조정)

VTON 무관

  • cody 코드 어디서도 vton store/component/API import 금지 (smoke test로 검증)

참조

  • cody-match-stage2.md (§2.1 ShopperPreference, §2.2 CanyMatchResult, §4.5 spike→prod, §6.2 ranking, §7 schema)
  • mvp-scope.md (§3 CodyAnalysis schema, §2 5화면, §4 비기능)
  • wireframes.md (③Conditions 조건들, ④Recommended 룩 세트 구조, ⑤Item Detail 아이템)
  • CONTEXT.md (Cody 용어 정의: Composition Variant ≠ Preference Variant, ShopperPreference ≠ fit, AnalysisQuality FE-only)
  • codyStore.ts (ShopperPreference state 형태) — F2 구현 시 F7 매핑과 맞춤 재확인

확인 항목 (deploy 직전)

  • codyStore의 ShopperPreference 상태와 adapter input contract 일치
  • ② analyze endpoint 응답(core_combination/substitute_axis optional 여부) 확인
  • ④⑤ CodyMatchService S2 gRPC proto 확인 (preference_variant enum)
  • catalog_items.affiliate_url이 ④④ 카드에서 조회됨 확인 (④에서 item 상세보기로 진입할 때)
  • mock fixture 가 full §2.2 형태로 complete인지 재확인

Metadata

Metadata

Assignees

No one assigned

    Labels

    aiAI/자동화codyCody Recreation Engine MVPenhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions