You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
코디 재현 엔진 MVP의 핵심 추천 화면 구현. 백엔드 CodyMatchResult(per-role 후보 SKU 리스트)를 받아 프론트엔드가 Composition Variant(저가/중가/무드정확) 조합으로 변주한 룩 세트 3~5개를 조합 및 표시. 유저의 유사도 점수·대체 이유·구매 링크 확인 후 ⑤ Item Detail로 진입하거나 피드백(👍/👎)으로 재랭킹.
범위
화면:packages/web/app/cody/recommended/page.tsx
스토어 통합: F1이 생성한 lib/stores/codyStore.ts의 CodyMatchResult 상태 읽기
Composition Variant 조합: F1 adapter에서 받은 단일 CodyMatchResult의 per_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.ts의 CodyMatchResult, 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로 복귀 옵션.
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, 모든 필드)
목적
코디 재현 엔진 MVP의 핵심 추천 화면 구현. 백엔드
CodyMatchResult(per-role 후보 SKU 리스트)를 받아 프론트엔드가 Composition Variant(저가/중가/무드정확) 조합으로 변주한 룩 세트 3~5개를 조합 및 표시. 유저의 유사도 점수·대체 이유·구매 링크 확인 후 ⑤ Item Detail로 진입하거나 피드백(👍/👎)으로 재랭킹.범위
packages/web/app/cody/recommended/page.tsxlib/stores/codyStore.ts의CodyMatchResult상태 읽기lib/components/cody/RecommendedLooks.tsx+RecommendedCard.tsx+CompositionVariantSelector.tsx+ 피드백 UIcody-match-mock.ts의 fullCodyMatchResultfixture (§2.2 전 필드)lib/api/adapters/cody-match.ts에서만 import포함 안 함 (F3, F4, 후속에서 다룸)
lib/components/vton— import 금지)핵심 파일
packages/web/app/cody/recommended/page.tsxpackages/web/lib/components/cody/RecommendedLooks.tsx(메인 레이아웃)packages/web/lib/components/cody/RecommendedCard.tsx(카드 단위)packages/web/lib/components/cody/CompositionVariantSelector.tsx(저가/중가/무드정확 탭)packages/web/lib/api/adapters/cody-match.ts(types + entry function)packages/web/lib/api/adapters/cody-match-mock.ts(full fixture)packages/web/lib/stores/codyStore.ts(F1이 생성한 상태 읽기)packages/web/lib/utils/composition-variants.ts(Composition Variant 조합 로직)Acceptance Criteria
Composition Variant 조합: F1 adapter에서 받은 단일
CodyMatchResult의per_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 매칭점수),'S4'),reason,relaxation_level('S1'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또는 hydratedcatalog_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.ts의CodyMatchResult,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에서 재사용)
Real Data (Stage 2 backend contract)
ShopperPreference(budget_krw, retailer_scope, gender, excluded_brands, preferred_retailers —fit필드 없음)/raw-posts/{id}/match?variant=budget-500k→CodyMatchResult(위 mock과 동일 스키마)per_rolekey 일치,confidence범위,relaxation_summary형식 검증은 F7이 담당.의존성
cody-match.ts생성 + mockcody-match-mock.tsfixture/cody/item/[skuHash]진입제약 · 주의
경로 규칙 (절대)
packages/web/app/cody/recommended/page.tsxpackages/web/lib/components/cody/안/recreate,app/request,app/(consumer),app/cody/screens,app/cody/detail단일 adapter seam
lib/api/adapters/cody-match.ts에 통합codyAnalysisAdapter.ts,recommended/adapter.ts,useCodyMatch.ts,CodyEngineAdapter.ts, etc.타입 mirror (누락·발명 금지)
cody-match-stage2 §2.1·§2.2의 모든 필드를 TS 타입으로 구현. 추가 필드 발명 금지:
주의:
affiliate_url은catalog_items(§7.1)소속,CandidateSKU가 아님confidence혼동 금지:CandidateSKU.confidence(매칭점수) ≠AnalysisQuality(분석품질 배지, F3 몫)fit필드는ShopperPreference에 없음 (client-only Fit Preference는 별도, 구현 deferred)실패상태 소유권 (F5가 구현)
F5 책임:
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건/cody/recommended로드 시 VTON modal 미작동 확인참조
/Users/kiyeol/development/decoded/decoded-docs/Architecture/cody-match-stage2.md§2.2 코드 블록 (full schema, 모든 필드)