AI 사이즈 추천·핏 가이드: 반품률 30% 줄이는 사이즈 시스템 설계

이커머스

사이즈 추천AI 개인화패션 테크반품 감소핏 가이드

이 글은 누구를 위한 것인가

  • 패션 상품 반품의 40-60%가 사이즈 불만족인 팀
  • 브랜드마다 사이즈가 달라 고객 불만이 쌓이는 쇼핑몰
  • 고객 신체 사이즈 데이터를 어떻게 활용할지 모르는 팀

들어가며

패션 이커머스 반품의 절반은 사이즈 때문이다. "보통 M인데 이 브랜드는 작아요" — 이 한 마디가 반품과 부정 리뷰로 이어진다. 정확한 사이즈 추천은 반품률을 30% 이상 낮출 수 있다.

이 글은 bluefoxdev.kr의 패션 이커머스 사이즈 시스템 를 참고하여 작성했습니다.


1. 사이즈 데이터 수집 전략

[신체 데이터 수집 방법]

직접 입력 (설정 페이지):
  키, 몸무게, 가슴둘레, 허리둘레, 엉덩이둘레
  평소 신는 신발 사이즈
  선호 핏 (타이트/레귤러/루즈)

구매 이력 기반 추론:
  구매 상품의 사이즈 + 반품 여부
  "작음/큼" 리뷰 키워드 분석
  교환 요청 패턴 (S→M: 작았음)

퀴즈/온보딩:
  "평소 어떤 핏을 선호하세요?"
  "이 상품과 비슷한 [기존 구매 상품]은 어떠셨나요?"
  
[브랜드 사이즈 정규화]
  각 브랜드의 사이즈 스펙 수집
  표준 신체 치수로 변환
  "이 브랜드 M = 일반 S-M"으로 표시

2. 사이즈 추천 엔진

from dataclasses import dataclass

@dataclass
class UserBodyProfile:
    user_id: str
    height_cm: float
    weight_kg: float
    chest_cm: float | None
    waist_cm: float | None
    hip_cm: float | None
    shoe_size_kr: int | None
    fit_preference: str  # 'tight', 'regular', 'loose'

@dataclass
class SizeRecommendation:
    recommended_size: str
    confidence: float
    reason: str
    size_guide: dict  # 실측 사이즈 정보
    fit_note: str     # "이 브랜드는 작게 나옵니다"

async def recommend_size(
    user_id: str,
    product_id: str,
) -> SizeRecommendation:
    
    profile = await get_user_body_profile(user_id)
    product = await get_product_with_size_chart(product_id)
    purchase_history = await get_size_purchase_history(user_id)
    
    # 1. 신체 사이즈 기반 추천
    body_recommendation = match_size_by_measurements(
        profile=profile,
        size_chart=product["size_chart"],
        category=product["category"],
    )
    
    # 2. 구매 이력 기반 보정
    history_adjustment = calculate_history_adjustment(
        purchase_history=purchase_history,
        brand=product["brand"],
        category=product["category"],
    )
    
    # 3. 브랜드 사이즈 편차 적용
    brand_offset = await get_brand_size_offset(product["brand"], product["category"])
    
    final_size = apply_adjustments(
        base_size=body_recommendation["size"],
        history_adj=history_adjustment,
        brand_offset=brand_offset,
        fit_preference=profile.fit_preference,
    )
    
    confidence = calculate_confidence(
        has_measurements=bool(profile.chest_cm),
        history_count=len(purchase_history),
        brand_data_quality=brand_offset["data_quality"],
    )
    
    return SizeRecommendation(
        recommended_size=final_size,
        confidence=confidence,
        reason=build_reason_text(body_recommendation, history_adjustment, brand_offset),
        size_guide=product["size_chart"][final_size],
        fit_note=brand_offset.get("fit_note", ""),
    )

def calculate_history_adjustment(
    purchase_history: list[dict],
    brand: str,
    category: str,
) -> int:
    """과거 구매 데이터로 사이즈 조정값 계산"""
    relevant = [
        h for h in purchase_history
        if h["category"] == category and not h["returned"]
    ]
    
    if not relevant:
        return 0
    
    # 반품된 경우의 사이즈 방향 분석
    returns_with_reason = [
        h for h in purchase_history
        if h["returned"] and h["return_reason"] in ("too_small", "too_large")
    ]
    
    adjustments = []
    for r in returns_with_reason:
        if r["return_reason"] == "too_small":
            adjustments.append(+1)  # 한 사이즈 업
        else:
            adjustments.append(-1)  # 한 사이즈 다운
    
    if not adjustments:
        return 0
    return round(sum(adjustments) / len(adjustments))

3. 반품 데이터 피드백 루프

async def process_return_feedback(return_request: dict):
    """반품 사유를 사이즈 모델에 피드백"""
    
    if return_request["reason"] not in ("too_small", "too_large", "size_issue"):
        return
    
    # 사이즈 이력 업데이트
    await db.execute("""
        UPDATE user_purchase_size_history
        SET returned = true,
            return_reason = $1,
            return_size_feedback = $2
        WHERE order_item_id = $3
    """, 
        return_request["reason"],
        return_request.get("comment"),
        return_request["order_item_id"]
    )
    
    # 브랜드 사이즈 편차 통계 업데이트
    await update_brand_size_stats(
        brand=return_request["brand"],
        category=return_request["category"],
        purchased_size=return_request["purchased_size"],
        return_reason=return_request["reason"],
    )
    
    # 사이즈 추천 정확도 메트릭 기록
    was_recommended = await check_if_size_was_recommended(
        user_id=return_request["user_id"],
        order_item_id=return_request["order_item_id"],
    )
    
    if was_recommended:
        await record_recommendation_miss(
            user_id=return_request["user_id"],
            product_id=return_request["product_id"],
        )

마무리

사이즈 추천의 시작은 신체 데이터 수집이다. 퀴즈형 온보딩으로 키·몸무게만 받아도 첫 추천이 가능하다. 브랜드 사이즈 편차 데이터는 반품 데이터에서 자동으로 학습하면 된다. "이 브랜드는 작게 나옵니다" 문구 하나가 반품을 막는 가장 간단한 방법이다.