# 동물약 투약지도서 API - AI 매핑 아키텍처 > 작성일: 2026-03-19 > 작성자: 용림 🐉 --- ## 1. 문제 정의 ### 현재 상황 ``` 약국 POS (PIT3000) └─ DrugCode: LB000003158 └─ GoodsName: "안텔민뽀삐(5kg이하)" └─ BARCODE: 9990000001134 (자체 바코드) ❌ APC 아님 └─ CD_ITEM_UNIT_MEMBER └─ 0230237010107 (APC) ✅ 있으면 좋음 └─ 9990000001134 (자체) ← 이것만 있는 경우 多 ``` ### 핵심 문제 | 상황 | 비율 | 처리 방법 | |------|------|----------| | APC 매핑 완료 | ~18% (17건) | APC로 바로 조회 ✅ | | APC 미매핑 | ~82% (32건+) | **제품명 기반 AI 매핑 필요** ⚠️ | ### 왜 어려운가? 1. **약국마다 바코드 체계 다름** - A약국: "안텔민사향" → 바코드 "A001" - B약국: "안텔민사향" → 바코드 "B999" - C약국: "안텔민사향" → 바코드 없음 (수기 입력) 2. **제품명 표기 불일치** - POS: "안텔민뽀삐(5kg이하)" - APDB: "뉴펫 안텔민 정사 정 100mg/25mg/10정" - 쇼핑몰: "안텔민 뽀삐 5kg이하" 3. **사이즈/용량 구분** - 하트세이버 mini/S/M/L → 각각 다른 APC - 넥스가드 XS/S/M/L → 각각 다른 APC --- ## 2. 제안 아키텍처 ### 2.1 전체 흐름 ``` ┌─────────────────────────────────────────────────────────────┐ │ API 요청 │ │ { │ │ "items": [ │ │ { "apc": "0230237010107", "name": "안텔민뽀삐" }, │ ← APC 있음 │ { "apc": null, "name": "하트세이버S(5.6~11kg)" } │ ← APC 없음 │ ], │ │ "patient_name": "김남곤", │ │ "pet_name": "뽀삐" │ │ } │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 매핑 분기 처리 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ for each item: │ │ if item.apc exists: │ │ ──────────────────▶ [직접 조회] │ │ else: │ │ ──────────────────▶ [AI 매핑 레이어] │ │ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ PDF 렌더링 │ │ │ │ 매핑된 APC들로 APDB 조회 → 템플릿 렌더링 → PDF 반환 │ └─────────────────────────────────────────────────────────────┘ ``` ### 2.2 AI 매핑 레이어 상세 ``` ┌─────────────────────────────────────────────────────────────┐ │ AI 매핑 레이어 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 입력: "하트세이버S(5.6~11kg)" │ │ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ Step 1: 정규화 (Normalize) │ │ │ │ - 공백/특수문자 정리 │ │ │ │ - 체중 정보 추출: 5.6~11kg → S사이즈 │ │ │ │ - 브랜드 추출: "하트세이버" │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ Step 2: 후보 검색 (Candidate Search) │ │ │ │ - PostgreSQL 퍼지 매칭 │ │ │ │ - 벡터 유사도 검색 (선택적) │ │ │ │ │ │ │ │ 결과: [ │ │ │ │ { apc: "0230474210202", name: "하트세이버S", score: 0.95 }, │ │ │ { apc: "0230474220200", name: "하트세이버M", score: 0.72 }, │ │ │ { apc: "0230470000008", name: "하트세이버mini", score: 0.68 }│ │ │ ] │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ Step 3: AI 최종 선택 (LLM Decision) │ │ │ │ │ │ │ │ 프롬프트: │ │ │ │ "POS 제품명: 하트세이버S(5.6~11kg) │ │ │ │ 후보 APC: │ │ │ │ 1. 0230474210202 - 하트세이버S (5.6~11kg) │ │ │ │ 2. 0230474220200 - 하트세이버M (12~22kg) │ │ │ │ 3. 0230470000008 - 하트세이버mini (5.6kg이하) │ │ │ │ │ │ │ │ 가장 적합한 APC는?" │ │ │ │ │ │ │ │ AI 응답: "0230474210202" (체중 범위 일치) │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ Step 4: 캐싱 (선택적) │ │ │ │ - 동일 제품명 재요청 시 캐시 사용 │ │ │ │ - Redis 또는 SQLite │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 3. 구현 옵션 비교 ### 옵션 A: 규칙 기반 매핑 (No AI) ```python def match_by_rules(product_name): # 정규식 + 키워드 매칭 if "안텔민" in product_name: if "뽀삐" in product_name or "5kg이하" in product_name: return "0230237010107" elif "킹" in product_name or "5kg이상" in product_name: return "0230237810109" # ... 수십 개 규칙 ``` | 장점 | 단점 | |------|------| | 빠름, 비용 0 | 유지보수 지옥 | | 예측 가능 | 새 제품 대응 불가 | | | 표기 변형에 취약 | **적합**: 제품 수 적고, 변동 없을 때 --- ### 옵션 B: 퍼지 매칭 + 점수 기반 ```python from rapidfuzz import fuzz def match_by_fuzzy(product_name, candidates): scores = [] for c in candidates: score = fuzz.token_sort_ratio(product_name, c['name']) scores.append((c['apc'], score)) best = max(scores, key=lambda x: x[1]) if best[1] > 80: # 임계값 return best[0] return None # 매칭 실패 ``` | 장점 | 단점 | |------|------| | AI 비용 없음 | 체중/사이즈 구분 어려움 | | 빠름 | 애매한 경우 오매핑 | **적합**: 1:1 매핑이 명확한 경우 --- ### 옵션 C: AI 하이브리드 (권장) ⭐ ```python def match_hybrid(product_name): # 1. 캐시 확인 cached = cache.get(product_name) if cached: return cached # 2. 퍼지 매칭으로 후보 추림 candidates = fuzzy_search(product_name, limit=5) # 3. 고신뢰 매칭이면 바로 반환 if candidates[0]['score'] > 95: return candidates[0]['apc'] # 4. 애매하면 AI 판단 if candidates[0]['score'] > 70: apc = llm_decide(product_name, candidates) cache.set(product_name, apc) return apc # 5. 전혀 못 찾으면 실패 return None ``` | 장점 | 단점 | |------|------| | 정확도 높음 | AI 비용 (캐싱으로 최소화) | | 새 제품 대응 가능 | 초기 구축 복잡 | | 체중/사이즈 정확 구분 | | **적합**: 현재 상황 (다양한 표기, 사이즈 구분 필요) --- ## 4. 비용 분석 ### AI 호출 비용 (GPT-4o-mini 기준) | 시나리오 | 월 요청 | AI 호출률 | AI 호출 수 | 비용 | |----------|---------|----------|-----------|------| | 소규모 | 100건 | 30% | 30건 | ~$0.01 | | 중규모 | 1,000건 | 20% | 200건 | ~$0.10 | | 대규모 | 10,000건 | 10% | 1,000건 | ~$0.50 | **캐싱 효과**: 동일 제품명 재요청 시 AI 호출 안 함 → 호출률 급감 --- ## 5. 내 생각 (용림) ### 현실적인 접근 1. **Phase 1: 규칙 기반 시작** - 현재 17건의 APC 매핑된 제품은 직접 조회 - 자주 쓰는 10~20개 제품은 수동 규칙 추가 - 나머지는 "매핑 실패" 로깅 2. **Phase 2: 퍼지 매칭 도입** - PostgreSQL `pg_trgm` 확장으로 유사도 검색 - 95% 이상 매칭은 자동 처리 - 70~95%는 로깅 + 수동 검토 3. **Phase 3: AI 레이어 추가** - 70~95% 구간에 LLM 판단 도입 - 판단 결과 캐싱 - 오매핑 피드백 루프 ### 왜 AI가 필요한가? ``` POS: "하트세이버츄어블S(5.6~11kg)" APDB: "뉴펫 하트세이버 츄어블 소형견용 5.6-11kg" → 퍼지 매칭만으로는 "소형견용 = S" 판단 어려움 → 체중 범위 파싱 + 의미 이해 필요 → LLM이 "5.6~11kg"와 "5.6-11kg" 같다고 판단 가능 ``` ### 우선순위 제안 | 순위 | 항목 | 이유 | |------|------|------| | 1 | APC 직접 매핑 확대 | 가장 정확, 비용 0 | | 2 | 캐시 레이어 | AI 비용 절감 | | 3 | 퍼지 매칭 | AI 호출 최소화 | | 4 | AI 최종 판단 | 애매한 케이스만 | --- ## 6. API 인터페이스 (안) ### 요청 ```json POST /api/guide/pdf { "items": [ { "apc": "0230237010107", "name": "안텔민뽀삐", "drugcode": "LB000003158" }, { "apc": null, "name": "하트세이버S(5.6~11kg)", "drugcode": "LB000003153" } ], "patient_name": "김남곤", "pet_name": "뽀삐", "pet_species": "푸들", "pet_age": "3세" } ``` ### 응답 ```json { "success": true, "pdf_url": "/output/guide_20260319_abc123.pdf", "mapping_results": [ { "name": "안텔민뽀삐", "apc": "0230237010107", "method": "direct", "confidence": 1.0 }, { "name": "하트세이버S(5.6~11kg)", "apc": "0230474210202", "method": "ai_matched", "confidence": 0.92, "candidates_considered": 3 } ] } ``` --- ## 7. 다음 단계 - [ ] APDB 퍼지 검색 인덱스 구축 (`pg_trgm`) - [ ] AI 매핑 프롬프트 설계 - [ ] 캐시 레이어 (Redis/SQLite) - [ ] 매핑 실패 로깅 & 대시보드 - [ ] 피드백 루프 (오매핑 수정) --- *이 문서는 논의용입니다. 피드백 주세요!* 🐉