docs: 제품 3단계분류 문서 추가, DB 초기화/복원 스크립트

- 제품 3단계분류.md: 성분→제품→로트 분류 체계, AI display_name 채우기 절차
- reset_operational_data.py: 마스터 보존 + 운영 데이터 초기화
- restore_backup.py: 백업 선택 복원 스크립트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 14:42:31 +00:00
parent 50883a6a84
commit 69be63d00d
3 changed files with 437 additions and 0 deletions

View File

@@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""
운영 데이터 초기화 스크립트
- 마스터 데이터는 보존
- 운영/거래 데이터만 삭제
- prescription_rules 중복 정리
실행: python3 scripts/reset_operational_data.py
"""
import sqlite3
import os
from datetime import datetime
DB_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'database', 'kdrug.db')
# ============================================================
# 보존할 마스터 테이블 (절대 건드리지 않음)
# ============================================================
MASTER_TABLES = [
'herb_masters', # 454 - 급여 한약재 성분코드 마스터
'herb_master_extended', # 454 - 약재 확장 정보 (성미귀경, 효능)
'herb_products', # 53,769 - 보험 제품 목록
'product_companies', # 128 - 제조/유통사
'official_formulas', # 100 - 100처방 원방 마스터
'official_formula_ingredients', # 68 - 100처방 구성 약재
'herb_efficacy_tags', # 18 - 효능 태그 정의
'herb_item_tags', # 22 - 약재-태그 매핑
'survey_templates', # 56 - 설문 템플릿
]
# ============================================================
# 삭제할 운영 데이터 테이블 (FK 순서 고려 — 자식 먼저)
# ============================================================
CLEAR_TABLES = [
# 조제/판매 하위
'compound_consumptions',
'compound_ingredients',
'sales_status_history',
'sales_transactions',
'mileage_transactions',
# 조제 마스터
'compounds',
# 재고 하위
'stock_ledger',
'stock_adjustment_details',
'stock_adjustments',
'lot_variants',
'inventory_lots',
'inventory_lots_v2',
# 입고 하위
'purchase_receipt_lines',
'purchase_receipts',
# 처방
'formula_ingredients',
'formula_ingredients_backup',
'formulas',
'price_policies',
# 환자/설문
'survey_responses',
'survey_progress',
'patient_surveys',
'patients',
# 도매상/약재
'supplier_product_catalog',
'suppliers',
'herb_items',
# 규칙/로그 (재정비)
'prescription_rules',
'data_update_logs',
'disease_herb_mapping',
'herb_research_papers',
'herb_safety_info',
]
def reset_db():
conn = sqlite3.connect(DB_PATH)
conn.execute("PRAGMA foreign_keys = OFF")
cursor = conn.cursor()
print(f"DB: {DB_PATH}")
print(f"시각: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print()
# 1. 마스터 테이블 행 수 확인 (보존 확인)
print("=" * 50)
print("보존 대상 마스터 테이블")
print("=" * 50)
for table in MASTER_TABLES:
try:
cursor.execute(f"SELECT COUNT(*) FROM [{table}]")
cnt = cursor.fetchone()[0]
print(f"{table}: {cnt}행 (보존)")
except:
print(f" - {table}: 테이블 없음 (skip)")
# 2. 운영 테이블 삭제
print()
print("=" * 50)
print("초기화 대상 운영 테이블")
print("=" * 50)
for table in CLEAR_TABLES:
try:
cursor.execute(f"SELECT COUNT(*) FROM [{table}]")
before = cursor.fetchone()[0]
cursor.execute(f"DELETE FROM [{table}]")
# AUTOINCREMENT 리셋
cursor.execute(f"DELETE FROM sqlite_sequence WHERE name = ?", (table,))
print(f"{table}: {before}행 → 0행")
except Exception as e:
print(f" - {table}: {e}")
# 3. prescription_rules 중복 제거 후 재삽입
print()
print("=" * 50)
print("prescription_rules 정리 (중복 제거)")
print("=" * 50)
rules = [
(298, 438, '상수', '두 약재가 함께 사용되면 보기 효과가 증강됨 (인삼+황기)', 0, 0),
(73, 358, '상수', '혈액순환 개선 효과가 증강됨 (당귀+천궁)', 0, 0),
(123, 193, '상사', '생강이 반하의 독성을 감소시킴', 0, 0),
(7, 6, '상반', '십팔반(十八反) - 함께 사용 금기', 5, 1),
(298, 252, '상반', '십구외(十九畏) - 함께 사용 주의', 4, 0),
]
for r in rules:
cursor.execute("""
INSERT INTO prescription_rules (herb1_id, herb2_id, relationship_type, description, severity_level, is_absolute)
VALUES (?, ?, ?, ?, ?, ?)
""", r)
print(f"{len(rules)}개 규칙 재삽입 (중복 제거)")
conn.commit()
conn.execute("PRAGMA foreign_keys = ON")
# 4. VACUUM
conn.execute("VACUUM")
print()
print("✓ VACUUM 완료")
# 5. 최종 확인
print()
print("=" * 50)
print("최종 상태")
print("=" * 50)
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name")
for row in cursor.fetchall():
table = row[0]
cursor.execute(f"SELECT COUNT(*) FROM [{table}]")
cnt = cursor.fetchone()[0]
marker = "" if cnt > 0 else " "
print(f" {marker} {table}: {cnt}")
conn.close()
print()
print("초기화 완료!")
if __name__ == '__main__':
confirm = input("운영 데이터를 모두 초기화합니다. 계속하시겠습니까? (yes/no): ")
if confirm.strip().lower() == 'yes':
reset_db()
else:
print("취소되었습니다.")

76
scripts/restore_backup.py Normal file
View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""
백업 DB 복원 스크립트
- 백업 파일에서 운영 DB로 복원
실행: python3 scripts/restore_backup.py
"""
import os
import shutil
import glob
from datetime import datetime
DB_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'database')
DB_PATH = os.path.join(DB_DIR, 'kdrug.db')
def list_backups():
"""사용 가능한 백업 파일 목록"""
pattern = os.path.join(DB_DIR, 'kdrug_backup*.db')
backups = sorted(glob.glob(pattern), key=os.path.getmtime, reverse=True)
return backups
def restore():
backups = list_backups()
if not backups:
print("사용 가능한 백업 파일이 없습니다.")
return
print("=" * 50)
print("사용 가능한 백업 파일")
print("=" * 50)
for i, path in enumerate(backups):
size_mb = os.path.getsize(path) / (1024 * 1024)
mtime = datetime.fromtimestamp(os.path.getmtime(path)).strftime('%Y-%m-%d %H:%M:%S')
name = os.path.basename(path)
print(f" [{i + 1}] {name} ({size_mb:.1f}MB, {mtime})")
print()
choice = input(f"복원할 백업 번호를 선택하세요 (1-{len(backups)}): ").strip()
try:
idx = int(choice) - 1
if idx < 0 or idx >= len(backups):
print("잘못된 번호입니다.")
return
except ValueError:
print("숫자를 입력하세요.")
return
selected = backups[idx]
print()
print(f"선택: {os.path.basename(selected)}")
confirm = input("현재 DB를 덮어쓰고 복원합니다. 계속하시겠습니까? (yes/no): ").strip().lower()
if confirm != 'yes':
print("취소되었습니다.")
return
# 현재 DB를 복원 전 백업
pre_restore = os.path.join(DB_DIR, f"kdrug_pre_restore_{datetime.now().strftime('%Y%m%d_%H%M%S')}.db")
shutil.copy2(DB_PATH, pre_restore)
print(f" 복원 전 현재 DB 백업 → {os.path.basename(pre_restore)}")
# 복원
shutil.copy2(selected, DB_PATH)
print(f" {os.path.basename(selected)} → kdrug.db 복원 완료")
print()
print("복원 완료! 앱을 재시작하세요.")
if __name__ == '__main__':
restore()