pharmacy-pos-qr-system/docs/AI_ERP_AUTO_ORDER_SYSTEM.html
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

1072 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>스마트헬스케어 사업제안서</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
line-height: 1.8;
color: #1e293b;
max-width: 210mm;
margin: 0 auto;
padding: 20mm;
background: #fff;
}
h1 {
font-size: 28px;
font-weight: 700;
color: #6366f1;
margin: 40px 0 20px;
padding-bottom: 10px;
border-bottom: 3px solid #6366f1;
}
h2 {
font-size: 22px;
font-weight: 700;
color: #334155;
margin: 35px 0 15px;
padding-bottom: 8px;
border-bottom: 2px solid #e2e8f0;
}
h3 {
font-size: 18px;
font-weight: 600;
color: #475569;
margin: 25px 0 12px;
}
h4 {
font-size: 16px;
font-weight: 600;
color: #64748b;
margin: 20px 0 10px;
}
p {
margin: 12px 0;
text-align: justify;
}
blockquote {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-left: 4px solid #6366f1;
padding: 16px 20px;
margin: 20px 0;
border-radius: 0 8px 8px 0;
font-style: italic;
color: #475569;
}
code {
background: #f1f5f9;
padding: 2px 6px;
border-radius: 4px;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
color: #dc2626;
}
pre {
background: #1e293b;
color: #e2e8f0;
padding: 20px;
border-radius: 12px;
overflow-x: auto;
margin: 20px 0;
font-size: 12px;
line-height: 1.6;
}
pre code {
background: none;
color: inherit;
padding: 0;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
font-size: 14px;
}
th {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: #fff;
padding: 12px 16px;
text-align: left;
font-weight: 600;
}
td {
padding: 12px 16px;
border-bottom: 1px solid #e2e8f0;
}
tr:nth-child(even) {
background: #f8fafc;
}
ul, ol {
margin: 15px 0;
padding-left: 25px;
}
li {
margin: 8px 0;
}
hr {
border: none;
height: 2px;
background: linear-gradient(90deg, #6366f1, #8b5cf6, #ec4899);
margin: 40px 0;
border-radius: 2px;
}
strong {
color: #334155;
font-weight: 600;
}
em {
color: #64748b;
}
/* 첫 페이지 타이틀 */
h1:first-of-type {
font-size: 32px;
text-align: center;
border-bottom: none;
margin-top: 60px;
margin-bottom: 10px;
}
h1:first-of-type + blockquote {
text-align: center;
border-left: none;
background: none;
font-size: 18px;
margin-bottom: 60px;
}
/* 프린트 스타일 */
@media print {
body {
padding: 15mm;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
}
h1, h2, h3 {
page-break-after: avoid;
}
table, pre, blockquote {
page-break-inside: avoid;
}
}
/* 페이지 구분 */
.page-break {
page-break-before: always;
}
</style>
</head>
<body>
<h1 id="ai-erp">AI ERP 자동 주문 시스템 기획서</h1>
<blockquote>
<p>버전: 1.0<br />
작성일: 2026-03-06<br />
목표: 약국 재고 관리 및 주문을 AI가 학습하여 완전 자동화</p>
</blockquote>
<hr />
<h2 id="executive-summary">📋 Executive Summary</h2>
<h3 id="_1">비전</h3>
<p><strong>"약사님이 주문에 신경 쓰지 않아도 되는 약국"</strong></p>
<p>AI가 사용량, 재고, 도매상 상황, 과거 주문 패턴을 학습하여:
- 언제 주문할지
- 어느 도매상에 주문할지
- 어떤 규격으로 주문할지
- 얼마나 주문할지</p>
<p>모든 것을 자동으로 결정하고 실행합니다.</p>
<h3 id="_2">핵심 가치</h3>
<table>
<thead>
<tr>
<th>AS-IS</th>
<th>TO-BE</th>
</tr>
</thead>
<tbody>
<tr>
<td>매일 재고 확인</td>
<td>AI가 자동 모니터링</td>
</tr>
<tr>
<td>수동으로 도매상 선택</td>
<td>AI가 최적 도매상 선택</td>
</tr>
<tr>
<td>경험에 의존한 주문량</td>
<td>데이터 기반 최적 주문량</td>
</tr>
<tr>
<td>주문 누락/지연 발생</td>
<td>선제적 자동 주문</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="_3">🎯 시스템 목표</h2>
<h3 id="1">1차 목표 (자동화)</h3>
<ul>
<li>[ ] 재고 부족 품목 자동 감지</li>
<li>[ ] 도매상 자동 선택 및 주문</li>
<li>[ ] 주문 결과 자동 피드백</li>
</ul>
<h3 id="2">2차 목표 (최적화)</h3>
<ul>
<li>[ ] 비용 최소화 (가격, 배송비)</li>
<li>[ ] 재고 최적화 (과잉/부족 방지)</li>
<li>[ ] 주문 타이밍 최적화</li>
</ul>
<h3 id="3">3차 목표 (예측)</h3>
<ul>
<li>[ ] 수요 예측 (계절, 요일, 이벤트)</li>
<li>[ ] 공급 리스크 예측 (품절, 단종)</li>
<li>[ ] 가격 변동 예측</li>
</ul>
<hr />
<h2 id="ai">🧠 AI 학습 요소</h2>
<h3 id="1_1">1. 주문 패턴 학습</h3>
<h4 id="11-spec-selection">1.1 규격 선택 패턴 (Spec Selection)</h4>
<pre><code>학습 데이터:
- 약품별 과거 주문 규격 (30T, 100T, 300T, 500T)
- 각 규격 선택 시점의 재고/사용량
- 선택 결과 (남은 재고, 다음 주문까지 기간)
학습 목표:
- 사용량 대비 최적 규격 예측
- 낭비 최소화 (유통기한 고려)
- 단가 최적화 (대용량 할인 vs 소량 회전)
</code></pre>
<p><strong>예시 시나리오:</strong>
| 사용량/월 | 학습된 최적 규격 | 이유 |
|-----------|-----------------|------|
| 50개 | 30T x 2 | 소량, 빠른 회전 |
| 200개 | 100T x 2 | 중간, 적정 재고 |
| 800개 | 300T x 3 | 대량, 단가 절감 |</p>
<h4 id="12-inventory-strategy">1.2 재고 전략 학습 (Inventory Strategy)</h4>
<pre><code>학습 데이터:
- 주문 시점의 재고 수준
- 재고 소진까지 남은 일수
- 주문 후 입고까지 리드타임
- 품절 발생 이력
학습 목표:
- 약사님의 재고 선호도 파악
- 타이트형: 최소 재고 유지 (현금 흐름 중시)
- 여유형: 안전 재고 확보 (품절 방지 중시)
</code></pre>
<p><strong>재고 전략 프로파일:</strong></p>
<pre><code class="language-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
}
</code></pre>
<h4 id="13-order-quantity">1.3 주문량 전략 학습 (Order Quantity)</h4>
<pre><code>학습 데이터:
- 사용량 (일별, 주별, 월별)
- 주문량
- 주문 후 소진까지 기간
- 사용량 변동성 (표준편차)
학습 패턴:
1. 정확 매칭형: 사용량 = 주문량
2. 안전 마진형: 사용량 + α
3. 라운드업형: 규격 단위로 올림
4. 할인 최적형: MOQ(최소주문량) 충족
</code></pre>
<h4 id="14-wholesaler-selection">1.4 도매상 선택 학습 (Wholesaler Selection)</h4>
<pre><code>학습 데이터:
- 도매상별 주문 빈도
- 도매상별 가격
- 도매상별 재고 상황
- 도매상별 배송 속도
- 분할 주문 패턴
학습 목표:
- 기본 도매상 선호도
- 상황별 대체 도매상
- 분할 주문 조건
</code></pre>
<p><strong>도매상 선택 로직:</strong></p>
<pre><code class="language-python">def select_wholesaler(product, quantity, urgency):
&quot;&quot;&quot;
AI가 학습한 도매상 선택 로직
고려 요소:
1. 재고 (있는 곳 우선)
2. 가격 (저렴한 곳)
3. 선호도 (과거 패턴)
4. 긴급도 (배송 속도)
&quot;&quot;&quot;
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])
</code></pre>
<hr />
<h2 id="_4">📊 데이터 모델</h2>
<h3 id="ai_1">주문 컨텍스트 (AI 학습용)</h3>
<pre><code class="language-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
);
</code></pre>
<h3 id="_5">사용량 시계열</h3>
<pre><code class="language-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)
);
</code></pre>
<h3 id="ai_2">AI 분석 결과</h3>
<pre><code class="language-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
);
</code></pre>
<hr />
<h2 id="_6">🔄 시스템 아키텍처</h2>
<h3 id="_7">전체 흐름</h3>
<pre><code>┌─────────────────────────────────────────────────────────────────┐
│ AI ERP 자동 주문 시스템 │
└─────────────────────────────────────────────────────────────────┘
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 데이터 수집 │ │ AI 분석 │ │ 자동 실행 │
│ │ │ │ │ │
│ • POS 판매 │─────▶│ • 사용량 예측 │─────▶│ • 도매상 API │
│ • 처방전 조제 │ │ • 재고 분석 │ │ • 주문 실행 │
│ • 현재 재고 │ │ • 주문 추천 │ │ • 결과 피드백 │
│ • 도매상 재고 │ │ • 패턴 학습 │ │ │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
└───────────────────────┼───────────────────────┘
┌───────────────────┐
│ 학습 루프 │
│ │
│ 주문 결과 평가 │
│ → 모델 업데이트 │
│ → 전략 조정 │
└───────────────────┘
</code></pre>
<h3 id="_8">컴포넌트 상세</h3>
<pre><code>┌──────────────────────────────────────────────────────────────────┐
│ 데이터 레이어 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ PIT3000 │ │ SQLite │ │ 지오영 │ │ 수인 │ │
│ │ (MSSQL) │ │ Orders DB │ │ API │ │ API │ │
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │ │ │
│ └───────────────┴───────────────┴───────────────┘ │
│ │ │
└────────────────────────────────┼─────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ 서비스 레이어 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ InventorySync │ │ UsageAnalyzer │ │ OrderExecutor │ │
│ │ │ │ │ │ │ │
│ │ • 재고 동기화 │ │ • 사용량 집계 │ │ • 주문 실행 │ │
│ │ • 실시간 추적 │ │ • 트렌드 분석 │ │ • 결과 처리 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ AIPredictor │ │ AIOptimizer │ │ AILearner │ │
│ │ │ │ │ │ │ │
│ │ • 수요 예측 │ │ • 규격 최적화 │ │ • 패턴 학습 │ │
│ │ • 재고 예측 │ │ • 도매상 선택 │ │ • 모델 업데이트 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ 인터페이스 레이어 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 웹 대시보드 │ │ 알림 시스템 │ │ 관리자 앱 │ │
│ │ │ │ │ │ │ │
│ │ • 재고 현황 │ │ • 주문 알림 │ │ • 수동 개입 │ │
│ │ • 주문 이력 │ │ • 이상 감지 │ │ • 설정 조정 │ │
│ │ • AI 추천 │ │ • 승인 요청 │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
</code></pre>
<hr />
<h2 id="ai_3">🤖 AI 모델 설계</h2>
<h3 id="1_2">1. 수요 예측 모델</h3>
<pre><code class="language-python">class DemandPredictor:
&quot;&quot;&quot;
약품별 일간 수요 예측
입력:
- 과거 30일 사용량
- 요일 (월~일)
- 계절/월
- 특수일 (공휴일, 이벤트)
출력:
- 향후 7일 예측 사용량
- 예측 신뢰구간
&quot;&quot;&quot;
def predict(self, drug_code: str, days: int = 7) -&gt; dict:
features = self._extract_features(drug_code)
prediction = {
'daily_forecast': [], # 일별 예측
'total_forecast': 0, # 총 예측량
'confidence': 0.0, # 신뢰도
'lower_bound': 0, # 하한
'upper_bound': 0 # 상한
}
return prediction
</code></pre>
<h3 id="2_1">2. 재고 최적화 모델</h3>
<pre><code class="language-python">class InventoryOptimizer:
&quot;&quot;&quot;
최적 재고 수준 및 재주문점 계산
입력:
- 예측 수요
- 리드타임 (주문~입고)
- 서비스 수준 (품절 허용률)
- 재고 유지 비용
출력:
- 재주문점 (Reorder Point)
- 안전 재고 (Safety Stock)
- 최적 주문량 (EOQ)
&quot;&quot;&quot;
def calculate_reorder_point(self, drug_code: str) -&gt; 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
}
</code></pre>
<h3 id="3_1">3. 규격 선택 모델</h3>
<pre><code class="language-python">class SpecSelector:
&quot;&quot;&quot;
최적 규격 선택
고려 요소:
- 예상 사용량
- 규격별 단가
- 유통기한
- 과거 선택 패턴
&quot;&quot;&quot;
def select_spec(self, drug_code: str, needed_qty: int,
available_specs: list) -&gt; dict:
candidates = []
for spec in available_specs:
spec_qty = self._parse_spec_qty(spec) # &quot;300T&quot; → 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'])
</code></pre>
<h3 id="4">4. 도매상 선택 모델</h3>
<pre><code class="language-python">class WholesalerSelector:
&quot;&quot;&quot;
최적 도매상 선택 (다중 도매상 지원)
고려 요소:
- 재고 유무
- 가격
- 배송 속도
- 과거 선호도
- 최소 주문 금액
&quot;&quot;&quot;
def select_wholesaler(self, drug_code: str, spec: str,
quantity: int, urgency: str) -&gt; dict:
wholesalers = ['geoyoung', 'sooin', 'baekje']
candidates = []
for ws in wholesalers:
# 재고 확인
stock = self._check_stock(ws, drug_code, spec)
if stock &lt; 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):
&quot;&quot;&quot;재고 없을 때: 분할 주문 또는 대체품&quot;&quot;&quot;
# 1. 다른 규격으로 분할
# 2. 다중 도매상 분할
# 3. 대체 약품 추천
pass
</code></pre>
<h3 id="5">5. 주문 결정 엔진</h3>
<pre><code class="language-python">class OrderDecisionEngine:
&quot;&quot;&quot;
종합 주문 결정
매일 실행:
1. 모든 약품 재고 스캔
2. 재주문점 도달 품목 식별
3. 각 품목별 최적 주문 계획 수립
4. 자동 실행 또는 승인 요청
&quot;&quot;&quot;
def daily_analysis(self) -&gt; 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 &lt;= reorder_point['reorder_point']:
# 주문 필요
order_plan = self._create_order_plan(drug)
recommendations.append(order_plan)
return recommendations
def _create_order_plan(self, drug) -&gt; 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)
}
</code></pre>
<hr />
<h2 id="_9">📈 학습 파이프라인</h2>
<h3 id="_10">피드백 루프</h3>
<pre><code>주문 실행 → 결과 기록 → 평가 → 학습 → 모델 업데이트
│ │
└────────────────────────────────────────┘
</code></pre>
<h3 id="_11">평가 지표</h3>
<pre><code class="language-python">class OrderEvaluator:
&quot;&quot;&quot;주문 결과 평가&quot;&quot;&quot;
def evaluate(self, order_id: int) -&gt; 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(...)
}
</code></pre>
<h3 id="_12">모델 업데이트</h3>
<pre><code class="language-python">class AILearner:
&quot;&quot;&quot;주문 결과로부터 학습&quot;&quot;&quot;
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'] &lt; 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']
)
</code></pre>
<hr />
<h2 id="_13">⚙️ 자동화 레벨</h2>
<h3 id="level-0">Level 0: 수동</h3>
<ul>
<li>AI 추천만 제공</li>
<li>모든 주문은 수동 실행</li>
</ul>
<h3 id="level-1">Level 1: 반자동</h3>
<ul>
<li>AI가 주문 계획 생성</li>
<li>약사님 승인 후 자동 실행</li>
<li>알림: 승인 요청</li>
</ul>
<h3 id="level-2">Level 2: 조건부 자동</h3>
<ul>
<li>신뢰도 높은 주문은 자동 실행</li>
<li>신뢰도 낮은 주문만 승인 요청</li>
<li>조건 예시:</li>
<li>자주 주문하는 품목</li>
<li>금액 임계값 이하</li>
<li>긴급하지 않은 주문</li>
</ul>
<h3 id="level-3">Level 3: 완전 자동</h3>
<ul>
<li>모든 주문 자동 실행</li>
<li>이상 상황만 알림</li>
<li>약사님은 대시보드로 모니터링</li>
</ul>
<pre><code class="language-python">class AutomationLevel:
def should_auto_execute(self, order_plan: dict) -&gt; bool:
level = self.settings.automation_level
if level == 0:
return False
if level == 1:
return False # 항상 승인 필요
if level == 2:
# 조건부 자동
conditions = [
order_plan['confidence'] &gt; 0.9,
order_plan['estimated_cost'] &lt; 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)
</code></pre>
<hr />
<h2 id="_14">🔔 알림 시스템</h2>
<h3 id="_15">알림 유형</h3>
<table>
<thead>
<tr>
<th>유형</th>
<th>조건</th>
<th>채널</th>
</tr>
</thead>
<tbody>
<tr>
<td>승인 요청</td>
<td>Level 1-2에서 자동 실행 안 되는 주문</td>
<td>카톡, 앱 푸시</td>
</tr>
<tr>
<td>주문 완료</td>
<td>자동 주문 실행됨</td>
<td>앱 푸시</td>
</tr>
<tr>
<td>재고 경고</td>
<td>안전 재고 이하</td>
<td>카톡</td>
</tr>
<tr>
<td>품절 긴급</td>
<td>재고 0, 당일 필요</td>
<td>전화, 카톡</td>
</tr>
<tr>
<td>이상 감지</td>
<td>비정상 사용량, 가격 급등</td>
<td>앱 푸시</td>
</tr>
<tr>
<td>일간 리포트</td>
<td>매일 오전</td>
<td>이메일</td>
</tr>
</tbody>
</table>
<h3 id="_16">알림 메시지 예시</h3>
<pre><code>📦 주문 승인 요청
약품: 콩코르정 2.5mg
현재고: 45개 (3일치)
추천 주문: 300T x 2박스
도매상: 지오영
예상 금액: 72,000원
[승인] [수정] [거절]
</code></pre>
<hr />
<h2 id="_17">📅 개발 로드맵</h2>
<h3 id="phase-1-1-2">Phase 1: 기반 구축 (1-2주)</h3>
<ul>
<li>[x] 지오영 API 연동</li>
<li>[x] 주문 DB 스키마 설계</li>
<li>[x] 주문 컨텍스트 로깅</li>
<li>[ ] 수인 API 연동</li>
<li>[ ] 일별 사용량 집계 자동화</li>
</ul>
<h3 id="phase-2-ai-2-3">Phase 2: AI 기본 (2-3주)</h3>
<ul>
<li>[ ] 수요 예측 모델 (단순 이동평균)</li>
<li>[ ] 재주문점 계산</li>
<li>[ ] 규격 선택 로직 (규칙 기반)</li>
<li>[ ] 도매상 선택 로직 (규칙 기반)</li>
<li>[ ] 주문 추천 대시보드</li>
</ul>
<h3 id="phase-3-2-3">Phase 3: 학습 시스템 (2-3주)</h3>
<ul>
<li>[ ] 피드백 루프 구현</li>
<li>[ ] 주문 평가 시스템</li>
<li>[ ] 패턴 학습 (규격, 도매상)</li>
<li>[ ] 재고 전략 프로파일링</li>
</ul>
<h3 id="phase-4-1-2">Phase 4: 자동화 (1-2주)</h3>
<ul>
<li>[ ] Level 1 (승인 후 자동)</li>
<li>[ ] 알림 시스템 연동</li>
<li>[ ] Level 2 (조건부 자동)</li>
<li>[ ] 모니터링 대시보드</li>
</ul>
<h3 id="phase-5">Phase 5: 고도화 (지속)</h3>
<ul>
<li>[ ] ML 모델 적용 (XGBoost, LSTM)</li>
<li>[ ] Level 3 (완전 자동)</li>
<li>[ ] 다중 약국 지원</li>
<li>[ ] 수요 예측 정교화</li>
</ul>
<hr />
<h2 id="kpi">📊 성공 지표 (KPI)</h2>
<table>
<thead>
<tr>
<th>지표</th>
<th>현재</th>
<th>목표</th>
</tr>
</thead>
<tbody>
<tr>
<td>주문 소요 시간</td>
<td>30분/일</td>
<td>0분 (자동)</td>
</tr>
<tr>
<td>품절 발생률</td>
<td>5%</td>
<td>&lt;1%</td>
</tr>
<tr>
<td>재고 회전율</td>
<td>-</td>
<td>+20%</td>
</tr>
<tr>
<td>주문 비용 절감</td>
<td>-</td>
<td>5-10%</td>
</tr>
<tr>
<td>폐기 손실</td>
<td>-</td>
<td>-30%</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="_18">🔐 보안 및 안전장치</h2>
<h3 id="_19">자동 주문 제한</h3>
<ul>
<li>일일 자동 주문 금액 상한</li>
<li>단일 품목 최대 수량</li>
<li>신규 품목 자동 주문 제외</li>
<li>가격 급등 시 수동 전환</li>
</ul>
<h3 id="_20">롤백 메커니즘</h3>
<ul>
<li>모든 주문 취소 가능 (확정 전)</li>
<li>자동화 레벨 즉시 변경</li>
<li>긴급 수동 모드 전환</li>
</ul>
<h3 id="_21">감사 로그</h3>
<ul>
<li>모든 AI 결정 기록</li>
<li>자동 실행 이력</li>
<li>승인/거절 이력</li>
</ul>
<hr />
<h2 id="_22">💡 핵심 인사이트</h2>
<blockquote>
<p>"AI는 약사님의 주문 습관을 학습합니다."</p>
</blockquote>
<ul>
<li>약사님이 항상 지오영에 먼저 주문하면 → AI도 지오영 우선</li>
<li>약사님이 300T보다 30T를 선호하면 → AI도 소량 주문</li>
<li>약사님이 여유 있게 주문하면 → AI도 안전 재고 확보</li>
<li>약사님이 가격에 민감하면 → AI도 최저가 추적</li>
</ul>
<p><strong>AI는 대체하는 것이 아니라, 약사님의 방식을 자동화합니다.</strong></p>
<hr />
<h2 id="_23">📚 참고 자료</h2>
<ul>
<li>지오영 API 문서: <code>docs/GEOYOUNG_API_REVERSE_ENGINEERING.md</code></li>
<li>주문 DB 스키마: <code>backend/order_db.py</code></li>
<li>사용량 조회 페이지: <code>docs/RX_USAGE_GEOYOUNG_GUIDE.md</code></li>
</ul>
</body>
</html>