pharmacy-pos-qr-system/backend/order_db.py
thug0bin c1596a6d35 feat: 도매상 API 통합 및 스키마 업데이트
- wholesale 패키지 연동 (SooinSession, GeoYoungSession)
- Flask Blueprint 분리 (sooin_api.py, geoyoung_api.py)
- order_context 스키마 확장 (wholesaler_id, internal_code 등)
- 수인약품 개별 취소 기능 (cancel_item, restore_item)
- 문서 추가: WHOLESALE_API_INTEGRATION.md
- 테스트 스크립트들
2026-03-06 11:50:46 +09:00

860 lines
30 KiB
Python

# -*- 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, -- 보험코드 (지오영 검색용)
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, 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('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()