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:
171
scripts/reset_operational_data.py
Normal file
171
scripts/reset_operational_data.py
Normal 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
76
scripts/restore_backup.py
Normal 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()
|
||||
Reference in New Issue
Block a user