# PostgreSQL Apache AGE 기반 약국 GraphRAG 추천 시스템 기획서 **작성일**: 2026-01-24 **프로젝트**: 약국 POS QR 마일리지 시스템 → 근거 기반 AI 추천 시스템 전환 --- ## 1. 프로젝트 개요 ### 목표 기존 SQLite 기반 약국 시스템을 **PostgreSQL + Apache AGE**로 확장하여, **PubMed 논문 근거 기반 GraphRAG 제품 추천 시스템** 구축 ### 핵심 가치 - ✅ **근거 기반 추천**: "왜 이 제품을 추천하는가?" → 논문(PMID) 근거 제시 - ✅ **복합 증상 매칭**: "피로 + 소화불량 + 스트레스" → 최적 복합제 추천 - ✅ **추론 경로 시각화**: Statin → 근육약화 → CoQ10 추천 경로 - ✅ **AI 카테고리 관리**: 무분별한 증가 방지, 23개 기본 카테고리 유지 --- ## 2. 현황 분석 ### 현재 시스템 - **MSSQL (PIT3000)**: 판매 거래 데이터 - **SQLite (mileage.db)**: 제품 마스터, 카테고리, 질병 코드 - **PubMed 파이프라인**: 완성 (여러 연구 스크립트로 근거 수집 중) ### 주요 데이터 - 제품 마스터: 바코드 기준 (동일 제품 다른 이름 가능) - 카테고리: 23개 기본 (진통제, 소화제, 복합비타민 등) - 질병 코드: ICD-10 기반 - 제품-카테고리 매핑: 다대다 (relevance_score 0.0~1.0) ### 현재 한계 - ❌ 복잡한 관계 표현 불가 (약물 → 부작용 → 예방 관계) - ❌ 추론 경로 제시 불가 - ❌ PubMed 근거와 제품 연결 미흡 - ❌ 성분 간 시너지 표현 불가 --- ## 3. 기술 아키텍처 ### 3.1 시스템 구조 ``` ┌─────────────────────────────────────┐ │ 웹앱 (Flask) + 관리자 대시보드 │ └───────────────┬─────────────────────┘ ↓ REST API ┌───────────────────────────────────────┐ │ Flask Backend (Python) │ │ - GraphRAG Service (Cypher 쿼리) │ │ - OpenAI (카테고리 태깅) │ │ - PubMed (Biopython) │ └───────────────┬───────────────────────┘ ↓ ┌───────────────────────────────────────┐ │ PostgreSQL 15 + Apache AGE │ ├───────────────────────────────────────┤ │ SQL 테이블: │ │ - products (제품 마스터) │ │ - categories (카테고리) │ │ - evidence (PubMed 논문) │ ├───────────────────────────────────────┤ │ 그래프 (Cypher): │ │ 노드: Product, Ingredient, │ │ Symptom, Disease, Evidence │ │ 엣지: CONTAINS, TREATS, CAUSES, │ │ PREVENTS, SYNERGY_WITH │ └───────────────────────────────────────┘ ``` ### 3.2 기술 스택 | 레이어 | 기술 | |--------|------| | **DB** | PostgreSQL 15 + Apache AGE 확장 | | **Backend** | Python 3.11 + Flask | | **그래프 쿼리** | Cypher (Apache AGE) | | **AI** | OpenAI GPT-4o (카테고리 태깅, 추천 설명) | | **PubMed** | Biopython (논문 검색 및 근거 수집) | | **캐싱** | Redis (추천 결과 캐싱) | | **라이브러리** | psycopg2, apache-age-python | --- ## 4. 데이터 모델 설계 ### 4.1 그래프 노드 타입 #### Product (제품) ```cypher (:Product { barcode: '8806436016712', name: '탁센캡슐', manufacturer: '동아제약', price: 8000 }) ``` #### Ingredient (성분) ```cypher (:Ingredient { id: 'naproxen', name: '나프록센', category: 'NSAID' }) ``` #### Symptom (증상) ```cypher (:Symptom { id: 'headache', name: '두통', severity: 'moderate' }) ``` #### Disease (질병) ```cypher (:Disease { icd_code: 'K30', name: '소화불량', category: '소화기질환' }) ``` #### Evidence (PubMed 논문) ```cypher (:Evidence { pmid: '27959716', title: 'CV Safety of Naproxen', journal: 'NEJM', year: 2016, reliability: 0.99 }) ``` ### 4.2 그래프 관계 타입 | 관계 | From → To | 속성 | 의미 | |------|-----------|------|------| | **CONTAINS** | Product → Ingredient | amount, role | 제품이 성분 포함 | | **TREATS** | Ingredient → Symptom/Disease | efficacy, evidence_pmid | 성분이 증상 치료 | | **CAUSES** | Ingredient → Symptom | probability, severity | 성분이 부작용 유발 | | **PREVENTS** | Ingredient → Symptom | efficacy, dosage | 성분이 부작용 예방 | | **SYNERGY_WITH** | Ingredient ↔ Ingredient | synergy_score, reason | 성분 간 시너지 | | **SAFER_THAN** | Product → Product | aspect, confidence | 제품 안전성 비교 | | **BELONGS_TO** | Product → Category | relevance_score | 제품-카테고리 매핑 | ### 4.3 PostgreSQL 테이블 ```sql -- 제품 마스터 CREATE TABLE products ( barcode TEXT PRIMARY KEY, product_name TEXT NOT NULL, manufacturer TEXT, price INTEGER, stock INTEGER, ingredients_json JSONB, search_vector tsvector ); -- 카테고리 (계층 구조) CREATE TABLE categories ( category_id SERIAL PRIMARY KEY, category_name TEXT UNIQUE NOT NULL, parent_id INTEGER REFERENCES categories(category_id), level INTEGER DEFAULT 1, -- 1: 기본 23개, 2-3: 서브 description TEXT ); -- PubMed 논문 CREATE TABLE evidence ( pmid TEXT PRIMARY KEY, title TEXT NOT NULL, journal TEXT, year INTEGER, study_type TEXT, abstract TEXT, reliability REAL ); ``` --- ## 5. 핵심 기능 ### 5.1 근거 기반 제품 추천 **입력**: - 증상: ["두통", "피로"] - 환자 프로필: 65세, 고혈압, Statin 복용 중 - 예산: 20,000원 **Cypher 쿼리**: ```cypher MATCH (p:Product)-[:CONTAINS]->(i:Ingredient)-[t:TREATS]->(s:Symptom) WHERE s.name IN ['두통', '피로'] AND p.price <= 20000 OPTIONAL MATCH (e:Evidence {pmid: t.evidence_pmid}) WHERE e.reliability > 0.8 RETURN p.name, p.price, COLLECT(s.name) AS symptoms, e.pmid, e.title, e.reliability ORDER BY e.reliability DESC, COUNT(DISTINCT s) DESC LIMIT 5 ``` **출력**: ```json { "recommendations": [ { "name": "비맥스제트", "price": 15000, "symptoms_covered": ["피로", "소화불량"], "evidence": { "pmid": "12345678", "title": "Vitamin B Complex for Fatigue", "reliability": 0.85 }, "reasoning": "비타민 B복합체가 피로 회복에 효과적입니다. (PMID:12345678, 신뢰도: 85%)" } ] } ``` ### 5.2 복합 증상 매칭 + 시너지 **Cypher 쿼리**: ```cypher -- 피로 + 근육통 + 스트레스를 동시에 치료하면서 시너지 있는 제품 MATCH (p:Product)-[:CONTAINS]->(i1:Ingredient)-[:TREATS]->(s:Symptom) WHERE s.name IN ['피로', '근육통', '스트레스'] WITH p, COUNT(DISTINCT s) AS coverage WHERE coverage >= 2 MATCH (p)-[:CONTAINS]->(i1:Ingredient)-[:TREATS]->(:Symptom {name: '피로'}) MATCH (p)-[:CONTAINS]->(i2:Ingredient)-[:TREATS]->(:Symptom {name: '근육통'}) OPTIONAL MATCH (i1)-[syn:SYNERGY_WITH]->(i2) RETURN p.name, coverage, i1.name + ' + ' + i2.name AS combo, syn.synergy_score AS synergy, p.price ORDER BY coverage DESC, synergy DESC ``` **예시 결과**: 비맥스제트 (비타민B1 + 마그네슘, 시너지 0.9) ### 5.3 추론 경로 시각화 **쿼리**: Statin → 근육약화 → CoQ10 추천 경로 ```cypher MATCH path = (drug:Ingredient {id: 'statin'}) -[:CAUSES]->(side:Symptom {name: 'muscle_weakness'}) <-[:PREVENTS]-(supplement:Ingredient {id: 'coq10'}) RETURN [node IN nodes(path) | node.name] AS reasoning_path, [rel IN relationships(path) | type(rel)] AS relationships ``` **결과**: ```json { "reasoning_path": ["Statin", "근육약화", "CoQ10"], "relationships": ["CAUSES", "PREVENTS"], "explanation": "Statin은 근육약화를 유발할 수 있으며, CoQ10이 이를 예방합니다." } ``` ### 5.4 AI 카테고리 자동 태깅 **프로세스**: 1. 제품명 + 성분 정보 → OpenAI GPT-4o 2. 기존 23개 카테고리에서 1~3개 선택 3. relevance_score (0.0~1.0) 자동 계산 4. 그래프에 BELONGS_TO 관계 생성 **예시**: ```python # 입력 product = { "name": "탁센캡슐", "ingredients": ["나프록센 250mg"] } # AI 응답 { "categories": [ {"name": "진통소염제", "score": 1.0, "reason": "주성분이 NSAID"}, {"name": "진통제", "score": 0.9, "reason": "진통 효과"} ] } # 그래프 저장 CREATE (p:Product {barcode: '8806436016712'}) -[:BELONGS_TO {relevance_score: 1.0, tagged_by: 'AI'}] ->(c:Category {name: '진통소염제'}) ``` --- ## 6. 카테고리 관리 전략 ### 6.1 3단계 계층 구조 ``` Level 1 (고정 23개 - 기본 카테고리) ├─ 진통소염제 │ ├─ Level 2 (서브 카테고리) │ │ ├─ NSAID │ │ └─ 아세트아미노펜 │ └─ Level 3 (세분화) │ └─ 심혈관 안전 NSAID ├─ 소화제 │ ├─ 위장약 │ └─ 정장제 └─ 복합비타민 ├─ B복합체 └─ 종합비타민 ``` ### 6.2 신규 카테고리 추가 규칙 **원칙**: - Level 1 (23개) = **절대 불변** - Level 2-3 = 약사/AI 제안 → 검토 후 승인 - 월 최대 2개 신규 허용 - 최소 10개 제품 예상 시에만 추가 **워크플로우**: 1. 약사/AI가 신규 카테고리 제안 2. AI 검증 (필요성, 중복 여부) 3. 영향도 분석 (기존 제품 재분류 필요성) 4. 승인 시 → 기존 전체 제품 재검토 (AI 일괄 태깅) --- ## 7. 마이그레이션 계획 ### Phase 1: 환경 설정 (1주) ```bash # PostgreSQL + AGE 설치 sudo apt install postgresql-15 postgresql-15-age # 그래프 생성 CREATE EXTENSION age; SELECT create_graph('pharmacy_graph'); # Python 라이브러리 pip install psycopg2-binary apache-age-python biopython ``` ### Phase 2: 데이터 마이그레이션 (2주) **Step 1**: SQLite → PostgreSQL (SQL 테이블) - products, categories, evidence **Step 2**: 그래프 노드 생성 - Product, Ingredient, Symptom, Disease 노드 **Step 3**: 관계 생성 - CONTAINS, TREATS, BELONGS_TO 엣지 **Step 4**: PubMed 근거 통합 - 주요 약물-증상 쌍 100개 논문 수집 - Evidence 노드 + TREATS 관계에 pmid 연결 ### Phase 3: 검증 및 테스트 (1주) - 노드/엣지 개수 검증 - Cypher 쿼리 성능 테스트 - SQLite vs PostgreSQL+AGE 병렬 운영 ### Phase 4: API 개발 (2주) - `/api/recommend` - GraphRAG 추천 - `/api/graph/path` - 추론 경로 - `/api/categories/tag` - AI 자동 태깅 --- ## 8. API 설계 ### 8.1 POST `/api/recommend` **Request**: ```json { "symptoms": ["두통", "피로"], "patient_profile": { "age": 65, "conditions": ["HTN"], "medications": ["Atorvastatin 10mg"] }, "budget": 20000 } ``` **Response**: ```json { "recommendations": [ { "barcode": "8806436016712", "name": "탁센캡슐", "price": 8000, "score": 0.95, "reasoning": { "path": ["Naproxen", "TREATS", "두통"], "evidence": { "pmid": "27959716", "title": "CV Safety of Naproxen", "reliability": 0.99 } } } ] } ``` ### 8.2 GET `/api/products//graph` 제품의 전체 관계 그래프 반환 (성분, 증상, 근거 논문) ### 8.3 POST `/api/categories/tag-batch` AI 일괄 카테고리 태깅 --- ## 9. 성능 최적화 ### 인덱스 ```sql CREATE INDEX idx_products_barcode ON products(barcode); CREATE INDEX idx_products_search ON products USING GIN(search_vector); CREATE INDEX idx_evidence_reliability ON evidence(reliability DESC); ``` ### 캐싱 (Redis) - 추천 결과: TTL 1시간 - 그래프 경로: TTL 24시간 - 카테고리 목록: TTL 7일 ### Cypher 최적화 - 증상부터 역방향 탐색 (인덱스 활용) - OPTIONAL MATCH 최소화 - WITH 절로 중간 필터링 --- ## 10. 구현 우선순위 ### 우선순위 1 (필수, 2주) - ✅ PostgreSQL + AGE 설치 - ✅ 기본 스키마 생성 - ✅ SQLite 데이터 마이그레이션 - ✅ Product, Ingredient 노드 생성 - ✅ CONTAINS, TREATS 관계 ### 우선순위 2 (핵심, 2주) - ✅ `/api/recommend` API - ✅ PubMed 근거 통합 (100개 논문) - ✅ 복합 증상 매칭 Cypher 쿼리 - ✅ 추론 경로 생성 ### 우선순위 3 (AI, 1주) - ✅ AI 카테고리 자동 태깅 - ✅ 신규 카테고리 제안 워크플로우 - ✅ 카테고리 계층 관리 ### 우선순위 4 (최적화, 1주) - ✅ 인덱스 최적화 - ✅ Redis 캐싱 - ✅ 성능 벤치마크 --- ## 11. 핵심 파일 구조 ``` backend/ ├─ migration/ │ ├─ migrate_to_postgres.py # SQLite → PostgreSQL │ ├─ build_graph.py # 그래프 노드 생성 │ └─ integrate_pubmed.py # PubMed 근거 통합 ├─ services/ │ ├─ graphrag_service.py # GraphRAG 추천 로직 │ ├─ ai_tagging.py # AI 카테고리 태깅 │ └─ category_service.py # 카테고리 관리 ├─ api/ │ ├─ recommend.py # 추천 API │ └─ categories.py # 카테고리 API ├─ db/ │ ├─ age_connector.py # Apache AGE 연결 │ └─ schema.sql # PostgreSQL 스키마 └─ pubmed_search.py # PubMed 검색 (재사용) ``` --- ## 12. 예상 효과 ### 비즈니스 가치 - **업셀링 증가**: 복합제 추천으로 객단가 +50% - **고객 신뢰**: 논문 근거 제시로 재방문율 +30% - **전문성 강화**: 약국 브랜드 이미지 향상 ### 기술적 이점 - **확장성**: 수만 개 제품 처리 가능 - **유연성**: 새 관계 타입 쉽게 추가 - **추론 가능**: 다단계 복합 쿼리 ### 차별화 - 타 약국 대비 **과학적 근거 기반 추천** - AI + GraphRAG 결합한 **최초 약국 시스템** --- ## 13. 리스크 및 대응 | 리스크 | 대응 방안 | |--------|----------| | PostgreSQL 학습 곡선 | 단계적 마이그레이션, SQLite 병렬 운영 | | Apache AGE 초기 버전 | 핵심 기능만 사용, SQL Fallback | | PubMed API 제한 | API 키 발급, 캐싱, 배치 처리 | | AI 비용 | GPT-4o-mini 사용, 결과 캐싱 | --- ## 14. 다음 단계 ### 즉시 시작 (이번 주) 1. PostgreSQL 15 + AGE 설치 테스트 2. 샘플 그래프 생성 (10개 제품) 3. 기본 Cypher 쿼리 실습 ### 다음 주 1. SQLite → PostgreSQL 마이그레이션 스크립트 2. PubMed 100개 논문 수집 3. `/api/recommend` 프로토타입 ### 다음 달 1. 전체 시스템 통합 2. 웹앱 UI 추가 3. 성능 최적화 --- **작성자**: Claude Sonnet 4.5 **검토 필요 사항**: - PostgreSQL 서버 사양 확인 - PubMed API 키 발급 - 카테고리 계층 최종 승인