feat: 한의사랑 카탈로그 import 스크립트 추가

- 마이페이지 데이터 파싱 및 DB 저장
- supplier_product_catalog 테이블 관리
- 가격 기반 매칭 시도 기능 포함

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
시골약사 2026-02-16 16:06:20 +00:00
parent 490553881f
commit 111c173692

View File

@ -0,0 +1,275 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
한의사랑 카탈로그 데이터 import 가격 매칭
"""
import sqlite3
import re
def get_connection():
"""데이터베이스 연결"""
return sqlite3.connect('database/kdrug.db')
def parse_catalog_data():
"""제공된 카탈로그 데이터 파싱"""
raw_data = """갈근.각5 배송중 42,000 400 0 롯데택배256733159384배송조회
감초.1[야생](1kg)5 배송중 110,500 0
건강10 배송중 62,000 600
건강.土3 배송중 77,100 750
계지5 배송중 14,500 100
구기자(영하)(1kg)3 배송중 53,700 510
길경.[]3 배송중 15,900 0
대추(절편)(1kg)5 배송중 100,000 1,000
마황(1kg)5 배송중 48,000 0
반하생강백반제(1kg)3 배송중 101,100 990
백출.[1kg]2 배송중 23,600 0
복령(1kg)5 배송중 57,500 550
석고[통포장](kg)4 배송중 18,800 160
세신.中3 배송중 193,500 0
숙지황(9)(신흥.1kg)[]5 배송중 100,000 1,000
오미자<토매지>(1kg)2 배송중 35,000 340
용안육.名품(1kg)3 배송중 62,100 600
육계.YB25 배송중 36,500 350
일당귀.(1kg)5 배송중 64,500 600
자소엽.土3 배송중 20,700 180
작약(1kg)3 배송중 56,100 540
작약주자.[酒炙]3 배송중 36,900 360
전호[재배]3 배송중 21,000 210
지각3 배송중 15,000 150
지황.[](1kg)1 배송중 11,500 110
진피.비열[非熱](1kg)5 배송중 68,500 0
창출[북창출.재배](1kg)3 배송중 40,500 0
천궁.<토매지>(1kg)3 배송중 35,700 330
황기(직절.)(1kg)3 배송중 29,700 270"""
items = []
for line in raw_data.split('\n'):
if not line.strip():
continue
# 택배 추적번호 제거
line = re.sub(r'롯데택배\d+배송조회', '', line)
parts = line.split('\t')
if len(parts) >= 4:
# 약재명 추출 (뒤의 수량 숫자 제거)
raw_name = re.sub(r'\d+$', '', parts[0])
# 가격 파싱 (콤마 제거)
total_price = int(parts[2].replace(',', ''))
# g당 단가
if len(parts) >= 5 and parts[4] != '0':
unit_price = int(parts[4].replace(',', ''))
else:
# g당 단가가 0이면 총액에서 계산 (1kg 기준)
if '1kg' in raw_name or 'kg' in raw_name:
unit_price = total_price / 1000
else:
unit_price = total_price / 1000 # 기본적으로 1kg로 가정
items.append({
'raw_name': raw_name.strip(),
'total_price': total_price,
'unit_price': unit_price,
'status': parts[1] if len(parts) > 1 else '배송중'
})
return items
def import_to_catalog():
"""카탈로그 데이터를 DB에 저장"""
conn = get_connection()
cursor = conn.cursor()
print("\n" + "="*80)
print("한의사랑 카탈로그 데이터 Import")
print("="*80)
# 한의사랑 supplier_id 조회
cursor.execute("SELECT supplier_id FROM suppliers WHERE name = '한의사랑'")
result = cursor.fetchone()
if not result:
# 한의사랑 공급처 생성
cursor.execute("""
INSERT INTO suppliers (name, is_active)
VALUES ('한의사랑', 1)
""")
supplier_id = cursor.lastrowid
print(f"한의사랑 공급처 생성 (ID: {supplier_id})")
else:
supplier_id = result[0]
print(f"한의사랑 공급처 확인 (ID: {supplier_id})")
# 기존 데이터 삭제
cursor.execute("DELETE FROM supplier_product_catalog WHERE supplier_id = ?", (supplier_id,))
# 카탈로그 데이터 파싱
items = parse_catalog_data()
print(f"\n{len(items)}개 항목을 파싱했습니다.")
print("-" * 60)
# 데이터 삽입
for item in items:
try:
cursor.execute("""
INSERT INTO supplier_product_catalog
(supplier_id, raw_name, unit_price, package_unit, stock_status, last_updated)
VALUES (?, ?, ?, '1kg', ?, date('now'))
""", (supplier_id, item['raw_name'], item['unit_price'], item['status']))
print(f"추가: {item['raw_name']:30s} | {item['unit_price']:8.1f}원/g | {item['status']}")
except sqlite3.IntegrityError:
print(f"중복: {item['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()
return items
def match_with_inventory():
"""현재 inventory_lots와 가격 매칭"""
conn = get_connection()
cursor = conn.cursor()
print("\n" + "="*80)
print("Inventory Lots와 가격 매칭")
print("="*80)
# 휴먼허브 inventory lots 조회
cursor.execute("""
SELECT
l.lot_id,
h.herb_name,
l.unit_price_per_g,
l.origin_country,
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 l.display_name IS NULL
ORDER BY h.herb_name, l.unit_price_per_g
""")
lots = cursor.fetchall()
print(f"\ndisplay_name이 없는 로트: {len(lots)}\n")
matched_count = 0
no_match = []
for lot in lots:
lot_id, herb_name, unit_price, origin, supplier = lot
# 한의사랑 카탈로그에서 비슷한 가격 찾기 (±10% 허용)
cursor.execute("""
SELECT raw_name, unit_price
FROM supplier_product_catalog spc
JOIN suppliers s ON spc.supplier_id = s.supplier_id
WHERE s.name = '한의사랑'
AND ABS(spc.unit_price - ?) / ? < 0.1
ORDER BY ABS(spc.unit_price - ?)
LIMIT 5
""", (unit_price, unit_price, unit_price))
matches = cursor.fetchall()
if matches:
print(f"\nLot #{lot_id}: {herb_name} ({unit_price:.1f}원/g, {origin})")
print(" 매칭 후보:")
best_match = None
for match in matches:
match_name, match_price = match
diff_percent = abs(match_price - unit_price) / unit_price * 100
print(f" - {match_name:30s} | {match_price:8.1f}원/g | 차이: {diff_percent:.1f}%")
# 약재명에서 핵심 단어 추출하여 매칭
herb_core = herb_name.replace('휴먼', '').replace('신흥', '')
if herb_core in match_name or any(keyword in match_name for keyword in [herb_core[:2], herb_core[-2:]]):
if not best_match or abs(match_price - unit_price) < abs(best_match[1] - unit_price):
best_match = match
if best_match:
# display_name 업데이트
cursor.execute("""
UPDATE inventory_lots
SET display_name = ?
WHERE lot_id = ?
""", (best_match[0], lot_id))
# lot_variants 추가/업데이트
try:
cursor.execute("""
INSERT INTO lot_variants
(lot_id, raw_name, parsed_at, parsed_method)
VALUES (?, ?, datetime('now'), 'catalog_price_match')
""", (lot_id, best_match[0]))
except sqlite3.IntegrityError:
cursor.execute("""
UPDATE lot_variants
SET raw_name = ?, parsed_at = datetime('now'), parsed_method = 'catalog_price_match'
WHERE lot_id = ?
""", (best_match[0], lot_id))
print(f" ✓ 매칭: {best_match[0]}")
matched_count += 1
else:
print(" ✗ 적합한 매칭 없음")
no_match.append((lot_id, herb_name, unit_price, origin))
else:
no_match.append((lot_id, herb_name, unit_price, origin))
conn.commit()
print("\n" + "="*80)
print("매칭 결과")
print("="*80)
print(f"✓ 매칭 성공: {matched_count}")
print(f"✗ 매칭 실패: {len(no_match)}")
if no_match:
print("\n매칭 실패한 로트:")
for lot in no_match:
print(f" Lot #{lot[0]}: {lot[1]:20s} | {lot[2]:8.1f}원/g | {lot[3]}")
# 최종 결과 확인
cursor.execute("""
SELECT COUNT(*) as total,
COUNT(display_name) as with_display
FROM inventory_lots
""")
result = cursor.fetchone()
print(f"\n전체 로트: {result[0]}")
print(f"display_name 설정됨: {result[1]}")
conn.close()
def main():
"""메인 실행"""
print("\n한의사랑 카탈로그 데이터 Import 및 매칭")
print("="*80)
# 1. 카탈로그 데이터 import
items = import_to_catalog()
# 2. inventory lots와 매칭
match_with_inventory()
print("\n완료!")
if __name__ == "__main__":
main()