공급사 상품 등록 · v2 → v3 · 개발(ERD·기술) 문서 · 2026-09-10
공급사 상품 등록 — 단어 유사도 점수 자동매칭 데이터 모델
공급사가 엑셀로 올린 상품 행을 MD 검수 전에 시스템이 기존 히어로(단품)와 매칭하는 구조. v3에서 매칭은 그룹 상품명 LIKE + 대분류(v2)를 버리고, 입력 행과 히어로 후보 사이의 단어 유사도(Similarity) 점수로 판정합니다 — 1.0 자동 매칭 / 0.75~0.99 후보 리스트(MD 선택) / 0.74 이하 신규. 유사도는 ★필수 6(상품명·과세여부·보관상태·중량·원산지 세부사항·브랜드) + ☆선택 3(원산지·등급·주문 배수 단위)으로 계산하고, 유통사 상품코드·유통사 상품명·net공급가는 필수지만 유사도엔 쓰지 않습니다. 1차 범위는 엑셀 업로드만(단건 등록 API/모달은 2차). 개발 9/16 · QA/스테이지 9/17.
바로가기 개요ERDDDL매칭유사도 설계엑셀 v3이미지상태인덱스·일정미정이력
seller_product_request · seller_product_request_item)은 이미 존재(개발DB 스키마 실측 — 컬럼명·타입만 확인, 수치 인용 없음). v3는 여기에 유사도 점수 · 후보 목록(JSON) · 매칭 상태 컬럼을 ALTER로 추가하고, 매칭 대상은 기존 히어로 seller_product입니다. 그룹 전용 테이블은 없고 seller_product.group_id·main_product_name이 그룹 축입니다.
📍 1차: 공급사 포털 › 상품 등록 › 엑셀 업로드 → 어드민 › 상품 관리 › 신규 공급사 상품 관리(supplier/product/new) 검수 / 2차 단건 등록 API·모달한눈 결론 (2026-09-10 개발 회의)
seller_product_request_item에 match_state(AUTO/CANDIDATE/NEW/ERROR) · match_score · match_candidates(JSONB) 추가. v2의 별도 후보 테이블은 JSON 컬럼으로 대체. 매칭 대상 = seller_product(히어로).개요 — v2는 무엇을, v3는 왜 바꾸나 · 1차 범위
그룹명 LIKE + 대분류로 후보를 뽑던 설계를 버리고, 행 ↔ 히어로 점수로 판정한다. 1차는 엑셀만.
v2는 "그룹이 신뢰 가능하게 가진 건 그룹 상품명 + 카테고리뿐"이라는 판단으로 그룹 상품명 LIKE + 대분류를 1차 검색으로 삼고 조건값 9개로 후보를 좁혔습니다. 9/10 개발 회의에서 이 구조는 공급사에게 그룹 상품명·대분류를 요구하는 것 자체가 무리(공급사는 그룹 개념을 모름)이고, LIKE 부분일치 범위를 정할 수 없다는 이유로 폐기됐습니다. v3는 공급사가 자기 상품 정보(★6 + ☆3)만 적으면 시스템이 기존 히어로들과 단어 유사도 점수를 내서 자동/후보/신규로 나눕니다.
1차 / 2차 범위
| 범위 | 포함 | 비고 |
|---|---|---|
| 1차 9/16 개발 · 9/17 QA | 엑셀 업로드(양식 v3) → 파싱·검증 → 비동기 배치 유사도 매칭 → 결과 화면(AUTO/CANDIDATE/NEW/ERROR) → 후보 선택·신규 확인 → [MD 검수로 제출] → MD 개별 행 검수 → 승인 시 seller_product 등록 | v2의 배치·제출·검수 흐름 승계 |
| 2차 | 단건 등록 API · 어드민/포털 단건 모달(그룹 검색·자동채움) · 이미지 Zip 일괄 · 자동 승인 정책 | 1차 산출물에서 |
진입 · 화면 분리 · 권한 분기 (v2 승계)
| 화면 | 권한 | 진입 경로 | 1차에서 하는 일 |
|---|---|---|---|
| 공급사 포털 | 공급사(seller) | 공급사 포털 › 상품 등록 › 엑셀 업로드 | 양식 v3 다운로드 · 업로드 · 진행률 · 결과 확인(후보 선택 · 신규 확인 · 오류행 재업로드) · [MD 검수로 제출] · 등록 이력 |
| 어드민(MD) | MD/내부 | 어드민 › 상품 관리 › 신규 공급사 상품 관리(supplier/product/new) | 제출된 행 개별 검수 · CANDIDATE 후보 확정 · NEW 확정(그룹 처리 🟠) · 행별 승인/거절(사유)/수정 후 승인 · 일괄 |
공급사 상품 리스트(product/manage 동일 컬럼) · 하위 행 복제 제거 · 어드민 신규 메뉴 공존은 v2 그대로. 리스트의 "카테고리" 컬럼은 히어로가 가진 값(category1~3)을 표시만 하고 양식에서는 받지 않습니다.
데이터 모델 (ERD)
기존 요청·행 테이블 확장(ALTER) + 매칭 대상 히어로. 유사도 계산 컬럼 강조(보라 hl). 개발DB 스키마 실측(컬럼명·타입만).
seller_product_request · _item · _match_candidate를 신규 3테이블로, 매칭 대상을 product_group으로 그렸습니다. 개발DB 스키마를 확인한 결과 seller_product_request · seller_product_request_item · seller_product_request_excel_history는 이미 존재하고(엑셀 업로드 v13 구현분으로 추정 — 스테이지 동일 여부는 확인 못 함), product_group 테이블은 없습니다. 그룹은 seller_product.group_id + main_product_name(그룹 이미지 seller_product_group_image는 main_product_name 키)이 축입니다. v3는 기존 행 테이블에 매칭 컬럼을 ALTER하고, 후보 테이블은 JSON 컬럼으로 대체합니다.status에 QUEUED→MATCHING→MATCHED→SUBMITTED 값 추가(기존 값과의 병합 🟠). 진행률 = matched_count/total_count.match_candidates JSON 스냅샷. 확정 히어로는 기존 hero_product_code 재사용.varchar 원문이라 정규화는 양쪽(입력·히어로)에 동일하게 적용해야 함(§4R). 후보 범위(use_yn·타 공급사 포함 여부)는 🟠.main_product_name이라 매칭 히어로의 그룹명으로 조회.양식 v3 ↔ 기존 컬럼 대응표 (request_item · seller_product)
v2 대응표를 승계하되 그룹 상품명·대분류 제거, 원산지 세부사항 필수 이동을 반영. 컬럼명은 개발DB 스키마 실측(두 테이블 모두 같은 이름 사용). ★유사도 필수 · ☆유사도 선택 · —유사도 미사용.
| # | 양식 v3 열 | 구분 | 유사도 | request_item / seller_product 컬럼 | 비고 |
|---|---|---|---|---|---|
| 1 | 유통사 상품코드 | 필수 | — | product_code | 공급사 식별 코드 |
| 2 | 유통사 상품명 | 필수 | — | original_product_name | 공급사 표기명 |
| 3 | net공급가 | 필수 | — | net_price | net_unit_price는 파생/시스템 🟠 · 단가 unit_price 미노출 |
| 4 | ★ 상품명 | 필수 | ★ | product_name | 토큰 유사도 주축 |
| 5 | ★ 과세여부 | 필수 | ★ | taxation | enum 정규화(과세/면세) 후 등가 |
| 6 | ★ 보관상태 | 필수 | ★ | storage | enum 정규화(냉장/냉동/상온) 후 등가 |
| 7 | ★ 중량 | 필수 | ★ | weight | 원문 저장 + norm_json.weight(수치·기준단위) |
| 8 | ★ 원산지 세부사항 | 필수(승격) | ★ | origin_raw | 예 "제주" · v2 선택 → v3 필수 ✅. origin과 분리 유지 여부 §4R 표 |
| 9 | ★ 브랜드 | 필수 | ★ | brand | 예 "제주농협" |
| 10 | ☆ 원산지 | 선택 | ☆ | origin | 예 "국내산" · 있으면 반영 |
| 11 | ☆ 등급 | 선택 | ☆ | grade | 있으면 반영 |
| 12 | ☆ 주문 배수 단위 | 선택 | ☆ | order_unit | int · 있으면 등가 비교 |
| 13 | 배송예정일수 | 선택 | — | expected_arrival_dday | "D-1" |
| 14 | 배송유형 | 선택 | — | delivery_type | MFC/허브 ↔ 코드값 매핑 🟠 |
| 15 | 외부 적재 | 선택 | — | outside_flag | 가능/불가능 → bool |
| 16 | 최소 주문 수량 | 선택 | — | min_order_quantity | int |
| 17 | 라벨용 엑셀 다운로드 수량 단위 | 선택 | — | unit_quantity_per_label | int |
| 18 | 제조사(생산자) | 선택 | — | producer | — |
| 19 | 수량 | 선택 | — | quantity | 입수 |
| 20 | 할인기준가 | 선택 | — | discounted_base_price | 🟠 필요 여부(§9) |
| 21 | 상품상세 | 선택 | — | product_detail | text |
| 22 | 색상 | 선택 | — | color | — |
| 23 | 상품단위 | 선택 | — | sell_unit | — |
| 24 | 크기 | 선택 | — | size | — |
| 25 | 수치 | 선택 | — | measurement_size | — |
| 26 | 특성 | 선택 | — | feature | 예 "10~11과" · v2부터 매칭 제외 |
| 27 | 일시품절 여부 | 선택 | — | sold_out | bool |
| 28 | 장기품절 여부 | 선택 | — | long_term_sold_out | bool |
| 29 | 재고수량 | 선택 | — | total_available_quantity (seller_product만) | request_item에 컬럼 없음 → 🆕 추가 필요 |
| 30 | 수량제한 | 선택 | — | order_limit (seller_product만) | request_item에 컬럼 없음 → 🆕 추가 필요 |
| 31 | 단위당 상품 개수 | 선택 | — | quantity_per_unit | int |
| 32 | 총량피킹 | 선택 | — | 확인 못 함 | 대응 컬럼을 스키마에서 특정 못 함(is_mfc_stock_item·packing_id 후보) 🟠 |
| — | 제거 | — | main_product_name | 양식·매핑 제거. 컬럼은 잔존(확정 히어로의 그룹명을 시스템이 채움 — 편입 정책 🟠) | |
| — | 제거 | — | category1 | 양식·매핑·드롭다운 제거 ✅. AUTO/CANDIDATE는 히어로 값 승계, NEW는 MD 검수 시 지정 🟠 |
DDL (Postgres · ALTER 초안)
기존 테이블 확장. 🆕 = v3 추가 · 🟠 = 미정. v2의 CREATE TABLE 3종·product_group ALTER·match_key 인덱스는 폐기.
-- 매칭 상태 enum (임계와 1:1 · 회의 확정값) CREATE TYPE match_state AS ENUM ('AUTO','CANDIDATE','NEW','ERROR'); -- AUTO : score = 1.000 (자동 매칭 · 히어로 확정 · 후보 미노출) -- CANDIDATE : 0.750 <= score <= 0.990 (후보 리스트 · MD 선택) ※ 0.99~1.0 사이 반올림 규칙 🟠(§4R) -- NEW : score <= 0.740 또는 후보 0 (신규) -- ERROR : 필수 누락·형식 오류 (매칭 시도 전 제외) -- 배치(요청) : 기존 seller_product_request 에 진행률·제출 시각 추가 ALTER TABLE seller_product_request ADD COLUMN matched_count INT NOT NULL DEFAULT 0, -- 🆕 진행률 = matched_count / total_count ADD COLUMN matched_at TIMESTAMP, -- 🆕 배치 매칭 완료(알림 트리거) ADD COLUMN submitted_at TIMESTAMP; -- 🆕 [MD 검수로 제출] 시각 (이력·검수 큐 등재 기준) -- status(varchar 20) 값에 QUEUED / MATCHING / MATCHED / SUBMITTED 추가 · 기존 값(승인/거절 계열)과의 병합 🟠 -- 행(항목) : 기존 seller_product_request_item 에 유사도 결과·검수 축 추가 ALTER TABLE seller_product_request_item ADD COLUMN row_no INT, -- 🆕 엑셀 원본 행번호(오류·재업로드 표시) ADD COLUMN match_state match_state, -- 🆕 AUTO / CANDIDATE / NEW / ERROR ADD COLUMN match_score NUMERIC(4,3), -- 🆕 최고 후보 점수 0.000~1.000 (AUTO=1.000) ADD COLUMN match_candidates JSONB, -- 🆕 상위 N 후보 스냅샷 (아래 구조) ADD COLUMN norm_json JSONB, -- 🆕 정규화 결과 {name_tokens, tax, storage, weight:{v,u}, origin_raw, brand, ...} ADD COLUMN error_code VARCHAR(24), -- 🆕 required_missing / bad_tax / bad_storage / unit_missing / bad_number …(기존 fail_reason은 사람용 문구) ADD COLUMN review_state VARCHAR(10) NOT NULL DEFAULT 'PENDING', -- 🆕 PENDING / APPROVED / REJECTED (행별 검수 · v2 승계) ADD COLUMN reject_reason VARCHAR(200), -- 🆕 행별 거절 사유 (요청 단위 reject_reason은 기존 유지) ADD COLUMN reviewed_by VARCHAR(100), ADD COLUMN reviewed_at TIMESTAMP, ADD COLUMN total_available_quantity INT, -- 🆕 양식 '재고수량' (seller_product엔 있고 item엔 없음) ADD COLUMN order_limit INT; -- 🆕 양식 '수량제한' -- 확정 히어로 = 기존 hero_product_code 컬럼 재사용 (AUTO: 시스템 세팅 / CANDIDATE: MD 선택 / NEW: NULL) -- 편입 그룹 = 기존 group_id · main_product_name 컬럼 재사용 (채우는 규칙 🟠 그룹 생성 로직) -- main_product_name · category1~3 : 양식 매핑 제거. 컬럼은 남기되 공급사 입력 경로 없음. -- match_candidates JSONB 구조 (설계 표기 · 상위 N=5 제안) -- [ { "hero": "H0001234", "score": 0.917, "group_id": 1234, "main_product_name": "감귤(10~11과),1kg,국산", -- "fields": { "product_name": 1.0, "taxation": 1.0, "storage": 1.0, "weight": 1.0, "origin_raw": 0.5, "brand": 1.0, -- "origin": 1.0, "grade": null, "order_unit": 1.0 }, -- "diff": ["origin_raw"] }, … ] -- diff = '무엇이 다른가' 하이라이트용 -- 인덱스 CREATE INDEX ix_spri_request_state ON seller_product_request_item(request_id, match_state); -- 결과·검수 화면 상태별 목록 CREATE INDEX ix_spri_review ON seller_product_request_item(review_state, created_at); -- 후보 프리필터용(🟠 §4R · §9 성능) : pg_trgm 이 되면 CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE INDEX ix_sp_name_trgm ON seller_product USING gin (product_name gin_trgm_ops); CREATE INDEX ix_sp_tax_storage ON seller_product(taxation, storage) WHERE use_yn = 'U'; -- use_yn 조건값은 후보 범위 🟠에 따름
🟠 미정 — status 값 병합 · match_score 반올림(0.995를 AUTO로 볼지) · norm_json 키 · 후보 범위(use_yn·타 공급사 포함) · 프리필터 인덱스 채택 여부. 물리 컬럼명은 구현 시 확정.
매칭 파이프라인 (v3 · 유사도 점수)
검증 → 정규화 → 후보 프리필터 → 히어로별 점수 → 최고 점수로 분기. 임계 ✅확정 · 산식 🟠개발 결정.
- 검증(ERROR 선별) — 필수 9열 빈값·과세/보관 enum 미매핑·중량 단위 없음·net공급가 비숫자·주문 배수 0/음수 →
match_state='ERROR'+error_code. 매칭 시도 없이 제외(오류행만 재업로드). - 정규화(입력 행 + 히어로 양쪽 동일) — 상품명 토큰화(공백·괄호·콤마 분리, 소문자, 특수문자 제거) · 과세/보관 enum 코드화 · 중량 "1KG/1kg/1,000g/1000 G" → 1000 G(계열 변환) · 원산지 세부/브랜드/원산지/등급 trim·공백 제거 · 주문 배수 int. 결과는
norm_json에 스냅샷(§4R 정규화 표 재사용). - 후보 프리필터(🟠 성능) — 전 히어로와 N×M을 계산하지 않도록 비교 대상을 먼저 줄임. 후보안: 과세·보관 등가 AND 상품명 trigram 유사도 하한(예:
product_name % 입력명) → 수십~수백 건. 프리필터 없이 전건 계산할지, 하한을 얼마로 둘지는 🟠(§9). - 점수 산출 — 프리필터 후보 각각에 대해 ★6 + ☆3(있을 때) 필드별 유사도 → 가중 평균 score ∈ [0,1]. score 내림차순 상위 N(제안 5)을
match_candidates에, 최고값을match_score에. - 분기(임계 ✅) — 아래 표. AUTO는
hero_product_code를 시스템이 세팅하고 후보 미노출. CANDIDATE는 후보 리스트를 노출해 MD가 선택(공급사 결과 화면에서 선택 허용 여부 🟠). NEW는 신규(그룹 처리 🟠).
match_score = 1.000. 동점 1.0 후보가 2개 이상이면(같은 값 히어로 중복) 자동 확정하지 않고 CANDIDATE로 강등해 사람이 고르게 함(제안 · 🟠). 승계값(카테고리·그룹·이미지)은 확정 히어로에서.0.750 ≤ match_score ≤ 0.990. match_candidates 상위 N 저장. 검수 화면에서 후보별 필드 점수·diff 표시. 후보 중 맞는 게 없으면 NEW 전환.match_score ≤ 0.740 또는 프리필터 후보 0. 감사용으로 최고 후보 1~2건은 JSON에 남길 수 있음(제안). 새 히어로 생성 → 새 그룹인지 기존 그룹 편입인지 🟠.error_code + 기존 fail_reason(사람용 문구). 제출 대상에서 제외, 수정 후 재업로드.유사도 점수제 설계 — 필드 · 산식 · 개발 결정 항목
v2 §4R(그룹명 LIKE·match_key·자동 통과 문턱)을 전면 교체. 임계·필드는 확정, 산식은 개발 결정.
유사도 계산 필드 — ★필수 6 · ☆선택 3 · 미사용 3
| 필드 | 구분 | 컬럼 | 비교 방식(후보안) | 정규화(유사도 전 전처리) | 가중치 🟠 |
|---|---|---|---|---|---|
| ★ 상품명 | 필수 | product_name | 토큰 집합 유사도(Jaccard/Dice) 또는 정규화 문자열 거리(Jaro-Winkler·Levenshtein 비율). 부분 점수 허용 | 공백·괄호·콤마·"/" 분리, 소문자, 특수문자 제거, 단위 토큰(1kg 등) 분리·중량 필드로 이관 | 가장 큼(예 0.35) |
| ★ 과세여부 | 필수 | taxation | 등가 1/0 | "과세/면세/영세" 동의어 → 코드 | 예 0.10 |
| ★ 보관상태 | 필수 | storage | 등가 1/0 | "냉장/냉동/상온/실온" 동의어 → COLD/FROZEN/ROOM | 예 0.10 |
| ★ 중량 | 필수 | weight | 기준단위 수치 등가 1/0 (계열 다르면 0). 근사 허용(±x%)은 🟠 | 1L/1,000ML/1000ml → 1000 ML · 1kg/1,000g → 1000 G · 12개/EA 12 → 12 EA (v2 §4R 정규화 표 재사용) | 예 0.15 |
| ★ 원산지 세부사항 | 필수(승격) | origin_raw | 정규화 문자열 등가 또는 토큰 유사도(예 "제주" vs "제주도") | trim·공백 제거·"도/시/군" 접미 제거 사전 🟠 | 예 0.15 |
| ★ 브랜드 | 필수 | brand | 토큰 유사도(예 "제주농협" vs "제주 농협") | 공백 제거·소문자·"(주)/㈜" 제거 | 예 0.15 |
| ☆ 원산지 | 선택 | origin | 등가 1/0 · 양쪽 다 있을 때만 | "국산/국내산" 동의어 | 보조(예 0.05) |
| ☆ 등급 | 선택 | grade | 등가 1/0 · 양쪽 다 있을 때만 | "특/상/보통" 사전 | 보조(예 0.03) |
| ☆ 주문 배수 단위 | 선택 | order_unit | int 등가 1/0 · 양쪽 다 있을 때만 | int 캐스팅 | 보조(예 0.02) |
| 유통사 상품코드 · 유통사 상품명 · net공급가 | 필수 | product_code · original_product_name · net_price | 유사도에 사용하지 않음 — 공급사 고유값이라 히어로 간 비교 의미 없음. 검증(빈값·숫자)만. | ||
가중치 예시는 합 1.0이 되도록 적은 후보안일 뿐 확정이 아닙니다. 예시대로면 ★ 하나가 완전히 틀릴 때 0.65~0.90 사이로 떨어져 대부분 CANDIDATE, 둘이 틀리면 NEW 근처가 됩니다 — 임계 0.75와 가중치는 함께 튜닝해야 합니다(스테이지 실데이터로, 9/17).
점수 산식 (후보안 · 개발 결정)
-- 필드별 유사도 s_f ∈ [0,1], 가중치 w_f. ☆ 필드는 입력·히어로 양쪽에 값이 있을 때만 포함(없으면 가중치 재정규화). score(item, hero) = Σ_f∈F w_f · s_f(norm(item.f), norm(hero.f)) / Σ_f∈F w_f F = ★6 ∪ { ☆f | item.f ≠ null AND hero.f ≠ null } -- 분기 (회의 확정 임계) best = max_hero score -- 프리필터 후보 중 best = 1.000 → AUTO (1.0 동점 다수면 CANDIDATE 강등 · 제안) 0.750 ≤ best ≤ 0.990 → CANDIDATE (상위 N 저장) best ≤ 0.740 | 후보 0 → NEW -- 0.990 < best < 1.000 구간: numeric(4,3) 저장 시 0.991~0.999 는 정의상 CANDIDATE. 반올림 자리수 🟠
개발 결정 항목 표
| 항목 | 후보안 | 제약 / 기획 의견 | 상태 |
|---|---|---|---|
| 토큰화 규칙 | 공백·괄호·콤마·슬래시 분리 → 소문자 → 특수문자 제거 → 단위 토큰(숫자+단위) 추출해 중량으로 이관 → 불용어(신선·국산 등) 제거 사전 | 입력·히어로 양쪽 같은 함수. 불용어 사전은 MD가 관리 가능해야(코드 상수 X) | 개발 결정 |
| 문자열 거리 함수 | 상품명·브랜드·원산지 세부 = 토큰 Jaccard 또는 Jaro-Winkler; enum/수치 = 등가 | 한글 조사·띄어쓰기 변이에 강한 쪽. pg_trgm similarity()를 그대로 쓰면 DB 안에서 계산 가능(프리필터와 동일 함수) | 개발 결정 |
| 필드 가중치 | 위 표 예시(상품명 0.35 · 중량/원산지세부/브랜드 0.15 · 과세/보관 0.10 · ☆ 합 0.10) | 임계 0.75와 세트로 튜닝. 설정 테이블/설정값으로 두면 스테이지에서 바로 조정 가능 | 개발 결정 |
| ☆ 결측 처리 | 양쪽 다 있을 때만 포함(가중치 재정규화) vs 한쪽만 있으면 0.5 | 회의 문구 "있으면 반영" → 전자가 자연스러움 | 개발 결정 |
| 반올림 · 저장 자리수 | numeric(4,3) · 비교는 반올림 전 값으로 | 0.995를 AUTO로 올리면 안 됨(1.0 = 전부 일치의 의미) | 개발 결정 |
| 후보 상한 N | 상위 5(제안). 0.75 이상만 저장 | 검수 화면에서 5개 초과는 비교 불가. JSON 크기 상한 | 제안 |
| 1.0 동점 다수 | CANDIDATE 강등(사람 선택) | 같은 값 히어로가 여럿 = 기존 데이터 중복. 자동 확정하면 임의 선택이 됨 | 제안 |
| 후보 프리필터 | 과세·보관 등가 AND 상품명 trigram 하한(예 0.3) → 그 안에서만 점수 계산 | N행 × M히어로 전건 계산은 배치라도 비용 큼. 하한이 너무 높으면 CANDIDATE를 놓침 → 재현율 확인 필요 | 🟠 §9 성능 |
| 후보 범위 | use_yn='U'(사용중)만 vs 전체 · 자기 공급사만 vs 전 공급사 | "같은 상품인지"가 목적이면 전 공급사 대상이 맞음. 삭제/중단 상품 포함 여부는 정책 | 🟠 |
| 원산지 세부 ↔ 원산지 | origin_raw(세부·★) / origin(☆) 분리 유지 | 양식이 둘 다 받으므로 컬럼도 분리. 세부만 있고 원산지가 비면 ☆ 결측 규칙 적용 | 유지 |
정규화 (재사용) — 유사도 전 전처리
v2 §4R의 정규화 표는 match_key 정확일치용이 아니라 유사도 계산 전 전처리로 그대로 재사용합니다. 표기가 달라도 같은 정규화 값이면 해당 필드 s_f = 1.
| 입력(표기 난립) | 정규화 값 | 필드 |
|---|---|---|
1L 1,000ML 1000ml 1,000ml 1리터 | 1000 · ML | ★ 중량 |
1kg 1KG 1,000g 1000 G 1킬로 | 1000 · G | ★ 중량 |
12개 1망(12개) EA 12 | 12 · EA | ★ 중량(수량계) |
면세 비과세 / 과세 | TAX_FREE / TAXABLE | ★ 과세여부 |
냉장 냉장보관 / 냉동 / 상온 실온 | COLD / FROZEN / ROOM | ★ 보관상태 |
제주 농협 제주농협(주) | 제주농협 | ★ 브랜드 |
국산 국내산 | KR | ☆ 원산지 |
product_group.match_key·정규화 컬럼 백필은 폐기 — 히어로 쪽 정규화는 매칭 시점에 온더플라이(프리필터 후보에만) 하거나, 성능이 안 나오면 seller_product에 정규화 캐시 컬럼을 두는 것을 2차 옵션으로 남깁니다.입력 통제 (v2 승계 · v3 조정)
양식에서 막는 것
1KG) — 단위 없으면 ⛔. 대분류 드롭다운 없음(제거).필수 강제로 얻는 것
한계 · 오매칭 방지
- 롤백 안전장치 — AUTO도
hero_product_code재지정으로 되돌릴 수 있게(MD 사후 수정).match_candidates에 감사 스냅샷 유지. - 튜닝 루프 — 스테이지 업로드 → match_score 분포 → 가중치·프리필터 하한 조정. 설정값으로 두면 재배포 없이 가능.
- 2차 확장 — 단건 API 동기 호출 · 정규화 캐시 컬럼 · 자동 승인.
엑셀 양식 v3 · 파싱 · 비동기 배치
양식 = 공급사상품등록_양식_v3.xlsx(필수 9 · 선택 23 · 실물 대조). 그룹 상품명·대분류 열 없음. 1차 유일 진입.
필수 열 9 (★ = 유사도 계산)
| 열 | 타입 | 파싱 · 정규화 | 실패 시(ERROR) |
|---|---|---|---|
| 유통사 상품코드 | text | → product_code | 빈값=⛔ required_missing |
| 유통사 상품명 | text | → original_product_name | 빈값=⛔ required_missing |
| net공급가 | number | → net_price · 예 400 | 비숫자/0 이하=⛔ bad_number |
| ★ 상품명 | text | 예 "감귤" → product_name + 토큰화 | 빈값=⛔ required_missing |
| ★ 과세여부 | 드롭다운 | 면세/과세 → taxation 코드 | 미매핑=⛔ bad_tax |
| ★ 보관상태 | 드롭다운 | 냉장/냉동/상온 → storage 코드 | 미매핑=⛔ bad_storage |
| ★ 중량 | number+단위 | 예 "1KG" → weight 원문 + norm {1000, G} | 단위없음/계열미상=⛔ unit_missing |
| ★ 원산지 세부사항 | text | 예 "제주" → origin_raw · v3 필수 승격 | 빈값=⛔ required_missing |
| ★ 브랜드 | text | 예 "제주농협" → brand | 빈값=⛔ required_missing |
| — | 양식에서 제거. 그룹명·대분류는 매칭 히어로에서 승계(NEW는 MD 지정 🟠), 단가는 시스템값 | ||
선택 열 23 (☆ = 유사도 보조 · 있으면 반영)
| 빈값 허용 · AUTO/CANDIDATE 확정 시 히어로 값과 다르면 처리 규칙 🟠(입력값 우선 vs 히어로 승계) | |||
|---|---|---|---|
| ☆ 원산지 | ☆ 등급 | ☆ 주문 배수 단위 | 배송예정일수 |
| 배송유형 | 외부 적재 | 최소 주문 수량 | 라벨용 엑셀 다운로드 수량 단위 |
| 제조사(생산자) | 수량 | 할인기준가 🟠 | 상품상세 |
| 색상 | 상품단위 | 크기 | 수치 |
| 특성 | 일시품절 여부 | 장기품절 여부 | 재고수량 |
| 수량제한 | 단위당 상품 개수 | 총량피킹(컬럼 확인 못 함) | — |
예시행(양식 v3 실물): A0111355 / 제주 노지 감귤 1kg / 400 / 감귤 · 면세 · 냉장 · 1KG · 제주 · 제주농협 / 국내산 · — · 1 / D-1 · MFC · 가능 · 1 / 특성 "10~11과". 컬럼 대응은 §2 표.
대량 업로드 = 비동기 배치 매칭 (유지 · 성능 🟠)
- 업로드·접수 — 파일 저장 +
seller_product_request(status=QUEUED, total_count) + 행 파싱·검증(ERROR 즉시 확정) 후 즉시 응답(요청 id). - 비동기 매칭 워커 — status=
MATCHING. 청크 단위로 정규화 → 프리필터 → 점수 → 분기,matched_count증가 → 진행률. - 완료 알림 — status=
MATCHED+matched_at. 공급사/MD 알림. 화면은 폴링 또는 푸시 🟠. - 결과 확인 — AUTO / CANDIDATE / NEW / ERROR 탭. CANDIDATE 후보 선택(공급사 화면에서 허용 여부 🟠 — 회의 문구는 "MD 선택"), NEW 확인, ERROR 재업로드.
- [MD 검수로 제출] — 제출 전 검증: CANDIDATE 미선택 행·NEW 필수 미입력 행이 있으면 제출 불가(ERROR는 제외 대상). status=
SUBMITTED+submitted_at→ 이력·검수 큐 등재.
seller_product에 등록.이미지 자동 승계
매칭 히어로의 그룹 이미지를 따온다. NEW만 직접 업로드(1차 = 행별 image1~3_path · Zip 일괄은 2차).
- 승계 대상 —
match_state ∈ {AUTO, CANDIDATE→확정}이고hero_product_code가 정해진 행. - 그룹 이미지 조회 — 확정 히어로의
main_product_name으로seller_product_group_image WHERE main_product_name = ? AND del_yn <> 'Y' ORDER BY picture_grade, picture_no. 공급사가 이미지를 안 올려도 됨. - 승계 방식 — 참조(그룹명 키 조인) vs 복사(
image1~3_path에 스냅샷). 🟠 미정(v2 승계). 기존 행 테이블에image1~3_path가 있어 복사가 구조상 쉬움. - NEW만 직접 — 그룹이 없으므로 직접 업로드. 신규 그룹 생성 여부(🟠)에 따라 그룹 대표 이미지가 될지 결정.
상태 전이
행 매칭(match_state 4종 · 임계 매핑) + 행 검수(review_state) + 배치(request.status).
행 매칭 상태(match_state) ↔ 임계
| 상태 | 진입 조건 | 사람 개입 | 다음 |
|---|---|---|---|
| 🟢 AUTO | match_score = 1.000 (1.0 유일) | 없음(후보 미노출·히어로 자동 확정) | → 검수 대기(PENDING) · 자동 승인 🟠 |
| 🟡 CANDIDATE | 0.750 ≤ score ≤ 0.990 (또는 1.0 동점 다수) | 후보 중 선택(MD · 공급사 허용 🟠) | 선택 → 검수 대기 / 맞는 게 없음 → NEW 전환 |
| 🔴 NEW | score ≤ 0.740 또는 후보 0 | 필수 확인 · 이미지 입력 | 신규 히어로 생성(그룹 처리 🟠) → 검수 대기 |
| ⛔ ERROR | 필수 누락 · enum 미매핑 · 단위 없음 · 비숫자 | 수정 | 제출 제외 → 재업로드 |
seller_product_request의 approved_by/at·reject_reason은 요청 단위 집계로 병행.
배치 상태(request.status)
QUEUED/MATCHING은 업로드 화면 진행률로만 보이고 이력엔 없음. SUBMITTED부터 등록 이력·검수 큐. 기존 status 값과 병합 🟠.
MD 검수 화면 — 개별 행 (v2 승계 · v3 컬럼 조정)
| 컬럼 | 내용 |
|---|---|
| 체크 | 일괄 승인/거절 |
| 공급사 · 요청자 · 요청일시 | request.seller_id · requested_by · submitted_at |
| 상품명 · 중량 · 브랜드 · 원산지 세부 | 행 ★ 값 ( |
| 매칭 | 🟢 AUTO · 🟡 CANDIDATE · 🔴 NEW + match_score 표시 |
| 후보 | CANDIDATE: match_candidates 상위 N — 히어로코드 · 그룹명 · 점수 · diff 하이라이트 → 선택 |
| 확정 히어로 / 그룹 | hero_product_code · group_id(NEW는 그룹 처리 🟠) |
| net공급가 · 유통사 상품명/코드 | 공급사 입력 3 · 단가 미노출 |
| 검수 | 승인 · 거절(사유) · 수정 후 승인 (review_state) |
등록 이력 페이지 = SUBMITTED 배치만 · 행별 검수 결과 부분집계(승인 N · 거절 M · 대기 K) — v2 승계. 원천 테이블(seller_product_request vs _excel_history)은 🟠 데이터소스.
인덱스 · 마이그레이션 · 일정
ALTER 중심. product_group 백필·match_key 인덱스는 폐기. 개발 9/16 · QA 9/17.
인덱스(초안)
| 테이블 | 인덱스 | 용도 |
|---|---|---|
| seller_product | gin (product_name gin_trgm_ops) 🟠 | 상품명 trigram 프리필터(채택 시) |
| seller_product | (taxation, storage) 부분 인덱스 | 등가 프리필터 · 기존 group_id·main_product_name 인덱스는 이미 있음 |
| request_item | (request_id, match_state) | 결과 탭·검수 상태별 목록(기존 request_id 단독 인덱스 있음) |
| request_item | (review_state, created_at) | 검수 큐 |
| v2 설계 폐기(테이블 없음 · 방식 변경) |
마이그레이션 순서
- enum · ALTER —
match_state타입 ·seller_product_request/_item컬럼 추가(§3). 기존 데이터에 영향 없음(전부 nullable/default). - 정규화·유사도 서비스 — 토큰화 · 단위 변환표 · 동의어 사전 · 가중치 설정값. 입력·히어로 공용 함수.
- 프리필터 인덱스 — pg_trgm 확장 가능 여부 확인 후 채택(🟠). 불가 시 과세·보관 등가 + 상품명 첫 토큰 prefix 등 대안.
- 배치 워커 — QUEUED→MATCHING→MATCHED, 진행률, 알림.
- 양식 v3 배포 — 필수 9 · 선택 23 · 과세/보관 드롭다운 · 대분류 드롭다운 없음.
- 스테이지 튜닝(9/17) — 실데이터 업로드로 score 분포 확인 → 가중치·프리필터 하한 조정.
일정
| 일자 | 마일스톤 | 산출 |
|---|---|---|
| 9/16 (화) | 개발 | ALTER · 유사도 서비스 · 배치 워커 · 결과/검수 화면 · 양식 v3 |
| 9/17 (수) | QA · 스테이지 | 스테이지 업로드 · score 분포 · 임계/가중치 튜닝 · 검수 흐름 QA |
미정(🟠) · 오픈 이슈
9/10 회의에서 남긴 6건 + 설계상 미확정. 결정된 것은 아래 별도.
| 항목 | 내용 | 영향 | 상태 |
|---|---|---|---|
| 1. 그룹 생성 로직 | NEW = 새 그룹을 만드나? · AUTO/CANDIDATE 확정 시 히어로의 그룹(group_id·main_product_name)에 편입하나? 같은 "시금치 1KG 냉동"이 그룹이 나뉘는 기존 케이스 처리 포함. | item.group_id 채우는 규칙 · seller_product_group_mscd 생성 · 이미지 대표 | 미정 |
| 2. 자동 승인 정책 | AUTO(1.0)를 검수 없이 통과시킬지. 현재는 전부 PENDING. MD 후속 수정 범위도 여기 묶임. | review_state 기본값 · 2차 | 미정 |
| 3. 엑셀 대량 성능 | 유사도는 N행 × M히어로 계산 → 후보 프리필터 필요(과세·보관 등가 + 상품명 trigram 하한 제안). 프리필터 방식·하한·워커/청크·폴링 vs 푸시 미정. 프리필터가 재현율을 깎지 않는지 9/17 확인. | 인덱스 · 워커 설계 | 검토필요 |
| 4. 그룹 메인상품 | 그룹 대표(메인) 상품·그룹명(main_product_name) 정의. v3에서 매칭 키는 아니지만 승계·이미지·편입의 기준으로 여전히 필요. | 이미지 승계 키 · 편입 | 미정 |
| 5. 할인기준가 | 선택 열 discounted_base_price를 실제로 받을지. 불필요하면 양식 정리. | 양식 v3 선택 23 → 22 | 검토 |
| 6. 데이터소스 | 신규 supplier/product/new ↔ 기존 supplier/product/manage·product/manage 통합/분리 · 등록 이력 원천(seller_product_request vs _excel_history). | 화면 조회 쿼리 | 미정 |
| 산식 세부(개발 결정) | 토큰화 · 거리 함수 · 가중치 · ☆ 결측 · 반올림 · 후보 상한 N · 1.0 동점 처리 — §4R 표 | — | 개발 결정 |
| 후보 범위 | use_yn 사용중만 vs 전체 · 자기 공급사 vs 전 공급사 | 프리필터 WHERE | 미정 |
| CANDIDATE 선택 주체 | 회의 문구 "MD 선택". 공급사 결과 화면에서도 선택 허용할지(v2는 공급사 선택 + 미선택 제출 불가). | 제출 전 검증 규칙 | 확인 |
| request.status 병합 | 기존 값 체계에 QUEUED/MATCHING/MATCHED/SUBMITTED 추가 방식 | 기존 화면 호환 | 미정 |
| 선택 열 ↔ 히어로 값 충돌 | AUTO/CANDIDATE 확정 시 입력한 선택값(색상·수량 등)이 히어로와 다르면 입력 우선 vs 승계 | 등록 시 값 결정 | 미정 |
| 총량피킹 컬럼 | 양식 열의 대응 컬럼을 스키마에서 특정 못 함 | 매핑 1건 | 확인 |
| 이미지 승계 형태 | 참조 vs image1~3_path 복사(v2 승계) | — | 경미 |
변경 이력
문서 버전별 핵심 변경.
| 버전 | 일자 | 변경 |
|---|---|---|
| v3 | 2026-09-10 | 개발 회의 반영. §4R 전면 교체 → 단어 유사도 점수제(1.0 / 0.75~0.99 / ≤0.74 · ★6 ☆3) · 그룹 상품명·대분류 제거(양식·ERD·매칭·인덱스) · 1차 = 엑셀만, 단건 API/모달 2차 · ERD 정정: request/item 테이블 기존 존재(ALTER로 확장), product_group 없음 → 매칭 대상 seller_product, 후보 테이블 → match_candidates JSON · 상태 enum AUTO/CANDIDATE/NEW/ERROR 임계 매핑 · 양식 v3 필수 9/선택 23 대응표 · 원산지 세부사항 필수 · 일정 9/16·9/17 · 미정 6 재정리 |
| v2 | 2026-09-02 | 스테이지 실화면·실양식(260901) 대조 + 개발팀 회의 반영. 그룹 상품명 LIKE + 대분류 1차 · 조건값 9개 보조 · 그룹값 자동채움·수정 불가 · 가격 반전(net공급가 입력) · 비동기 배치 · 개별 행 검수 |
| v13(프로토) | 2026-08 | 히어로코드 단건 매칭 · 자동통과/확인/신규/오류 + 검수 보류/반려/승인/수정후승인 |
기존 테이블·컬럼명 = 개발DB 스키마 실측(컬럼·타입·인덱스만, 데이터 수치 없음) · 추가 컬럼·enum·JSON·산식 후보 = 설계 표기(물리 스키마는 구현 시 확정) · 참고 v13: md-supplier-product.pages.dev