공급사 상품 등록 · 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.

개발 문서 · ERD · v3 단어 유사도 점수제 1.0 / 0.75~0.99 / ≤0.74 ★6 + ☆3 필드 1차 = 엑셀만 양식 v3 필수 9 · 선택 23 개발 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 개발 회의)

매칭 방식단어 유사도(Similarity) 점수제. 입력 행 ↔ 히어로 후보마다 점수(0~1)를 산출해 분기. v2의 그룹 상품명 LIKE · 대분류 필터 · match_key 정확일치 설계는 폐기.
임계 ✅확정1.0 = 자동 매칭(AUTO) · 0.75~0.99 = 후보 리스트(CANDIDATE, MD가 선택) · ≤0.74 = 신규(NEW). 값 누락·형식 오류는 매칭 전 ERROR.
유사도 필드★필수 6 = 상품명 · 과세여부 · 보관상태 · 중량 · 원산지 세부사항 · 브랜드. ☆선택 3 = 원산지 · 등급 · 주문 배수 단위(있으면 반영). 유사도에 안 쓰는 필수 3 = 유통사 상품코드 · 유통사 상품명 · net공급가.
산식토큰화 · 필드 가중치 · 문자열 거리 함수 · 선택 필드 결측 처리 · 반올림 규칙은 개발 결정 항목(§4R 표). 문서는 후보안과 제약만 제시. 후보 상한 상위 N(제안 5).
제거그룹 상품명 · 대분류 컬럼/매핑 제거(양식·매칭·ERD 모두). 대분류 소멸 ✅확정. 단위 정규화(1L/1000ml→1000ML) 같은 전처리는 유사도 계산 전 정규화로 재사용.
1차 범위엑셀 업로드만 — 비동기 배치 매칭 · 진행률 · [MD 검수로 제출] 흐름 유지. 단건 등록 API/모달은 2차.
양식 v3필수 9(★유사도 6 + 입력 3) · 선택 23(☆유사도 보조 3 포함). 원산지 세부사항 필수 승격 ✅확정.
ERD기존 seller_product_request_item에 match_state(AUTO/CANDIDATE/NEW/ERROR) · match_score · match_candidates(JSONB) 추가. v2의 별도 후보 테이블은 JSON 컬럼으로 대체. 매칭 대상 = seller_product(히어로).
일정개발 9/16(화) · QA/스테이지 9/17(수).
🟠 미정 6그룹 생성 로직(신규=새 그룹? 자동 매칭 시 히어로 그룹 편입?) · 자동 승인 정책 · 엑셀 대량 성능(유사도 N×M → 후보 프리필터 필요) · 그룹 메인상품 · 할인기준가 · 데이터소스. §9.
문서 성격 · 확신 수준임계(1.0 / 0.75~0.99 / ≤0.74) · 유사도 필드(★6 ☆3) · 원산지 세부사항 필수 · 대분류 소멸 · 1차=엑셀만 · 일정은 2026-09-10 개발 회의 결정입니다. 양식 v3 필드는 공급사상품등록_양식_v3.xlsx(필수 9 · 선택 23)와 대조했습니다. 기존 테이블·컬럼명은 개발DB 스키마를 실측(컬럼명·타입·인덱스만, 데이터 수치 인용 없음)했고, 추가 컬럼명·enum·JSON 구조·산식 후보는 설계 표기입니다. 확정 안 된 지점은 본문과 §9에 🟠.
01

개요 — v2는 무엇을, v3는 왜 바꾸나 · 1차 범위

그룹명 LIKE + 대분류로 후보를 뽑던 설계를 버리고, 행 ↔ 히어로 점수로 판정한다. 1차는 엑셀만.

