pharmacy-pos-qr-system/docs/ARCHITECTURE.md

25 KiB
Raw Blame History

🏗️ 약국 통합 솔루션 아키텍처

📋 개요

본 시스템은 동물약 도매상(애니팜), 개별 약국 POS, 마일리지 솔루션을 통합하는 멀티 데이터베이스 아키텍처입니다.

┌─────────────────────────────────────────────────────────────────────────────┐
│                        🏢 애니팜 (동물약 도매상)                              │
│                         PostgreSQL Database                                  │
│                    제품 마스터, 재고, 주문, 거래처                            │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    │ 제품 정보 / 발주
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                        💊 개별 약국 (청춘약국 등)                             │
│  ┌──────────────────────┐    ┌──────────────────────┐                       │
│  │   MSSQL (팜IT3000)   │    │   SQLite (솔루션)    │                       │
│  │   - 제품 마스터      │    │   - 마일리지         │                       │
│  │   - 판매 내역        │◄──►│   - AI 추천          │                       │
│  │   - 조제 이력        │    │   - 알림톡 로그      │                       │
│  │   - 회원 정보        │    │   - 동물약 태그      │                       │
│  └──────────────────────┘    └──────────────────────┘                       │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    │ API / 웹 인터페이스
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                         🌐 Flask 웹 서버 (7001)                              │
│         QR 적립 | AI 챗봇 | 관리자 | 회원 조회 | 알림톡                      │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    │ 외부 서비스
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│   🔌 외부 API 연동                                                           │
│   - OpenAI GPT (동물약 챗봇, AI 업셀링)                                      │
│   - 카카오 OAuth (로그인)                                                    │
│   - NHN Cloud 알림톡                                                         │
│   - Clawdbot Gateway (AI 에이전트)                                           │
└─────────────────────────────────────────────────────────────────────────────┘

🗄️ 데이터베이스 구조

1 PostgreSQL (애니팜 - 동물약 도매상)

역할: 동물약 도매 사업의 핵심 DB. 제품 마스터, 거래처(약국), 주문/발주 관리

테이블 설명 주요 컬럼
products 제품 마스터 id, name, barcode, price, category
customers 거래처 (약국) id, pharmacy_name, owner, phone
orders 주문 내역 id, customer_id, order_date, status
order_items 주문 상세 order_id, product_id, qty, price
inventory 재고 현황 product_id, stock_qty, location
-- 예시: 인기 동물약 TOP 10 조회
SELECT p.name, SUM(oi.qty) as total_sold
FROM order_items oi
JOIN products p ON oi.product_id = p.id
WHERE oi.created_at >= NOW() - INTERVAL '30 days'
GROUP BY p.name
ORDER BY total_sold DESC
LIMIT 10;

2 MSSQL (팜IT3000 - 약국 POS)

역할: 약국 청구/POS 프로그램의 DB. 제품, 판매, 조제, 회원 정보

주요 데이터베이스

DB명 설명
PM_DRUG 제품 마스터 (의약품/건기식)
PM_PRES 판매/조제 내역
PM_BASE 회원/거래처 기본 정보

핵심 테이블

PM_DRUG.dbo.CD_GOODS - 제품 마스터

컬럼 설명
DrugCode 제품 코드 (PK)
GoodsName 제품명
BARCODE 바코드
Saleprice 판매가
Price 원가
POS_BOON 분류코드 (010103 = 동물약)
GoodsSelCode 판매상태 (B = 판매중)

PM_PRES.dbo.SALE_MAIN - 판매 헤더

컬럼 설명
SL_NO_order 거래번호 (PK)
InsertTime 거래 일시
SL_MY_total 총 금액
SL_CD_custom 고객 코드

PM_PRES.dbo.SALE_SUB - 판매 상세

컬럼 설명
SL_NO_order 거래번호 (FK)
DrugCode 제품 코드
SL_NM_item 수량
SL_TOTAL_PRICE 금액

PM_BASE.dbo.CD_PERSON - 회원 정보

컬럼 설명
CUSCODE 고객 코드 (PK)
PANAME 이름
PHONE 전화번호
PANUM 주민번호

PM_PRES.dbo.PS_sub_pharm - 조제 약품 상세

