# 제품 3단계 분류 체계 > 한약재는 **성분 → 제품 → 로트** 3단계로 식별된다. > 같은 성분코드의 약재도 제조사, 형태, 산지, 등급에 따라 품질과 가격이 크게 다르다. --- ## 1. 3단계 구조 ``` [1단계] 성분 (herb_masters) — 약재의 본질 └─ ingredient_code: 3002H1AHM = "갈근" │ [2단계] 제품 (herb_items) — 도매상별 상품 └─ insurance_code: 062401050 = "휴먼갈근" (주식회사휴먼허브) │ [3단계] 로트 (inventory_lots) — 입고 건별 실물 └─ lot_id: 190 = "갈근.각" (한국산, ₩17/g) ``` ### 테이블 매핑 | 단계 | 테이블 | PK | 식별키 | 행수 | 예시 | |------|--------|-----|-------|------|------| | 성분 | `herb_masters` | herb_id | `ingredient_code` | 454 | 갈근 (3002H1AHM) | | 제품 | `herb_items` | herb_item_id | `insurance_code` | ~30 | 휴먼갈근 (062401050) | | 로트 | `inventory_lots` | lot_id | receipt_line_id | ~30 | 갈근.각 (한국, ₩17) | ### herb_items 컬럼 역할 | 컬럼 | 실제 내용 | 예시 | |------|----------|------| | `herb_name` | 제품명 (품명) | "휴먼갈근" | | `insurance_code` | 보험코드 (=product_code) | "062401050" | | `ingredient_code` | 성분코드 (herb_masters FK) | "3002H1AHM" | | `specification` | 제조사명 | "주식회사휴먼허브" | --- ## 2. 로트 세부 분류 (display_name + lot_variants) ### 2-1. `inventory_lots.display_name` **엑셀 입고 시 NULL로 들어감**. AI가 쇼핑몰/카탈로그 정보를 참고하여 사후에 채워넣는 값. 도매상 카탈로그의 실제 품명으로, 같은 제품(herb_items)이라도 로트마다 다를 수 있다: | herb_name (제품) | display_name (로트) | 차이점 | |-----------------|-------------------|--------| | 휴먼건강 | 건강 | 페루산, ₩12/g | | 휴먼건강 | 건강.土 | 한국산(토종), ₩51/g | | 휴먼일당귀 | 일당귀(한국산) | 한국산, ₩19/g | | 휴먼일당귀 | 일당귀.中(1kg) | 중국산 중품, ₩13/g | ### 2-2. `lot_variants` 테이블 (파싱 결과) `display_name`을 구조화된 필드로 파싱한 결과를 저장: | 컬럼 | 용도 | 파싱 예시 | |------|------|----------| | `raw_name` | 원본 품명 | "건강.土" | | `form` | 형태 | 각(角), 片(편), 절편, 통 | | `processing` | 포제/가공 | 초(炒), 자(炙), 酒炙, 9증 | | `selection_state` | 선별/원산 | 土(토종), 正, 中, 재배, 야생 | | `grade` | 등급 | 1호, 특, 名品, 소(小) | | `age_years` | 연근 | 4, 6 (년근) | --- ## 3. display_name 명명 패턴 (도매상 기준) ### 기본 구조 ``` 약재명.형태[등급](포장단위) 약재명.선별<유통경로>(포장단위)[비고] ``` ### 실제 패턴 분석 (30개 로트) | display_name | 약재 | form | processing | selection | grade | |-------------|------|------|-----------|-----------|-------| | `갈근.각` | 갈근 | 각(角) | - | - | - | | `감초.1호[야생](1kg)` | 감초 | - | - | 야생 | 1호 | | `건강` | 건강 | - | - | - | - | | `건강.土` | 건강 | - | - | 土(토종) | - | | `길경.片[특]` | 길경 | 片(편) | - | - | 특 | | `세신.中` | 세신 | - | - | 中(중품) | - | | `백출.당[1kg]` | 백출 | - | - | 당(當) | - | | `작약주자.土[酒炙]` | 작약주자 | - | 酒炙 | 土(토종) | - | | `숙지황(9증)(신흥.1kg)[완]` | 숙지황 | - | 9증 | - | 완 | | `육계.YB` | 육계 | - | - | YB | - | | `진피.비열[非熱](1kg)` | 진피 | - | 非熱(비열) | - | - | | `창출[북창술.재배](1kg)` | 창출 | - | - | 재배 | - | | `천궁.일<토매지>(1kg)` | 천궁 | - | - | 일(日) | - | | `황기(직절.小)(1kg)` | 황기 | 직절 | - | - | 小 | | `용안육.名品(1kg)` | 용안육 | - | - | - | 名品 | | `오미자<토매지>(1kg)` | 오미자 | - | - | - | - | | `전호[재배]` | 전호 | - | - | 재배 | - | | `지황.건[회](1kg)` | 지황 | - | 건(乾) | 회(灰) | - | ### 구분자 규칙 | 구분자 | 의미 | 예시 | |--------|------|------| | `.` | 주 속성 구분 | `건강.土` → 토종 | | `[...]` | 부가 정보/등급 | `길경.片[특]` → 특등 | | `(...)` | 포장/가공/산지 | `대추(절편)(1kg)` | | `<...>` | 유통경로 | `오미자<토매지>(1kg)` | --- ## 4. AI가 display_name / lot_variants를 채우는 절차 ### 언제 실행하는가 1. 엑셀 입고 완료 후 (`inventory_lots.display_name = NULL`) 2. 도매상 쇼핑몰에서 해당 품목 정보를 AI에게 제공 3. AI가 정보를 파싱하여 `display_name` + `lot_variants` 업데이트 ### Step 1: NULL인 로트 확인 ```sql SELECT il.lot_id, h.herb_name, h.insurance_code, il.origin_country, il.unit_price_per_g, il.quantity_received FROM inventory_lots il JOIN herb_items h ON il.herb_item_id = h.herb_item_id WHERE il.display_name IS NULL AND il.is_depleted = 0; ``` ### Step 2: 쇼핑몰/카탈로그 정보 참고 사용자가 도매상 쇼핑몰에서 해당 제품의 상세 품명을 제공하면, AI가 **가격, 단가, 원산지, 포장단위** 등을 교차 참고하여 올바른 로트에 매칭. 참고 가능한 매칭 단서: - **보험코드** (insurance_code) — 제품 특정 - **원산지** (origin_country) — 같은 제품의 로트 구분 - **단가** (unit_price_per_g) — 등급/선별 구분 (土 > 일반, 한국산 > 중국산) - **수량** (quantity_received) — 포장 단위 매칭 ### Step 3: display_name 업데이트 ```sql UPDATE inventory_lots SET display_name = '건강.土' WHERE lot_id = 193; ``` ### Step 4: lot_variants 파싱 결과 저장 ```sql INSERT INTO lot_variants (lot_id, raw_name, form, processing, selection_state, grade, parsed_method) VALUES (193, '건강.土', NULL, NULL, '土', NULL, 'ai_parsing'); ``` ### Step 5: 검증 ```sql SELECT il.lot_id, il.display_name, h.herb_name, il.origin_country, lv.form, lv.processing, lv.selection_state, lv.grade FROM inventory_lots il JOIN herb_items h ON il.herb_item_id = h.herb_item_id LEFT JOIN lot_variants lv ON il.lot_id = lv.lot_id WHERE il.lot_id = 193; ``` --- ## 5. 활용처 | 화면 | 사용 값 | 표시 예시 | |------|---------|----------| | 입고장 상세 | `display_name` | 갈근.각, 건강.土 | | 재고 상세 모달 | `display_name` + `origin_country` | 건강.土 (한국) | | 조제 시 로트 선택 | `display_name` + `unit_price` | 건강.土 ₩51/g vs 건강 ₩12/g | | 재고 원장 | `display_name` | 입출고 이력에 로트 구분 | --- ## 6. 주의사항 1. **엑셀 입고 로직은 수정하지 않는다** — `display_name`은 항상 NULL로 입고 2. **AI가 사후에 채운다** — 쇼핑몰 정보 기반, `parsed_method = 'ai_parsing'` 3. **같은 herb_item_id에 여러 display_name 가능** — 로트마다 다른 실물이므로 정상 4. **lot_variants 파싱이 안 되어도 display_name만으로 구분 가능** — 파싱은 선택적 --- *이 문서는 kdrug 시스템의 약재 3단계 분류 체계와 AI 기반 로트 분류 절차를 정의합니다.* *최종 수정: 2026-02-18*