v2는 "그룹이 신뢰 가능하게 가진 건 그룹 상품명 + 카테고리뿐"이라는 판단으로 그룹 상품명 LIKE + 대분류를 1차 검색으로 삼고 조건값 9개로 후보를 좁혔습니다. 9/10 개발 회의에서 이 구조는 공급사에게 그룹 상품명·대분류를 요구하는 것 자체가 무리(공급사는 그룹 개념을 모름)이고, LIKE 부분일치 범위를 정할 수 없다는 이유로 폐기됐습니다. v3는 공급사가 자기 상품 정보(★6 + ☆3)만 적으면 시스템이 기존 히어로들과 단어 유사도 점수를 내서 자동/후보/신규로 나눕니다.

v3 핵심 전환 5 ① 매칭 = 단어 유사도 점수제(1.0 자동 / 0.75~0.99 후보 / ≤0.74 신규) · ② 그룹 상품명·대분류 제거(양식·ERD·매칭) · ③ 유사도 필드 = ★필수 6 + ☆선택 3, 원산지 세부사항 필수 승격 · ④ 1차 = 엑셀 업로드만, 단건 API/모달 2차 · ⑤ 후보는 별도 테이블 대신 행의 JSON 컬럼(점수·후보 목록 스냅샷).

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)을 표시만 하고 양식에서는 받지 않습니다.

02

데이터 모델 (ERD)

기존 요청·행 테이블 확장(ALTER) + 매칭 대상 히어로. 유사도 계산 컬럼 강조(보라 hl). 개발DB 스키마 실측(컬럼명·타입만).

v2 → v3 ERD 정정 (스키마 실측)v2 문서는 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 컬럼으로 대체합니다.
기존 테이블 + v3 ALTER (요청 · 행)
seller_product_request_excel_history EXIST
idPK
seller_id공급사
upload_typevarchar(10)
file_name / excel_path원본
total/success/fail_count집계
created_by / created_atts
등록 이력 페이지 원천. v3에서 제출(SUBMITTED) 시점에만 적재할지 업로드 시점 적재를 유지할지 🟠(§9 데이터소스).
매칭 대상 · 승계 원천 (READ)
seller_product (히어로) READ
hero_product_codePK 단품
seller_id공급사
group_id / main_product_name그룹 축(인덱스 有)
product_name★ 상품명
taxation★ 과세
storage★ 보관
weight★ 중량(문자열)
origin_raw / origin★ 원산지 세부 / ☆ 원산지
brand★ 브랜드
grade / order_unit☆ 등급 / ☆ 주문배수
category1~3승계 표시용(양식 X)
use_yn / open_yn / sold_out후보 범위 필터 🟠
net_price / unit_price가격(공급사 입력/시스템)
유사도 비교 상대 = 이 테이블의 행(히어로). 매칭 필드 대부분이 varchar 원문이라 정규화는 양쪽(입력·히어로)에 동일하게 적용해야 함(§4R). 후보 범위(use_yn·타 공급사 포함 여부)는 🟠.
seller_product_group_image READ
main_product_name + picture_no그룹명 키
file_path / file_name이미지
picture_grade / del_yn대표·삭제
이미지 승계 원천(§6). 키가 main_product_name이라 매칭 히어로의 그룹명으로 조회.
seller_product_group_mscd READ
group_id그룹
main_product_name그룹명
category1~3 / mscd분류
그룹 ↔ 분류 축. NEW 행의 그룹 생성·분류 지정 규칙은 🟠(§9 그룹 생성 로직).
seller_product_request 1 ─< seller_product_request_item  ·  item.match_candidates[ ] ─ (hero_product_code, score) ─> seller_product
request_item(★6 ☆3 정규화) ≈ sim(·) seller_product(★6 ☆3 정규화) → score ∈ [0,1] → AUTO / CANDIDATE / NEW
확정 시 item.hero_product_code = 선택 히어로 · item.group_id = 그 히어로의 group_id(편입 정책 🟠) · 이미지 = seller_product_group_image(main_product_name)

양식 v3 ↔ 기존 컬럼 대응표 (request_item · seller_product)

