Skip to content

feat(cody): ④ Recommended 화면 (full CodyMatchResult fixture + FE composition) #630

Description

@thxforall

목적

코디 재현 엔진 MVP의 핵심 추천 화면 구현. 백엔드 CodyMatchResult(per-role 후보 SKU 리스트)를 받아 프론트엔드가 Composition Variant(저가/중가/무드정확) 조합으로 변주한 룩 세트 3~5개를 조합 및 표시. 유저의 유사도 점수·대체 이유·구매 링크 확인 후 ⑤ Item Detail로 진입하거나 피드백(👍/👎)으로 재랭킹.

범위

  • 화면: packages/web/app/cody/recommended/page.tsx
  • 스토어 통합: F1이 생성한 lib/stores/codyStore.tsCodyMatchResult 상태 읽기
  • 컴포넌트: lib/components/cody/RecommendedLooks.tsx + RecommendedCard.tsx + CompositionVariantSelector.tsx + 피드백 UI
  • Mock 데이터: F1 adapter에서 import한 cody-match-mock.ts의 full CodyMatchResult fixture (§2.2 전 필드)
  • 실패 상태: 예산초과·빈결과(0건)·품절auto-swap·affiliate 미커버·👎재랭킹 — F5가 소유
  • API 통합: F1이 정의한 단일 adapter lib/api/adapters/cody-match.ts에서만 import

포함 안 함 (F3, F4, 후속에서 다룸)

  • 분석 confidence 배지 (F3 Analysis 화면 소유)
  • 조건 슬라이더·체크박스 (F4 Conditions 화면 소유)
  • VTON modal·state (repo 내 별도 lib/components/vton — import 금지)

핵심 파일

용도 경로
화면 packages/web/app/cody/recommended/page.tsx
컴포넌트 packages/web/lib/components/cody/RecommendedLooks.tsx (메인 레이아웃)
packages/web/lib/components/cody/RecommendedCard.tsx (카드 단위)
packages/web/lib/components/cody/CompositionVariantSelector.tsx (저가/중가/무드정확 탭)
단일 adapter (F1 생성) packages/web/lib/api/adapters/cody-match.ts (types + entry function)
mock adapter (F1 생성) packages/web/lib/api/adapters/cody-match-mock.ts (full fixture)
스토어 packages/web/lib/stores/codyStore.ts (F1이 생성한 상태 읽기)
utils packages/web/lib/utils/composition-variants.ts (Composition Variant 조합 로직)