컬럼 설명
PreSerial 처방번호 (FK)
SUB_SERIAL 약품 순번
DrugCode 제품 코드
Days 복용일수
QUAN 1회 복용량
QUAN_TIME 1일 복용횟수
INV_QUAN 총 투약량
PS_Type 조제 유형 (아래 참고)

PS_Type 값 (대체조제 구분)

PS_Type 의미 표시
0 일반 처방 표시
1 일반 대체조제 표시 + 대) 배지 (주황색)
4 저가대체 인센티브 - 실제 조제약 표시 + 저) 배지 (초록색)
9 저가대체 인센티브 - 원본 처방약 숨김 (약가 계산용)

대체조제 데이터 패턴:

SUB_SERIAL 순서로 4(실제) → 9(원본) 쌍으로 저장됨

예시 (김현지 처방):
PS_Type=4 | 사이톱신정     ← 실제 조제 (표시)
PS_Type=9 | 씨프러스정     ← 원본 처방 (숨김, 사이톱신의 원처방)
PS_Type=4 | 티로파정       ← 실제 조제 (표시)  
PS_Type=9 | 티램정         ← 원본 처방 (숨김, 티로파의 원처방)

쿼리 예시:

-- 실제 조제약만 조회 (대체조제 원본 제외)
SELECT * FROM PS_sub_pharm WHERE PreSerial = '처방번호' AND PS_Type != '9'

-- 대체조제 쌍 확인
SELECT 
    s1.DrugCode AS 실제조제,
    s2.DrugCode AS 원본처방
FROM PS_sub_pharm s1
JOIN PS_sub_pharm s2 ON s1.PreSerial = s2.PreSerial 
    AND s1.SUB_SERIAL + 1 = s2.SUB_SERIAL
WHERE s1.PS_Type = '4' AND s2.PS_Type = '9'
-- 예시: 오늘 판매 내역 + 제품명 조회
SELECT 
    M.SL_NO_order AS 거래번호,
    M.InsertTime AS 거래일시,
    G.GoodsName AS 제품명,
    S.SL_NM_item AS 수량,
    S.SL_TOTAL_PRICE AS 금액
FROM PM_PRES.dbo.SALE_MAIN M
JOIN PM_PRES.dbo.SALE_SUB S ON M.SL_NO_order = S.SL_NO_order
JOIN PM_DRUG.dbo.CD_GOODS G ON S.DrugCode = G.DrugCode
WHERE CONVERT(DATE, M.InsertTime) = CONVERT(DATE, GETDATE())
ORDER BY M.InsertTime DESC;
-- 예시: 동물약 목록 조회 (POS_BOON = '010103')
SELECT DrugCode, GoodsName, Saleprice, BARCODE
FROM PM_DRUG.dbo.CD_GOODS
WHERE POS_BOON = '010103' AND GoodsSelCode = 'B'
ORDER BY GoodsName;

3 SQLite (마일리지 솔루션)

역할: 약국별 마일리지 적립, AI 추천, 알림톡 로그 등 부가 기능

경로: backend/db/mileage.db

핵심 테이블

users - 마일리지 회원

컬럼 타입 설명
id INTEGER PK
nickname TEXT 이름
phone TEXT 전화번호 (UNIQUE)
mileage_balance INTEGER 포인트 잔액
birthday TEXT 생년월일
created_at TIMESTAMP 가입일

claim_tokens - QR 적립 토큰

컬럼 타입 설명
id INTEGER PK
transaction_id TEXT POS 거래번호 (UNIQUE)
token_hash TEXT 토큰 해시
total_amount REAL 구매 금액
claimable_points INTEGER 적립 가능 포인트
claimed_at TIMESTAMP 적립 완료 시각
claimed_by_user_id INTEGER 적립한 회원 ID

mileage_ledger - 포인트 원장

컬럼 타입 설명
id INTEGER PK
user_id INTEGER 회원 ID
transaction_id TEXT 거래번호
points INTEGER 적립/차감 포인트
balance_after INTEGER 변동 후 잔액
reason TEXT CLAIM / USE / ADMIN

ai_recommendations - AI 업셀링 추천

컬럼 타입 설명
id INTEGER PK
user_id INTEGER 회원 ID
recommended_product TEXT 추천 제품
recommendation_message TEXT 추천 메시지
status TEXT active / interested / dismissed