v2 대응표를 승계하되 그룹 상품명·대분류 제거, 원산지 세부사항 필수 이동을 반영. 컬럼명은 개발DB 스키마 실측(두 테이블 모두 같은 이름 사용). ★유사도 필수 · ☆유사도 선택 · —유사도 미사용.

#양식 v3 열구분유사도request_item / seller_product 컬럼비고
1유통사 상품코드필수—product_code공급사 식별 코드
2유통사 상품명필수—original_product_name공급사 표기명
3net공급가필수—net_pricenet_unit_price는 파생/시스템 🟠 · 단가 unit_price 미노출
4★ 상품명필수★product_name토큰 유사도 주축
5★ 과세여부필수★taxationenum 정규화(과세/면세) 후 등가
6★ 보관상태필수★storageenum 정규화(냉장/냉동/상온) 후 등가
7★ 중량필수★weight원문 저장 + norm_json.weight(수치·기준단위)
8★ 원산지 세부사항필수(승격)★origin_raw예 "제주" · v2 선택 → v3 필수 ✅. origin과 분리 유지 여부 §4R 표
9★ 브랜드필수★brand예 "제주농협"
10☆ 원산지선택☆origin예 "국내산" · 있으면 반영
11☆ 등급선택☆grade있으면 반영
12☆ 주문 배수 단위선택☆order_unitint · 있으면 등가 비교
13배송예정일수선택—expected_arrival_dday"D-1"
14배송유형선택—delivery_typeMFC/허브 ↔ 코드값 매핑 🟠
15외부 적재선택—outside_flag가능/불가능 → bool
16최소 주문 수량선택—min_order_quantityint
17라벨용 엑셀 다운로드 수량 단위선택—unit_quantity_per_labelint
18제조사(생산자)선택—producer—
19수량선택—quantity입수
20할인기준가선택—discounted_base_price🟠 필요 여부(§9)
21상품상세선택—product_detailtext
22색상선택—color—
23상품단위선택—sell_unit—
24크기선택—size—
25수치선택—measurement_size—
26특성선택—feature예 "10~11과" · v2부터 매칭 제외
27일시품절 여부선택—sold_outbool
28장기품절 여부선택—long_term_sold_outbool
29재고수량선택—total_available_quantity (seller_product만)request_item에 컬럼 없음 → 🆕 추가 필요
30수량제한선택—order_limit (seller_product만)request_item에 컬럼 없음 → 🆕 추가 필요
31단위당 상품 개수선택—quantity_per_unitint
32총량피킹선택—확인 못 함대응 컬럼을 스키마에서 특정 못 함(is_mfc_stock_item·packing_id 후보) 🟠
—그룹 상품명제거—main_product_name양식·매핑 제거. 컬럼은 잔존(확정 히어로의 그룹명을 시스템이 채움 — 편입 정책 🟠)
—대분류제거—category1양식·매핑·드롭다운 제거 ✅. AUTO/CANDIDATE는 히어로 값 승계, NEW는 MD 검수 시 지정 🟠
03

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·타 공급사 포함) · 프리필터 인덱스 채택 여부. 물리 컬럼명은 구현 시 확정.

04

매칭 파이프라인 (v3 · 유사도 점수)

