refactor: 주성분코드 기반 데이터베이스 리팩토링 완료
DB 구조 개선: - 454개 주성분코드 기반 herb_masters 테이블 생성 - 53,769개 제품 데이터를 herb_products 테이블에 임포트 - 128개 업체 정보를 product_companies 테이블에 추가 - 기존 herb_items에 ingredient_code 매핑 (100% 완료) UI/API 개선: - 급여 약재 보유 현황 표시 (28/454 = 6.2%) - 재고 현황에 프로그레스 바 추가 - 주성분코드 기준 통계 API 추가 문서화: - 데이터베이스 리팩토링 제안서 작성 - 리팩토링 결과 보고서 작성 - 백업 정보 문서화 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
172
refactoring/01_create_new_tables.py
Normal file
172
refactoring/01_create_new_tables.py
Normal file
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Step 1: 주성분코드 기반 새로운 테이블 생성
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
|
||||
def create_new_tables():
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
print("=== 주성분코드 기반 테이블 생성 시작 ===\n")
|
||||
|
||||
# 1. 약재 마스터 테이블 (주성분코드 기준)
|
||||
print("1. herb_masters 테이블 생성...")
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS herb_masters (
|
||||
ingredient_code VARCHAR(10) PRIMARY KEY,
|
||||
herb_name VARCHAR(100) NOT NULL,
|
||||
herb_name_hanja VARCHAR(100),
|
||||
herb_name_latin VARCHAR(200),
|
||||
description TEXT,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
print(" ✅ herb_masters 테이블 생성 완료")
|
||||
|
||||
# 2. 제품 마스터 테이블 (업체별 제품)
|
||||
print("\n2. herb_products 테이블 생성...")
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS herb_products (
|
||||
product_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ingredient_code VARCHAR(10) NOT NULL,
|
||||
product_code VARCHAR(9) NOT NULL,
|
||||
company_name VARCHAR(200) NOT NULL,
|
||||
product_name VARCHAR(200) NOT NULL,
|
||||
standard_code VARCHAR(20),
|
||||
representative_code VARCHAR(20),
|
||||
package_size VARCHAR(20),
|
||||
package_unit VARCHAR(20),
|
||||
valid_from DATE,
|
||||
valid_to DATE,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (ingredient_code) REFERENCES herb_masters(ingredient_code),
|
||||
UNIQUE(product_code, package_size, package_unit)
|
||||
)
|
||||
""")
|
||||
print(" ✅ herb_products 테이블 생성 완료")
|
||||
|
||||
# 3. 인덱스 생성
|
||||
print("\n3. 인덱스 생성...")
|
||||
|
||||
# herb_products 인덱스
|
||||
cursor.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_product_ingredient
|
||||
ON herb_products(ingredient_code)
|
||||
""")
|
||||
cursor.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_product_company
|
||||
ON herb_products(company_name)
|
||||
""")
|
||||
cursor.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_product_barcode
|
||||
ON herb_products(standard_code)
|
||||
""")
|
||||
cursor.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_product_code
|
||||
ON herb_products(product_code)
|
||||
""")
|
||||
print(" ✅ 인덱스 생성 완료")
|
||||
|
||||
# 4. 기존 herb_items 테이블에 ingredient_code 컬럼 추가
|
||||
print("\n4. 기존 herb_items 테이블에 ingredient_code 컬럼 추가...")
|
||||
|
||||
# 컬럼이 이미 있는지 확인
|
||||
cursor.execute("PRAGMA table_info(herb_items)")
|
||||
columns = [col[1] for col in cursor.fetchall()]
|
||||
|
||||
if 'ingredient_code' not in columns:
|
||||
cursor.execute("""
|
||||
ALTER TABLE herb_items
|
||||
ADD COLUMN ingredient_code VARCHAR(10)
|
||||
""")
|
||||
print(" ✅ ingredient_code 컬럼 추가 완료")
|
||||
else:
|
||||
print(" ⚠️ ingredient_code 컬럼이 이미 존재합니다")
|
||||
|
||||
# 5. 개선된 재고 로트 테이블 (선택사항)
|
||||
print("\n5. inventory_lots_v2 테이블 생성...")
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS inventory_lots_v2 (
|
||||
lot_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
product_id INTEGER,
|
||||
herb_item_id INTEGER, -- 하위 호환성을 위해 유지
|
||||
lot_no VARCHAR(50),
|
||||
origin_country VARCHAR(50),
|
||||
manufacture_date DATE,
|
||||
expiry_date DATE,
|
||||
received_date DATE NOT NULL,
|
||||
quantity_onhand DECIMAL(10,2) NOT NULL DEFAULT 0,
|
||||
unit_price_per_g DECIMAL(10,2) NOT NULL,
|
||||
total_value DECIMAL(10,2),
|
||||
is_depleted BOOLEAN DEFAULT 0,
|
||||
supplier_id INTEGER,
|
||||
receipt_id INTEGER,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (product_id) REFERENCES herb_products(product_id),
|
||||
FOREIGN KEY (herb_item_id) REFERENCES herb_items(herb_item_id)
|
||||
)
|
||||
""")
|
||||
print(" ✅ inventory_lots_v2 테이블 생성 완료")
|
||||
|
||||
# 6. 제품 업체 테이블 (업체 정보 관리)
|
||||
print("\n6. product_companies 테이블 생성...")
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS product_companies (
|
||||
company_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
company_name VARCHAR(200) NOT NULL UNIQUE,
|
||||
business_no VARCHAR(50),
|
||||
contact_person VARCHAR(100),
|
||||
phone VARCHAR(50),
|
||||
email VARCHAR(100),
|
||||
address TEXT,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
print(" ✅ product_companies 테이블 생성 완료")
|
||||
|
||||
conn.commit()
|
||||
|
||||
print("\n=== 테이블 생성 완료 ===")
|
||||
print("\n생성된 테이블:")
|
||||
print(" • herb_masters - 주성분코드 기반 약재 마스터")
|
||||
print(" • herb_products - 업체별 제품 정보")
|
||||
print(" • inventory_lots_v2 - 개선된 재고 관리")
|
||||
print(" • product_companies - 제품 업체 정보")
|
||||
print("\n기존 테이블 수정:")
|
||||
print(" • herb_items - ingredient_code 컬럼 추가")
|
||||
|
||||
# 테이블 정보 확인
|
||||
print("\n=== 테이블 구조 확인 ===")
|
||||
cursor.execute("""
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='table'
|
||||
AND name IN ('herb_masters', 'herb_products', 'inventory_lots_v2', 'product_companies')
|
||||
ORDER BY name
|
||||
""")
|
||||
|
||||
tables = cursor.fetchall()
|
||||
print(f"\n신규 테이블 수: {len(tables)}개")
|
||||
for table in tables:
|
||||
print(f" - {table[0]}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 오류 발생: {e}")
|
||||
conn.rollback()
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_new_tables()
|
||||
222
refactoring/02_import_product_codes.py
Normal file
222
refactoring/02_import_product_codes.py
Normal file
@@ -0,0 +1,222 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Step 2: 한약재제품코드 데이터 임포트
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import pandas as pd
|
||||
from datetime import datetime
|
||||
|
||||
def import_product_codes():
|
||||
"""한약재제품코드 엑셀 파일에서 데이터를 임포트"""
|
||||
|
||||
file_path = 'sample/(게시)한약재제품코드_2510.xlsx'
|
||||
sheet_name = '한약재 제품코드_20250930기준(유효코드만 공지)'
|
||||
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
print("=== 한약재제품코드 데이터 임포트 시작 ===\n")
|
||||
|
||||
# 엑셀 파일 읽기 - 제품코드를 문자열로 유지
|
||||
print("1. 엑셀 파일 읽기 중...")
|
||||
df = pd.read_excel(file_path, sheet_name=sheet_name,
|
||||
dtype={'제품코드': str, '적용시작일': str, '적용종료일': str})
|
||||
print(f" ✅ {len(df):,}개 데이터 로드 완료")
|
||||
|
||||
# 제품코드 9자리로 패딩
|
||||
df['제품코드'] = df['제품코드'].apply(lambda x: str(x).zfill(9) if pd.notna(x) else None)
|
||||
|
||||
# 날짜 형식 변환 (20201120 → 2020-11-20)
|
||||
def convert_date(date_str):
|
||||
if pd.isna(date_str) or date_str == '99991231':
|
||||
return None
|
||||
date_str = str(int(date_str))
|
||||
return f"{date_str[:4]}-{date_str[4:6]}-{date_str[6:8]}"
|
||||
|
||||
df['valid_from'] = df['적용시작일'].apply(convert_date)
|
||||
df['valid_to'] = df['적용종료일'].apply(convert_date)
|
||||
|
||||
# 1. herb_masters 테이블 채우기 (주성분코드별 약재)
|
||||
print("\n2. herb_masters 테이블 데이터 임포트...")
|
||||
|
||||
# 유일한 주성분코드 추출
|
||||
unique_herbs = df[['주성분코드', '한약재 품목명']].drop_duplicates()
|
||||
herb_count = 0
|
||||
|
||||
for _, row in unique_herbs.iterrows():
|
||||
if pd.notna(row['주성분코드']):
|
||||
try:
|
||||
cursor.execute("""
|
||||
INSERT OR IGNORE INTO herb_masters
|
||||
(ingredient_code, herb_name, is_active)
|
||||
VALUES (?, ?, 1)
|
||||
""", (row['주성분코드'], row['한약재 품목명']))
|
||||
if cursor.rowcount > 0:
|
||||
herb_count += 1
|
||||
except Exception as e:
|
||||
print(f" ⚠️ {row['한약재 품목명']} 임포트 실패: {e}")
|
||||
|
||||
print(f" ✅ {herb_count}개 약재 마스터 등록 완료")
|
||||
|
||||
# 2. product_companies 테이블 채우기
|
||||
print("\n3. product_companies 테이블 데이터 임포트...")
|
||||
|
||||
unique_companies = df['업체명'].unique()
|
||||
company_count = 0
|
||||
|
||||
for company in unique_companies:
|
||||
if pd.notna(company):
|
||||
try:
|
||||
cursor.execute("""
|
||||
INSERT OR IGNORE INTO product_companies
|
||||
(company_name, is_active)
|
||||
VALUES (?, 1)
|
||||
""", (company,))
|
||||
if cursor.rowcount > 0:
|
||||
company_count += 1
|
||||
except Exception as e:
|
||||
print(f" ⚠️ {company} 임포트 실패: {e}")
|
||||
|
||||
print(f" ✅ {company_count}개 업체 등록 완료")
|
||||
|
||||
# 3. herb_products 테이블 채우기
|
||||
print("\n4. herb_products 테이블 데이터 임포트 (시간이 걸릴 수 있습니다)...")
|
||||
|
||||
product_count = 0
|
||||
error_count = 0
|
||||
batch_size = 1000
|
||||
|
||||
for idx, row in df.iterrows():
|
||||
if pd.notna(row['주성분코드']) and pd.notna(row['제품코드']):
|
||||
try:
|
||||
# 표준코드와 대표코드를 문자열로 변환
|
||||
standard_code = str(int(row['표준코드'])) if pd.notna(row['표준코드']) else None
|
||||
rep_code = str(int(row['대표코드'])) if pd.notna(row['대표코드']) else None
|
||||
|
||||
cursor.execute("""
|
||||
INSERT OR IGNORE INTO herb_products
|
||||
(ingredient_code, product_code, company_name, product_name,
|
||||
standard_code, representative_code,
|
||||
package_size, package_unit, valid_from, valid_to, is_active)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)
|
||||
""", (
|
||||
row['주성분코드'],
|
||||
row['제품코드'],
|
||||
row['업체명'],
|
||||
row['제품명'],
|
||||
standard_code,
|
||||
rep_code,
|
||||
row['약품규격(숫자)'] if row['약품규격(숫자)'] != '-' else None,
|
||||
row['약품규격(단위)'] if row['약품규격(단위)'] != '-' else None,
|
||||
row['valid_from'],
|
||||
row['valid_to']
|
||||
))
|
||||
if cursor.rowcount > 0:
|
||||
product_count += 1
|
||||
except Exception as e:
|
||||
error_count += 1
|
||||
if error_count < 10: # 처음 10개만 오류 표시
|
||||
print(f" ⚠️ 행 {idx} 임포트 실패: {e}")
|
||||
|
||||
# 진행상황 표시
|
||||
if (idx + 1) % batch_size == 0:
|
||||
print(f" ... {idx + 1:,}/{len(df):,} 처리 중 ({product_count:,}개 등록)")
|
||||
conn.commit() # 배치 단위로 커밋
|
||||
|
||||
print(f" ✅ {product_count:,}개 제품 등록 완료 (오류: {error_count}개)")
|
||||
|
||||
# 4. 기존 herb_items와 매핑
|
||||
print("\n5. 기존 herb_items에 ingredient_code 매핑...")
|
||||
|
||||
# 약재명 기준으로 매핑
|
||||
cursor.execute("""
|
||||
UPDATE herb_items
|
||||
SET ingredient_code = (
|
||||
SELECT ingredient_code
|
||||
FROM herb_masters
|
||||
WHERE REPLACE(herb_masters.herb_name, ' ', '') = REPLACE(herb_items.herb_name, ' ', '')
|
||||
LIMIT 1
|
||||
)
|
||||
WHERE ingredient_code IS NULL
|
||||
""")
|
||||
mapped_count = cursor.rowcount
|
||||
print(f" ✅ {mapped_count}개 약재 매핑 완료")
|
||||
|
||||
# 매핑 확인
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM herb_items WHERE ingredient_code IS NOT NULL
|
||||
""")
|
||||
mapped_total = cursor.fetchone()[0]
|
||||
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM herb_items
|
||||
""")
|
||||
total_herbs = cursor.fetchone()[0]
|
||||
|
||||
print(f" 📊 매핑 결과: {mapped_total}/{total_herbs} ({mapped_total*100//total_herbs}%)")
|
||||
|
||||
# 매핑되지 않은 약재 확인
|
||||
cursor.execute("""
|
||||
SELECT herb_name FROM herb_items
|
||||
WHERE ingredient_code IS NULL
|
||||
ORDER BY herb_name
|
||||
""")
|
||||
unmapped = cursor.fetchall()
|
||||
|
||||
if unmapped:
|
||||
print(f"\n ⚠️ 매핑되지 않은 약재 ({len(unmapped)}개):")
|
||||
for herb in unmapped[:10]: # 처음 10개만 표시
|
||||
print(f" - {herb[0]}")
|
||||
if len(unmapped) > 10:
|
||||
print(f" ... 외 {len(unmapped)-10}개")
|
||||
|
||||
conn.commit()
|
||||
|
||||
# 최종 통계
|
||||
print("\n=== 임포트 완료 ===")
|
||||
|
||||
# 통계 조회
|
||||
cursor.execute("SELECT COUNT(*) FROM herb_masters")
|
||||
herb_master_count = cursor.fetchone()[0]
|
||||
|
||||
cursor.execute("SELECT COUNT(*) FROM herb_products")
|
||||
product_total = cursor.fetchone()[0]
|
||||
|
||||
cursor.execute("SELECT COUNT(*) FROM product_companies")
|
||||
company_total = cursor.fetchone()[0]
|
||||
|
||||
print(f"\n📊 최종 통계:")
|
||||
print(f" • herb_masters: {herb_master_count}개 약재")
|
||||
print(f" • herb_products: {product_total:,}개 제품")
|
||||
print(f" • product_companies: {company_total}개 업체")
|
||||
|
||||
# 샘플 데이터 확인
|
||||
print("\n📋 샘플 데이터 (건강):")
|
||||
cursor.execute("""
|
||||
SELECT p.product_code, p.company_name, p.product_name,
|
||||
p.package_size || COALESCE(' ' || p.package_unit, '') as package
|
||||
FROM herb_products p
|
||||
JOIN herb_masters m ON p.ingredient_code = m.ingredient_code
|
||||
WHERE m.herb_name = '건강'
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
for row in cursor.fetchall():
|
||||
print(f" - [{row[0]}] {row[1]} - {row[2]} ({row[3]})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 오류 발생: {e}")
|
||||
conn.rollback()
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import time
|
||||
start = time.time()
|
||||
import_product_codes()
|
||||
elapsed = time.time() - start
|
||||
print(f"\n⏱️ 실행 시간: {elapsed:.2f}초")
|
||||
126
refactoring/03_fix_mappings.py
Normal file
126
refactoring/03_fix_mappings.py
Normal file
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Step 3: 매핑되지 않은 약재 수정
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
|
||||
def fix_mappings():
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
print("=== 약재 매핑 수정 ===\n")
|
||||
|
||||
# 1. 백작약 → 작약으로 매핑
|
||||
print("1. '백작약'을 '작약'으로 매핑...")
|
||||
|
||||
# 작약의 주성분코드 확인
|
||||
cursor.execute("""
|
||||
SELECT ingredient_code FROM herb_masters
|
||||
WHERE herb_name = '작약'
|
||||
""")
|
||||
result = cursor.fetchone()
|
||||
|
||||
if result:
|
||||
jaknyak_code = result[0]
|
||||
cursor.execute("""
|
||||
UPDATE herb_items
|
||||
SET ingredient_code = ?
|
||||
WHERE herb_name = '백작약'
|
||||
""", (jaknyak_code,))
|
||||
print(f" ✅ 백작약 → 작약 ({jaknyak_code}) 매핑 완료")
|
||||
else:
|
||||
print(" ⚠️ '작약'을 찾을 수 없습니다")
|
||||
|
||||
# 2. 진피 매핑
|
||||
print("\n2. '진피' 매핑 확인...")
|
||||
|
||||
# 진피 관련 항목 찾기
|
||||
cursor.execute("""
|
||||
SELECT ingredient_code, herb_name
|
||||
FROM herb_masters
|
||||
WHERE herb_name LIKE '%진피%'
|
||||
""")
|
||||
jinpi_options = cursor.fetchall()
|
||||
|
||||
if jinpi_options:
|
||||
print(f" 발견된 진피 관련 항목:")
|
||||
for code, name in jinpi_options:
|
||||
print(f" - {name} ({code})")
|
||||
|
||||
# 첫 번째 항목으로 매핑
|
||||
if len(jinpi_options) > 0:
|
||||
jinpi_code = jinpi_options[0][0]
|
||||
cursor.execute("""
|
||||
UPDATE herb_items
|
||||
SET ingredient_code = ?
|
||||
WHERE herb_name = '진피'
|
||||
""", (jinpi_code,))
|
||||
print(f" ✅ 진피 → {jinpi_options[0][1]} ({jinpi_code}) 매핑 완료")
|
||||
else:
|
||||
print(" ⚠️ '진피' 관련 항목을 찾을 수 없습니다")
|
||||
|
||||
# 3. 최종 확인
|
||||
print("\n3. 매핑 상태 확인...")
|
||||
|
||||
cursor.execute("""
|
||||
SELECT herb_name, ingredient_code
|
||||
FROM herb_items
|
||||
ORDER BY herb_name
|
||||
""")
|
||||
all_items = cursor.fetchall()
|
||||
|
||||
mapped = 0
|
||||
unmapped = []
|
||||
|
||||
for herb_name, code in all_items:
|
||||
if code:
|
||||
mapped += 1
|
||||
else:
|
||||
unmapped.append(herb_name)
|
||||
|
||||
print(f"\n📊 최종 매핑 결과:")
|
||||
print(f" • 전체: {len(all_items)}개")
|
||||
print(f" • 매핑됨: {mapped}개 ({mapped*100//len(all_items)}%)")
|
||||
print(f" • 미매핑: {len(unmapped)}개")
|
||||
|
||||
if unmapped:
|
||||
print(f"\n ⚠️ 아직 매핑되지 않은 약재:")
|
||||
for name in unmapped:
|
||||
print(f" - {name}")
|
||||
|
||||
# 4. 중요 약재들의 매핑 확인
|
||||
print("\n4. 주요 약재 매핑 확인...")
|
||||
|
||||
important_herbs = [
|
||||
'건강', '감초', '당귀', '황기', '숙지황',
|
||||
'백출', '천궁', '육계', '인삼', '생강', '대추'
|
||||
]
|
||||
|
||||
for herb_name in important_herbs:
|
||||
cursor.execute("""
|
||||
SELECT h.herb_name, h.ingredient_code, m.herb_name
|
||||
FROM herb_items h
|
||||
LEFT JOIN herb_masters m ON h.ingredient_code = m.ingredient_code
|
||||
WHERE h.herb_name = ?
|
||||
""", (herb_name,))
|
||||
result = cursor.fetchone()
|
||||
|
||||
if result and result[1]:
|
||||
print(f" ✅ {result[0]} → {result[1]} ({result[2]})")
|
||||
else:
|
||||
print(f" ❌ {herb_name} - 매핑 안 됨")
|
||||
|
||||
conn.commit()
|
||||
print("\n✅ 매핑 수정 완료!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 오류 발생: {e}")
|
||||
conn.rollback()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
fix_mappings()
|
||||
124
refactoring/REFACTORING_RESULT.md
Normal file
124
refactoring/REFACTORING_RESULT.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 데이터베이스 리팩토링 결과 보고서
|
||||
|
||||
## 📅 실행 일시
|
||||
- **날짜**: 2026년 2월 15일
|
||||
- **백업 완료**: backups/kdrug_full_backup_20260215_before_refactoring.tar.gz
|
||||
|
||||
## ✅ 완료된 작업
|
||||
|
||||
### 1. 새로운 테이블 생성
|
||||
#### herb_masters (주성분코드 기반 약재 마스터)
|
||||
- **레코드 수**: 454개
|
||||
- **주요 필드**: ingredient_code (주성분코드), herb_name (약재명)
|
||||
- **용도**: 표준화된 약재 코드 관리
|
||||
|
||||
#### herb_products (업체별 제품)
|
||||
- **레코드 수**: 53,769개
|
||||
- **주요 필드**: product_code (9자리), company_name, product_name, package_size, package_unit
|
||||
- **용도**: 업체별 제품 정보 및 포장 규격 관리
|
||||
|
||||
#### product_companies (제품 업체)
|
||||
- **레코드 수**: 128개
|
||||
- **주요 필드**: company_name
|
||||
- **용도**: 한약재 제조/유통 업체 정보 관리
|
||||
|
||||
#### inventory_lots_v2 (개선된 재고 관리)
|
||||
- **용도**: 제품 단위 재고 관리 (향후 마이그레이션용)
|
||||
|
||||
### 2. 기존 테이블 수정
|
||||
#### herb_items
|
||||
- **변경사항**: ingredient_code 컬럼 추가
|
||||
- **매핑 완료**: 32/32 (100%)
|
||||
- **주요 매핑**:
|
||||
- 건강 → 3017H1AHM
|
||||
- 감초 → 3007H1AHM
|
||||
- 당귀 → 3105H1AHM
|
||||
- 황기 → 3583H1AHM
|
||||
- 백작약 → 3419H1AHM (작약)
|
||||
- 진피 → 3467H1AHM
|
||||
|
||||
### 3. 데이터 임포트 통계
|
||||
- **처리 시간**: 10.86초
|
||||
- **총 처리 건수**: 53,775개
|
||||
- **임포트 성공**: 53,769개 (99.99%)
|
||||
- **오류**: 6개 (중복 데이터)
|
||||
|
||||
## 📊 현재 시스템 상태
|
||||
|
||||
### 데이터베이스 구조
|
||||
```
|
||||
기존 시스템 (유지)
|
||||
├── herb_items (32개) - ingredient_code 추가됨
|
||||
├── inventory_lots (재고 로트)
|
||||
├── formulas (처방)
|
||||
└── compounds (조제)
|
||||
|
||||
신규 시스템 (추가)
|
||||
├── herb_masters (454개 주성분코드)
|
||||
├── herb_products (53,769개 제품)
|
||||
├── product_companies (128개 업체)
|
||||
└── inventory_lots_v2 (미사용)
|
||||
```
|
||||
|
||||
### 주요 약재별 제품 수
|
||||
- 복령: 284개 제품
|
||||
- 감초: 284개 제품
|
||||
- 마황: 282개 제품
|
||||
- 작약: 280개 제품
|
||||
- 황기: 275개 제품
|
||||
- 천궁: 272개 제품
|
||||
- 당귀: 264개 제품
|
||||
- 건강: 246개 제품
|
||||
|
||||
### 주요 업체별 제품 수
|
||||
- 주식회사 바른한방제약: 1,609개
|
||||
- 나눔제약주식회사: 1,605개
|
||||
- 씨케이주식회사: 1,603개
|
||||
- (주)현진제약: 1,591개
|
||||
- (주)자연세상: 1,476개
|
||||
|
||||
## 🔄 향후 작업 계획
|
||||
|
||||
### 단기 (즉시 가능)
|
||||
1. ✅ 주성분코드 기반 검색 API 추가
|
||||
2. ✅ 제품 선택 UI 개선
|
||||
3. ✅ 바코드 조회 기능
|
||||
|
||||
### 중기 (단계적 구현)
|
||||
1. ⏳ inventory_lots_v2로 재고 마이그레이션
|
||||
2. ⏳ 제품별 입고/재고 관리
|
||||
3. ⏳ 업체별 가격 비교
|
||||
|
||||
### 장기 (추가 개발)
|
||||
1. 📋 바코드 스캔 기능
|
||||
2. 📋 도매상 API 연동
|
||||
3. 📋 자동 발주 시스템
|
||||
|
||||
## ⚡ 성능 개선 사항
|
||||
- 인덱스 생성 완료:
|
||||
- idx_product_ingredient (약재별 제품 검색)
|
||||
- idx_product_company (업체별 제품 검색)
|
||||
- idx_product_barcode (바코드 검색)
|
||||
- idx_product_code (제품코드 검색)
|
||||
|
||||
## 📝 주의사항
|
||||
1. **하위 호환성 유지**: 기존 시스템은 그대로 작동
|
||||
2. **제품코드 형식**: 9자리 (앞자리 0 포함 필수)
|
||||
3. **중복 관리**: 동일 제품의 다양한 규격은 별도 레코드로 관리
|
||||
|
||||
## 🎯 달성 효과
|
||||
1. **표준화**: 건강보험 급여 코드체계 준수
|
||||
2. **확장성**: 53,000개 이상 제품 관리 가능
|
||||
3. **정확성**: 업체별, 규격별 정확한 관리
|
||||
4. **호환성**: 외부 시스템과 데이터 교환 가능
|
||||
|
||||
## 📁 관련 파일
|
||||
- `/refactoring/01_create_new_tables.py` - 테이블 생성
|
||||
- `/refactoring/02_import_product_codes.py` - 데이터 임포트
|
||||
- `/refactoring/03_fix_mappings.py` - 매핑 수정
|
||||
- `/backups/kdrug_backup_20260215_before_refactoring.db` - DB 백업
|
||||
- `/backups/kdrug_full_backup_20260215_before_refactoring.tar.gz` - 전체 백업
|
||||
|
||||
---
|
||||
작성일: 2026-02-15
|
||||
작성자: Claude Assistant
|
||||
Reference in New Issue
Block a user