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:
parent
490553881f
commit
111c173692
275
import_hanisarang_catalog.py
Normal file
275
import_hanisarang_catalog.py
Normal 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()
|
||||
Loading…
Reference in New Issue
Block a user