검증 → 정규화 → 후보 프리필터 → 히어로별 점수 → 최고 점수로 분기. 임계 ✅확정 · 산식 🟠개발 결정.

  1. 검증(ERROR 선별) — 필수 9열 빈값·과세/보관 enum 미매핑·중량 단위 없음·net공급가 비숫자·주문 배수 0/음수 → match_state='ERROR' + error_code. 매칭 시도 없이 제외(오류행만 재업로드).
  2. 정규화(입력 행 + 히어로 양쪽 동일) — 상품명 토큰화(공백·괄호·콤마 분리, 소문자, 특수문자 제거) · 과세/보관 enum 코드화 · 중량 "1KG/1kg/1,000g/1000 G" → 1000 G(계열 변환) · 원산지 세부/브랜드/원산지/등급 trim·공백 제거 · 주문 배수 int. 결과는 norm_json에 스냅샷(§4R 정규화 표 재사용).
  3. 후보 프리필터(🟠 성능) — 전 히어로와 N×M을 계산하지 않도록 비교 대상을 먼저 줄임. 후보안: 과세·보관 등가 AND 상품명 trigram 유사도 하한(예: product_name % 입력명) → 수십~수백 건. 프리필터 없이 전건 계산할지, 하한을 얼마로 둘지는 🟠(§9).
  4. 점수 산출 — 프리필터 후보 각각에 대해 ★6 + ☆3(있을 때) 필드별 유사도 → 가중 평균 score ∈ [0,1]. score 내림차순 상위 N(제안 5)을 match_candidates에, 최고값을 match_score에.
  5. 분기(임계 ✅) — 아래 표. AUTO는 hero_product_code를 시스템이 세팅하고 후보 미노출. CANDIDATE는 후보 리스트를 노출해 MD가 선택(공급사 결과 화면에서 선택 허용 여부 🟠). NEW는 신규(그룹 처리 🟠).
1.0 → AUTO
★6(+☆) 전부 정규화 후 일치. 자동 매칭 · 히어로 확정 · 후보 미노출 · 검수 대기(자동 승인 🟠)
0.75 ~ 0.99 → CANDIDATE
후보 리스트(상위 N) + '무엇이 다른가' 하이라이트. MD가 후보 중 선택 또는 NEW 전환
≤ 0.74 → NEW
기존 히어로 없음으로 판정. 신규 등록(이미지 직접 · 분류/그룹은 MD 검수 시 🟠)
AUTO
match_score = 1.000. 동점 1.0 후보가 2개 이상이면(같은 값 히어로 중복) 자동 확정하지 않고 CANDIDATE로 강등해 사람이 고르게 함(제안 · 🟠). 승계값(카테고리·그룹·이미지)은 확정 히어로에서.
CANDIDATE
0.750 ≤ match_score ≤ 0.990. match_candidates 상위 N 저장. 검수 화면에서 후보별 필드 점수·diff 표시. 후보 중 맞는 게 없으면 NEW 전환.
NEW
match_score ≤ 0.740 또는 프리필터 후보 0. 감사용으로 최고 후보 1~2건은 JSON에 남길 수 있음(제안). 새 히어로 생성 → 새 그룹인지 기존 그룹 편입인지 🟠.
ERROR
검증 실패. error_code + 기존 fail_reason(사람용 문구). 제출 대상에서 제외, 수정 후 재업로드.
v2 → v3 매칭 차이v2: 그룹 상품명 LIKE + 대분류로 후보군 → 조건값 9개로 좁힘 → 후보 수(1/≥2/0)로 분기. v3: 그룹명·대분류 없이 행 ↔ 히어로 점수를 내고 점수 임계로 분기. "후보 수"가 아니라 "최고 점수"가 기준이므로 후보가 여럿이어도 1.0이 유일하면 AUTO, 1개뿐이어도 0.8이면 CANDIDATE.
단건 등록 · 자동채움 모달 — 2차v2의 "단건 등록 모달에서 그룹 검색 → 그룹값 자동채움(승계·수정 불가) → net공급가·공급사 상품명/코드만 입력" 설계는 2차 범위로 이동합니다. 2차에서 단건 API는 같은 유사도 함수를 동기 호출(행 1건)하는 형태가 자연스럽고, 승계·수정 불가 정책은 그대로 승계할 전제입니다.
4R

유사도 점수제 설계 — 필드 · 산식 · 개발 결정 항목

v2 §4R(그룹명 LIKE·match_key·자동 통과 문턱)을 전면 교체. 임계·필드는 확정, 산식은 개발 결정.