Acceptance Criteria

  • Composition Variant 조합: F1 adapter에서 받은 단일 CodyMatchResultper_role 후보를 조합하여 3~5개의 Look Set(저가 ≤20만·중가 ≤50만·무드정확·조건시 무신사only) 생성. 각 Look Set = {top, bottom, footwear, …} SKU 조합 (stage2 §6.4 outfit composition은 미설계이므로 FE-only로직).

  • Full fixture: mock adapter에 cody-match-stage2 §2.2의 full CodyMatchResult 구조 반영. 포함 필드: raw_post_id, preference_variant, matched_at, per_role (role별 CandidateSKU[]), coverage (role별 개수), relaxation_summary (이유), cost_usd, cache_hit. CandidateSKU 전 필드: sku_signature_hash, brand, product, color, category, price_krw, primary_image_url, product_url, source_domain, retailer_scope, confidence (01 매칭점수), reason, relaxation_level ('S1''S4'), image_verification ('og_verified'|'og_missing'|'hyperbrowser_fallback').

  • 유사도·대체 이유 표시: 각 아이템 카드에 CandidateSKU.confidence (0~1 범위를 %로 표시, 예: 88%)와 reason (예: "원본 실루엣·뉴트럴 톤 유지, 소재·브랜드는 대체") 표시.

  • 예산초과 실패상태: per_role 합산 가격이 선택된 예산(ShopperPreference.budget_krw)을 초과하면 "예산을 한 단계 위/아래로 조정하시겠어요?" 제안 + 가장 근접한 세트 노출. 예산 배지는 탭의 총액으로 표시 (예: "17만원").

  • 빈결과(0건) 실패상태: per_role 중 1개 이상이 0건(coverage=0)이면 "조건이 너무 좁아요" + ③ Conditions 화면으로 이동 CTA. relaxation_summary의 사유 표시 (예: "S4_empty — no candidates after global fallback").

  • 품절·사이즈 없음 auto-swap: 각 role별 후보 중 unavailable 항목(CandidateSKU §2.2엔 재고 필드 없음 — mock은 coverage/relaxation_summary 또는 hydrated catalog_items 재고로 시뮬레이션, CandidateSKU에 필드 발명 금지)은 다음 고신뢰 후보로 자동 교체. UI는 "유사도 N% (대체됨)" 표기.

  • Affiliate 미커버 처리: hydrated catalog_items.affiliate_url(§7.1)이 null이면(CandidateSKU/§2.2엔 이 필드 없음) 비제휴 표시 + 제휴 가능 대체 상품 우선 노출. 구매 CTA는 "구매하기" 링크가 비활성화 또는 gated.

  • 👎 재랭킹: 각 세트 카드에 [👍] [👎 안 비슷해요] 버튼. 👎 클릭 시 cody_match_feedback 신호(future, mock은 local store) 기록 후 UI에서 해당 세트 순서 변경 또는 재제시(F8 QA 대상, F5는 신호 수집만).

  • F1 adapter import만: lib/api/adapters/cody-match.tsCodyMatchResult, CandidateSKU, ShopperPreference 타입과 entry function만 사용. 별도 adapter 파일 생성 금지 (CodyEngineAdapter, useCodyMatch, recommended/adapter.ts 등). 모든 로직은 컴포넌트 또는 utils/composition-variants.ts에서 처리.

  • VTON 의존성 0: app/cody/*, lib/components/cody/*에서 lib/components/vton, lib/stores/vtonStore, /api/v1/vton 으로부터 import 0건. smoke test로 ④ 화면 로드 시 VTON modal/state 미의존 확인.

  • E2·E3 entry 지원: wireframes §3에 따라 E2(pre-decoded 룩·Content-first)·E3(자연어 검색·Search-first)로 직진입 시 상단에 "원본 룩" 요약 칩으로 맥락 유지. E1(Upload-first)은 ③ Conditions 완료 후 진입.

  • 비로그인 뷰·👍👎 저장: 분석·추천 뷰는 비로그인 가능. 👍👎·저장(Saved Looks·Shop-this-look) 기능은 gated(F6 Item Detail과 공동 로직).

  • loading·error 상태: P5 단계(10~30초) loading ⟳ 표시 + "카탈로그에서 비슷한 룩 찾는 중" 메시지. error 시 재시도 버튼 + ③ Conditions로 복귀 옵션.

Mock · Real 경계 (Contract 출처)

Mock Data (F5 작성, F1 mock adapter에서 재사용)

// lib/api/adapters/cody-match-mock.ts 의 fixture 예시
const mockCodyMatchResult: CodyMatchResult = {
  raw_post_id: "uuid-xxx",
  preference_variant: "budget-500k",
  matched_at: "2026-06-06T10:00:00Z",
  per_role: {
    "top": [
      {
        sku_signature_hash: "hash1",
        brand: "무신사HAUS",
        product: "오버사이즈 울 블레이저",
        color: "navy",
        category: "top",
        price_krw: 89000,
        primary_image_url: "https://...",
        product_url: "https://musinsa.com/products/xxx",
        source_domain: "musinsa.com",
        retailer_scope: "kr",
        confidence: 0.88,
        reason: "원본 실루엣·뉴트럴 톤 유지, 소재·브랜드는 대체",
        relaxation_level: "S1",
        image_verification: "og_verified"
      },
      // ... 대체 후보
    ],
    "bottom": [ /* 비슷한 구조 */ ],
    "footwear": [ /* ... */ ]
  },
  coverage: { "top": 5, "bottom": 4, "footwear": 3 },
  relaxation_summary: { "top": "S1", "bottom": "S2 — brand relaxed" },
  cost_usd: 0.0036,
  cache_hit: false
};

Real Data (Stage 2 backend contract)

  • 입력: F1의 ShopperPreference (budget_krw, retailer_scope, gender, excluded_brands, preferred_retailers — fit 필드 없음)
  • 출력: backend /raw-posts/{id}/match?variant=budget-500kCodyMatchResult (위 mock과 동일 스키마)
  • mock→real reconcile (F7 체크리스트): F5는 full fixture만 제공. real API 호출 시 타입·필드 누락 여부, per_role key 일치, confidence 범위, relaxation_summary 형식 검증은 F7이 담당.

의존성

