kdrug-inventory-system/apply_variants_to_lots.py
시골약사 d6410fa273 test: variant 시스템 적용 테스트 스크립트 추가
- inventory_lots 현황 확인
- supplier_product_catalog 샘플 데이터 추가
- 가격 기반 매칭 테스트
- variant 속성 파싱 로직 검증

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-16 16:06:55 +00:00

428 lines
14 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
기존 inventory_lots에 variant 정보 적용
"""
import sqlite3
import json
from datetime import datetime
def get_connection():
"""데이터베이스 연결"""
return sqlite3.connect('database/kdrug.db')
def check_current_lots():
"""현재 inventory_lots 상태 확인"""
conn = get_connection()
cursor = conn.cursor()
print("\n" + "="*80)
print("현재 입고된 Inventory Lots 현황")
print("="*80)
cursor.execute("""
SELECT
l.lot_id,
l.herb_item_id,
h.herb_name,
h.insurance_code,
l.quantity_onhand,
l.unit_price_per_g,
l.origin_country,
l.received_date,
s.name as supplier_name,
l.display_name
FROM inventory_lots l
JOIN herb_items h ON l.herb_item_id = h.herb_item_id
JOIN purchase_receipt_lines prl ON l.receipt_line_id = prl.line_id
JOIN purchase_receipts pr ON prl.receipt_id = pr.receipt_id
JOIN suppliers s ON pr.supplier_id = s.supplier_id
ORDER BY l.received_date DESC, h.herb_name
""")
lots = cursor.fetchall()
print(f"\n{len(lots)}개의 로트가 있습니다.\n")
for lot in lots[:10]: # 처음 10개만 출력
print(f"Lot #{lot[0]}: {lot[2]} (보험코드: {lot[3]})")
print(f" - 재고: {lot[4]:.1f}g, 단가: {lot[5]:.1f}원/g")
print(f" - 원산지: {lot[6]}, 공급처: {lot[8]}")
print(f" - display_name: {lot[9] if lot[9] else '없음'}")
print()
conn.close()
return lots
def insert_sample_catalog_data():
"""공급처 카탈로그 샘플 데이터 추가"""
conn = get_connection()
cursor = conn.cursor()
print("\n" + "="*80)
print("공급처 카탈로그 샘플 데이터 추가")
print("="*80)
# 휴먼허브 supplier_id 조회
cursor.execute("SELECT supplier_id FROM suppliers WHERE name = '(주)휴먼허브'")
supplier_id = cursor.fetchone()
if not supplier_id:
print("휴먼허브 공급처를 찾을 수 없습니다.")
conn.close()
return
supplier_id = supplier_id[0]
# 휴먼허브 실제 가격 기반 데이터 (원산지별로 구분)
catalog_items = [
('신흥숙지황(9증)', 20.0, '1kg'),
('휴먼갈근[한국산]', 16.8, '1kg'),
('휴먼감초', 22.1, '1kg'),
('휴먼건강[페루산]', 12.4, '1kg'),
('휴먼건강.土[한국산]', 51.4, '1kg'),
('휴먼계지', 5.8, '1kg'),
('휴먼구기자(영하)', 17.9, '1kg'),
('휴먼길경.片', 10.6, '1kg'),
('휴먼대추(한국산)', 20.0, '1kg'),
('휴먼마황', 9.6, '1kg'),
('휴먼반하(생강백반제)', 33.7, '1kg'),
('휴먼백출', 11.8, '1kg'),
('휴먼복령', 11.5, '1kg'),
('휴먼석고', 4.7, '1kg'),
('휴먼세신.中', 129.0, '1kg'),
('휴먼오미자<토매지>', 17.5, '1kg'),
('휴먼용안육', 20.7, '1kg'),
('휴먼육계', 14.6, '1kg'),
('휴먼일당귀', 12.9, '1kg'),
('휴먼자소엽.土', 13.8, '1kg'),
('휴먼작약', 18.7, '1kg'),
('휴먼작약(주자.酒炙)', 24.6, '1kg'),
('휴먼전호[재배]', 14.0, '1kg'),
('휴먼지각', 10.0, '1kg'),
('휴먼지황.건', 11.5, '1kg'),
('휴먼진피', 13.7, '1kg'),
('휴먼창출[북창출]', 13.5, '1kg'),
('휴먼천궁.일', 11.9, '1kg'),
('휴먼황기(직절.小)', 9.9, '1kg'),
]
# 기존 데이터 삭제
cursor.execute("DELETE FROM supplier_product_catalog WHERE supplier_id = ?", (supplier_id,))
# 새 데이터 삽입
for item in catalog_items:
raw_name, unit_price, package_unit = item
try:
cursor.execute("""
INSERT INTO supplier_product_catalog
(supplier_id, raw_name, unit_price, package_unit, stock_status, last_updated)
VALUES (?, ?, ?, ?, '재고있음', date('now'))
""", (supplier_id, raw_name, unit_price, package_unit))
print(f" 추가: {raw_name} - {unit_price:.1f}원/g")
except sqlite3.IntegrityError:
print(f" 중복: {raw_name} (이미 존재)")
conn.commit()
# 추가된 항목 수 확인
cursor.execute("SELECT COUNT(*) FROM supplier_product_catalog WHERE supplier_id = ?", (supplier_id,))
count = cursor.fetchone()[0]
print(f"\n{count}개의 카탈로그 항목이 등록되었습니다.")
conn.close()
def match_lots_with_catalog():
"""가격 기반으로 lot과 카탈로그 매칭"""
conn = get_connection()
cursor = conn.cursor()
print("\n" + "="*80)
print("가격 기반 Lot-Catalog 매칭")
print("="*80)
# 한의사랑에서 입고된 lot들 조회
cursor.execute("""
SELECT DISTINCT
l.lot_id,
h.herb_name,
l.unit_price_per_g,
s.supplier_id,
s.name as supplier_name
FROM inventory_lots l
JOIN herb_items h ON l.herb_item_id = h.herb_item_id
JOIN purchase_receipt_lines prl ON l.receipt_line_id = prl.line_id
JOIN purchase_receipts pr ON prl.receipt_id = pr.receipt_id
JOIN suppliers s ON pr.supplier_id = s.supplier_id
WHERE s.name = '한의사랑'
AND l.display_name IS NULL
""")
lots = cursor.fetchall()
matched_count = 0
for lot in lots:
lot_id, herb_name, unit_price, supplier_id, supplier_name = lot
# 가격 기반 매칭 (±0.5원 허용)
cursor.execute("""
SELECT raw_name, unit_price
FROM supplier_product_catalog
WHERE supplier_id = ?
AND ABS(unit_price - ?) < 0.5
""", (supplier_id, unit_price))
matches = cursor.fetchall()
if matches:
if len(matches) == 1:
# 정확히 1개 매칭
raw_name = matches[0][0]
# display_name 업데이트
cursor.execute("""
UPDATE inventory_lots
SET display_name = ?
WHERE lot_id = ?
""", (raw_name, lot_id))
# lot_variants 생성
cursor.execute("""
INSERT OR REPLACE INTO lot_variants
(lot_id, raw_name, parsed_at, parsed_method)
VALUES (?, ?, datetime('now'), 'price_match')
""", (lot_id, raw_name))
print(f" 매칭 성공: Lot #{lot_id} {herb_name} -> {raw_name}")
matched_count += 1
else:
# 여러 개 매칭 - 약재명으로 추가 필터링
print(f" 다중 매칭: Lot #{lot_id} {herb_name} (단가: {unit_price:.1f}원)")
for match in matches:
print(f" - {match[0]} ({match[1]:.1f}원/g)")
# 약재명이 포함된 것 선택
if herb_name in match[0]:
raw_name = match[0]
cursor.execute("""
UPDATE inventory_lots
SET display_name = ?
WHERE lot_id = ?
""", (raw_name, lot_id))
cursor.execute("""
INSERT OR REPLACE INTO lot_variants
(lot_id, raw_name, parsed_at, parsed_method)
VALUES (?, ?, datetime('now'), 'price_herb_match')
""", (lot_id, raw_name))
print(f" -> 선택: {raw_name}")
matched_count += 1
break
else:
print(f" 매칭 실패: Lot #{lot_id} {herb_name} (단가: {unit_price:.1f}원)")
conn.commit()
print(f"\n{matched_count}/{len(lots)}개의 로트가 매칭되었습니다.")
conn.close()
return matched_count
def parse_variant_attributes():
"""raw_name에서 variant 속성 파싱"""
conn = get_connection()
cursor = conn.cursor()
print("\n" + "="*80)
print("Variant 속성 파싱")
print("="*80)
cursor.execute("""
SELECT variant_id, raw_name
FROM lot_variants
WHERE form IS NULL AND processing IS NULL
""")
variants = cursor.fetchall()
for variant_id, raw_name in variants:
# 기본 파싱 로직
form = None
processing = None
selection_state = None
grade = None
# 형태 파싱 (각, 片, 절편, 직절, 土 등)
if '.각' in raw_name:
form = ''
elif '.片' in raw_name or '[片]' in raw_name:
form = ''
elif '절편' in raw_name:
form = '절편'
elif '직절' in raw_name:
form = '직절'
elif '.土' in raw_name:
form = ''
# 가공 파싱 (9증, 酒炙, 비열, 회 등)
if '9증' in raw_name:
processing = '9증'
elif '酒炙' in raw_name or '주자' in raw_name:
processing = '酒炙'
elif '비열' in raw_name or '非熱' in raw_name:
processing = '비열'
elif '생강백반제' in raw_name:
processing = '생강백반제'
elif '.건[회]' in raw_name or '[회]' in raw_name:
processing = ''
# 선별상태 파싱 (야생, 토매지, 재배 등)
if '야생' in raw_name:
selection_state = '야생'
elif '토매지' in raw_name:
selection_state = '토매지'
elif '재배' in raw_name:
selection_state = '재배'
elif '영하' in raw_name:
selection_state = '영하'
# 등급 파싱 (특, 名品, 中, 小, 1호, YB2, 당 등)
if '[특]' in raw_name or '.특' in raw_name:
grade = ''
elif '名品' in raw_name:
grade = '名品'
elif '.中' in raw_name:
grade = ''
elif '.小' in raw_name:
grade = ''
elif '1호' in raw_name:
grade = '1호'
elif 'YB2' in raw_name:
grade = 'YB2'
elif '.당' in raw_name:
grade = ''
elif '[완]' in raw_name:
grade = ''
# 업데이트
cursor.execute("""
UPDATE lot_variants
SET form = ?, processing = ?, selection_state = ?, grade = ?
WHERE variant_id = ?
""", (form, processing, selection_state, grade, variant_id))
attributes = []
if form: attributes.append(f"형태:{form}")
if processing: attributes.append(f"가공:{processing}")
if selection_state: attributes.append(f"선별:{selection_state}")
if grade: attributes.append(f"등급:{grade}")
if attributes:
print(f" {raw_name}")
print(f" -> {', '.join(attributes)}")
conn.commit()
conn.close()
def show_results():
"""최종 결과 확인"""
conn = get_connection()
cursor = conn.cursor()
print("\n" + "="*80)
print("Variant 적용 결과")
print("="*80)
cursor.execute("""
SELECT
l.lot_id,
h.herb_name,
l.display_name,
v.form,
v.processing,
v.selection_state,
v.grade,
l.unit_price_per_g,
l.quantity_onhand
FROM inventory_lots l
JOIN herb_items h ON l.herb_item_id = h.herb_item_id
LEFT JOIN lot_variants v ON l.lot_id = v.lot_id
WHERE l.display_name IS NOT NULL
ORDER BY h.herb_name, l.lot_id
""")
results = cursor.fetchall()
current_herb = None
for result in results:
lot_id, herb_name, display_name, form, processing, selection_state, grade, price, qty = result
if herb_name != current_herb:
print(f"\n[{herb_name}]")
current_herb = herb_name
print(f" Lot #{lot_id}: {display_name or herb_name}")
print(f" 재고: {qty:.0f}g, 단가: {price:.1f}원/g")
attributes = []
if form: attributes.append(f"형태:{form}")
if processing: attributes.append(f"가공:{processing}")
if selection_state: attributes.append(f"선별:{selection_state}")
if grade: attributes.append(f"등급:{grade}")
if attributes:
print(f" 속성: {', '.join(attributes)}")
# 통계
cursor.execute("""
SELECT
COUNT(DISTINCT l.lot_id) as total_lots,
COUNT(DISTINCT CASE WHEN l.display_name IS NOT NULL THEN l.lot_id END) as lots_with_display,
COUNT(DISTINCT v.lot_id) as lots_with_variants
FROM inventory_lots l
LEFT JOIN lot_variants v ON l.lot_id = v.lot_id
""")
stats = cursor.fetchone()
print("\n" + "-"*40)
print(f"전체 로트: {stats[0]}")
print(f"display_name 설정됨: {stats[1]}")
print(f"variant 정보 있음: {stats[2]}")
conn.close()
def main():
"""메인 실행 함수"""
print("\n" + "="*80)
print("Inventory Lots Variant System 적용")
print("="*80)
# 1. 현재 lot 상태 확인
lots = check_current_lots()
if not lots:
print("입고된 로트가 없습니다.")
return
# 2. 공급처 카탈로그 데이터 추가
insert_sample_catalog_data()
# 3. 가격 기반 매칭
matched = match_lots_with_catalog()
if matched > 0:
# 4. variant 속성 파싱
parse_variant_attributes()
# 5. 결과 확인
show_results()
print("\n완료!")
if __name__ == "__main__":
main()