🗣 2026-09-10 회의 결정 · 이 섹션의 확정/미확정확정: 점수제 채택 · 임계 1.0 / 0.75~0.99 / ≤0.74 · 유사도 필드 ★6 ☆3 · 유사도 미사용 필수 3 · 그룹명/대분류 제거 · 원산지 세부사항 필수. 개발 결정(아래 표): 토큰화 규칙 · 필드 가중치 · 문자열 거리 함수 · 선택 필드 결측 처리 · 반올림 · 후보 상한 · 프리필터.

유사도 계산 필드 — ★필수 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_unitint 등가 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 1212 · EA★ 중량(수량계)
면세 비과세 / 과세TAX_FREE / TAXABLE★ 과세여부
냉장 냉장보관 / 냉동 / 상온 실온COLD / FROZEN / ROOM★ 보관상태
제주 농협 제주농협(주)제주농협★ 브랜드
국산 국내산KR☆ 원산지
정규화 규칙의 위치스키마가 아니라 서비스 코드 + 단위 변환표 + 동의어 사전에 삽니다. v2에서 제안했던 product_group.match_key·정규화 컬럼 백필은 폐기 — 히어로 쪽 정규화는 매칭 시점에 온더플라이(프리필터 후보에만) 하거나, 성능이 안 나오면 seller_product에 정규화 캐시 컬럼을 두는 것을 2차 옵션으로 남깁니다.

입력 통제 (v2 승계 · v3 조정)

양식에서 막는 것

과세여부·보관상태 = 드롭다운. 중량은 "숫자+단위" 한 칸(양식 v3 실물 기준, 예 1KG) — 단위 없으면 ⛔. 대분류 드롭다운 없음(제거).

필수 강제로 얻는 것

★6이 전부 필수라 score 계산에 결측이 없음(☆만 결측 가능). 원산지 세부사항 필수 승격이 그 이유.

한계 · 오매칭 방지

점수제의 함정① 상품명 가중치가 크면 "감귤 1kg 제주" vs "감귤 1kg 창원"이 0.85로 CANDIDATE — 의도된 동작(사람이 고름). ② 임계 0.75는 가중치가 정해져야 의미가 생김 — 9/17 스테이지에서 실데이터로 분포를 보고 조정. ③ 자동(1.0)은 정규화 후 전부 일치라서 오매칭 위험은 낮지만, 히어로 데이터 자체가 중복이면 동점 다수 → 강등 규칙 필요.
  1. 롤백 안전장치 — AUTO도 hero_product_code 재지정으로 되돌릴 수 있게(MD 사후 수정). match_candidates에 감사 스냅샷 유지.
  2. 튜닝 루프 — 스테이지 업로드 → match_score 분포 → 가중치·프리필터 하한 조정. 설정값으로 두면 재배포 없이 가능.
  3. 2차 확장 — 단건 API 동기 호출 · 정규화 캐시 컬럼 · 자동 승인.
05

엑셀 양식 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 표.

단위 변환/분기 규칙 (v2 승계)같은 계열만 상호변환: 중량계 {G,KG}, 용량계 {ML,L}, 수량계 {EA}. 계열이 다르면 중량 필드 s=0(1KG ≠ 1L). 변환 후 기준단위(G·ML·EA)로 비교. 단위가 없거나 계열 미상이면 ⛔ unit_missing.

대량 업로드 = 비동기 배치 매칭 (유지 · 성능 🟠)

