# -*- coding: utf-8 -*- """ 주문 관리 DB (SQLite) - 다중 도매상 지원 (지오영, 수인, 백제 등) - 주문 상태 추적 - 품목별 결과 관리 - 자동화 ERP 확장 대비 """ import sqlite3 import os from datetime import datetime from typing import Optional, List, Dict import json # DB 경로 DB_PATH = os.path.join(os.path.dirname(__file__), 'db', 'orders.db') def get_connection(): """DB 연결""" os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row return conn def init_db(): """DB 초기화 - 테이블 생성""" conn = get_connection() cursor = conn.cursor() # ───────────────────────────────────────────── # 도매상 마스터 # ───────────────────────────────────────────── cursor.execute(''' CREATE TABLE IF NOT EXISTS wholesalers ( id TEXT PRIMARY KEY, -- 'geoyoung', 'sooin', 'baekje' name TEXT NOT NULL, -- '지오영', '수인', '백제' api_type TEXT, -- 'playwright', 'api', 'manual' base_url TEXT, is_active INTEGER DEFAULT 1, config_json TEXT, -- 로그인 정보 등 (암호화 권장) created_at TEXT DEFAULT CURRENT_TIMESTAMP ) ''') # 기본 도매상 등록 cursor.execute(''' INSERT OR IGNORE INTO wholesalers (id, name, api_type, base_url) VALUES ('geoyoung', '지오영', 'playwright', 'https://gwn.geoweb.kr'), ('sooin', '수인', 'manual', NULL), ('baekje', '백제', 'manual', NULL) ''') # ───────────────────────────────────────────── # 주문 헤더 # ───────────────────────────────────────────── cursor.execute(''' CREATE TABLE IF NOT EXISTS orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_no TEXT UNIQUE, -- 주문번호 (ORD-20260306-001) wholesaler_id TEXT NOT NULL, -- 도매상 ID -- 주문 정보 order_date TEXT NOT NULL, -- 주문일 (YYYY-MM-DD) order_time TEXT, -- 주문시간 (HH:MM:SS) order_type TEXT DEFAULT 'manual', -- 'manual', 'auto', 'scheduled' order_session TEXT, -- 'morning', 'afternoon', 'evening' -- 상태 status TEXT DEFAULT 'draft', -- draft, pending, submitted, partial, completed, failed, cancelled -- 집계 total_items INTEGER DEFAULT 0, total_qty INTEGER DEFAULT 0, success_items INTEGER DEFAULT 0, failed_items INTEGER DEFAULT 0, -- 참조 parent_order_id INTEGER, -- 재주문 시 원주문 참조 reference_period TEXT, -- 사용량 조회 기간 (2026-03-01~2026-03-06) -- 메타 note TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, submitted_at TEXT, -- 실제 제출 시간 completed_at TEXT, FOREIGN KEY (wholesaler_id) REFERENCES wholesalers(id), FOREIGN KEY (parent_order_id) REFERENCES orders(id) ) ''') # ───────────────────────────────────────────── # 주문 품목 상세 # ───────────────────────────────────────────── cursor.execute(''' CREATE TABLE IF NOT EXISTS order_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id INTEGER NOT NULL, -- 약품 정보 drug_code TEXT NOT NULL, -- PIT3000 약품코드 kd_code TEXT, -- 보험코드 (지오영 검색용) internal_code TEXT, -- 🔧 도매상 내부 코드 (장바구니 직접 추가용!) product_name TEXT NOT NULL, manufacturer TEXT, -- 규격/수량 specification TEXT, -- '30T', '300T', '500T' unit_qty INTEGER, -- 규격당 수량 (30, 300, 500) order_qty INTEGER NOT NULL, -- 주문 수량 (단위 개수) total_dose INTEGER, -- 총 정제수 (order_qty * unit_qty) -- 주문 근거 usage_qty INTEGER, -- 사용량 (조회 기간) current_stock INTEGER, -- 주문 시점 재고 -- 가격 (선택) unit_price INTEGER, total_price INTEGER, -- 상태 status TEXT DEFAULT 'pending', -- pending, submitted, success, failed, cancelled -- 결과 result_code TEXT, -- 'OK', 'OUT_OF_STOCK', 'NOT_FOUND', 'ERROR' result_message TEXT, wholesaler_order_no TEXT, -- 도매상 측 주문번호 -- 메타 created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE ) ''') # ───────────────────────────────────────────── # 주문 로그 (상태 변경 이력) # ───────────────────────────────────────────── cursor.execute(''' CREATE TABLE IF NOT EXISTS order_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id INTEGER NOT NULL, order_item_id INTEGER, -- NULL이면 주문 전체 로그 action TEXT NOT NULL, -- 'created', 'submitted', 'success', 'failed', 'cancelled' old_status TEXT, new_status TEXT, message TEXT, detail_json TEXT, -- API 응답 등 상세 정보 created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE, FOREIGN KEY (order_item_id) REFERENCES order_items(id) ON DELETE CASCADE ) ''') # ───────────────────────────────────────────── # 주문 컨텍스트 (AI 학습용 스냅샷) # ───────────────────────────────────────────── cursor.execute(''' CREATE TABLE IF NOT EXISTS order_context ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_item_id INTEGER NOT NULL, -- 약품 정보 drug_code TEXT NOT NULL, product_name TEXT, -- 주문 시점 재고 stock_at_order INTEGER, -- 주문 시점 현재고 -- 사용량 분석 usage_1d INTEGER, -- 최근 1일 사용량 usage_7d INTEGER, -- 최근 7일 사용량 usage_30d INTEGER, -- 최근 30일 사용량 avg_daily_usage REAL, -- 일평균 사용량 (30일 기준) -- 주문 패턴 ordered_spec TEXT, -- 주문한 규격 (30T, 300T) ordered_qty INTEGER, -- 주문 수량 (단위 개수) ordered_dose INTEGER, -- 주문 총 정제수 -- 규격 선택 이유 (AI 분석용) available_specs TEXT, -- 가능한 규격들 JSON ["30T", "300T"] spec_stocks TEXT, -- 규격별 도매상 재고 JSON {"30T": 50, "300T": 0} selection_reason TEXT, -- 'stock_available', 'best_fit', 'only_option', 'user_choice' -- 예측 vs 실제 (나중에 업데이트) days_until_stockout REAL, -- 주문 시점 예상 재고 소진일 actual_reorder_days INTEGER, -- 실제 재주문까지 일수 (나중에 업데이트) -- 메타 created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (order_item_id) REFERENCES order_items(id) ON DELETE CASCADE ) ''') # ───────────────────────────────────────────── # 일별 사용량 추적 (시계열 데이터) # ───────────────────────────────────────────── cursor.execute(''' CREATE TABLE IF NOT EXISTS daily_usage ( id INTEGER PRIMARY KEY AUTOINCREMENT, drug_code TEXT NOT NULL, usage_date TEXT NOT NULL, -- YYYY-MM-DD -- 처방 데이터 rx_count INTEGER DEFAULT 0, -- 처방 건수 rx_qty INTEGER DEFAULT 0, -- 처방 수량 (정제수) -- POS 데이터 (일반약) pos_count INTEGER DEFAULT 0, pos_qty INTEGER DEFAULT 0, -- 집계 total_qty INTEGER DEFAULT 0, -- 재고 스냅샷 stock_start INTEGER, -- 시작 재고 stock_end INTEGER, -- 종료 재고 created_at TEXT DEFAULT CURRENT_TIMESTAMP, UNIQUE(drug_code, usage_date) ) ''') # ───────────────────────────────────────────── # AI 분석 결과/패턴 # ───────────────────────────────────────────── cursor.execute(''' CREATE TABLE IF NOT EXISTS order_patterns ( id INTEGER PRIMARY KEY AUTOINCREMENT, drug_code TEXT NOT NULL, -- 분석 기간 analysis_date TEXT NOT NULL, analysis_period_days INTEGER, -- 사용 패턴 avg_daily_usage REAL, usage_stddev REAL, -- 사용량 표준편차 (변동성) peak_usage INTEGER, -- 최대 사용량 -- 주문 패턴 typical_order_spec TEXT, -- 주로 주문하는 규격 typical_order_qty INTEGER, -- 주로 주문하는 수량 order_frequency_days REAL, -- 평균 주문 주기 (일) -- AI 추천 recommended_spec TEXT, -- 추천 규격 recommended_qty INTEGER, -- 추천 수량 recommended_reorder_point INTEGER,-- 추천 재주문점 (재고가 이 이하면 주문) confidence_score REAL, -- 추천 신뢰도 (0-1) -- 모델 정보 model_version TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, UNIQUE(drug_code, analysis_date) ) ''') # ───────────────────────────────────────────── # 인덱스 # ───────────────────────────────────────────── cursor.execute('CREATE INDEX IF NOT EXISTS idx_orders_date ON orders(order_date)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_orders_wholesaler ON orders(wholesaler_id)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_order_items_drug ON order_items(drug_code)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_order_items_status ON order_items(status)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_order_context_drug ON order_context(drug_code)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_daily_usage_drug ON daily_usage(drug_code)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_daily_usage_date ON daily_usage(usage_date)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_order_patterns_drug ON order_patterns(drug_code)') conn.commit() conn.close() return True def generate_order_no(wholesaler_id: str) -> str: """주문번호 생성 (ORD-GEO-20260306-001)""" prefix_map = { 'geoyoung': 'GEO', 'sooin': 'SOO', 'baekje': 'BAK' } prefix = prefix_map.get(wholesaler_id, 'ORD') date_str = datetime.now().strftime('%Y%m%d') conn = get_connection() cursor = conn.cursor() # 오늘 해당 도매상 주문 수 카운트 cursor.execute(''' SELECT COUNT(*) FROM orders WHERE wholesaler_id = ? AND order_date = ? ''', (wholesaler_id, datetime.now().strftime('%Y-%m-%d'))) count = cursor.fetchone()[0] + 1 conn.close() return f"ORD-{prefix}-{date_str}-{count:03d}" def create_order(wholesaler_id: str, items: List[Dict], order_type: str = 'manual', order_session: str = None, reference_period: str = None, note: str = None) -> Dict: """ 주문 생성 (draft 상태) items: [ { 'drug_code': '670400830', 'kd_code': '670400830', 'product_name': '레바미피드정 30T', 'manufacturer': '휴온스', 'specification': '30T', 'unit_qty': 30, 'order_qty': 10, 'usage_qty': 280, 'current_stock': 50 } ] """ conn = get_connection() cursor = conn.cursor() try: order_no = generate_order_no(wholesaler_id) now = datetime.now() # 주문 헤더 생성 cursor.execute(''' INSERT INTO orders ( order_no, wholesaler_id, order_date, order_time, order_type, order_session, reference_period, note, total_items, total_qty, status ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'draft') ''', ( order_no, wholesaler_id, now.strftime('%Y-%m-%d'), now.strftime('%H:%M:%S'), order_type, order_session, reference_period, note, len(items), sum(item.get('order_qty', 0) for item in items) )) order_id = cursor.lastrowid # 주문 품목 생성 for item in items: unit_qty = item.get('unit_qty', 1) order_qty = item.get('order_qty', 0) cursor.execute(''' INSERT INTO order_items ( order_id, drug_code, kd_code, internal_code, product_name, manufacturer, specification, unit_qty, order_qty, total_dose, usage_qty, current_stock, status ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending') ''', ( order_id, item.get('drug_code'), item.get('kd_code'), item.get('internal_code'), # 🔧 도매상 내부 코드 저장! item.get('product_name'), item.get('manufacturer'), item.get('specification'), unit_qty, order_qty, order_qty * unit_qty, item.get('usage_qty'), item.get('current_stock') )) # 로그 cursor.execute(''' INSERT INTO order_logs (order_id, action, new_status, message) VALUES (?, 'created', 'draft', ?) ''', (order_id, f'{len(items)}개 품목 주문 생성')) conn.commit() return { 'success': True, 'order_id': order_id, 'order_no': order_no, 'total_items': len(items) } except Exception as e: conn.rollback() return {'success': False, 'error': str(e)} finally: conn.close() def get_order(order_id: int) -> Optional[Dict]: """주문 조회 (품목 포함)""" conn = get_connection() cursor = conn.cursor() cursor.execute('SELECT * FROM orders WHERE id = ?', (order_id,)) order = cursor.fetchone() if not order: conn.close() return None cursor.execute('SELECT * FROM order_items WHERE order_id = ?', (order_id,)) items = cursor.fetchall() conn.close() return { **dict(order), 'items': [dict(item) for item in items] } def update_order_status(order_id: int, status: str, message: str = None) -> bool: """주문 상태 업데이트""" conn = get_connection() cursor = conn.cursor() try: # 현재 상태 조회 cursor.execute('SELECT status FROM orders WHERE id = ?', (order_id,)) row = cursor.fetchone() if not row: return False old_status = row['status'] # 상태 업데이트 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') update_fields = ['status = ?', 'updated_at = ?'] params = [status, now] if status == 'submitted': update_fields.append('submitted_at = ?') params.append(now) elif status in ('completed', 'failed'): update_fields.append('completed_at = ?') params.append(now) params.append(order_id) cursor.execute(f''' UPDATE orders SET {', '.join(update_fields)} WHERE id = ? ''', params) # 로그 cursor.execute(''' INSERT INTO order_logs (order_id, action, old_status, new_status, message) VALUES (?, ?, ?, ?, ?) ''', (order_id, status, old_status, status, message)) conn.commit() return True except Exception as e: conn.rollback() return False finally: conn.close() def update_item_result(item_id: int, status: str, result_code: str = None, result_message: str = None, wholesaler_order_no: str = None) -> bool: """품목 결과 업데이트""" conn = get_connection() cursor = conn.cursor() try: cursor.execute(''' UPDATE order_items SET status = ?, result_code = ?, result_message = ?, wholesaler_order_no = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? ''', (status, result_code, result_message, wholesaler_order_no, item_id)) # 주문 집계 업데이트 cursor.execute('SELECT order_id FROM order_items WHERE id = ?', (item_id,)) order_id = cursor.fetchone()['order_id'] cursor.execute(''' UPDATE orders SET success_items = (SELECT COUNT(*) FROM order_items WHERE order_id = ? AND status = 'success'), failed_items = (SELECT COUNT(*) FROM order_items WHERE order_id = ? AND status = 'failed'), updated_at = CURRENT_TIMESTAMP WHERE id = ? ''', (order_id, order_id, order_id)) conn.commit() return True except Exception as e: conn.rollback() return False finally: conn.close() def get_order_history(wholesaler_id: str = None, start_date: str = None, end_date: str = None, status: str = None, limit: int = 50) -> List[Dict]: """주문 이력 조회""" conn = get_connection() cursor = conn.cursor() query = 'SELECT * FROM orders WHERE 1=1' params = [] if wholesaler_id: query += ' AND wholesaler_id = ?' params.append(wholesaler_id) if start_date: query += ' AND order_date >= ?' params.append(start_date) if end_date: query += ' AND order_date <= ?' params.append(end_date) if status: query += ' AND status = ?' params.append(status) query += ' ORDER BY created_at DESC LIMIT ?' params.append(limit) cursor.execute(query, params) orders = [dict(row) for row in cursor.fetchall()] conn.close() return orders # ───────────────────────────────────────────── # AI 학습용 함수들 # ───────────────────────────────────────────── def save_order_context(order_item_id: int, context: Dict) -> bool: """ 주문 시점 컨텍스트 저장 (AI 학습용) context: { 'drug_code': '670400830', 'product_name': '레바미피드정', 'stock_at_order': 50, 'usage_1d': 30, 'usage_7d': 180, 'usage_30d': 800, 'ordered_spec': '30T', 'ordered_qty': 10, 'available_specs': ['30T', '300T'], 'spec_stocks': {'30T': 50, '300T': 0}, 'selection_reason': 'stock_available' } """ conn = get_connection() cursor = conn.cursor() try: # 일평균 사용량 계산 usage_30d = context.get('usage_30d', 0) avg_daily = usage_30d / 30.0 if usage_30d else 0 # 재고 소진 예상일 계산 stock = context.get('stock_at_order', 0) days_until_stockout = stock / avg_daily if avg_daily > 0 else None # 주문 총 정제수 ordered_qty = context.get('ordered_qty', 0) spec = context.get('ordered_spec', '') unit_qty = int(''.join(filter(str.isdigit, spec))) if spec else 1 ordered_dose = ordered_qty * unit_qty cursor.execute(''' INSERT INTO order_context ( order_item_id, drug_code, product_name, stock_at_order, usage_1d, usage_7d, usage_30d, avg_daily_usage, ordered_spec, ordered_qty, ordered_dose, available_specs, spec_stocks, selection_reason, days_until_stockout ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( order_item_id, context.get('drug_code'), context.get('product_name'), context.get('stock_at_order'), context.get('usage_1d'), context.get('usage_7d'), context.get('usage_30d'), avg_daily, context.get('ordered_spec'), ordered_qty, ordered_dose, json.dumps(context.get('available_specs', []), ensure_ascii=False), json.dumps(context.get('spec_stocks', {}), ensure_ascii=False), context.get('selection_reason'), days_until_stockout )) conn.commit() return True except Exception as e: conn.rollback() return False finally: conn.close() def update_daily_usage(drug_code: str, usage_date: str, rx_count: int = 0, rx_qty: int = 0, pos_count: int = 0, pos_qty: int = 0, stock_end: int = None) -> bool: """일별 사용량 업데이트 (UPSERT)""" conn = get_connection() cursor = conn.cursor() try: total_qty = rx_qty + pos_qty cursor.execute(''' INSERT INTO daily_usage ( drug_code, usage_date, rx_count, rx_qty, pos_count, pos_qty, total_qty, stock_end ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(drug_code, usage_date) DO UPDATE SET rx_count = rx_count + excluded.rx_count, rx_qty = rx_qty + excluded.rx_qty, pos_count = pos_count + excluded.pos_count, pos_qty = pos_qty + excluded.pos_qty, total_qty = total_qty + excluded.total_qty, stock_end = COALESCE(excluded.stock_end, stock_end) ''', (drug_code, usage_date, rx_count, rx_qty, pos_count, pos_qty, total_qty, stock_end)) conn.commit() return True except Exception as e: conn.rollback() return False finally: conn.close() def get_usage_stats(drug_code: str, days: int = 30) -> Dict: """약품 사용량 통계 조회 (AI 분석용)""" conn = get_connection() cursor = conn.cursor() from datetime import datetime, timedelta end_date = datetime.now().strftime('%Y-%m-%d') start_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d') cursor.execute(''' SELECT COUNT(*) as days_with_data, SUM(total_qty) as total_usage, AVG(total_qty) as avg_daily, MAX(total_qty) as max_daily, MIN(total_qty) as min_daily FROM daily_usage WHERE drug_code = ? AND usage_date BETWEEN ? AND ? ''', (drug_code, start_date, end_date)) row = cursor.fetchone() conn.close() if row and row['total_usage']: return { 'drug_code': drug_code, 'period_days': days, 'days_with_data': row['days_with_data'], 'total_usage': row['total_usage'], 'avg_daily': round(row['avg_daily'], 2) if row['avg_daily'] else 0, 'max_daily': row['max_daily'], 'min_daily': row['min_daily'] } return { 'drug_code': drug_code, 'period_days': days, 'days_with_data': 0, 'total_usage': 0, 'avg_daily': 0, 'max_daily': 0, 'min_daily': 0 } def get_order_pattern(drug_code: str) -> Optional[Dict]: """약품 주문 패턴 조회""" conn = get_connection() cursor = conn.cursor() # 최근 주문 이력 분석 cursor.execute(''' SELECT oi.specification, oi.order_qty, oi.total_dose, o.order_date FROM order_items oi JOIN orders o ON oi.order_id = o.id WHERE oi.drug_code = ? AND oi.status = 'success' ORDER BY o.order_date DESC LIMIT 10 ''', (drug_code,)) orders = [dict(row) for row in cursor.fetchall()] if not orders: conn.close() return None # 가장 많이 사용된 규격 spec_counts = {} for o in orders: spec = o['specification'] spec_counts[spec] = spec_counts.get(spec, 0) + 1 typical_spec = max(spec_counts, key=spec_counts.get) # 평균 주문 수량 typical_qty = sum(o['order_qty'] for o in orders) // len(orders) # 주문 주기 계산 if len(orders) >= 2: dates = [datetime.strptime(o['order_date'], '%Y-%m-%d') for o in orders] intervals = [(dates[i] - dates[i+1]).days for i in range(len(dates)-1)] avg_interval = sum(intervals) / len(intervals) if intervals else 0 else: avg_interval = 0 conn.close() return { 'drug_code': drug_code, 'order_count': len(orders), 'typical_spec': typical_spec, 'typical_qty': typical_qty, 'avg_order_interval_days': round(avg_interval, 1), 'recent_orders': orders[:5] } def get_ai_training_data(limit: int = 1000) -> List[Dict]: """AI 학습용 데이터 추출""" conn = get_connection() cursor = conn.cursor() cursor.execute(''' SELECT oc.*, oi.status as order_status, oi.result_code, o.order_date, o.wholesaler_id FROM order_context oc JOIN order_items oi ON oc.order_item_id = oi.id JOIN orders o ON oi.order_id = o.id ORDER BY oc.created_at DESC LIMIT ? ''', (limit,)) data = [] for row in cursor.fetchall(): item = dict(row) # JSON 필드 파싱 if item.get('available_specs'): item['available_specs'] = json.loads(item['available_specs']) if item.get('spec_stocks'): item['spec_stocks'] = json.loads(item['spec_stocks']) data.append(item) conn.close() return data def save_ai_pattern(drug_code: str, pattern: Dict) -> bool: """AI 분석 결과 저장""" conn = get_connection() cursor = conn.cursor() try: today = datetime.now().strftime('%Y-%m-%d') cursor.execute(''' INSERT INTO order_patterns ( drug_code, analysis_date, analysis_period_days, avg_daily_usage, usage_stddev, peak_usage, typical_order_spec, typical_order_qty, order_frequency_days, recommended_spec, recommended_qty, recommended_reorder_point, confidence_score, model_version ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(drug_code, analysis_date) DO UPDATE SET avg_daily_usage = excluded.avg_daily_usage, recommended_spec = excluded.recommended_spec, recommended_qty = excluded.recommended_qty, confidence_score = excluded.confidence_score ''', ( drug_code, today, pattern.get('period_days', 30), pattern.get('avg_daily_usage'), pattern.get('usage_stddev'), pattern.get('peak_usage'), pattern.get('typical_order_spec'), pattern.get('typical_order_qty'), pattern.get('order_frequency_days'), pattern.get('recommended_spec'), pattern.get('recommended_qty'), pattern.get('recommended_reorder_point'), pattern.get('confidence_score'), pattern.get('model_version', 'v1') )) conn.commit() return True except Exception as e: conn.rollback() return False finally: conn.close() # 초기화 실행 init_db()