drug_tags - 동물약 태그 (별도 DB: drug_tags.db)

컬럼 타입 설명
drug_code TEXT 제품 코드
drug_name TEXT 제품명
tag_type TEXT animal_drug 등
tag_value TEXT all / dog / cat
-- 예시: 회원별 적립 내역 조회
SELECT 
    u.nickname, u.phone, u.mileage_balance,
    ml.points, ml.reason, ml.created_at
FROM users u
JOIN mileage_ledger ml ON u.id = ml.user_id
WHERE u.phone = '01012345678'
ORDER BY ml.created_at DESC;

🔄 데이터 흐름 예시

📱 시나리오 1: QR 마일리지 적립

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   POS 결제   │────►│  QR 발행    │────►│  고객 스캔   │────►│  적립 완료   │
│   (MSSQL)   │     │  (SQLite)   │     │  (Flask)    │     │  (SQLite)   │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
      │                   │                   │                   │
      │                   │                   │                   │
      ▼                   ▼                   ▼                   ▼
  SALE_MAIN          claim_tokens         users 조회         mileage_ledger
  SALE_SUB           생성 & 저장         /생성              적립 기록

쿼리 흐름:

-- 1. POS 판매 완료 시 (MSSQL)
INSERT INTO SALE_MAIN (SL_NO_order, SL_MY_total, ...) VALUES (...)

-- 2. QR 토큰 생성 (SQLite)
INSERT INTO claim_tokens (transaction_id, total_amount, claimable_points, ...)
VALUES ('20260228001234', 50000, 1500, ...)

-- 3. 고객 QR 스캔 → 회원 조회/생성 (SQLite)
SELECT * FROM users WHERE phone = '01012345678'
-- 없으면:
INSERT INTO users (nickname, phone, mileage_balance) VALUES ('홍길동', '01012345678', 0)

-- 4. 적립 처리 (SQLite)
UPDATE users SET mileage_balance = mileage_balance + 1500 WHERE id = 1
INSERT INTO mileage_ledger (user_id, transaction_id, points, balance_after, reason)
VALUES (1, '20260228001234', 1500, 1500, 'CLAIM')

-- 5. 토큰 사용 완료 표시 (SQLite)
UPDATE claim_tokens SET claimed_at = datetime('now'), claimed_by_user_id = 1
WHERE transaction_id = '20260228001234'

🐾 시나리오 2: 동물약 AI 챗봇

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  사용자 질문 │────►│  동물약 조회 │────►│  OpenAI API │────►│  응답 생성   │
│ "구충제 추천" │     │   (MSSQL)   │     │  (RAG)      │     │ + 제품 매칭  │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
                          │                   │
                          │                   │
                          ▼                   ▼
                    CD_GOODS에서          지식 베이스 +
                    동물약 38개           제품 목록 전달
                    가격 포함 조회

쿼리 흐름:

-- 1. 동물약 목록 조회 (MSSQL → RAG 컨텍스트)
SELECT DrugCode, GoodsName, Saleprice, BARCODE
FROM PM_DRUG.dbo.CD_GOODS
WHERE POS_BOON = '010103' AND GoodsSelCode = 'B'
ORDER BY GoodsName;
-- 결과: 안텔민(5000원), 넥스가드L(84000원), ... 38개

-- 2. OpenAI API 호출 (Python)
# System Prompt에 포함:
# - 동물약 지식 (심장사상충, 구충제, 외부기생충 )
# - 현재 보유 제품 목록 + 가격

# User: "구충제 추천해줘"
# AI 응답: "구충제로는 **안텔민**을 추천드려요! 프라지콴텔+피란텔 성분으로..."

-- 3. 응답에서 제품명 매칭 (Python)
# AI 응답에 "안텔민" 포함  가격 5000 표시

👤 시나리오 3: 회원 상세 조회 (통합)

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  전화번호    │────►│  DB 3곳     │────►│  통합 응답   │
│  입력       │     │  동시 조회   │     │  반환       │
└─────────────┘     └─────────────┘     └─────────────┘
                          │
          ┌───────────────┼───────────────┐
          ▼               ▼               ▼
     ┌─────────┐    ┌─────────┐    ┌─────────┐
     │ SQLite  │    │  MSSQL  │    │  MSSQL  │
     │ users   │    │PM_BASE  │    │PM_PRES  │
     │마일리지  │    │회원정보  │    │조제이력  │
     └─────────┘    └─────────┘    └─────────┘