🟠 유사도 N×M 비용v2는 "이름 LIKE + 카테고리 인덱스 조회라 N×M이 아니다"였지만, v3 점수제는 정의상 행 N × 히어로 M 비교입니다. 그대로면 1,000행 × 전체 히어로는 배치라도 무겁습니다 → 후보 프리필터(§4R)가 필수 전제. 프리필터 방식·하한·워커/청크 크기는 미확정.
  1. 업로드·접수 — 파일 저장 + seller_product_request(status=QUEUED, total_count) + 행 파싱·검증(ERROR 즉시 확정) 후 즉시 응답(요청 id).
  2. 비동기 매칭 워커 — status=MATCHING. 청크 단위로 정규화 → 프리필터 → 점수 → 분기, matched_count 증가 → 진행률.
  3. 완료 알림 — status=MATCHED + matched_at. 공급사/MD 알림. 화면은 폴링 또는 푸시 🟠.
  4. 결과 확인 — AUTO / CANDIDATE / NEW / ERROR 탭. CANDIDATE 후보 선택(공급사 화면에서 허용 여부 🟠 — 회의 문구는 "MD 선택"), NEW 확인, ERROR 재업로드.
  5. [MD 검수로 제출] — 제출 전 검증: CANDIDATE 미선택 행·NEW 필수 미입력 행이 있으면 제출 불가(ERROR는 제외 대상). status=SUBMITTED + submitted_at → 이력·검수 큐 등재.
다 판매 등록 아님 — 오해 주의 (v2 승계)AUTO만 등록되는 게 아닙니다. CANDIDATE(선택 완료)·NEW(필수 입력 완료)도 이번 제출분에 함께 검수로 넘어가고, 제외되는 건 ERROR뿐. 승인된 행만 seller_product에 등록.
06

이미지 자동 승계

매칭 히어로의 그룹 이미지를 따온다. NEW만 직접 업로드(1차 = 행별 image1~3_path · Zip 일괄은 2차).

  1. 승계 대상 — match_state ∈ {AUTO, CANDIDATE→확정}이고 hero_product_code가 정해진 행.
  2. 그룹 이미지 조회 — 확정 히어로의 main_product_name으로 seller_product_group_image WHERE main_product_name = ? AND del_yn <> 'Y' ORDER BY picture_grade, picture_no. 공급사가 이미지를 안 올려도 됨.
  3. 승계 방식 — 참조(그룹명 키 조인) vs 복사(image1~3_path에 스냅샷). 🟠 미정(v2 승계). 기존 행 테이블에 image1~3_path가 있어 복사가 구조상 쉬움.
  4. NEW만 직접 — 그룹이 없으므로 직접 업로드. 신규 그룹 생성 여부(🟠)에 따라 그룹 대표 이미지가 될지 결정.
07

상태 전이

행 매칭(match_state 4종 · 임계 매핑) + 행 검수(review_state) + 배치(request.status).

행 매칭 상태(match_state) ↔ 임계

🟢 AUTO = 1.0· 🟡 CANDIDATE 0.75~0.99· 🔴 NEW ≤ 0.74· ⛔ ERROR 검증 실패
상태진입 조건사람 개입다음
🟢 AUTOmatch_score = 1.000 (1.0 유일)없음(후보 미노출·히어로 자동 확정)→ 검수 대기(PENDING) · 자동 승인 🟠
🟡 CANDIDATE0.750 ≤ score ≤ 0.990 (또는 1.0 동점 다수)후보 중 선택(MD · 공급사 허용 🟠)선택 → 검수 대기 / 맞는 게 없음 → NEW 전환
🔴 NEWscore ≤ 0.740 또는 후보 0필수 확인 · 이미지 입력신규 히어로 생성(그룹 처리 🟠) → 검수 대기
⛔ ERROR필수 누락 · enum 미매핑 · 단위 없음 · 비숫자수정제출 제외 → 재업로드
검수는 매칭과 별개 축(review_state · v2 승계)AUTO도 검수 대기(PENDING)로 들어감 — 자동 승인 정책 🟠, 현재는 전부 검수. 행별 PENDING → APPROVED / REJECTED(사유), 수정 후 승인은 값 편집 후 APPROVED. 기존 seller_product_request의 approved_by/at·reject_reason은 요청 단위 집계로 병행.
PENDING 검수 대기→ APPROVED 승인 → seller_product 등록/ REJECTED 거절(사유)

