약국 POS 시스템의 GraphRAG 기반 추천 시스템 구축 관련 문서:
## 핵심 설계 문서
1. 질병코드기반 제품추천.md
- ICD-10 질병 코드 활용 추천 시스템 설계
- 계층 구조 (질병 → 질병군 → 제품군 → 개별 제품)
- 처방전 기반 추천 알고리즘
2. complex-product-graph-modeling.md
- 복합제(비맥스제트 등) 그래프 모델링
- 성분 간 시너지 효과 표현
- 복합 증상 매칭 쿼리 예시
3. pubmed-graphrag-workflow.md
- PubMed → GraphRAG 전체 워크플로우 (5단계)
- 논문 검색, 근거 추출, 지식 그래프 구축
- MCP Server 개발 가이드
## 그래프 DB 비교 및 평가
4. sqlite-graph-evaluation.md
- SQLite vs SQLite-Graph vs Neo4j 비교
- 현 시점(2026-01) 평가: 기존 SQL 유지 권장
- 6개월 후 재평가 계획
5. opensource-graph-db-comparison.md
- 오픈소스 그래프 DB 비교 (Neo4j, ArangoDB 등)
6. 온톨로지로전환.md
- 관계형 DB → 온톨로지 구조 전환 가이드
- PubMed RAG 활용 방안
- 추론 규칙 설계
## PubMed GraphRAG 활용
7. pycnogenol-multi-indication-graphrag.md
- 피크노제놀 다중 적응증 GraphRAG 구축 사례
- 7가지 적응증별 근거 수준
8. grpahrag_아쉬아간다.md
- Ashwagandha GraphRAG 구축 사례
9. pubdmed이용ai.md
- PubMed + AI 통합 활용 가이드
## 추가 워크플로우
10. pubmed-graphrag-workflow_next.md
- 다음 단계 워크플로우
11. PostgresGRAPH전환.md
- PostgreSQL + Apache AGE 전환 가이드
모든 문서는 한국어로 작성되었으며, 코드 예시는 영어로 포함.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
561 lines
15 KiB
Markdown
561 lines
15 KiB
Markdown
# 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/<barcode>/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 키 발급
|
|
- 카테고리 계층 최종 승인
|