쿼리 흐름:

-- 1. 마일리지 회원 조회 (SQLite)
SELECT id, nickname, phone, mileage_balance, created_at
FROM users WHERE phone = '01012345678'

-- 2. 적립 이력 조회 (SQLite)
SELECT points, balance_after, reason, created_at, transaction_id
FROM mileage_ledger WHERE user_id = 1
ORDER BY created_at DESC LIMIT 50

-- 3. POS 고객 코드 조회 (MSSQL PM_BASE)
SELECT CUSCODE, PANAME FROM CD_PERSON
WHERE REPLACE(PHONE, '-', '') = '01012345678'

-- 4. 조제 이력 조회 (MSSQL PM_PRES)
SELECT P.PreSerial, P.Indate, P.Drname, P.OrderName
FROM PS_main P
WHERE P.CusCode = 'C00001234'
ORDER BY P.Indate DESC

-- 5. 구매 상세 조회 (MSSQL PM_PRES + PM_DRUG)
SELECT G.GoodsName, S.SL_NM_item, S.SL_TOTAL_PRICE
FROM SALE_SUB S
JOIN PM_DRUG.dbo.CD_GOODS G ON S.DrugCode = G.DrugCode
WHERE S.SL_NO_order = '20260228001234'

🛠️ 기술 스택

계층 기술 용도
Frontend HTML/CSS/JS 관리자 페이지, 키오스크, 마이페이지
Backend Flask (Python) REST API, 템플릿 렌더링
Database PostgreSQL 애니팜 (도매상)
MSSQL 팜IT3000 (약국 POS)
SQLite 마일리지 솔루션
AI OpenAI GPT-4o-mini 동물약 챗봇, 업셀링 추천
인증 카카오 OAuth 소셜 로그인
알림 NHN Cloud 알림톡/SMS
프로세스 PM2 서버 관리
도메인 Cloudflare SSL, 프록시

📁 프로젝트 구조

pharmacy-pos-qr-system/
├── backend/
│   ├── app.py              # Flask 메인 앱
│   ├── db/
│   │   ├── dbsetup.py      # DB 연결 관리
│   │   ├── mileage.db      # SQLite (마일리지)
│   │   └── drug_tags.db    # SQLite (동물약 태그)
│   ├── templates/          # HTML 템플릿
│   │   ├── admin.html
│   │   ├── admin_products.html  # 제품 검색 + AI 챗봇
│   │   ├── admin_members.html
│   │   ├── kiosk.html
│   │   └── my_page.html
│   ├── services/
│   │   ├── kakao_client.py    # 카카오 OAuth
│   │   ├── nhn_alimtalk.py    # 알림톡
│   │   └── clawdbot_client.py # AI 에이전트
│   ├── utils/
│   │   └── qr_token_generator.py
│   └── .env                # 환경 변수
├── docs/
│   └── ARCHITECTURE.md     # 이 문서
├── logs/
└── ecosystem.config.js     # PM2 설정

🔐 환경 변수 (.env)

# 카카오 OAuth
KAKAO_CLIENT_ID=xxx
KAKAO_CLIENT_SECRET=xxx
KAKAO_REDIRECT_URI=https://mile.0bin.in/claim/kakao/callback

# OpenAI API
OPENAI_API_KEY=sk-xxx
OPENAI_MODEL=gpt-4o-mini

# MSSQL 연결 (dbsetup.py에서 설정)
# SQLite 경로 (backend/db/)

📊 주요 API 엔드포인트

경로 메서드 설명 DB
/api/products GET 제품 검색 MSSQL
/api/animal-chat POST 동물약 AI 챗봇 MSSQL + OpenAI
/api/animal-drugs GET 동물약 목록 MSSQL
/api/claim POST 마일리지 적립 SQLite
/api/members/search GET 회원 검색 MSSQL
/api/members/history/:phone GET 회원 이력 통합 전체
/admin/user/:id GET 회원 상세 (적립+구매+조제) 전체


