docs: 데이터베이스 구조 문서화 (APC, MSSQL, PostgreSQL)

This commit is contained in:
thug0bin 2026-02-28 10:44:50 +09:00
parent 68ad59285a
commit dd28958a59
5 changed files with 1302 additions and 0 deletions

299
docs/APC_MAPPING_PLAN.md Normal file
View File

@ -0,0 +1,299 @@
# 🎯 APC 기반 동물약 매핑 기획서
> ⚠️ **주의**: 기획 단계에서는 MSSQL **READ ONLY**. 절대 데이터 입력/수정 금지.
---
## 📋 현상황 분석
### 1. 동물약 바코드 문제
#### 문제 1: 바코드 없음
- 동물약은 **수의사 소분 판매 방지** 목적으로 공산품이지만 바코드가 없는 경우 많음
- "판매 최소포장단위"별 바코드가 부여되지 않음
- 예: 다이로하트, 넥스가드 등 → 바코드 없음
#### 문제 2: 바코드 중복
- 바코드가 있어도 **여러 사이즈 제품이 동일 바코드** 사용
- 예: 다이로하트정 SS/S/M/L → 모두 동일 바코드
- 바코드만으로 사이즈/체중 구분 불가
#### 문제 3: 약국별 자체 바코드
- 약국은 POS 재고관리를 위해 **자체 바코드 생성**하여 사용
- 원래 바코드 무시하고 새로 지정
- 이유: POS에서 스캔 시 제품별 즉시 구분 + 재고 차감 필요
#### 결과: 중앙 매핑 불가
```
약국 A: "안텔민사사" → 바코드 "A001"
약국 B: "안텔민사사" → 바코드 "B999"
약국 C: "안텔민사사" → 바코드 없음 (수기 입력)
↓ 중앙 시스템 입장
바코드 "A001" = ??? (알 수 없음)
바코드 "B999" = ??? (알 수 없음)
```
---
### 2. 현재 데이터 구조 (2025-06-30 최종 확인)
```
┌─────────────────────────────────────────────────────────────┐
│ MSSQL (팜IT3000 - 약국 POS) │
├─────────────────────────────────────────────────────────────┤
│ │
│ CD_GOODS (제품 마스터) - 178,182개 │
│ ├── DrugCode: LB000003157 (PK) │
│ ├── GoodsName: "안텔민킹(5kg이상)" │
│ └── 팜IT3000 전체 제품 DB │
│ │ │
│ ├────────────────┬─────────────────────────────┐ │
│ ▼ ▼ ▼ │
│ CD_SALEGOODS CD_ITEM_UNIT_MEMBER CD_BARCODE│
│ (대표 바코드 1개) (바코드 N개!) ★ (인체용) │
│ 3,053개 ├ CD_CD_BARCODE 306,565개│
│ BARCODE: │ 0230237810109 (APC!) 동물약X │
│ 9990000001134 │ 9990000001134 (자체) │
│ └ DRUGCODE → CD_GOODS.DrugCode │
│ │
└─────────────────────────────────────────────────────────────┘
★ 핵심: 한 제품에 여러 바코드 가능! → CD_ITEM_UNIT_MEMBER
★ APC 저장 위치: CD_ITEM_UNIT_MEMBER.CD_CD_BARCODE
★ APC로 이미지 조회: https://ani.0bin.in/img/{APC}_F.jpg
```
│ 매핑 필요
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL (애니팜 - 동물약 마스터) │
├─────────────────────────────────────────────────────────────┤
│ apc 테이블 │
│ ├── apc: "0230237010107" (고유!) │
│ ├── product_name: "대성 안텔민 사사 정 100mg/25mg/10정" │
│ ├── company_name: "(주)대성미생물연구소" │
│ ├── for_pets: true │
│ ├── image_url1: "https://ani.0bin.in/img/..." │
│ └── godoimage_url_f: "https://cdn.../..." │
└─────────────────────────────────────────────────────────────┘
```
---
## 💡 해결책: APC 기반 매핑
### 핵심 아이디어
**APC(Animal Product Code)를 고유 매핑 키로 사용**
```
CD_GOODS.DrugCode ←→ CD_BARCODE.DRUGCODE ←→ APC(새로추가) ←→ PostgreSQL.apc
```
### 왜 APC인가?
| 키 | 고유성 | 중앙관리 | 이미지 | 현황 |
|----|--------|----------|--------|------|
| 바코드 | ❌ 중복/없음 | ❌ 약국별 다름 | ❌ | 사용 불가 |
| DrugCode | ⚠️ 약국내 고유 | ❌ 약국별 다름 | ❌ | 내부용 |
| **APC** | ✅ 전국 고유 | ✅ 애니팜 관리 | ✅ | **사용 가능** |
---
## 🔄 구현 계획
### Phase 1: 동물약 태깅 (완료 ✅)
```
CD_GOODS에서 POS_BOON='010103' 추출 → 38개 동물약 식별
```
### Phase 2: AI 기반 APC 매핑
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ MSSQL 동물약 │ │ AI 분석 │ │ PostgreSQL │
│ 38개 제품 │────►│ 제품명 매칭 │────►│ apc 후보 추천 │
│ │ │ 성분/체중 분석 │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ 관리자 확인 │
│ 매핑 승인/수정 │
└─────────────────┘
```
**AI 매칭 로직:**
```python
# MSSQL 제품
mssql_product = "안텔민사사(5kg이하)"
# PostgreSQL 검색
pgsql_candidates = search_apc("안텔민 사사")
# → [
# "대성 안텔민 사사 정 100mg/25mg/10정" (APC: 0230237010107),
# "대성 안텔민 사사 정 100mg/25mg/50정" (APC: 0230237010205),
# ...
# ]
# AI 추천: 체중 범위, 포장단위 분석
recommended_apc = "0230237010107" # 10정 (최소 판매단위)
```
### Phase 3: APC 바코드 등록 방법
**옵션 A: CD_SALEGOODS.BARCODE 업데이트 (현재 구조 활용)**
```sql
-- CD_SALEGOODS에서 바코드를 APC로 변경
UPDATE CD_SALEGOODS
SET BARCODE = '0230237010107' -- APC 코드
WHERE DrugCode = 'LB000003158'; -- 안텔민뽀삐
```
또는 POS에서 직접:
1. 제품 선택 → 바코드 수정 → APC 입력 → 저장
**옵션 B: 별도 매핑 테이블 (SQLite) - MSSQL 수정 최소화**
```sql
-- SQLite에 매핑 테이블 생성
CREATE TABLE animal_drug_apc_mapping (
id INTEGER PRIMARY KEY,
mssql_drug_code TEXT NOT NULL, -- CD_GOODS.DrugCode
mssql_barcode TEXT, -- CD_SALEGOODS.BARCODE (현재값)
apc_code TEXT NOT NULL, -- PostgreSQL apc
product_name TEXT, -- 확인용
verified BOOLEAN DEFAULT 0, -- 관리자 검증 여부
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
**현재 동물약 바코드 현황:**
| 제품 | DrugCode | CD_SALEGOODS.BARCODE |
|------|----------|---------------------|
| 안텔민뽀삐 | LB000003158 | 9990000001133 |
| 안텔민킹 | LB000003157 | 9990000001134 |
| 다이로하트S | LB000003150 | 9990000001131 |
| 다이로하트M | LB000003151 | 9990000001132 |
### Phase 4: QR 라벨 출력 연동
```
┌─────────────────────────────────────────────────────────────┐
│ 프론트엔드: 제품 검색 → QR 라벨 출력 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 제품 선택 (MSSQL) │
│ └── DrugCode: LB000003158 │
│ │
│ 2. APC 매핑 확인 │
│ └── APC: 0230237010107 (매핑됨 ✅) │
│ │
│ 3. QR 라벨 생성 │
│ ├── QR 내용: APC 코드 │
│ ├── 라벨 텍스트: 제품명 + 가격 │
│ └── [인쇄] 버튼 활성화 │
│ │
│ ※ APC 미매핑 제품 → [인쇄] 버튼 비활성화 또는 경고 │
└─────────────────────────────────────────────────────────────┘
```
### Phase 5: 이미지 표시 연동
```
챗봇 응답: "안텔민을 추천드려요"
├── MSSQL: "안텔민사사(5kg이하)" 재고 확인
├── APC 매핑: LB000003158 → 0230237010107
├── PostgreSQL: 이미지 URL 조회
│ └── https://ani.0bin.in/img/0230237010107_F.jpg
└── 프론트: 제품 칩 + 이미지 썸네일 표시
┌──────────────────────────┐
│ 📦 안텔민사사 (5,000원) │
│ [썸네일 이미지] │
└──────────────────────────┘
```
---
## 📊 예상 데이터 흐름
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 전체 데이터 흐름 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ [약국 POS] │
│ │ │
│ ▼ │
│ CD_GOODS ──────► CD_BARCODE (APC 추가) │
│ │ │ │
│ │ │ APC = "0230237010107" │
│ │ │ │
│ ▼ ▼ │
│ 제품 판매 ◄──── QR 스캔 (APC 인식) │
│ │ │ │
│ │ │ │
│ ▼ ▼ │
│ [챗봇/이미지] [애니팜 연동] │
│ │ │ │
│ │ │ │
│ ▼ ▼ │
│ PostgreSQL ◄─────────────┘ │
│ (이미지, 상세정보, 용법용량) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## ✅ TODO 체크리스트
### 1단계: 분석 (READ ONLY)
- [x] MSSQL 동물약 38개 추출
- [x] CD_BARCODE 구조 분석
- [x] PostgreSQL apc 테이블 구조 분석
- [x] 매핑 가능 제품 샘플 확인 (안텔민, 하트가드 등)
- [ ] 전체 38개 제품 APC 후보 목록 생성
### 2단계: 기획
- [x] 매핑 전략 수립 (APC 기반)
- [ ] CD_BARCODE 활용 vs SQLite 매핑 테이블 결정
- [ ] 관리자 매핑 UI 설계
- [ ] QR 라벨 출력 연동 설계
### 3단계: 구현 (약사님 승인 후)
- [ ] 매핑 테이블 생성
- [ ] AI 매핑 추천 기능
- [ ] 관리자 매핑 확인/수정 UI
- [ ] QR 라벨 출력 (APC 기반)
- [ ] 챗봇 이미지 연동
---
## ⚠️ 주의사항
1. **MSSQL 수정 금지** (기획 단계)
- READ ONLY 유지
- 테스트도 SELECT만
2. **APC 신뢰성**
- PostgreSQL apc 테이블이 마스터
- 애니팜에서 관리하는 공식 코드
3. **약국별 차이**
- 자체 바코드 사용 중인 약국 고려
- 기존 워크플로우 방해하지 않도록
4. **단계적 적용**
- 매핑 확인된 제품만 QR 출력 허용
- 미매핑 제품은 기존 방식 유지
---
*작성일: 2025-06-30*
*작성자: 용림 (Clawdbot)*
*상태: 기획 중*

433
docs/ARCHITECTURE.md Normal file
View File

@ -0,0 +1,433 @@
# 🏗️ 약국 통합 솔루션 아키텍처
## 📋 개요
본 시스템은 **동물약 도매상(애니팜)**, **개별 약국 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 |
```sql
-- 예시: 인기 동물약 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` | 주민번호 |
```sql
-- 예시: 오늘 판매 내역 + 제품명 조회
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;
```
```sql
-- 예시: 동물약 목록 조회 (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 |
```sql
-- 예시: 회원별 적립 내역 조회
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 생성 & 저장 /생성 적립 기록
```
**쿼리 흐름:**
```sql
-- 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개 제품 목록 전달
가격 포함 조회
```
**쿼리 흐름:**
```sql
-- 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 │
│마일리지 │ │회원정보 │ │조제이력 │
└─────────┘ └─────────┘ └─────────┘
```
**쿼리 흐름:**
```sql
-- 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)
```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 | 회원 상세 (적립+구매+조제) | 전체 |
---
## 📝 버전 이력
| 날짜 | 버전 | 변경 내용 |
|------|------|----------|
| 2026-02-28 | 1.0 | 초기 아키텍처 문서 작성 |
| | | 동물약 AI 챗봇 추가 |
| | | 플로팅 챗봇 UI 구현 |
---
*작성: Clawdbot AI | 청춘약국 통합 솔루션*

165
docs/DATABASE_STRUCTURE.md Normal file
View File

@ -0,0 +1,165 @@
# 데이터베이스 구조 (2025-06-30 정리)
## 개요
양구청춘약국 시스템은 3개의 데이터베이스를 사용합니다:
| DB | 용도 | 위치 |
|----|------|------|
| **MSSQL (PM_DRUG)** | POS 제품/재고/판매 | localhost (팜IT3000) |
| **MSSQL (PM_PRES)** | 처방전/조제 | localhost (팜IT3000) |
| **PostgreSQL** | 동물약 상세 정보 (RAG) | 192.168.0.87:5432 |
| **SQLite** | 마일리지 시스템 | backend/db/mileage.db |
---
## MSSQL 테이블 구조 (PM_DRUG)
### 핵심 테이블 관계
```
┌─────────────────────────────────────────────────────────────┐
│ CD_GOODS (제품 마스터) - 178,182개 │
│ └── DrugCode (PK): LB000003157 │
│ │ │
│ ┌─────────┴─────────────┬──────────────────────────┐ │
│ ▼ ▼ ▼ │
│ CD_SALEGOODS CD_ITEM_UNIT_MEMBER CD_BARCODE│
│ (대표 바코드) (바코드 N개) ★ (인체용) │
│ 3,053개 N:1 관계 306,565개│
└─────────────────────────────────────────────────────────────┘
```
### CD_GOODS (제품 마스터)
팜IT3000 전체 제품 DB. 약국이 개별 등록한 제품은 `LB`, `S`로 시작.
| 컬럼 | 타입 | 설명 |
|------|------|------|
| DrugCode | nvarchar | PK. `LB000003157` (약국등록), `050000010` (표준) |
| GoodsName | nvarchar | 제품명 |
| Saleprice | decimal | 판매가 |
| BARCODE | nvarchar | (보통 비어있음 - CD_SALEGOODS 사용) |
| POS_BOON | nvarchar | 분류코드. `010103` = 동물약 |
| GoodsSelCode | nvarchar | `B` = 판매용 |
### CD_SALEGOODS (판매용 제품)
약국에서 실제 판매하는 제품. **대표 바코드 1개** 저장.
| 컬럼 | 타입 | 설명 |
|------|------|------|
| DrugCode | nvarchar | FK → CD_GOODS |
| GoodsName | nvarchar | 제품명 |
| BARCODE | nvarchar | **대표 바코드** (자체생성: `999000000xxxx`) |
| SplCode | nvarchar | 공급처 코드 |
| SplName | nvarchar | 공급처명 |
### CD_ITEM_UNIT_MEMBER (바코드 N개) ★
**한 제품에 여러 바코드** 저장. APC 코드는 여기에 저장됨!
| 컬럼 | 타입 | 설명 |
|------|------|------|
| DRUGCODE | nvarchar | FK → CD_GOODS.DrugCode |
| CD_CD_BARCODE | nvarchar | **바코드** (APC: `0230237810109`) |
| CD_CD_UNIT | nvarchar | 단위코드 (13, 015 등) |
| CD_MY_UNIT | decimal | 판매가 |
| CD_IN_UNIT | decimal | 입고가 |
| CHANGE_DATE | nvarchar | 변경일 (YYYYMMDD) |
| SN | bigint | 일련번호 |
### CD_BARCODE (인체용 표준)
식약처 인체용 의약품 표준 바코드. **동물약은 없음!**
| 컬럼 | 타입 | 설명 |
|------|------|------|
| DRUGCODE | nvarchar | 제품코드 |
| BARCODE | nvarchar | 표준 바코드 |
| BASECODE | nvarchar | 표준코드 |
| ETCNAME | nvarchar | 제품명 |
| CL_GUBUN | nvarchar | 구분 (전문의약품 등) |
---
## PostgreSQL 구조 (apdb_master)
동물약품 상세 정보. 농림축산검역본부 데이터 + LLM 가공.
### apc 테이블 (핵심)
| 컬럼 | 타입 | 설명 |
|------|------|------|
| apc | varchar | **PK**. `0230237810109` |
| product_name | varchar | 제품명 |
| company_name | varchar | 제조사 |
| main_ingredient | varchar | 주성분 |
| efficacy_effect | text | 효능/효과 (HTML) |
| dosage_instructions | text | 용법/용량 (HTML) |
| precautions | text | 주의사항 (HTML) |
| **llm_pharm** | jsonb | **LLM 가공 정보** ★ |
| image_url1 | varchar | 앞면 이미지 |
| image_url2 | varchar | 뒷면 이미지 |
| weight_min_kg | float | 최소 체중 |
| weight_max_kg | float | 최대 체중 |
### llm_pharm JSON 구조 (핵심!)
```json
{
"사용가능 동물": "개, 고양이",
"분류": "내부구충제",
"성분1": "메벤다졸",
"성분2": "프라지콴텔",
"체중/부위": "체중 5~9kg: 1정, 10~19kg: 2정...",
"기간/용법": "1일 1회, 1~2일간 경구투여",
"월령금기": "생후 1주 미만 사용 금지",
"반려인주의": "사람이 복용 시 즉시 의사의 조치 필요",
"앞이미지": "https://...",
"뒤이미지": "https://..."
}
```
---
## 바코드 체계
| 패턴 | 설명 | 예시 |
|------|------|------|
| `023xxxxxxxx` | **APC (동물약 표준)** | `0230237810109` |
| `999000000xxxx` | 약국 자체 생성 | `9990000001134` |
| `880xxxxxxxxx` | 일반 GS1 바코드 | `8809989000009` |
---
## 연결 예시
**안텔민킹(5kg이상) 조회:**
```sql
-- MSSQL: 바코드 조회
SELECT CD_CD_BARCODE
FROM CD_ITEM_UNIT_MEMBER
WHERE DRUGCODE = 'LB000003157'
AND CD_CD_BARCODE LIKE '023%';
-- → 0230237810109
-- PostgreSQL: 상세 정보 조회
SELECT llm_pharm->>'사용가능 동물', efficacy_effect
FROM apc
WHERE apc = '0230237810109';
-- → 개, 고양이
```
---
## 이미지 URL 규칙
```
https://ani.0bin.in/img/{APC}_F.jpg # 앞면
https://ani.0bin.in/img/{APC}_B.jpg # 뒷면
https://ani.0bin.in/img/{APC}_D.jpg # 상세
```
예: `https://ani.0bin.in/img/0230237810109_F.jpg`

167
docs/ENCODING_GUIDE.md Normal file
View File

@ -0,0 +1,167 @@
# 🔤 인코딩 가이드 (필독!)
> ⚠️ **중요**: 한글 데이터 처리 시 반드시 이 가이드를 따를 것
---
## ✅ 현재 설정 (2025-06-30 적용됨)
```
환경변수: PYTHONIOENCODING=utf-8 (User 레벨)
```
이 설정으로 모든 Python 스크립트에서 UTF-8 출력이 기본 적용됩니다.
---
## 📋 문제 상황
### 증상
```
DB 실제 값: "안텔민뽀삐"
콘솔 출력: "안텔민사사" ← 깨져서 다른 글자로 보임!
```
### 원인
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ DB (UTF-8) │ ──► │ Python │ ──► │ Windows 콘솔 │
│ "뽀삐" │ │ stdout │ │ (CP949) │
│ U+BF40 │ │ │ │ "사사" │
└──────────────┘ └──────────────┘ └──────────────┘
인코딩 변환 실패!
```
- Windows 콘솔 기본 인코딩: **CP949** (한국어 완성형)
- CP949에서 지원하지 않거나 다르게 매핑되는 유니코드 문자 존재
- "뽀삐" 같은 글자가 "사사"로 잘못 표시됨
---
## ✅ 해결책
### 1. 스크립트 상단에 인코딩 설정 추가 (필수!)
```python
# -*- coding: utf-8 -*-
import sys
import io
# stdout을 UTF-8로 강제 설정
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
```
### 2. 환경변수 설정 (권장)
```powershell
# PowerShell에서 실행 전 설정
$env:PYTHONIOENCODING = "utf-8"
# 또는 시스템 환경변수로 영구 설정
[Environment]::SetEnvironmentVariable("PYTHONIOENCODING", "utf-8", "User")
```
### 3. Windows Terminal UTF-8 모드
```powershell
# 콘솔 코드페이지를 UTF-8로 변경
chcp 65001
```
### 4. JSON 출력 사용 (가장 안전)
```python
import json
# 콘솔 출력 대신 JSON으로 반환
result = {
"product_name": "안텔민뽀삐",
"apc": "0230237010107"
}
print(json.dumps(result, ensure_ascii=False, indent=2))
```
---
## 📝 스크립트 템플릿
모든 DB 조회 스크립트는 이 템플릿을 사용할 것:
```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
스크립트 설명
"""
import sys
import io
import json
# ═══════════════════════════════════════════════════════════
# 인코딩 설정 (Windows CP949 문제 방지)
# ═══════════════════════════════════════════════════════════
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# ═══════════════════════════════════════════════════════════
# 메인 로직
# ═══════════════════════════════════════════════════════════
def main():
# ... 로직 ...
# 결과는 JSON으로 출력 (가장 안전)
result = {"data": [...]}
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == '__main__':
main()
```
---
## 🔧 기존 스크립트 수정 목록
| 스크립트 | 상태 | 수정 필요 |
|----------|------|-----------|
| `scripts/query_mileage.py` | ⚠️ | 인코딩 설정 추가 |
| `scripts/query_sales.py` | ⚠️ | 인코딩 설정 추가 |
| `scripts/query_aniparm.py` | ⚠️ | 인코딩 설정 추가 |
| `scripts/search_mssql.py` | ⚠️ | 인코딩 설정 추가 |
| `scripts/check_*.py` | ⚠️ | 인코딩 설정 추가 |
---
## 🧪 테스트 방법
```python
# 인코딩 테스트 스크립트
# -*- coding: utf-8 -*-
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
test_words = ["뽀삐", "킹", "안텔민뽀삐(5kg이하)", "다이로하트정M(12~22kg)"]
for word in test_words:
print(f"원본: {word}")
print(f"유니코드: {[f'U+{ord(c):04X}' for c in word]}")
print()
```
---
## ⚠️ 주의사항
1. **절대 CP949 출력을 믿지 말 것** - 깨진 글자가 다른 글자로 보일 수 있음
2. **DB 데이터 확인 시** - 직접 DB 툴로 확인하거나 JSON 출력 사용
3. **AI 분석 시** - 유니코드 코드포인트로 확인 (U+XXXX)
4. **매핑 작업 시** - 반드시 양쪽 DB 직접 확인 후 진행
---
*작성일: 2025-06-30*
*사유: "뽀삐"가 "사사"로 잘못 표시되는 인코딩 문제 발생*

238
docs/IMAGE_MAPPING_PLAN.md Normal file
View File

@ -0,0 +1,238 @@
# 🖼️ 동물약 이미지 매핑 계획
## 📋 목표
챗봇에서 동물약 추천 시 **제품 이미지**를 함께 표시
---
## 🗄️ 데이터 현황
### MSSQL (약국 POS - PM_DRUG.CD_GOODS)
| 컬럼 | 설명 | 현황 |
|------|------|------|
| `DrugCode` | 제품코드 | ✅ 전체 있음 (예: LB000003151) |
| `GoodsName` | 제품명 | ✅ 전체 있음 |
| `BARCODE` | 바코드 | ⚠️ **14/38개만 있음 (37%)** |
| `BaseCode` | 표준코드 | ❌ **0개** (사용 불가) |
### PostgreSQL (애니팜 - apc 테이블)
| 컬럼 | 설명 |
|------|------|
| `idx` | 고유 ID |
| `apc` | APC 코드 (고유) |
| `product_name` | 제품명 |
| `image_url1` ~ `image_url3` | 이미지 URL |
| `godoimage_url_f` | 고도몰 CDN - 앞 이미지 |
| `godoimage_url_b` | 고도몰 CDN - 뒤 이미지 |
| `godoimage_url_d` | 고도몰 CDN - 상세 이미지 |
**확인 필요**: `apc` 테이블에 바코드 컬럼이 있는지?
---
## 🔗 매핑 전략
### 옵션 1: 바코드 매핑 (37% 커버)
```
MSSQL.BARCODE ↔ PostgreSQL.barcode(?)
```
- 장점: 정확한 매칭
- 단점: 14/38개만 매핑 가능
### 옵션 2: 제품명 유사도 매핑 (Fuzzy Matching)
```python
from fuzzywuzzy import fuzz
# MSSQL: "다이로하트정M(12~22kg)"
# PostgreSQL: "다이로하트 정M 12~22kg" 등 유사 이름 매칭
score = fuzz.partial_ratio(mssql_name, pgsql_name)
if score > 80:
matched = True
```
- 장점: 100% 커버 가능
- 단점: 오매칭 위험
### 옵션 3: 매핑 테이블 생성 (권장) ✅
```sql
-- SQLite에 매핑 테이블 생성
CREATE TABLE drug_image_mapping (
id INTEGER PRIMARY KEY,
mssql_drug_code TEXT UNIQUE, -- MSSQL DrugCode
pgsql_apc TEXT, -- PostgreSQL apc 코드
image_url TEXT, -- 확정된 이미지 URL
verified BOOLEAN DEFAULT 0, -- 수동 검증 여부
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
**작업 흐름:**
1. 바코드 매칭 (자동) → 14개 즉시 매핑
2. 제품명 유사도로 후보 추천 → 관리자 확인
3. 수동 매핑 → 나머지 제품
---
## 📊 MSSQL 동물약 바코드 현황 (38개)
### ✅ 바코드 있음 (14개)
| 제품명 | DrugCode | 바코드 |
|--------|----------|--------|
| 가드L(20~40kg) | LB000003570 | 8801244508268 |
| 가드M(10~20kg) | LB000003569 | 8801244508237 |
| 가드S(2~10kg) | LB000003568 | 8801244508220 |
| 하트가드정L(10~20kg) | LB000003564 | 8801244508343 |
| 하트가드정M(5~10kg) | LB000003453 | 8801244508329 |
| 하트가드정S(2.5~5kg) | LB000003452 | 8801244508312 |
| 하트가드정SS(2.5kg이하) | LB000003451 | 8801244508305 |
| 심파리카L(10~25kg) | LB000003634 | 8801244508534 |
| 심파리카M(4~10kg) | LB000003635 | 8801244508435 |
| 안텔민 | S0000001 | 8809989000009 |
| 세레타정(10정) | LB000003146 | 8809720800455 |
| 파라칸L(5kg이상) | LB000003159 | 8809625390914 |
| 파라칸S(5kg이하) | LB000003160 | 8809625390655 |
| 하트칸츄어블(11kg이하) | LB000003696 | 8809625390563 |
### ❌ 바코드 없음 (24개)
| 제품명 | DrugCode |
|--------|----------|
| (동)클리어민50(100정) | LB000003504 |
| 넥스가드L(15~30kg) | LB000003531 |
| 넥스가드xs(2~3.5kg) | LB000003530 |
| 다이로하트정M(12~22kg) | LB000003151 |
| 다이로하트정S(5.6~11kg) | LB000003150 |
| 다이로하트정SS(5.6kg이하) | LB000003149 |
| 레보M(10~20kg) | LB000003161 |
| 레보S(2~10kg) | LB000003162 |
| 밀베마이신A정16mg(대동미어) | LB000003353 |
| 밀베마이신A정24mg(대동미어) | LB000003354 |
| 하트가드정XL(20~40kg) | LB000003545 |
| 안텔민사사(5kg이하) | LB000003158 |
| 안텔민킹(5kg이상) | LB000003157 |
| 캐치펫캅(2.5~7.5kg)/고양이 | LB000003167 |
| 캐치펫L(10~20kg)/개 | LB000003166 |
| 캐치펫M(5~10kg)/개 | LB000003165 |
| 캐치펫S(2.5~5kg)/개 | LB000003164 |
| 캐치펫SS(2.5kg이하/개,고양이가능) | LB000003163 |
| 하트플레이버블정L(23~45kg) | LB000003544 |
| 하트플레이버블정M(12~22kg) | LB000003152 |
| 하트플레이버블정mini(5.6kg이하) | LB000003154 |
| 하트플레이버블정S(5.6~11kg) | LB000003153 |
| 하트플라블러스정M(12~22kg) | LB000003155 |
| 하트플라블러스정S(11kg이하) | LB000003156 |
---
## 🛠️ 구현 단계
### Phase 1: PostgreSQL 조사
- [ ] apc 테이블에 바코드 컬럼 확인
- [ ] 이미지 URL 실제 데이터 샘플 확인
- [ ] 동물약 제품 필터링 방법 확인 (`for_pets = true`?)
### Phase 2: 매핑 테이블 생성
- [ ] SQLite에 `drug_image_mapping` 테이블 생성
- [ ] 바코드 있는 14개 자동 매핑 시도
- [ ] 관리자 페이지에 매핑 UI 추가
### Phase 3: 챗봇 연동
- [ ] AI 응답에서 제품 매칭 시 이미지 URL 포함
- [ ] 프론트엔드에 이미지 표시 (썸네일)
- [ ] 클릭 시 큰 이미지 또는 상세 페이지
---
## 📝 다음 작업
1. **PostgreSQL apc 테이블 샘플 조회**
```sql
SELECT product_name, image_url1, godoimage_url_f
FROM apc
WHERE for_pets = true
LIMIT 10;
```
2. **바코드 컬럼 존재 여부 확인**
```sql
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'apc' AND column_name LIKE '%barcode%';
```
---
## 🗃️ MSSQL 테이블 구조 상세
### CD_GOODS vs CD_BARCODE 관계
```
CD_GOODS (제품 마스터) CD_BARCODE (바코드 마스터)
├── DrugCode (PK) ──────────► DRUGCODE (FK)
├── GoodsName ├── BARCODE (개별 바코드)
├── BARCODE (대표 바코드) ├── TITLECODE (대표 바코드)
├── BaseCode (❌ 비어있음) ├── BASECODE ✅ (100% 있음!)
├── SUNG_CODE ├── SUNG_CODE (성분코드)
└── Saleprice ├── DIK_CODE (의약품통합코드)
├── ETCNAME (제품명)
└── SPLNAME (제조사)
```
**핵심 포인트:**
- `CD_GOODS.BaseCode`는 비어있음 (사용 안 함)
- `CD_BARCODE.BASECODE`에 표준코드 100% 있음!
- 1개 제품(DrugCode)에 여러 바코드 가능 (낱개, 박스 등)
### CD_BARCODE 매핑 키 통계
| 컬럼 | 보유율 | 설명 | 외부 매핑 |
|------|--------|------|-----------|
| `BARCODE` | 100% | 개별 바코드 | ⭐ PostgreSQL 매핑 가능 |
| `BASECODE` | 100% | 표준코드 (식약처) | 인체용만 |
| `TITLECODE` | 100% | 대표 바코드 | |
| `DIK_CODE` | 68.2% | 의약품통합코드 | |
| `SUNG_CODE` | 44.5% | 성분코드 | |
### 동물약 특이사항
```
동물약 38개
├── CD_GOODS에 있음 ✅ (POS_BOON = '010103')
├── CD_BARCODE에 없음 ❌ (인체용 아님)
├── DrugCode가 "LB"로 시작 (로컬/자체 등록)
└── BASECODE 매핑 불가 → PostgreSQL(애니팜) 필요
```
---
## 🔗 PostgreSQL(애니팜) 매핑 전략
### 확인 필요 사항
PostgreSQL `apc` 테이블에서 확인할 컬럼:
```sql
-- 바코드 관련 컬럼 확인
SELECT column_name FROM information_schema.columns
WHERE table_name = 'apc'
AND column_name ILIKE '%barcode%';
-- 코드 관련 컬럼 확인
SELECT column_name FROM information_schema.columns
WHERE table_name = 'apc'
AND (column_name ILIKE '%code%' OR column_name ILIKE '%apc%');
```
### 예상 매핑 키
| MSSQL (CD_GOODS/BARCODE) | PostgreSQL (apc) | 매핑 방식 |
|--------------------------|------------------|-----------|
| `BARCODE` | `barcode`? | 직접 매핑 |
| `GoodsName` | `product_name` | 유사도 매칭 |
| `제조사` | `company_name` | 보조 키 |
---
*작성일: 2025-06-30*
*업데이트: 2025-06-30 - CD_BARCODE 구조 분석 추가*
*프로젝트: pharmacy-pos-qr-system*