feat(order): 지오영/수인 선택적 주문 + 장바구니 보존 기능
- internal_code DB 저장 → 프론트에서 선택한 제품 그대로 주문 - 기존 장바구니 백업/복구로 사용자 장바구니 보존 - 수인약품 submit_order() 수정 (체크박스 제외 방식) - 테스트 파일 정리 및 문서 추가
This commit is contained in:
1148
docs/AI_자동발주시스템_통합기획서_v1.html
Normal file
1148
docs/AI_자동발주시스템_통합기획서_v1.html
Normal file
File diff suppressed because it is too large
Load Diff
692
docs/AI_자동발주시스템_통합기획서_v1.md
Normal file
692
docs/AI_자동발주시스템_통합기획서_v1.md
Normal file
@@ -0,0 +1,692 @@
|
||||
# 🤖 AI 자동발주시스템 통합 기획서
|
||||
|
||||
> **버전**: 1.0
|
||||
> **작성일**: 2026-03-06
|
||||
> **작성자**: 용림 (with 약사님)
|
||||
> **상태**: 기획 완료, 개발 대기
|
||||
|
||||
---
|
||||
|
||||
## 📋 목차
|
||||
|
||||
1. [비전 및 목표](#1-비전-및-목표)
|
||||
2. [현재 구현 현황](#2-현재-구현-현황)
|
||||
3. [시스템 아키텍처](#3-시스템-아키텍처)
|
||||
4. [AI 학습 요소](#4-ai-학습-요소)
|
||||
5. [핵심 기능 설계](#5-핵심-기능-설계)
|
||||
6. [데이터 모델](#6-데이터-모델)
|
||||
7. [API 설계](#7-api-설계)
|
||||
8. [자동화 레벨](#8-자동화-레벨)
|
||||
9. [알림 시스템](#9-알림-시스템)
|
||||
10. [개발 로드맵](#10-개발-로드맵)
|
||||
11. [성공 지표](#11-성공-지표)
|
||||
|
||||
---
|
||||
|
||||
## 1. 비전 및 목표
|
||||
|
||||
### 🎯 비전
|
||||
> **"약사님이 주문에 신경 쓰지 않아도 되는 약국"**
|
||||
|
||||
AI가 사용량, 재고, 도매상 상황, 과거 주문 패턴을 학습하여:
|
||||
- **언제** 주문할지
|
||||
- **어느 도매상**에 주문할지
|
||||
- **어떤 규격**으로 주문할지
|
||||
- **얼마나** 주문할지
|
||||
|
||||
모든 것을 자동으로 결정하고 실행합니다.
|
||||
|
||||
### 핵심 가치
|
||||
|
||||
| AS-IS | TO-BE |
|
||||
|-------|-------|
|
||||
| 매일 재고 확인 | AI가 자동 모니터링 |
|
||||
| 수동으로 도매상 선택 | AI가 최적 도매상 선택 |
|
||||
| 경험에 의존한 주문량 | 데이터 기반 최적 주문량 |
|
||||
| 주문 누락/지연 발생 | 선제적 자동 주문 |
|
||||
| 배송 마감 놓침 | 마감시간 자동 알림 |
|
||||
|
||||
### 핵심 원칙
|
||||
|
||||
> **"AI는 대체하는 것이 아니라, 약사님의 방식을 자동화합니다."**
|
||||
|
||||
- 약사님이 항상 지오영에 먼저 주문하면 → AI도 지오영 우선
|
||||
- 약사님이 300T보다 30T를 선호하면 → AI도 소량 주문
|
||||
- 약사님이 여유 있게 주문하면 → AI도 안전 재고 확보
|
||||
- 약사님이 가격에 민감하면 → AI도 최저가 추적 (OTC/비급여)
|
||||
|
||||
---
|
||||
|
||||
## 2. 현재 구현 현황
|
||||
|
||||
### 2.1 도매상 API (✅ 완료)
|
||||
|
||||
| 도매상 | 재고조회 | 장바구니 | 주문 | 취소/복원 | 잔고 | 월매출 |
|
||||
|--------|:--------:|:--------:|:----:|:---------:|:----:|:------:|
|
||||
| **지오영** | ✅ | ✅ | ✅ 확정포함 | ✅ | ✅ | ✅ |
|
||||
| **수인약품** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **백제약품** | ✅ | ✅ | ✅ | ⏳ | ✅ | ✅ |
|
||||
|
||||
### 2.2 주문 DB (✅ 완료)
|
||||
|
||||
```
|
||||
orders.db
|
||||
├── wholesalers # 도매상 마스터
|
||||
├── orders # 주문 헤더
|
||||
├── order_items # 주문 품목
|
||||
├── order_logs # 주문 이력
|
||||
├── order_context # AI 학습용 컨텍스트 ⭐
|
||||
├── daily_usage # 일별 사용량 시계열
|
||||
└── order_patterns # AI 분석 결과
|
||||
```
|
||||
|
||||
### 2.3 배송 스케줄 (✅ 확인 완료)
|
||||
|
||||
```
|
||||
┌──────────┬──────────┬──────────────┬──────────────┬──────────┐
|
||||
│ 도매상 │ 배송 │ 주문 마감 │ 도착 예정 │ 비고 │
|
||||
├──────────┼──────────┼──────────────┼──────────────┼──────────┤
|
||||
│ 지오영 │ 오전 │ 10:00 │ 11:30 │ 당일 │
|
||||
│ │ 오후 │ 13:00 │ 15:00 │ 당일 │
|
||||
├──────────┼──────────┼──────────────┼──────────────┼──────────┤
|
||||
│ 수인 │ 오후 │ 13:00 │ 14:30 │ 당일 │
|
||||
├──────────┼──────────┼──────────────┼──────────────┼──────────┤
|
||||
│ 백제 │ 익일 │ 16:00 │ 다음날 15:00 │ ⚠️ 익일 │
|
||||
└──────────┴──────────┴──────────────┴──────────────┴──────────┘
|
||||
```
|
||||
|
||||
### 2.4 UI (✅ 완료)
|
||||
|
||||
- Rx 사용량 페이지 (처방 기반)
|
||||
- 장바구니 모달
|
||||
- 도매상 잔고/월매출 모달
|
||||
|
||||
---
|
||||
|
||||
## 3. 시스템 아키텍처
|
||||
|
||||
### 전체 흐름
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ AI 자동발주시스템 │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────────────┼───────────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
||||
│ 데이터 수집 │ │ AI 분석 │ │ 자동 실행 │
|
||||
│ │ │ │ │ │
|
||||
│ • POS 판매 │─────▶│ • 사용량 예측 │─────▶│ • 도매상 API │
|
||||
│ • 처방전 조제 │ │ • 재고 분석 │ │ • 주문 실행 │
|
||||
│ • 현재 재고 │ │ • 주문 추천 │ │ • 결과 피드백 │
|
||||
│ • 도매상 재고 │ │ • 패턴 학습 │ │ │
|
||||
└───────────────┘ └───────────────┘ └───────────────┘
|
||||
│ │ │
|
||||
└───────────────────────┼───────────────────────┘
|
||||
▼
|
||||
┌───────────────────┐
|
||||
│ 학습 루프 │
|
||||
│ │
|
||||
│ 주문 결과 평가 │
|
||||
│ → 모델 업데이트 │
|
||||
│ → 전략 조정 │
|
||||
└───────────────────┘
|
||||
```
|
||||
|
||||
### 컴포넌트 구조
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ 데이터 레이어 │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
|
||||
│ │ PIT3000 │ │ SQLite │ │ 지오영 │ │ 수인 │ │
|
||||
│ │ (MSSQL) │ │ Orders DB │ │ API │ │ API │ │
|
||||
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
|
||||
│ └───────────────┴───────────────┴───────────────┘ │
|
||||
└────────────────────────────────┬─────────────────────────────────┘
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ 서비스 레이어 │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ InventorySync │ │ UsageAnalyzer │ │ OrderExecutor │ │
|
||||
│ │ 재고 동기화 │ │ 사용량 분석 │ │ 주문 실행 │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ AIPredictor │ │ AIOptimizer │ │ AILearner │ │
|
||||
│ │ 수요 예측 │ │ 규격/도매상 │ │ 패턴 학습 │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ 인터페이스 레이어 │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 웹 대시보드 │ │ 알림 시스템 │ │ 관리자 앱 │ │
|
||||
│ │ 재고/주문/AI │ │ 카톡/텔레그램 │ │ 수동 개입 │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. AI 학습 요소
|
||||
|
||||
### 4.1 규격 선택 학습 (Spec Selection)
|
||||
|
||||
```
|
||||
⚠️ 중요: 전문의약품(ETC)은 보험약가 고정!
|
||||
- 30T든 300T든 1T당 가격 동일
|
||||
- 단가 효율은 OTC/비급여에서만 의미 있음
|
||||
|
||||
학습 데이터:
|
||||
- 약품별 과거 주문 규격 (30T, 100T, 300T, 500T)
|
||||
- 각 규격 선택 시점의 재고/사용량
|
||||
- 선택 결과 (남은 재고, 다음 주문까지 기간)
|
||||
- 도매상별 규격 재고 현황
|
||||
|
||||
학습 목표:
|
||||
- 사용량 대비 최적 규격 예측
|
||||
- 재고 있는 규격 우선 선택
|
||||
- 낭비 최소화 (유통기한 고려)
|
||||
- 소분 vs 대용량 선호도 파악
|
||||
```
|
||||
|
||||
**예시 시나리오:**
|
||||
| 필요량 | 가능 규격 | AI 선택 | 이유 |
|
||||
|--------|-----------|---------|------|
|
||||
| 280T | 30T(재고50), 100T(품절), 300T(재고10) | 30T x 10 | 100T 품절, 소분 선호 |
|
||||
| 800T | 30T(재고100), 300T(재고5) | 300T x 3 | 대량, 재고 충분 |
|
||||
| 50T | 30T(재고20), 100T(재고10) | 30T x 2 | 소량, 빠른 회전 |
|
||||
|
||||
### 4.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
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 도매상 선택 학습 (Wholesaler Selection)
|
||||
|
||||
```
|
||||
학습 데이터:
|
||||
- 도매상별 주문 빈도
|
||||
- 도매상별 재고 상황
|
||||
- 도매상별 배송 스케줄
|
||||
- 월별 한도 사용량
|
||||
- 분할 주문 패턴
|
||||
|
||||
학습 목표:
|
||||
- 기본 도매상 선호도
|
||||
- 상황별 대체 도매상
|
||||
- 한도 고려한 분배
|
||||
- 배송 시간 고려 (긴급 시)
|
||||
```
|
||||
|
||||
**도매상 선택 로직:**
|
||||
```python
|
||||
def select_wholesaler(drug_code, quantity, need_by_time=None):
|
||||
"""
|
||||
AI가 학습한 도매상 선택 로직
|
||||
|
||||
우선순위:
|
||||
1. 재고 (있는 곳 우선)
|
||||
2. 배송 (need_by_time 충족 가능한 곳)
|
||||
3. 한도 (여유 있는 곳)
|
||||
4. 선호도 (과거 패턴)
|
||||
"""
|
||||
candidates = []
|
||||
|
||||
for ws in ['geoyoung', 'sooin', 'baekje']:
|
||||
score = 0
|
||||
|
||||
# 1. 재고 체크
|
||||
if has_stock(ws, drug_code, quantity):
|
||||
score += 100
|
||||
else:
|
||||
continue # 재고 없으면 제외
|
||||
|
||||
# 2. 배송 시간 체크
|
||||
if need_by_time:
|
||||
delivery = get_next_delivery(ws, need_by_time)
|
||||
if delivery['can_deliver']:
|
||||
score += 50
|
||||
else:
|
||||
score -= 30 # 감점
|
||||
|
||||
# 3. 한도 체크
|
||||
limit_usage = get_limit_usage(ws)
|
||||
if limit_usage < 0.9:
|
||||
score += 30
|
||||
elif limit_usage >= 1.0:
|
||||
score -= 50 # 한도 초과
|
||||
|
||||
# 4. 학습된 선호도
|
||||
score += ai_model.preference_score(ws, drug_code) * 20
|
||||
|
||||
candidates.append((ws, score))
|
||||
|
||||
return max(candidates, key=lambda x: x[1])[0]
|
||||
```
|
||||
|
||||
### 4.4 주문 타이밍 학습
|
||||
|
||||
```
|
||||
학습 데이터:
|
||||
- 하루 중 주문 시점 (오전/오후)
|
||||
- 요일별 주문 패턴
|
||||
- 배송 마감 시간 전 주문 여부
|
||||
|
||||
학습 목표:
|
||||
- 최적 주문 시점 파악
|
||||
- 배송 마감 놓치지 않기
|
||||
- 분할 주문 (오전/오후) 패턴
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 핵심 기능 설계
|
||||
|
||||
### 5.1 선주문 반영 시스템
|
||||
|
||||
**목적**: 같은 날 이미 주문한 품목 자동 차감
|
||||
|
||||
```python
|
||||
def calculate_order_qty(drug_code, usage_qty, current_stock):
|
||||
# 오늘 "실제로" 주문 완료된 수량 조회
|
||||
today_ordered = get_today_orders(drug_code)
|
||||
|
||||
# 필요량 = 사용량 - 현재고 - 선주문량
|
||||
needed = usage_qty - current_stock - today_ordered
|
||||
|
||||
if needed > 0:
|
||||
return calculate_spec_qty(needed)
|
||||
return 0
|
||||
```
|
||||
|
||||
**⚠️ 핵심: 실제 주문만 카운트**
|
||||
|
||||
```sql
|
||||
SELECT SUM(oi.total_dose) as today_ordered
|
||||
FROM order_items oi
|
||||
JOIN orders o ON oi.order_id = o.id
|
||||
WHERE oi.drug_code = ?
|
||||
AND o.order_date = DATE('now')
|
||||
AND o.is_dry_run = 0 -- dry_run 제외!
|
||||
AND oi.status IN ('success', 'submitted')
|
||||
```
|
||||
|
||||
### 5.2 도매상 한도 관리
|
||||
|
||||
**목적**: 월별 거래 한도 설정 및 자동 분배
|
||||
|
||||
```
|
||||
[한도 도달 시 동작]
|
||||
1. 90% 도달 → 경고 알림
|
||||
2. 100% 도달 → 다른 도매상으로 자동 전환
|
||||
3. 장바구니 단계에서 미리 분류
|
||||
```
|
||||
|
||||
### 5.3 배송 스케줄 기반 주문
|
||||
|
||||
**목적**: 주문 마감시간 + 배송 도착시간 분리 관리
|
||||
|
||||
```
|
||||
AI 판단 예시:
|
||||
|
||||
현재 오전 11시, "오후 3시에 필요"
|
||||
|
||||
→ 지오영 오전: 10시 마감 지남 ❌
|
||||
→ 지오영 오후: 13시 마감 → 15:00 도착 (⚠️ 딱 맞음)
|
||||
→ 수인: 13시 마감 → 14:30 도착 (✅ 여유)
|
||||
→ 백제: 내일 도착 ❌
|
||||
|
||||
결론: 수인 추천 (14:30 도착, 30분 여유)
|
||||
```
|
||||
|
||||
### 5.4 주문 실패 시 재시도
|
||||
|
||||
```
|
||||
시나리오 1: 재고 없음
|
||||
- A도매상 재고 0 → B도매상 검색 → 재고 있으면 B로 주문
|
||||
|
||||
시나리오 2: 주문 오류
|
||||
- A도매상 API 오류 → 3회 재시도 → 실패 시 B도매상
|
||||
|
||||
시나리오 3: 부분 성공
|
||||
- 10개 품목 중 7개 성공, 3개 실패
|
||||
- 실패한 3개 → B도매상으로 자동 재시도
|
||||
|
||||
[리포트]
|
||||
- 최종 주문 결과 리포트
|
||||
- 알림: "A도매상 품절로 B도매상으로 변경됨"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 데이터 모델
|
||||
|
||||
### 6.1 핵심 테이블 (기존)
|
||||
|
||||
```sql
|
||||
-- 주문 컨텍스트 (AI 학습용)
|
||||
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,
|
||||
usage_7d INTEGER,
|
||||
usage_30d INTEGER,
|
||||
avg_daily_usage REAL,
|
||||
|
||||
-- 주문 결정
|
||||
ordered_spec TEXT,
|
||||
ordered_qty INTEGER,
|
||||
wholesaler_id TEXT,
|
||||
|
||||
-- 선택지 정보 (AI 학습용)
|
||||
available_specs JSON,
|
||||
spec_stocks JSON,
|
||||
selection_reason TEXT,
|
||||
|
||||
-- 예측 vs 실제
|
||||
predicted_days_coverage REAL,
|
||||
actual_days_to_reorder INTEGER,
|
||||
|
||||
-- 결과 평가
|
||||
was_optimal BOOLEAN,
|
||||
stockout_occurred BOOLEAN,
|
||||
|
||||
created_at TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### 6.2 신규 테이블
|
||||
|
||||
```sql
|
||||
-- 도매상 한도 관리
|
||||
CREATE TABLE wholesaler_limits (
|
||||
id INTEGER PRIMARY KEY,
|
||||
wholesaler_id TEXT NOT NULL,
|
||||
monthly_limit INTEGER DEFAULT 0,
|
||||
warning_threshold REAL DEFAULT 0.9,
|
||||
priority INTEGER DEFAULT 1,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMP,
|
||||
FOREIGN KEY (wholesaler_id) REFERENCES wholesalers(id)
|
||||
);
|
||||
|
||||
-- 배송 스케줄
|
||||
CREATE TABLE delivery_schedules (
|
||||
id INTEGER PRIMARY KEY,
|
||||
wholesaler_id TEXT NOT NULL,
|
||||
delivery_seq INTEGER NOT NULL,
|
||||
delivery_name TEXT,
|
||||
order_cutoff_time TEXT NOT NULL, -- 주문 마감 (HH:MM)
|
||||
delivery_days_offset INTEGER DEFAULT 0, -- 0=당일, 1=익일
|
||||
delivery_arrival_time TEXT NOT NULL, -- 도착 예정 (HH:MM)
|
||||
weekdays TEXT, -- JSON [1,2,3,4,5]
|
||||
is_active INTEGER DEFAULT 1,
|
||||
UNIQUE(wholesaler_id, delivery_seq)
|
||||
);
|
||||
|
||||
-- 실제 배송 스케줄 데이터
|
||||
INSERT INTO delivery_schedules VALUES
|
||||
('geoyoung', 1, '오전배송', '10:00', 0, '11:30'),
|
||||
('geoyoung', 2, '오후배송', '13:00', 0, '15:00'),
|
||||
('sooin', 1, '오후배송', '13:00', 0, '14:30'),
|
||||
('baekje', 1, '익일배송', '16:00', 1, '15:00');
|
||||
|
||||
-- 월별 사용량 추적
|
||||
CREATE TABLE wholesaler_monthly_usage (
|
||||
id INTEGER PRIMARY KEY,
|
||||
wholesaler_id TEXT NOT NULL,
|
||||
year_month TEXT NOT NULL,
|
||||
total_orders INTEGER DEFAULT 0,
|
||||
total_amount INTEGER DEFAULT 0,
|
||||
UNIQUE(wholesaler_id, year_month)
|
||||
);
|
||||
|
||||
-- 주문 재시도 로그
|
||||
CREATE TABLE order_fallback_log (
|
||||
id INTEGER PRIMARY KEY,
|
||||
order_item_id INTEGER NOT NULL,
|
||||
original_wholesaler TEXT NOT NULL,
|
||||
original_error TEXT,
|
||||
fallback_wholesaler TEXT NOT NULL,
|
||||
fallback_result TEXT,
|
||||
created_at TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### 6.3 기존 테이블 확장
|
||||
|
||||
```sql
|
||||
-- orders 테이블 확장
|
||||
ALTER TABLE orders ADD COLUMN is_dry_run INTEGER DEFAULT 0;
|
||||
|
||||
-- order_items 테이블 확장
|
||||
ALTER TABLE order_items ADD COLUMN fallback_from_wholesaler TEXT;
|
||||
ALTER TABLE order_items ADD COLUMN prior_order_qty INTEGER DEFAULT 0;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. API 설계
|
||||
|
||||
### 7.1 도매상 관리 API
|
||||
|
||||
| 엔드포인트 | 메서드 | 기능 |
|
||||
|------------|--------|------|
|
||||
| `/api/wholesaler/limits` | GET | 한도 조회 |
|
||||
| `/api/wholesaler/limits/{id}` | PUT | 한도 설정 |
|
||||
| `/api/wholesaler/schedules` | GET | 배송 스케줄 |
|
||||
| `/api/wholesaler/can-deliver-by` | POST | 배송 가능 여부 |
|
||||
|
||||
### 7.2 주문 API
|
||||
|
||||
| 엔드포인트 | 메서드 | 기능 |
|
||||
|------------|--------|------|
|
||||
| `/api/order/today/{drug_code}` | GET | 오늘 주문량 |
|
||||
| `/api/order/recommend-spec` | POST | 규격 추천 |
|
||||
| `/api/order/create` | POST | 주문 생성 |
|
||||
| `/api/order/submit` | POST | 주문 제출 (dry_run 지원) |
|
||||
| `/api/order/retry` | POST | 실패 재시도 |
|
||||
|
||||
### 7.3 AI API
|
||||
|
||||
| 엔드포인트 | 메서드 | 기능 |
|
||||
|------------|--------|------|
|
||||
| `/api/ai/daily-analysis` | GET | 일일 분석 |
|
||||
| `/api/ai/recommendations` | GET | 주문 추천 |
|
||||
| `/api/ai/training-data` | GET | 학습 데이터 |
|
||||
| `/api/ai/patterns/{drug_code}` | GET | 패턴 분석 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 자동화 레벨
|
||||
|
||||
### Level 0: 수동
|
||||
- AI 추천만 제공
|
||||
- 모든 주문은 수동 실행
|
||||
|
||||
### Level 1: 반자동
|
||||
- AI가 주문 계획 생성
|
||||
- 약사님 승인 후 자동 실행
|
||||
- 알림: 승인 요청
|
||||
|
||||
### Level 2: 조건부 자동
|
||||
- 신뢰도 높은 주문은 자동 실행
|
||||
- 신뢰도 낮은 주문만 승인 요청
|
||||
- 조건:
|
||||
- 자주 주문하는 품목
|
||||
- 금액 임계값 이하
|
||||
- 긴급하지 않은 주문
|
||||
|
||||
### Level 3: 완전 자동
|
||||
- 모든 주문 자동 실행
|
||||
- 이상 상황만 알림
|
||||
- 약사님은 대시보드로 모니터링
|
||||
|
||||
```python
|
||||
def should_auto_execute(order_plan):
|
||||
level = 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 trusted_drugs,
|
||||
order_plan['urgency'] != 'critical'
|
||||
]
|
||||
return all(conditions)
|
||||
|
||||
if level == 3:
|
||||
return not is_anomaly(order_plan)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 알림 시스템
|
||||
|
||||
### 알림 유형
|
||||
|
||||
| 유형 | 조건 | 우선순위 |
|
||||
|------|------|----------|
|
||||
| 승인 요청 | 자동 실행 안 되는 주문 | 높음 |
|
||||
| 주문 완료 | 자동 주문 실행됨 | 보통 |
|
||||
| 한도 경고 | 90% 도달 | 높음 |
|
||||
| 품절 긴급 | 재고 0, 당일 필요 | 긴급 |
|
||||
| 배송 마감 | 마감 30분 전 | 높음 |
|
||||
| 도매상 변경 | 품절로 다른 도매상 | 보통 |
|
||||
|
||||
### 알림 예시
|
||||
|
||||
```
|
||||
📦 주문 승인 요청
|
||||
|
||||
약품: 콩코르정 2.5mg
|
||||
현재고: 45개 (3일치)
|
||||
추천 주문: 300T x 2박스
|
||||
도매상: 지오영 (점심배송 11:00 마감)
|
||||
예상 금액: 72,000원
|
||||
|
||||
[승인] [수정] [거절]
|
||||
```
|
||||
|
||||
```
|
||||
⚠️ 배송 마감 알림
|
||||
|
||||
지오영 오후배송 마감 30분 전!
|
||||
현재 장바구니: 5품목
|
||||
|
||||
13:00까지 주문하지 않으면 다음 배송은 내일입니다.
|
||||
|
||||
[지금 주문] [나중에]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 개발 로드맵
|
||||
|
||||
### Phase 1: 핵심 기반 (1주차)
|
||||
- [x] 도매상 API 연동 (3개)
|
||||
- [x] 주문 DB 스키마
|
||||
- [x] dry_run 테스트 모드
|
||||
- [ ] 선주문 조회 API
|
||||
- [ ] 도매상 한도 테이블
|
||||
- [ ] 배송 스케줄 테이블
|
||||
|
||||
### Phase 2: 주문 자동화 (2주차)
|
||||
- [ ] 규격 추천 API
|
||||
- [ ] 한도 체크 로직
|
||||
- [ ] 주문 재시도 로직
|
||||
- [ ] 장바구니 동기화
|
||||
|
||||
### Phase 3: UI 개선 (2주차)
|
||||
- [ ] 한도 대시보드
|
||||
- [ ] 주문 화면 (선주문 반영)
|
||||
- [ ] 배송 스케줄 표시
|
||||
|
||||
### Phase 4: AI 학습 (3주차)
|
||||
- [ ] 피드백 루프 구현
|
||||
- [ ] 주문 평가 시스템
|
||||
- [ ] 패턴 학습 (규격, 도매상)
|
||||
- [ ] 수요 예측 (단순 이동평균)
|
||||
|
||||
### Phase 5: 완전 자동화 (4주차~)
|
||||
- [ ] Level 1 자동화
|
||||
- [ ] 알림 시스템 연동
|
||||
- [ ] Level 2 조건부 자동화
|
||||
- [ ] 모니터링 대시보드
|
||||
|
||||
---
|
||||
|
||||
## 11. 성공 지표 (KPI)
|
||||
|
||||
| 지표 | 현재 | 목표 |
|
||||
|------|------|------|
|
||||
| 주문 소요 시간 | 30분/일 | 0분 (자동) |
|
||||
| 품절 발생률 | 5% | <1% |
|
||||
| 재고 회전율 | - | +20% |
|
||||
| 배송 마감 놓침 | 가끔 | 0회 |
|
||||
| 주문 비용 절감 | - | 5-10% (OTC) |
|
||||
|
||||
---
|
||||
|
||||
## 📚 참고 문서
|
||||
|
||||
- 어제 작성 (AI 비전/모델): `docs/AI_ERP_AUTO_ORDER_SYSTEM.md`
|
||||
- 오늘 작성 (API/DB 상세): `docs/자동발주시스템_고도화_기획서_v2.md`
|
||||
- 도매상 API 분석: `docs/GEOYOUNG_API_REVERSE_ENGINEERING.md`
|
||||
- Rx 사용량 가이드: `docs/RX_USAGE_GEOYOUNG_GUIDE.md`
|
||||
|
||||
---
|
||||
|
||||
> 🐉 **용림**: 이 문서는 AI_ERP_AUTO_ORDER_SYSTEM.md(비전/AI모델)와
|
||||
> 자동발주시스템_고도화_기획서_v2.md(API/DB상세)를 통합한 마스터 기획서입니다.
|
||||
823
docs/자동발주시스템_고도화_기획서_v2.md
Normal file
823
docs/자동발주시스템_고도화_기획서_v2.md
Normal file
@@ -0,0 +1,823 @@
|
||||
# 🏥 자동발주시스템 고도화 기획서 v2
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **작성자**: 용림 (with 약사님)
|
||||
> **상태**: 기획 검토 중
|
||||
|
||||
---
|
||||
|
||||
## 📋 목차
|
||||
|
||||
1. [현재 구현 현황](#1-현재-구현-현황)
|
||||
2. [핵심 목표](#2-핵심-목표)
|
||||
3. [신규 기능 기획](#3-신규-기능-기획)
|
||||
4. [API 개발 계획](#4-api-개발-계획)
|
||||
5. [DB 스키마 확장](#5-db-스키마-확장)
|
||||
6. [UI 개선 계획](#6-ui-개선-계획)
|
||||
7. [개발 우선순위](#7-개발-우선순위)
|
||||
|
||||
---
|
||||
|
||||
## 1. 현재 구현 현황
|
||||
|
||||
### 1.1 도매상 API (✅ 완료)
|
||||
|
||||
| 도매상 | 재고조회 | 장바구니 | 주문 | 취소/복원 | 잔고 | 월매출 |
|
||||
|--------|:--------:|:--------:|:----:|:---------:|:----:|:------:|
|
||||
| **지오영** | ✅ | ✅ | ✅ 확정포함 | ✅ (삭제만) | ✅ | ✅ |
|
||||
| **수인약품** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **백제약품** | ✅ | ✅ | ✅ | ⏳ | ✅ | ✅ |
|
||||
|
||||
### 1.2 주문 DB 스키마 (✅ 완료)
|
||||
|
||||
```
|
||||
orders.db
|
||||
├── wholesalers # 도매상 정보
|
||||
├── orders # 주문 헤더
|
||||
├── order_items # 주문 품목
|
||||
├── order_logs # 주문 로그
|
||||
├── order_context # AI 학습용 컨텍스트
|
||||
├── daily_usage # 일별 사용량
|
||||
└── order_patterns # 주문 패턴 (AI용)
|
||||
```
|
||||
|
||||
### 1.3 통합 주문 API (✅ 완료)
|
||||
|
||||
| 엔드포인트 | 기능 | dry_run |
|
||||
|------------|------|:-------:|
|
||||
| `POST /api/order/create` | 주문 생성 (draft) | - |
|
||||
| `POST /api/order/submit` | 주문 제출 | ✅ |
|
||||
| `POST /api/order/quick-submit` | 빠른 주문 | ✅ |
|
||||
| `GET /api/order/history` | 주문 이력 | - |
|
||||
| `GET /api/order/ai/training-data` | AI 학습 데이터 | - |
|
||||
|
||||
### 1.4 UI 현황 (✅ 완료)
|
||||
|
||||
- **Rx 사용량 페이지**: 처방 기반 사용량 조회 + 주문수량 계산
|
||||
- **장바구니 모달**: 선택 품목 담기 + 도매상 선택
|
||||
- **도매상 잔고 모달**: 잔고 + 월매출 동시 표시
|
||||
|
||||
---
|
||||
|
||||
## 2. 핵심 목표
|
||||
|
||||
### 🎯 최종 목표
|
||||
> **사용량 기반 AI 분석 통합주문 및 자동화주문 시스템**
|
||||
|
||||
### 2.1 핵심 시나리오
|
||||
|
||||
```
|
||||
📅 하루 주문 흐름
|
||||
|
||||
[오전 10시] ─────────────────────────────────────────────
|
||||
│
|
||||
├─ 사용량 집계: 아세탑 500T 사용
|
||||
├─ 규격 판단: 30T x 10개? 300T x 2개?
|
||||
│ └─ 도매상 재고 확인 → 재고 있는 것 우선
|
||||
├─ 도매상 선택: A도매상 (배송 3회/일)
|
||||
│ └─ 월 한도 확인 (5000만원 중 3000만원 사용)
|
||||
├─ 주문 실행: 300T x 2개 = 600T
|
||||
└─ 로깅: orders.db에 기록
|
||||
|
||||
[오후 4시] ──────────────────────────────────────────────
|
||||
│
|
||||
├─ 사용량 재집계: 아세탑 910T 사용
|
||||
├─ 선주문 반영: 오전 300T 주문 확인
|
||||
│ └─ 남은 필요량: 910 - 300 = 610T
|
||||
├─ 추가 주문: 300T x 2개 + 30T x 1개 = 630T
|
||||
└─ 도매상 재선택 (한도/재고 기반)
|
||||
```
|
||||
|
||||
### 2.2 핵심 해결 과제
|
||||
|
||||
| # | 과제 | 현재 상태 | 목표 |
|
||||
|---|------|----------|------|
|
||||
| 1 | 선주문 반영 | ❌ 미구현 | 같은 날 선주문량 자동 차감 |
|
||||
| 2 | 규격 자동 선택 | ⏳ 부분 | 재고+경제성 기반 자동 판단 |
|
||||
| 3 | 도매상 한도 관리 | ❌ 미구현 | 월별 한도 설정/알림 |
|
||||
| 4 | 장바구니 동기화 | ⏳ 조회만 | 양방향 동기화 |
|
||||
| 5 | 실패 시 재시도 | ❌ 미구현 | A실패→B 자동 재시도 |
|
||||
| 6 | 배송 스케줄 | ❌ 미구현 | 배송 횟수 고려 주문 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 신규 기능 기획
|
||||
|
||||
### 3.1 도매상 월별 한도 관리 🆕
|
||||
|
||||
**목적**: 도매상별 월 거래 한도 설정 및 자동 분배
|
||||
|
||||
```
|
||||
예시:
|
||||
- A도매상 (지오영): 월 5000만원 한도
|
||||
- B도매상 (수인): 월 3000만원 한도
|
||||
- C도매상 (백제): 월 2000만원 한도
|
||||
|
||||
[한도 도달 시 동작]
|
||||
1. A도매상 한도 90% → 경고 알림
|
||||
2. A도매상 한도 100% → B도매상으로 자동 전환
|
||||
3. 장바구니 단계에서 미리 분류 (주문 확정 전 조정 가능)
|
||||
```
|
||||
|
||||
**UI 요구사항**:
|
||||
- 도매상별 한도 설정 화면
|
||||
- 현재 사용량/잔여 한도 표시
|
||||
- 한도 초과 시 경고 + 대안 도매상 제안
|
||||
|
||||
### 3.2 선주문 반영 시스템 🆕
|
||||
|
||||
**목적**: 같은 날 이미 주문한 품목 자동 차감
|
||||
|
||||
```python
|
||||
# 로직 예시
|
||||
def calculate_order_qty(drug_code, usage_qty, current_stock):
|
||||
# 오늘 "실제로" 주문 완료된 수량 조회
|
||||
today_ordered = get_today_orders(drug_code) # 300T
|
||||
|
||||
# 필요량 = 사용량 - 현재고 - 선주문량
|
||||
needed = usage_qty - current_stock - today_ordered
|
||||
|
||||
# 필요량이 양수일 때만 주문
|
||||
if needed > 0:
|
||||
return calculate_spec_qty(needed) # 규격별 수량 계산
|
||||
return 0
|
||||
```
|
||||
|
||||
**⚠️ 핵심: 실제 주문만 카운트**
|
||||
|
||||
```sql
|
||||
-- 선주문 조회 쿼리
|
||||
SELECT SUM(oi.total_dose) as today_ordered
|
||||
FROM order_items oi
|
||||
JOIN orders o ON oi.order_id = o.id
|
||||
WHERE oi.drug_code = ?
|
||||
AND o.order_date = DATE('now')
|
||||
AND o.is_dry_run = 0 -- ⭐ dry_run 제외!
|
||||
AND oi.status IN ('success', 'submitted') -- 실제 완료된 것만
|
||||
```
|
||||
|
||||
**DB 스키마 수정 필요**:
|
||||
- `orders` 테이블에 `is_dry_run INTEGER DEFAULT 0` 컬럼 추가
|
||||
- 선주문 조회 시 `is_dry_run=0`인 것만 카운트
|
||||
- `status`가 `success` 또는 `submitted`인 것만 (pending/failed 제외)
|
||||
|
||||
### 3.3 규격 자동 선택 로직 🆕
|
||||
|
||||
**목적**: 재고 기반 최적 규격 선택
|
||||
|
||||
```
|
||||
⚠️ 단가 참고사항:
|
||||
- 전문의약품(ETC): 보험약가 고정 → 30T든 300T든 1T당 가격 동일
|
||||
- 일반의약품(OTC): 도매상/규격별 단가 차이 가능
|
||||
- 비급여 약품: 도매상간 가격 비교 의미 있음
|
||||
|
||||
우선순위:
|
||||
1. 재고 있는 규격 (품절 규격 제외)
|
||||
2. 필요량과 가장 근접한 규격 (과주문 최소화)
|
||||
3. 소분 선호 (30T x 10 > 300T x 1) - 유통기한/재고관리 유리
|
||||
4. 사용자 선호 패턴 (AI 학습 데이터 기반)
|
||||
|
||||
예시:
|
||||
- 필요량: 280T
|
||||
- 가능 규격: 30T(재고50), 100T(품절), 300T(재고10)
|
||||
- 선택: 30T x 10개 = 300T (100T 품절, 소분 선호)
|
||||
```
|
||||
|
||||
### 3.4 주문 실패 시 재시도 🆕
|
||||
|
||||
**목적**: A도매상 실패 → B도매상 자동 재시도
|
||||
|
||||
```
|
||||
[재시도 시나리오]
|
||||
|
||||
시나리오 1: 재고 없음
|
||||
- A도매상 재고 0 → B도매상 검색 → 재고 있으면 B로 주문
|
||||
|
||||
시나리오 2: 주문 오류
|
||||
- A도매상 API 오류 → 3회 재시도 → 실패 시 B도매상
|
||||
|
||||
시나리오 3: 부분 성공
|
||||
- 10개 품목 중 7개 성공, 3개 실패
|
||||
- 실패한 3개 → B도매상으로 자동 재시도
|
||||
|
||||
[리포트]
|
||||
- 최종 주문 결과 리포트 (어느 도매상에서 성공/실패)
|
||||
- 알림: "A도매상 품절로 B도매상으로 주문 변경됨"
|
||||
```
|
||||
|
||||
### 3.5 장바구니 동기화 🆕
|
||||
|
||||
**목적**: 약국 시스템 ↔ 도매상 사이트 장바구니 일치
|
||||
|
||||
```
|
||||
[동기화 흐름]
|
||||
|
||||
1. 약국에서 장바구니 담기
|
||||
└─ 도매상 API로 add_to_cart 호출
|
||||
└─ 성공 시 로컬 DB에도 기록
|
||||
|
||||
2. 주문 확정 전 동기화 체크
|
||||
└─ 도매상 get_cart() 호출
|
||||
└─ 로컬 DB와 비교
|
||||
└─ 불일치 시 알림 (누군가 도매상 사이트에서 직접 수정?)
|
||||
|
||||
3. 주문 확정
|
||||
└─ 도매상 submit_order() 호출
|
||||
└─ 결과 로깅
|
||||
```
|
||||
|
||||
### 3.6 배송 스케줄 관리 🆕
|
||||
|
||||
**목적**: 도매상별 **주문 마감시간** + **배송 도착시간** 분리 관리
|
||||
|
||||
```
|
||||
⚠️ 핵심: 두 가지 시간을 구분!
|
||||
|
||||
1. 주문 마감시간 (order_cutoff) - 언제까지 주문해야 하나
|
||||
2. 배송 도착시간 (delivery_arrival) - 실제 약국에 언제 도착하나
|
||||
|
||||
┌──────────┬──────────┬──────────────┬──────────────┬──────────┐
|
||||
│ 도매상 │ 배송 │ 주문 마감 │ 도착 예정 │ 비고 │
|
||||
├──────────┼──────────┼──────────────┼──────────────┼──────────┤
|
||||
│ 지오영 │ 오전 │ 10:00 │ 11:30 │ 당일 │
|
||||
│ │ 오후 │ 13:00 │ 15:00 │ 당일 │
|
||||
├──────────┼──────────┼──────────────┼──────────────┼──────────┤
|
||||
│ 수인 │ 오후 │ 13:00 │ 14:30 │ 당일 │
|
||||
├──────────┼──────────┼──────────────┼──────────────┼──────────┤
|
||||
│ 백제 │ 익일 │ 16:00 │ 다음날 15:00 │ ⚠️ 익일 │
|
||||
└──────────┴──────────┴──────────────┴──────────────┴──────────┘
|
||||
```
|
||||
|
||||
**AI 판단 시나리오**:
|
||||
```
|
||||
상황: 오전 9시, "오늘 오후 2시에 필요"
|
||||
|
||||
→ 지오영 오전: 10시 마감 전 → 11:30 도착 (✅ 여유)
|
||||
→ 지오영 오후: 13시 마감 전 → 15:00 도착 (❌ 늦음)
|
||||
→ 수인: 13시 마감 전 → 14:30 도착 (✅ 가능)
|
||||
→ 백제: 16시 마감 → 내일 15시 (❌ 늦음)
|
||||
→ 결론: 지오영 오전배송 추천 (가장 빠름)
|
||||
|
||||
상황: 오전 11시, "오늘 오후 3시에 필요"
|
||||
|
||||
→ 지오영 오전: 10시 마감 지남 ❌
|
||||
→ 지오영 오후: 13시 마감 전 → 15:00 도착 (⚠️ 딱 맞음)
|
||||
→ 수인: 13시 마감 전 → 14:30 도착 (✅ 여유)
|
||||
→ 결론: 수인 추천 (14:30 도착)
|
||||
|
||||
상황: 오후 2시, "오늘 필요"
|
||||
|
||||
→ 지오영: 오전/오후 마감 모두 지남 ❌
|
||||
→ 수인: 13시 마감 지남 ❌
|
||||
→ 백제: 16시 마감 전 → 내일 15시 도착
|
||||
→ 결론: 오늘 배송 불가! 백제 익일배송만 가능 → 알림 발송
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. API 개발 계획
|
||||
|
||||
### 4.1 신규 API 목록
|
||||
|
||||
#### 🔧 도매상 한도 관리 API
|
||||
|
||||
| 엔드포인트 | 메서드 | 기능 |
|
||||
|------------|--------|------|
|
||||
| `/api/wholesaler/limits` | GET | 전체 도매상 한도 조회 |
|
||||
| `/api/wholesaler/limits/{id}` | GET | 특정 도매상 한도 상세 |
|
||||
| `/api/wholesaler/limits/{id}` | PUT | 한도 설정/수정 |
|
||||
| `/api/wholesaler/limits/{id}/usage` | GET | 현재 사용량 조회 |
|
||||
| `/api/wholesaler/limits/check` | POST | 주문 전 한도 체크 |
|
||||
|
||||
```json
|
||||
// PUT /api/wholesaler/limits/geoyoung
|
||||
{
|
||||
"monthly_limit": 50000000,
|
||||
"warning_threshold": 0.9,
|
||||
"priority": 1,
|
||||
"is_active": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 🔧 배송 스케줄 API 🆕
|
||||
|
||||
| 엔드포인트 | 메서드 | 기능 |
|
||||
|------------|--------|------|
|
||||
| `/api/wholesaler/schedules` | GET | 전체 배송 스케줄 |
|
||||
| `/api/wholesaler/schedules/{id}` | GET | 특정 도매상 스케줄 |
|
||||
| `/api/wholesaler/schedules/{id}` | PUT | 스케줄 수정 |
|
||||
| `/api/wholesaler/next-delivery` | GET | 다음 가능한 배송 조회 |
|
||||
| `/api/wholesaler/can-deliver-by` | POST | 특정 시간까지 배송 가능 여부 |
|
||||
|
||||
```json
|
||||
// GET /api/wholesaler/schedules/geoyoung
|
||||
{
|
||||
"success": true,
|
||||
"wholesaler": "geoyoung",
|
||||
"schedules": [
|
||||
{
|
||||
"seq": 1,
|
||||
"name": "오전배송",
|
||||
"order_cutoff": "08:30",
|
||||
"arrival": "10:30"
|
||||
},
|
||||
{
|
||||
"seq": 2,
|
||||
"name": "점심배송",
|
||||
"order_cutoff": "11:00",
|
||||
"arrival": "13:30"
|
||||
},
|
||||
{
|
||||
"seq": 3,
|
||||
"name": "오후배송",
|
||||
"order_cutoff": "15:00",
|
||||
"arrival": "17:30"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// POST /api/wholesaler/can-deliver-by
|
||||
// "오후 3시까지 받을 수 있는 도매상은?"
|
||||
{
|
||||
"need_by": "15:00",
|
||||
"drug_codes": ["670400830", "654301800"]
|
||||
}
|
||||
|
||||
// Response
|
||||
{
|
||||
"success": true,
|
||||
"current_time": "10:30",
|
||||
"need_by": "15:00",
|
||||
"options": [
|
||||
{
|
||||
"wholesaler": "geoyoung",
|
||||
"delivery": "점심배송",
|
||||
"order_cutoff": "11:00",
|
||||
"arrival": "13:30",
|
||||
"status": "✅ 주문 가능 (30분 남음)"
|
||||
},
|
||||
{
|
||||
"wholesaler": "sooin",
|
||||
"delivery": "오전배송",
|
||||
"order_cutoff": "09:00",
|
||||
"arrival": "11:00",
|
||||
"status": "❌ 마감됨"
|
||||
},
|
||||
{
|
||||
"wholesaler": "sooin",
|
||||
"delivery": "오후배송",
|
||||
"order_cutoff": "14:00",
|
||||
"arrival": "17:00",
|
||||
"status": "❌ 도착 늦음 (17:00)"
|
||||
}
|
||||
],
|
||||
"recommendation": "geoyoung 점심배송 (11:00 마감, 13:30 도착)"
|
||||
}
|
||||
```
|
||||
|
||||
#### 🔧 선주문 조회 API
|
||||
|
||||
| 엔드포인트 | 메서드 | 기능 |
|
||||
|------------|--------|------|
|
||||
| `/api/order/today` | GET | 오늘 주문 조회 |
|
||||
| `/api/order/today/{drug_code}` | GET | 특정 약품 오늘 주문량 |
|
||||
| `/api/order/pending` | GET | 아직 확정 안된 주문 |
|
||||
|
||||
```json
|
||||
// GET /api/order/today/670400830
|
||||
{
|
||||
"success": true,
|
||||
"drug_code": "670400830",
|
||||
"today_ordered_qty": 300,
|
||||
"orders": [
|
||||
{
|
||||
"order_no": "ORD-20260306-001",
|
||||
"wholesaler": "geoyoung",
|
||||
"specification": "300T",
|
||||
"qty": 1,
|
||||
"status": "submitted",
|
||||
"ordered_at": "2026-03-06T10:30:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 🔧 규격 추천 API
|
||||
|
||||
| 엔드포인트 | 메서드 | 기능 |
|
||||
|------------|--------|------|
|
||||
| `/api/order/recommend-spec` | POST | 규격 추천 (단건) |
|
||||
| `/api/order/recommend-specs` | POST | 규격 추천 (복수) |
|
||||
| `/api/order/optimize` | POST | 전체 주문 최적화 |
|
||||
|
||||
```json
|
||||
// POST /api/order/recommend-spec
|
||||
{
|
||||
"drug_code": "670400830",
|
||||
"needed_qty": 280,
|
||||
"prefer_wholesaler": "geoyoung"
|
||||
}
|
||||
|
||||
// Response
|
||||
{
|
||||
"success": true,
|
||||
"drug_type": "ETC", // ETC: 보험약가 고정, OTC: 단가 비교 가능
|
||||
"recommendations": [
|
||||
{
|
||||
"wholesaler": "geoyoung",
|
||||
"spec": "30T",
|
||||
"qty": 10,
|
||||
"total_dose": 300,
|
||||
"stock": 50,
|
||||
"unit_price": 1200,
|
||||
"total_price": 12000,
|
||||
"reason": "재고 충분, 소분 선호"
|
||||
},
|
||||
{
|
||||
"wholesaler": "sooin",
|
||||
"spec": "300T",
|
||||
"qty": 1,
|
||||
"total_dose": 300,
|
||||
"stock": 5,
|
||||
"unit_price": 12000,
|
||||
"total_price": 12000,
|
||||
"reason": "재고 있음 (ETC 단가 동일)"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 🔧 주문 재시도 API
|
||||
|
||||
| 엔드포인트 | 메서드 | 기능 |
|
||||
|------------|--------|------|
|
||||
| `/api/order/retry` | POST | 실패 품목 재시도 |
|
||||
| `/api/order/fallback` | POST | 다른 도매상으로 재주문 |
|
||||
| `/api/order/redistribute` | POST | 한도 기반 재분배 |
|
||||
|
||||
```json
|
||||
// POST /api/order/retry
|
||||
{
|
||||
"order_id": 123,
|
||||
"item_ids": [456, 457], // 실패한 품목
|
||||
"fallback_wholesaler": "sooin"
|
||||
}
|
||||
```
|
||||
|
||||
#### 🔧 장바구니 동기화 API
|
||||
|
||||
| 엔드포인트 | 메서드 | 기능 |
|
||||
|------------|--------|------|
|
||||
| `/api/cart/sync` | POST | 전체 동기화 |
|
||||
| `/api/cart/compare` | GET | 로컬 vs 도매상 비교 |
|
||||
| `/api/cart/resolve` | POST | 불일치 해결 |
|
||||
|
||||
### 4.2 기존 API 확장
|
||||
|
||||
#### 📝 `/api/order/create` 확장
|
||||
|
||||
```json
|
||||
// 기존
|
||||
{
|
||||
"wholesaler_id": "geoyoung",
|
||||
"items": [...]
|
||||
}
|
||||
|
||||
// 확장
|
||||
{
|
||||
"wholesaler_id": "geoyoung",
|
||||
"items": [...],
|
||||
"options": {
|
||||
"check_prior_orders": true, // 선주문 반영
|
||||
"auto_spec_select": true, // 규격 자동 선택
|
||||
"respect_limits": true, // 한도 준수
|
||||
"allow_fallback": true, // 실패 시 다른 도매상
|
||||
"fallback_order": ["sooin", "baekje"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 📝 `/api/order/submit` 확장
|
||||
|
||||
```json
|
||||
{
|
||||
"order_id": 123,
|
||||
"dry_run": false,
|
||||
"options": {
|
||||
"retry_on_fail": 3, // 실패 시 재시도 횟수
|
||||
"fallback_enabled": true,
|
||||
"notify_on_fallback": true // 도매상 변경 시 알림
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. DB 스키마 확장
|
||||
|
||||
### 5.1 신규 테이블
|
||||
|
||||
#### `wholesaler_limits` - 도매상 한도 관리
|
||||
|
||||
```sql
|
||||
CREATE TABLE wholesaler_limits (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
wholesaler_id TEXT NOT NULL,
|
||||
|
||||
-- 한도 설정
|
||||
monthly_limit INTEGER DEFAULT 0, -- 월 한도 (원)
|
||||
warning_threshold REAL DEFAULT 0.9, -- 경고 임계값 (90%)
|
||||
|
||||
-- 우선순위
|
||||
priority INTEGER DEFAULT 1, -- 1이 최우선
|
||||
|
||||
-- 상태
|
||||
is_active INTEGER DEFAULT 1,
|
||||
|
||||
-- 메타
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (wholesaler_id) REFERENCES wholesalers(id)
|
||||
);
|
||||
```
|
||||
|
||||
#### `delivery_schedules` - 배송 스케줄 🆕
|
||||
|
||||
```sql
|
||||
CREATE TABLE delivery_schedules (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
wholesaler_id TEXT NOT NULL,
|
||||
|
||||
-- 배송 회차
|
||||
delivery_seq INTEGER NOT NULL, -- 1, 2, 3...
|
||||
delivery_name TEXT, -- '오전배송', '오후배송', '익일배송'
|
||||
|
||||
-- ⭐ 주문 마감시간
|
||||
order_cutoff_time TEXT NOT NULL, -- 'HH:MM' (예: '10:00')
|
||||
|
||||
-- ⭐ 배송 도착
|
||||
delivery_days_offset INTEGER DEFAULT 0, -- 0=당일, 1=익일, 2=2일후...
|
||||
delivery_arrival_time TEXT NOT NULL, -- 'HH:MM' (예: '11:30')
|
||||
|
||||
-- 요일별 운영 (NULL=매일)
|
||||
weekdays TEXT, -- JSON [1,2,3,4,5] (평일만)
|
||||
|
||||
-- 상태
|
||||
is_active INTEGER DEFAULT 1,
|
||||
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (wholesaler_id) REFERENCES wholesalers(id),
|
||||
UNIQUE(wholesaler_id, delivery_seq)
|
||||
);
|
||||
|
||||
-- 실제 배송 스케줄 (2026-03-06 확인)
|
||||
INSERT INTO delivery_schedules
|
||||
(wholesaler_id, delivery_seq, delivery_name, order_cutoff_time, delivery_days_offset, delivery_arrival_time)
|
||||
VALUES
|
||||
-- 지오영 (2배송, 당일)
|
||||
('geoyoung', 1, '오전배송', '10:00', 0, '11:30'),
|
||||
('geoyoung', 2, '오후배송', '13:00', 0, '15:00'),
|
||||
-- 수인 (1배송, 당일)
|
||||
('sooin', 1, '오후배송', '13:00', 0, '14:30'),
|
||||
-- 백제 (1배송, 익일!) ⚠️
|
||||
('baekje', 1, '익일배송', '16:00', 1, '15:00'); -- days_offset=1 → 다음날
|
||||
```
|
||||
|
||||
#### `wholesaler_monthly_usage` - 월별 사용량 추적
|
||||
|
||||
```sql
|
||||
CREATE TABLE wholesaler_monthly_usage (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
wholesaler_id TEXT NOT NULL,
|
||||
year_month TEXT NOT NULL, -- 'YYYY-MM'
|
||||
|
||||
-- 집계
|
||||
total_orders INTEGER DEFAULT 0, -- 주문 건수
|
||||
total_items INTEGER DEFAULT 0, -- 주문 품목 수
|
||||
total_amount INTEGER DEFAULT 0, -- 총 주문 금액
|
||||
|
||||
-- 상태별 집계
|
||||
success_amount INTEGER DEFAULT 0,
|
||||
failed_amount INTEGER DEFAULT 0,
|
||||
|
||||
-- 메타
|
||||
last_updated TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
UNIQUE(wholesaler_id, year_month)
|
||||
);
|
||||
```
|
||||
|
||||
#### `order_fallback_log` - 재시도 로그
|
||||
|
||||
```sql
|
||||
CREATE TABLE order_fallback_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_item_id INTEGER NOT NULL,
|
||||
|
||||
-- 원래 도매상
|
||||
original_wholesaler TEXT NOT NULL,
|
||||
original_error TEXT, -- 실패 사유
|
||||
|
||||
-- 재시도 도매상
|
||||
fallback_wholesaler TEXT NOT NULL,
|
||||
fallback_result TEXT, -- 'success', 'failed'
|
||||
fallback_message TEXT,
|
||||
|
||||
-- 메타
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (order_item_id) REFERENCES order_items(id)
|
||||
);
|
||||
```
|
||||
|
||||
#### `cart_sync_log` - 장바구니 동기화 로그
|
||||
|
||||
```sql
|
||||
CREATE TABLE cart_sync_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
wholesaler_id TEXT NOT NULL,
|
||||
|
||||
-- 동기화 정보
|
||||
sync_type TEXT, -- 'full', 'partial', 'compare'
|
||||
local_items INTEGER,
|
||||
remote_items INTEGER,
|
||||
matched INTEGER,
|
||||
mismatched INTEGER,
|
||||
|
||||
-- 상세
|
||||
detail_json TEXT,
|
||||
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### 5.2 기존 테이블 확장
|
||||
|
||||
#### `orders` 확장 ⭐ 중요
|
||||
|
||||
```sql
|
||||
-- dry_run 구분 (선주문 조회 시 제외용)
|
||||
ALTER TABLE orders ADD COLUMN is_dry_run INTEGER DEFAULT 0;
|
||||
```
|
||||
|
||||
#### `order_items` 확장
|
||||
|
||||
```sql
|
||||
ALTER TABLE order_items ADD COLUMN fallback_from_wholesaler TEXT;
|
||||
ALTER TABLE order_items ADD COLUMN fallback_reason TEXT;
|
||||
ALTER TABLE order_items ADD COLUMN prior_order_qty INTEGER DEFAULT 0; -- 선주문량
|
||||
```
|
||||
|
||||
#### `order_context` 확장
|
||||
|
||||
```sql
|
||||
ALTER TABLE order_context ADD COLUMN limit_check_result TEXT;
|
||||
ALTER TABLE order_context ADD COLUMN recommended_by TEXT; -- 'user', 'ai', 'system'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. UI 개선 계획
|
||||
|
||||
### 6.1 도매상 한도 대시보드 🆕
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 💰 도매상 한도 현황 (2026년 3월) │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 🏢 지오영 │
|
||||
│ ████████████████████░░░░░ 35,124,164 / 50,000,000 │
|
||||
│ 70.2% 사용 | 남은 한도: 14,875,836원 │
|
||||
│ [배송 3회] 09:00, 13:00, 17:00 │
|
||||
│ │
|
||||
│ 🏢 수인약품 │
|
||||
│ ██████████████░░░░░░░░░░ 14,293,001 / 30,000,000 │
|
||||
│ 47.6% 사용 | 남은 한도: 15,706,999원 │
|
||||
│ [배송 2회] 09:00, 17:00 │
|
||||
│ │
|
||||
│ 🏢 백제약품 │
|
||||
│ ███████████████████░░░░░ 14,563,978 / 20,000,000 │
|
||||
│ 72.8% 사용 | 남은 한도: 5,436,022원 ⚠️ 주의 │
|
||||
│ [배송 2회] 09:00, 17:00 │
|
||||
│ │
|
||||
│ [⚙️ 한도 설정] [📊 상세 리포트] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 6.2 주문 화면 개선
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 📦 주문 생성 - 2026-03-06 오후 배치 │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ [선주문 반영 ✓] 오늘 오전 주문: 15품목, 2,340,000원 │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ 품목 │ 필요량 │ 선주문 │ 추가주문 │ │
|
||||
│ ├─────────────────────────────────────────────────┤ │
|
||||
│ │ 아세탑정 │ 910T │ 300T │ 610T │ │
|
||||
│ │ └ 추천: 300T x 2 (지오영) │ │
|
||||
│ │ └ 대안: 30T x 21 (수인, 재고 충분) │ │
|
||||
│ │ │ │
|
||||
│ │ 레바미피드정 │ 500T │ 0T │ 500T │ │
|
||||
│ │ └ 추천: 30T x 17 (지오영) ⚠️ 품절위험 │ │
|
||||
│ │ └ 대안: 100T x 5 (백제) │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 📊 도매상 분배 미리보기 │
|
||||
│ - 지오영: 8품목 (1,200,000원) [한도 여유 ✓] │
|
||||
│ - 수인: 3품목 (450,000원) │
|
||||
│ - 백제: 2품목 (350,000원) [한도 주의 ⚠️] │
|
||||
│ │
|
||||
│ [🔄 재분배] [✅ 주문 확정] [💾 장바구니 저장] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 6.3 알림/노티피케이션
|
||||
|
||||
```
|
||||
[알림 유형]
|
||||
|
||||
📢 한도 알림
|
||||
- "지오영 한도 90% 도달 (4,500만원/5,000만원)"
|
||||
- "백제약품 한도 초과! 신규 주문 불가"
|
||||
|
||||
📢 도매상 변경 알림
|
||||
- "아세탑정: 지오영 품절 → 수인약품으로 변경됨"
|
||||
|
||||
📢 주문 결과 알림
|
||||
- "오후 주문 완료: 15품목 중 14개 성공, 1개 재시도 중"
|
||||
|
||||
📢 배송 알림
|
||||
- "지오영 점심 배송 마감 30분 전 (12:30까지)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 개발 우선순위
|
||||
|
||||
### Phase 1: 핵심 기능 (1주차)
|
||||
|
||||
| 순위 | 기능 | 예상 공수 | 의존성 |
|
||||
|:----:|------|:--------:|--------|
|
||||
| 1 | 선주문 조회 API | 0.5일 | - |
|
||||
| 2 | 도매상 한도 테이블 + API | 1일 | - |
|
||||
| 3 | 규격 추천 API | 1일 | 선주문 API |
|
||||
| 4 | 한도 체크 로직 | 0.5일 | 한도 테이블 |
|
||||
|
||||
### Phase 2: 자동화 (2주차)
|
||||
|
||||
| 순위 | 기능 | 예상 공수 | 의존성 |
|
||||
|:----:|------|:--------:|--------|
|
||||
| 5 | 주문 재시도 로직 | 1일 | Phase 1 |
|
||||
| 6 | 장바구니 동기화 | 1일 | - |
|
||||
| 7 | UI: 한도 대시보드 | 1일 | 한도 API |
|
||||
| 8 | UI: 주문 화면 개선 | 1일 | 규격 추천 API |
|
||||
|
||||
### Phase 3: 고도화 (3주차)
|
||||
|
||||
| 순위 | 기능 | 예상 공수 | 의존성 |
|
||||
|:----:|------|:--------:|--------|
|
||||
| 9 | 배송 스케줄 관리 | 1일 | - |
|
||||
| 10 | 알림 시스템 | 1일 | - |
|
||||
| 11 | AI 학습 파이프라인 | 2일 | Phase 1-2 데이터 |
|
||||
| 12 | 자동 스케줄링 | 1일 | 배송 스케줄 |
|
||||
|
||||
---
|
||||
|
||||
## 📝 검토 요청 사항
|
||||
|
||||
### 1. 한도 기본값
|
||||
도매상별 초기 한도 얼마로 설정?
|
||||
- 지오영: ____만원
|
||||
- 수인: ____만원
|
||||
- 백제: ____만원
|
||||
|
||||
### 2. 배송 스케줄 ✅ 확인 완료
|
||||
|
||||
| 도매상 | 배송 | 주문 마감 | 도착 예정 | 비고 |
|
||||
|--------|------|----------|----------|------|
|
||||
| **지오영** | 오전 | 10:00 | 11:30 | 당일 |
|
||||
| | 오후 | 13:00 | 15:00 | 당일 |
|
||||
| **수인** | 오후 | 13:00 | 14:30 | 당일 |
|
||||
| **백제** | 익일 | 16:00 | 다음날 15:00 | ⚠️ 익일배송 |
|
||||
|
||||
### 3. 알림 채널
|
||||
어디로 받으실 건가요?
|
||||
- [ ] 텔레그램
|
||||
- [ ] 카카오톡
|
||||
- [ ] 웹 알림
|
||||
- [ ] 기타: ____
|
||||
|
||||
### 4. 재시도 정책
|
||||
- A도매상 실패 시 바로 B로?
|
||||
- 몇 번까지 재시도?
|
||||
|
||||
---
|
||||
|
||||
> 🐉 **용림 메모**: 기획서 검토 후 Phase 1부터 순차 개발 예정.
|
||||
> 약사님 확인 후 수정사항 반영하겠습니다!
|
||||
Reference in New Issue
Block a user