# AI ERP 자동 주문 시스템 기획서 > 버전: 1.0 > 작성일: 2026-03-06 > 목표: 약국 재고 관리 및 주문을 AI가 학습하여 완전 자동화 --- ## 📋 Executive Summary ### 비전 **"약사님이 주문에 신경 쓰지 않아도 되는 약국"** AI가 사용량, 재고, 도매상 상황, 과거 주문 패턴을 학습하여: - 언제 주문할지 - 어느 도매상에 주문할지 - 어떤 규격으로 주문할지 - 얼마나 주문할지 모든 것을 자동으로 결정하고 실행합니다. ### 핵심 가치 | AS-IS | TO-BE | |-------|-------| | 매일 재고 확인 | AI가 자동 모니터링 | | 수동으로 도매상 선택 | AI가 최적 도매상 선택 | | 경험에 의존한 주문량 | 데이터 기반 최적 주문량 | | 주문 누락/지연 발생 | 선제적 자동 주문 | --- ## 🎯 시스템 목표 ### 1차 목표 (자동화) - [ ] 재고 부족 품목 자동 감지 - [ ] 도매상 자동 선택 및 주문 - [ ] 주문 결과 자동 피드백 ### 2차 목표 (최적화) - [ ] 비용 최소화 (가격, 배송비) - [ ] 재고 최적화 (과잉/부족 방지) - [ ] 주문 타이밍 최적화 ### 3차 목표 (예측) - [ ] 수요 예측 (계절, 요일, 이벤트) - [ ] 공급 리스크 예측 (품절, 단종) - [ ] 가격 변동 예측 --- ## 🧠 AI 학습 요소 ### 1. 주문 패턴 학습 #### 1.1 규격 선택 패턴 (Spec Selection) ``` 학습 데이터: - 약품별 과거 주문 규격 (30T, 100T, 300T, 500T) - 각 규격 선택 시점의 재고/사용량 - 선택 결과 (남은 재고, 다음 주문까지 기간) 학습 목표: - 사용량 대비 최적 규격 예측 - 낭비 최소화 (유통기한 고려) - 단가 최적화 (대용량 할인 vs 소량 회전) ``` **예시 시나리오:** | 사용량/월 | 학습된 최적 규격 | 이유 | |-----------|-----------------|------| | 50개 | 30T x 2 | 소량, 빠른 회전 | | 200개 | 100T x 2 | 중간, 적정 재고 | | 800개 | 300T x 3 | 대량, 단가 절감 | #### 1.2 재고 전략 학습 (Inventory Strategy) ``` 학습 데이터: - 주문 시점의 재고 수준 - 재고 소진까지 남은 일수 - 주문 후 입고까지 리드타임 - 품절 발생 이력 학습 목표: - 약사님의 재고 선호도 파악 - 타이트형: 최소 재고 유지 (현금 흐름 중시) - 여유형: 안전 재고 확보 (품절 방지 중시) ``` **재고 전략 프로파일:** ```python class InventoryStrategy: TIGHT = { 'safety_days': 2, # 안전 재고 2일치 'reorder_point': 0.8, # 80% 소진 시 주문 'order_coverage': 7 # 7일치 주문 } MODERATE = { 'safety_days': 5, 'reorder_point': 0.6, 'order_coverage': 14 } CONSERVATIVE = { 'safety_days': 10, 'reorder_point': 0.5, 'order_coverage': 30 } ``` #### 1.3 주문량 전략 학습 (Order Quantity) ``` 학습 데이터: - 사용량 (일별, 주별, 월별) - 주문량 - 주문 후 소진까지 기간 - 사용량 변동성 (표준편차) 학습 패턴: 1. 정확 매칭형: 사용량 = 주문량 2. 안전 마진형: 사용량 + α 3. 라운드업형: 규격 단위로 올림 4. 할인 최적형: MOQ(최소주문량) 충족 ``` #### 1.4 도매상 선택 학습 (Wholesaler Selection) ``` 학습 데이터: - 도매상별 주문 빈도 - 도매상별 가격 - 도매상별 재고 상황 - 도매상별 배송 속도 - 분할 주문 패턴 학습 목표: - 기본 도매상 선호도 - 상황별 대체 도매상 - 분할 주문 조건 ``` **도매상 선택 로직:** ```python def select_wholesaler(product, quantity, urgency): """ AI가 학습한 도매상 선택 로직 고려 요소: 1. 재고 (있는 곳 우선) 2. 가격 (저렴한 곳) 3. 선호도 (과거 패턴) 4. 긴급도 (배송 속도) """ candidates = [] for ws in wholesalers: score = 0 # 재고 체크 if ws.has_stock(product, quantity): score += 100 # 가격 (낮을수록 높은 점수) score += (1 - ws.price_ratio) * 50 # 학습된 선호도 score += ai_model.preference_score(ws, product) * 30 # 긴급도 반영 if urgency == 'high': score += ws.delivery_speed * 20 candidates.append((ws, score)) return max(candidates, key=lambda x: x[1]) ``` --- ## 📊 데이터 모델 ### 주문 컨텍스트 (AI 학습용) ```sql CREATE TABLE order_context ( id INTEGER PRIMARY KEY, order_item_id INTEGER, -- 약품 정보 drug_code TEXT, product_name TEXT, -- 주문 시점 상황 stock_at_order INTEGER, -- 주문 시점 재고 usage_1d INTEGER, -- 최근 1일 사용량 usage_7d INTEGER, -- 최근 7일 사용량 usage_30d INTEGER, -- 최근 30일 사용량 avg_daily_usage REAL, -- 일평균 사용량 usage_stddev REAL, -- 사용량 변동성 -- 주문 결정 ordered_spec TEXT, -- 선택한 규격 (30T, 300T) ordered_qty INTEGER, -- 주문 수량 ordered_dose INTEGER, -- 총 정제수 wholesaler_id TEXT, -- 선택한 도매상 -- 선택지 정보 available_specs JSON, -- 가능했던 규격들 available_wholesalers JSON, -- 가능했던 도매상들 spec_stocks JSON, -- 규격별 재고 wholesaler_prices JSON, -- 도매상별 가격 -- 선택 이유 (AI 분석용) selection_reason TEXT, -- 'price', 'stock', 'preference', 'urgency' -- 예측 vs 실제 predicted_days_coverage REAL, -- 예상 커버 일수 actual_days_to_reorder INT, -- 실제 재주문까지 일수 -- 결과 평가 was_optimal BOOLEAN, -- 최적 선택이었나 waste_amount INTEGER, -- 낭비량 (폐기, 유통기한) stockout_occurred BOOLEAN, -- 품절 발생했나 created_at TIMESTAMP ); ``` ### 사용량 시계열 ```sql CREATE TABLE daily_usage ( id INTEGER PRIMARY KEY, drug_code TEXT, usage_date DATE, -- 출처별 사용량 rx_qty INTEGER, -- 처방전 사용량 pos_qty INTEGER, -- POS 판매량 return_qty INTEGER, -- 반품량 -- 집계 net_usage INTEGER, -- 순 사용량 -- 재고 스냅샷 stock_start INTEGER, stock_end INTEGER, -- 특이사항 is_holiday BOOLEAN, is_event BOOLEAN, -- 프로모션 등 weather TEXT, -- 날씨 (선택) UNIQUE(drug_code, usage_date) ); ``` ### AI 분석 결과 ```sql CREATE TABLE ai_recommendations ( id INTEGER PRIMARY KEY, drug_code TEXT, analysis_date DATE, -- 현재 상황 current_stock INTEGER, avg_daily_usage REAL, days_of_stock REAL, -- AI 추천 should_order BOOLEAN, recommended_qty INTEGER, recommended_spec TEXT, recommended_wholesaler TEXT, urgency_level TEXT, -- 'low', 'medium', 'high', 'critical' -- 추천 근거 reasoning JSON, confidence_score REAL, -- 실행 상태 auto_executed BOOLEAN, executed_at TIMESTAMP, execution_result TEXT, created_at TIMESTAMP ); ``` --- ## 🔄 시스템 아키텍처 ### 전체 흐름 ``` ┌─────────────────────────────────────────────────────────────────┐ │ AI ERP 자동 주문 시스템 │ └─────────────────────────────────────────────────────────────────┘ │ ┌───────────────────────┼───────────────────────┐ ▼ ▼ ▼ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ 데이터 수집 │ │ AI 분석 │ │ 자동 실행 │ │ │ │ │ │ │ │ • POS 판매 │─────▶│ • 사용량 예측 │─────▶│ • 도매상 API │ │ • 처방전 조제 │ │ • 재고 분석 │ │ • 주문 실행 │ │ • 현재 재고 │ │ • 주문 추천 │ │ • 결과 피드백 │ │ • 도매상 재고 │ │ • 패턴 학습 │ │ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │ │ └───────────────────────┼───────────────────────┘ ▼ ┌───────────────────┐ │ 학습 루프 │ │ │ │ 주문 결과 평가 │ │ → 모델 업데이트 │ │ → 전략 조정 │ └───────────────────┘ ``` ### 컴포넌트 상세 ``` ┌──────────────────────────────────────────────────────────────────┐ │ 데이터 레이어 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ PIT3000 │ │ SQLite │ │ 지오영 │ │ 수인 │ │ │ │ (MSSQL) │ │ Orders DB │ │ API │ │ API │ │ │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │ │ │ │ │ │ │ │ └───────────────┴───────────────┴───────────────┘ │ │ │ │ └────────────────────────────────┼─────────────────────────────────┘ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ 서비스 레이어 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ InventorySync │ │ UsageAnalyzer │ │ OrderExecutor │ │ │ │ │ │ │ │ │ │ │ │ • 재고 동기화 │ │ • 사용량 집계 │ │ • 주문 실행 │ │ │ │ • 실시간 추적 │ │ • 트렌드 분석 │ │ • 결과 처리 │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ AIPredictor │ │ AIOptimizer │ │ AILearner │ │ │ │ │ │ │ │ │ │ │ │ • 수요 예측 │ │ • 규격 최적화 │ │ • 패턴 학습 │ │ │ │ • 재고 예측 │ │ • 도매상 선택 │ │ • 모델 업데이트 │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ 인터페이스 레이어 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ 웹 대시보드 │ │ 알림 시스템 │ │ 관리자 앱 │ │ │ │ │ │ │ │ │ │ │ │ • 재고 현황 │ │ • 주문 알림 │ │ • 수동 개입 │ │ │ │ • 주문 이력 │ │ • 이상 감지 │ │ • 설정 조정 │ │ │ │ • AI 추천 │ │ • 승인 요청 │ │ │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘ ``` --- ## 🤖 AI 모델 설계 ### 1. 수요 예측 모델 ```python class DemandPredictor: """ 약품별 일간 수요 예측 입력: - 과거 30일 사용량 - 요일 (월~일) - 계절/월 - 특수일 (공휴일, 이벤트) 출력: - 향후 7일 예측 사용량 - 예측 신뢰구간 """ def predict(self, drug_code: str, days: int = 7) -> dict: features = self._extract_features(drug_code) prediction = { 'daily_forecast': [], # 일별 예측 'total_forecast': 0, # 총 예측량 'confidence': 0.0, # 신뢰도 'lower_bound': 0, # 하한 'upper_bound': 0 # 상한 } return prediction ``` ### 2. 재고 최적화 모델 ```python class InventoryOptimizer: """ 최적 재고 수준 및 재주문점 계산 입력: - 예측 수요 - 리드타임 (주문~입고) - 서비스 수준 (품절 허용률) - 재고 유지 비용 출력: - 재주문점 (Reorder Point) - 안전 재고 (Safety Stock) - 최적 주문량 (EOQ) """ def calculate_reorder_point(self, drug_code: str) -> dict: demand = self.demand_predictor.predict(drug_code) lead_time = self._get_lead_time(drug_code) # 재주문점 = 리드타임 수요 + 안전재고 lead_time_demand = demand['daily_avg'] * lead_time safety_stock = self._calculate_safety_stock(drug_code) return { 'reorder_point': lead_time_demand + safety_stock, 'safety_stock': safety_stock, 'lead_time_days': lead_time } ``` ### 3. 규격 선택 모델 ```python class SpecSelector: """ 최적 규격 선택 고려 요소: - 예상 사용량 - 규격별 단가 - 유통기한 - 과거 선택 패턴 """ def select_spec(self, drug_code: str, needed_qty: int, available_specs: list) -> dict: candidates = [] for spec in available_specs: spec_qty = self._parse_spec_qty(spec) # "300T" → 300 # 필요 단위 수 계산 units_needed = math.ceil(needed_qty / spec_qty) total_qty = units_needed * spec_qty waste = total_qty - needed_qty # 비용 계산 unit_price = self._get_unit_price(drug_code, spec) total_cost = units_needed * unit_price cost_per_dose = total_cost / total_qty # 학습된 선호도 preference = self.ai_model.spec_preference(drug_code, spec) # 점수 계산 score = self._calculate_score( waste_ratio=waste / total_qty, cost_efficiency=1 / cost_per_dose, preference=preference ) candidates.append({ 'spec': spec, 'units': units_needed, 'total_qty': total_qty, 'waste': waste, 'cost': total_cost, 'score': score }) return max(candidates, key=lambda x: x['score']) ``` ### 4. 도매상 선택 모델 ```python class WholesalerSelector: """ 최적 도매상 선택 (다중 도매상 지원) 고려 요소: - 재고 유무 - 가격 - 배송 속도 - 과거 선호도 - 최소 주문 금액 """ def select_wholesaler(self, drug_code: str, spec: str, quantity: int, urgency: str) -> dict: wholesalers = ['geoyoung', 'sooin', 'baekje'] candidates = [] for ws in wholesalers: # 재고 확인 stock = self._check_stock(ws, drug_code, spec) if stock < quantity: continue # 가격 조회 price = self._get_price(ws, drug_code, spec) # 배송 속도 delivery_hours = self._get_delivery_time(ws) # AI 학습 선호도 preference = self.ai_model.wholesaler_preference( drug_code, ws ) # 종합 점수 score = self._calculate_score( has_stock=True, price=price, delivery=delivery_hours, preference=preference, urgency=urgency ) candidates.append({ 'wholesaler': ws, 'stock': stock, 'price': price, 'delivery_hours': delivery_hours, 'score': score }) if not candidates: return self._handle_no_stock(drug_code, spec, quantity) return max(candidates, key=lambda x: x['score']) def _handle_no_stock(self, drug_code, spec, quantity): """재고 없을 때: 분할 주문 또는 대체품""" # 1. 다른 규격으로 분할 # 2. 다중 도매상 분할 # 3. 대체 약품 추천 pass ``` ### 5. 주문 결정 엔진 ```python class OrderDecisionEngine: """ 종합 주문 결정 매일 실행: 1. 모든 약품 재고 스캔 2. 재주문점 도달 품목 식별 3. 각 품목별 최적 주문 계획 수립 4. 자동 실행 또는 승인 요청 """ def daily_analysis(self) -> list: recommendations = [] for drug in self._get_all_drugs(): current_stock = self._get_stock(drug.code) reorder_point = self.inventory_optimizer.calculate_reorder_point(drug.code) if current_stock <= reorder_point['reorder_point']: # 주문 필요 order_plan = self._create_order_plan(drug) recommendations.append(order_plan) return recommendations def _create_order_plan(self, drug) -> dict: # 1. 필요 수량 계산 needed_qty = self._calculate_needed_qty(drug) # 2. 최적 규격 선택 spec = self.spec_selector.select_spec( drug.code, needed_qty, drug.available_specs ) # 3. 최적 도매상 선택 wholesaler = self.wholesaler_selector.select_wholesaler( drug.code, spec['spec'], spec['units'], urgency=self._determine_urgency(drug) ) return { 'drug_code': drug.code, 'drug_name': drug.name, 'current_stock': self._get_stock(drug.code), 'needed_qty': needed_qty, 'recommended_spec': spec['spec'], 'recommended_units': spec['units'], 'recommended_wholesaler': wholesaler['wholesaler'], 'estimated_cost': wholesaler['price'] * spec['units'], 'urgency': self._determine_urgency(drug), 'confidence': self._calculate_confidence(), 'auto_execute': self._should_auto_execute(drug) } ``` --- ## 📈 학습 파이프라인 ### 피드백 루프 ``` 주문 실행 → 결과 기록 → 평가 → 학습 → 모델 업데이트 │ │ └────────────────────────────────────────┘ ``` ### 평가 지표 ```python class OrderEvaluator: """주문 결과 평가""" def evaluate(self, order_id: int) -> dict: order = self._get_order(order_id) # 1. 재고 효율성 days_covered = self._calculate_days_covered(order) expected_days = order.expected_coverage coverage_accuracy = days_covered / expected_days # 2. 비용 효율성 actual_cost_per_dose = order.total_cost / order.total_dose market_avg_cost = self._get_market_avg_cost(order.drug_code) cost_efficiency = market_avg_cost / actual_cost_per_dose # 3. 낭비율 waste = self._calculate_waste(order) waste_ratio = waste / order.total_dose # 4. 품절 발생 여부 stockout = self._check_stockout_before_next_order(order) return { 'coverage_accuracy': coverage_accuracy, 'cost_efficiency': cost_efficiency, 'waste_ratio': waste_ratio, 'stockout_occurred': stockout, 'overall_score': self._calculate_overall_score(...) } ``` ### 모델 업데이트 ```python class AILearner: """주문 결과로부터 학습""" def learn_from_order(self, order_id: int): evaluation = self.evaluator.evaluate(order_id) context = self._get_order_context(order_id) # 1. 규격 선택 학습 self.spec_model.update( drug_code=context.drug_code, chosen_spec=context.ordered_spec, was_optimal=evaluation['waste_ratio'] < 0.1 ) # 2. 재고 전략 학습 self.inventory_model.update( drug_code=context.drug_code, reorder_point=context.stock_at_order, was_optimal=not evaluation['stockout_occurred'] ) # 3. 도매상 선호도 학습 self.wholesaler_model.update( drug_code=context.drug_code, chosen_wholesaler=context.wholesaler_id, satisfaction=evaluation['cost_efficiency'] ) ``` --- ## ⚙️ 자동화 레벨 ### Level 0: 수동 - AI 추천만 제공 - 모든 주문은 수동 실행 ### Level 1: 반자동 - AI가 주문 계획 생성 - 약사님 승인 후 자동 실행 - 알림: 승인 요청 ### Level 2: 조건부 자동 - 신뢰도 높은 주문은 자동 실행 - 신뢰도 낮은 주문만 승인 요청 - 조건 예시: - 자주 주문하는 품목 - 금액 임계값 이하 - 긴급하지 않은 주문 ### Level 3: 완전 자동 - 모든 주문 자동 실행 - 이상 상황만 알림 - 약사님은 대시보드로 모니터링 ```python class AutomationLevel: def should_auto_execute(self, order_plan: dict) -> bool: level = self.settings.automation_level if level == 0: return False if level == 1: return False # 항상 승인 필요 if level == 2: # 조건부 자동 conditions = [ order_plan['confidence'] > 0.9, order_plan['estimated_cost'] < 100000, order_plan['drug_code'] in self.trusted_drugs, order_plan['urgency'] != 'critical' ] return all(conditions) if level == 3: # 완전 자동 (이상 상황만 제외) return not self._is_anomaly(order_plan) ``` --- ## 🔔 알림 시스템 ### 알림 유형 | 유형 | 조건 | 채널 | |------|------|------| | 승인 요청 | Level 1-2에서 자동 실행 안 되는 주문 | 카톡, 앱 푸시 | | 주문 완료 | 자동 주문 실행됨 | 앱 푸시 | | 재고 경고 | 안전 재고 이하 | 카톡 | | 품절 긴급 | 재고 0, 당일 필요 | 전화, 카톡 | | 이상 감지 | 비정상 사용량, 가격 급등 | 앱 푸시 | | 일간 리포트 | 매일 오전 | 이메일 | ### 알림 메시지 예시 ``` 📦 주문 승인 요청 약품: 콩코르정 2.5mg 현재고: 45개 (3일치) 추천 주문: 300T x 2박스 도매상: 지오영 예상 금액: 72,000원 [승인] [수정] [거절] ``` --- ## 📅 개발 로드맵 ### Phase 1: 기반 구축 (1-2주) - [x] 지오영 API 연동 - [x] 주문 DB 스키마 설계 - [x] 주문 컨텍스트 로깅 - [ ] 수인 API 연동 - [ ] 일별 사용량 집계 자동화 ### Phase 2: AI 기본 (2-3주) - [ ] 수요 예측 모델 (단순 이동평균) - [ ] 재주문점 계산 - [ ] 규격 선택 로직 (규칙 기반) - [ ] 도매상 선택 로직 (규칙 기반) - [ ] 주문 추천 대시보드 ### Phase 3: 학습 시스템 (2-3주) - [ ] 피드백 루프 구현 - [ ] 주문 평가 시스템 - [ ] 패턴 학습 (규격, 도매상) - [ ] 재고 전략 프로파일링 ### Phase 4: 자동화 (1-2주) - [ ] Level 1 (승인 후 자동) - [ ] 알림 시스템 연동 - [ ] Level 2 (조건부 자동) - [ ] 모니터링 대시보드 ### Phase 5: 고도화 (지속) - [ ] ML 모델 적용 (XGBoost, LSTM) - [ ] Level 3 (완전 자동) - [ ] 다중 약국 지원 - [ ] 수요 예측 정교화 --- ## 📊 성공 지표 (KPI) | 지표 | 현재 | 목표 | |------|------|------| | 주문 소요 시간 | 30분/일 | 0분 (자동) | | 품절 발생률 | 5% | <1% | | 재고 회전율 | - | +20% | | 주문 비용 절감 | - | 5-10% | | 폐기 손실 | - | -30% | --- ## 🔐 보안 및 안전장치 ### 자동 주문 제한 - 일일 자동 주문 금액 상한 - 단일 품목 최대 수량 - 신규 품목 자동 주문 제외 - 가격 급등 시 수동 전환 ### 롤백 메커니즘 - 모든 주문 취소 가능 (확정 전) - 자동화 레벨 즉시 변경 - 긴급 수동 모드 전환 ### 감사 로그 - 모든 AI 결정 기록 - 자동 실행 이력 - 승인/거절 이력 --- ## 💡 핵심 인사이트 > "AI는 약사님의 주문 습관을 학습합니다." - 약사님이 항상 지오영에 먼저 주문하면 → AI도 지오영 우선 - 약사님이 300T보다 30T를 선호하면 → AI도 소량 주문 - 약사님이 여유 있게 주문하면 → AI도 안전 재고 확보 - 약사님이 가격에 민감하면 → AI도 최저가 추적 **AI는 대체하는 것이 아니라, 약사님의 방식을 자동화합니다.** --- ## 📚 참고 자료 - 지오영 API 문서: `docs/GEOYOUNG_API_REVERSE_ENGINEERING.md` - 주문 DB 스키마: `backend/order_db.py` - 사용량 조회 페이지: `docs/RX_USAGE_GEOYOUNG_GUIDE.md`