배치 상태(request.status)

QUEUED 접수→ MATCHING 매칭중(진행률)→ MATCHED 완료(알림)→ SUBMITTED 검수로 제출

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)은 🟠 데이터소스.

08

인덱스 · 마이그레이션 · 일정

ALTER 중심. product_group 백필·match_key 인덱스는 폐기. 개발 9/16 · QA 9/17.

인덱스(초안)

테이블인덱스용도
seller_productgin (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)검수 큐
product_groupmatch_key · trigram(group_prod_name) · categoryv2 설계 폐기(테이블 없음 · 방식 변경)

마이그레이션 순서

  1. enum · ALTER — match_state 타입 · seller_product_request/_item 컬럼 추가(§3). 기존 데이터에 영향 없음(전부 nullable/default).
  2. 정규화·유사도 서비스 — 토큰화 · 단위 변환표 · 동의어 사전 · 가중치 설정값. 입력·히어로 공용 함수.
  3. 프리필터 인덱스 — pg_trgm 확장 가능 여부 확인 후 채택(🟠). 불가 시 과세·보관 등가 + 상품명 첫 토큰 prefix 등 대안.
  4. 배치 워커 — QUEUED→MATCHING→MATCHED, 진행률, 알림.
  5. 양식 v3 배포 — 필수 9 · 선택 23 · 과세/보관 드롭다운 · 대분류 드롭다운 없음.
  6. 스테이지 튜닝(9/17) — 실데이터 업로드로 score 분포 확인 → 가중치·프리필터 하한 조정.

일정

일자마일스톤산출
9/16 (화)개발ALTER · 유사도 서비스 · 배치 워커 · 결과/검수 화면 · 양식 v3
9/17 (수)QA · 스테이지스테이지 업로드 · score 분포 · 임계/가중치 튜닝 · 검수 흐름 QA
09

미정(🟠) · 오픈 이슈

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 승계)—경미
확정 (2026-09-10)매칭 = 단어 유사도 점수제 · 임계 1.0 / 0.75~0.99 / ≤0.74 · 유사도 필드 ★6(상품명·과세·보관·중량·원산지 세부·브랜드) + ☆3(원산지·등급·주문 배수) · 유사도 미사용 필수 3(유통사 코드·유통사 상품명·net공급가) · 원산지 세부사항 필수 · 대분류 소멸 · 그룹 상품명 제거 · 1차 = 엑셀 업로드만(단건 2차) · 비동기 배치·제출·행별 검수 흐름 유지 · 개발 9/16 · QA 9/17. v2에서 승계된 확정: 화면 분리·권한 분기 · 어드민 신규 메뉴 공존 · 가격(공급사 입력 = net공급가·유통사 상품명/코드, 단가 미노출) · 승계값 수정 불가 · 하위 행 복제 제거 · 등록 이력 부분집계.
—

변경 이력

문서 버전별 핵심 변경.

버전일자변경
v32026-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 재정리
v22026-09-02스테이지 실화면·실양식(260901) 대조 + 개발팀 회의 반영. 그룹 상품명 LIKE + 대분류 1차 · 조건값 9개 보조 · 그룹값 자동채움·수정 불가 · 가격 반전(net공급가 입력) · 비동기 배치 · 개별 행 검수
v13(프로토)2026-08히어로코드 단건 매칭 · 자동통과/확인/신규/오류 + 검수 보류/반려/승인/수정후승인
OrderHero · MD 공급사 상품 등록 개발문서 v3 (ERD · 유사도 매칭) · 2026-09-10 개발 회의 반영 · v2 → v3 · rev 2026-09-10
기존 테이블·컬럼명 = 개발DB 스키마 실측(컬럼·타입·인덱스만, 데이터 수치 없음) · 추가 컬럼·enum·JSON·산식 후보 = 설계 표기(물리 스키마는 구현 시 확정) · 참고 v13: md-supplier-product.pages.dev