선행 작업 역할 상태(예상)
F1 · CodyMatchService adapter 단일 adapter cody-match.ts 생성 + mock cody-match-mock.ts fixture 선행(F5는 import만)
F3 · Analysis 화면 cody_analysis 객체 + confidence 배지 + low-confidence UX 선행(F5가 S2 의존하지는 않으나, 화면 워크플로우 상 선행)
F4 · Conditions 화면 ShopperPreference 입력 + 조건 저장(codyStore) 선행(F5가 읽기)
추후 F6 Item Detail 라우트 클릭 → /cody/item/[skuHash] 진입 F5 완료 후 구현
추후 F7 mock→real 재조정, O2O test coverage 검증 F5 완료 후 QA
추후 F8 8종 실패상태 cross-cutting 통합 QA F6 완료 후

제약 · 주의

경로 규칙 (절대)

  • 신규 화면 반드시 packages/web/app/cody/recommended/page.tsx
  • 컴포넌트 반드시 packages/web/lib/components/cody/
  • 금지 경로: /recreate, app/request, app/(consumer), app/cody/screens, app/cody/detail

단일 adapter seam

  • 모든 타입·entry function은 lib/api/adapters/cody-match.ts 에 통합
  • F3, F5, F6, F9가 여기서 import만 (재정의 금지)
  • 별도 adapter 파일 생성 절대 금지: codyAnalysisAdapter.ts, recommended/adapter.ts, useCodyMatch.ts, CodyEngineAdapter.ts, etc.

타입 mirror (누락·발명 금지)

cody-match-stage2 §2.1·§2.2의 모든 필드를 TS 타입으로 구현. 추가 필드 발명 금지:

// cody-match.ts 에 포함되어야 할 필드 (§2.2 정확히)
type CandidateSKU = {
  sku_signature_hash: string;
  brand: string;
  product: string;
  color?: string;
  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-1
  reason: string;
  relaxation_level: 'S1' | 'S2' | 'S3' | 'S4';
  image_verification: 'og_verified' | 'og_missing' | 'hyperbrowser_fallback';
};
type CodyMatchResult = {
  raw_post_id: string;
  preference_variant: string;
  matched_at: string;
  per_role: Record<string, CandidateSKU[]>;
  coverage: Record<string, number>;
  relaxation_summary: Record<string, string>;
  cost_usd: number;
  cache_hit: boolean;
};
type ShopperPreference = {
  budget_krw: number;
  retailer_scope: 'kr' | 'global' | 'both';
  country?: string;
  gender?: 'male' | 'female' | 'unisex';
  excluded_brands?: string[];
  preferred_retailers?: string[];
};

주의:

  • affiliate_urlcatalog_items(§7.1) 소속, CandidateSKU가 아님
  • confidence 혼동 금지: CandidateSKU.confidence(매칭점수) ≠ AnalysisQuality(분석품질 배지, F3 몫)
  • fit 필드는 ShopperPreference에 없음 (client-only Fit Preference는 별도, 구현 deferred)

실패상태 소유권 (F5가 구현)

F5 책임:

  • 예산초과: 슬라이더 ±1단계 제안
  • 빈결과(0건): 조건 완화 유도
  • 품절·사이즈: auto-swap + 유사도 N% 표기
  • affiliate 미커버: 참고 표시 + 제휴 대체 우선
  • 👎 재랭킹: 피드백 수집

F8이 통합 검증만 (구현 아님): 위 8종이 F2~F6에 누락되지 않았는지 cross-cutting QA.

VTON 분리 (testable AC)

  • lib/components/vton/VtonModal.tsx, /lab/vton, /api/v1/vton 존재
  • app/cody/*, lib/components/cody/*에서 import 0건
  • smoke test: /cody/recommended 로드 시 VTON modal 미작동 확인

참조

  • wireframes.md — §2 ④ Recommended Looks 와이어프레임 (레이아웃·상태·전환)
  • mvp-scope.md — §2 5화면 · §3 데이터 스키마 · §4 비기능 (locked)
  • user-scenarios.md — §4 실패 루프 · §6 대표 narrative
  • cody-match-stage2.md — §2.1·§2.2 Input/Output 계약 · §6 budget filter / ranking · §7.1 catalog_items schema
  • CONTEXT.md — Cody glossary: Composition Variant vs Preference Variant · ShopperPreference vs Client-only Fit · Analysis Quality vs CandidateSKU.confidence
  • Stage 2 output fixture: /Users/kiyeol/development/decoded/decoded-docs/Architecture/cody-match-stage2.md §2.2 코드 블록 (full schema, 모든 필드)

Metadata

Metadata

Assignees

No one assigned

    Labels

    codyCody Recreation Engine MVPenhancementNew feature or requestfrontend프론트엔드

    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