🤖 PAAI 시스템 (처방 AI 분석)

개요

**PAAI (Prescription AI Analysis)**는 처방 접수 시 자동으로 AI 분석을 수행하고, 분석 결과를 영수증 프린터로 출력하는 시스템입니다.

아키텍처

┌─────────────────────────────────────────────────────────────────────────────┐
│                           PAAI 시스템 흐름                                   │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  POS 접수   │────►│  PM_PRES_LOG    │────►│ Trigger Module  │
│  (처방입력)  │     │  (MSSQL)        │     │ (폴링 감지)     │
└─────────────┘     └─────────────────┘     └─────────────────┘
                                                    │
                    ┌───────────────────────────────┤
                    │                               │
                    ▼                               ▼
           ┌─────────────────┐            ┌─────────────────┐
           │  WebSocket 알림  │            │  PAAI 분석 요청  │
           │  (ws://8765)    │            │  Flask API      │
           └─────────────────┘            └─────────────────┘
                    │                               │
                    ▼                               ▼
           ┌─────────────────┐            ┌─────────────────┐
           │  프론트엔드     │            │  Claude API     │
           │  pmr.html       │            │  (분석 수행)     │
           └─────────────────┘            └─────────────────┘
                    │                               │
                    │◄──────────────────────────────┤
                    │    analysis_completed 이벤트
                    ▼
           ┌─────────────────┐
           │  자동 인쇄      │
           │  ESC/POS 프린터 │
           └─────────────────┘

구성 요소

모듈 위치 역할
Trigger Module prescription-trigger/prescription_trigger.py PM_PRES_LOG 폴링, 처방 감지, 분석 요청
WebSocket Server Trigger 내장 (port 8765) 프론트엔드에 실시간 이벤트 전송
PAAI API backend/pmr_api.py 분석 요청 처리, Claude API 호출, 결과 저장
프론트엔드 backend/templates/pmr.html 조제관리 UI, 자동인쇄 토글
프린터 모듈 backend/paai_printer.py ESC/POS 영수증 프린터 출력

WebSocket 이벤트

이벤트 방향 설명
prescription_detected Server → Client 새 처방 감지됨
analysis_started Server → Client AI 분석 시작
analysis_completed Server → Client 분석 완료 (결과 포함)
analysis_failed Server → Client 분석 실패 (에러 포함)

자동 인쇄 흐름

// 1. WebSocket으로 analysis_completed 수신
ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    if (data.event === 'analysis_completed') {
        // 2. 자동인쇄 ON 상태면 인쇄
        if (window.autoPrintEnabled) {
            printPaaiResult(data.pre_serial, data.patient_name, data);
        }
    }
};

// 3. 인쇄 API 호출
POST /pmr/api/paai/print
{
    "pre_serial": "20260305000099",
    "patient_name": "홍길동",
    "result": { "analysis": {...}, "kims_summary": {...} }
}

// 4. ESC/POS 프린터로 출력

중복 방지

// window.printedSerials (Set) 으로 중복 인쇄 방지
if (window.printedSerials.has(preSerial)) {
    console.log('[AutoPrint] 이미 인쇄됨, 스킵:', preSerial);
    return;
}
window.printedSerials.add(preSerial);  // 요청 전에 추가 (race condition 방지)

자동 재시도

시도 대기 시간 상태
1회차 - 최초 시도
2회차 2초 첫 번째 재시도
3회차 4초 두 번째 재시도
실패 - analysis_failed 이벤트 발송

로그 파일

파일 위치 내용
print_history.log backend/logs/ 인쇄 성공/실패 기록
analysis_failures.log prescription-trigger/logs/ 분석 실패 상세 기록
paai_logs.db backend/db/ 분석 결과 SQLite 저장

관련 문서

  • docs/PAAI_AUTO_PRINT_TROUBLESHOOTING.md - 자동인쇄 트러블슈팅 가이드

📝 버전 이력

날짜 버전 변경 내용
2026-02-28 1.0 초기 아키텍처 문서 작성
동물약 AI 챗봇 추가
플로팅 챗봇 UI 구현
2026-03-05 1.1 PAAI 시스템 아키텍처 추가
자동인쇄, WebSocket, 재시도 로직

작성: Clawdbot AI | 청춘약국 통합 솔루션