diff --git a/CLAUDECODE.md b/CLAUDECODE.md index be92641..4bd9ef6 100644 --- a/CLAUDECODE.md +++ b/CLAUDECODE.md @@ -130,11 +130,19 @@ pharmacy-pos-qr-system/ ```sql - SL_NO_order: 거래 번호 (Foreign Key) - DrugCode: 약품 코드 +- BARCODE: 제품 바코드 (nvarchar(20)) ⭐ 95.79% 보유율 - SL_NM_item: 수량 (decimal) - SL_INPUT_PRICE: 입력 단가 - SL_TOTAL_PRICE: 합계 금액 ``` +**바코드 통계**: + +- 전체 제품 수: 3,120개 +- 바코드 종류: 3,307개 +- 바코드 보유율: **95.79%** (174,327건 / 181,985건) +- 활용: AI 기반 제품 태깅, 온톨로지 구축, 개인화 추천 + #### CD_GOODS (약품 마스터 - PM_DRUG 데이터베이스) ```sql diff --git a/README.md b/README.md index d708a1a..9040052 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,10 @@ npm run dev - **PM_PRES.SALE_MAIN**: 판매 헤더 - **PM_PRES.SALE_SUB**: 판매 상세 + - ⭐ **BARCODE 컬럼**: 제품 바코드 (95.79% 보유율) + - 활용: AI 기반 제품 태깅, 온톨로지 구축, 개인화 추천 - **PM_BASE.CD_PERSON**: 고객 정보 +- **PM_DRUG.CD_GOODS**: 약품 마스터 (제품명) ### SQLite (신규 마일리지) diff --git a/backend/apply_product_schema.py b/backend/apply_product_schema.py new file mode 100644 index 0000000..eb36af7 --- /dev/null +++ b/backend/apply_product_schema.py @@ -0,0 +1,70 @@ +""" +제품 태깅 시스템 스키마 적용 +""" + +import sqlite3 +import os + +def apply_product_schema(): + """product_tagging_schema.sql을 mileage.db에 적용""" + db_path = os.path.join(os.path.dirname(__file__), 'db', 'mileage.db') + schema_path = os.path.join(os.path.dirname(__file__), 'db', 'product_tagging_schema.sql') + + print(f"DB 경로: {db_path}") + print(f"스키마 경로: {schema_path}") + + # 스키마 파일 읽기 + with open(schema_path, 'r', encoding='utf-8') as f: + schema_sql = f.read() + + # DB 연결 및 실행 + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + try: + # 전체 스키마 실행 + cursor.executescript(schema_sql) + conn.commit() + + print("\n[OK] 제품 태깅 시스템 스키마 적용 완료!") + + # 테이블 확인 + cursor.execute(""" + SELECT name FROM sqlite_master + WHERE type='table' AND name LIKE '%product%' OR name LIKE '%disease%' + ORDER BY name + """) + + tables = cursor.fetchall() + print("\n생성된 테이블:") + for table in tables: + print(f" - {table[0]}") + + # 초기 데이터 확인 + cursor.execute("SELECT COUNT(*) FROM product_categories") + cat_count = cursor.fetchone()[0] + print(f"\n제품 카테고리: {cat_count}개") + + cursor.execute("SELECT COUNT(*) FROM disease_codes") + disease_count = cursor.fetchone()[0] + print(f"질병 코드: {disease_count}개") + + cursor.execute("SELECT COUNT(*) FROM disease_product_mapping") + mapping_count = cursor.fetchone()[0] + print(f"질병-제품 매핑: {mapping_count}개") + + # 카테고리 목록 출력 + print("\n제품 카테고리 목록:") + cursor.execute("SELECT category_name, description FROM product_categories ORDER BY category_id") + categories = cursor.fetchall() + for cat, desc in categories: + print(f" - {cat:15} : {desc}") + + except Exception as e: + print(f"\n[ERROR] 오류 발생: {e}") + conn.rollback() + finally: + conn.close() + +if __name__ == '__main__': + apply_product_schema() diff --git a/backend/check_barcodes.py b/backend/check_barcodes.py new file mode 100644 index 0000000..e7af70c --- /dev/null +++ b/backend/check_barcodes.py @@ -0,0 +1,70 @@ +""" +바코드가 있는 제품 샘플 조회 +""" + +import sys +import os +sys.path.insert(0, os.path.dirname(__file__)) + +from db.dbsetup import DatabaseManager +from sqlalchemy import text + +def check_barcode_samples(): + """바코드가 있는 제품 샘플 조회""" + db_manager = DatabaseManager() + + try: + session = db_manager.get_session('PM_PRES') + + # 바코드가 있는 제품 샘플 조회 + query = text(""" + SELECT TOP 10 + S.DrugCode, + S.BARCODE, + G.GoodsName, + S.SL_NM_cost_a as price + FROM SALE_SUB S + LEFT JOIN PM_DRUG.dbo.CD_GOODS G ON S.DrugCode = G.DrugCode + WHERE S.BARCODE IS NOT NULL AND S.BARCODE != '' + ORDER BY S.SL_NO_order DESC + """) + + results = session.execute(query).fetchall() + + print('=' * 100) + print('바코드가 있는 제품 샘플 (최근 10개)') + print('=' * 100) + for r in results: + barcode = r.BARCODE if r.BARCODE else '(없음)' + goods_name = r.GoodsName if r.GoodsName else '(약품명 없음)' + print(f'DrugCode: {r.DrugCode:20} | BARCODE: {barcode:20} | 제품명: {goods_name}') + print('=' * 100) + + # 바코드 통계 + stats_query = text(""" + SELECT + COUNT(DISTINCT DrugCode) as total_drugs, + COUNT(DISTINCT BARCODE) as total_barcodes, + SUM(CASE WHEN BARCODE IS NOT NULL AND BARCODE != '' THEN 1 ELSE 0 END) as with_barcode, + COUNT(*) as total_sales + FROM SALE_SUB + """) + + stats = session.execute(stats_query).fetchone() + + print('\n바코드 통계') + print('=' * 100) + print(f'전체 제품 수 (DrugCode): {stats.total_drugs:,}') + print(f'바코드 종류 수: {stats.total_barcodes:,}') + print(f'바코드가 있는 판매 건수: {stats.with_barcode:,}') + print(f'전체 판매 건수: {stats.total_sales:,}') + print(f'바코드 보유율: {stats.with_barcode / stats.total_sales * 100:.2f}%') + print('=' * 100) + + except Exception as e: + print(f"오류 발생: {e}") + finally: + db_manager.close_all() + +if __name__ == '__main__': + check_barcode_samples() diff --git a/backend/db/product_tagging_schema.sql b/backend/db/product_tagging_schema.sql new file mode 100644 index 0000000..62af423 --- /dev/null +++ b/backend/db/product_tagging_schema.sql @@ -0,0 +1,269 @@ +-- ============================================================================ +-- 제품 태깅 시스템 스키마 (Product Tagging System) +-- 바코드 기반 AI 자동 태깅, 온톨로지 구축, 질병 코드 매핑 +-- ============================================================================ + +-- ============================================================================ +-- 1. 제품 마스터 (Product Master) +-- ============================================================================ +CREATE TABLE IF NOT EXISTS product_master ( + barcode TEXT PRIMARY KEY, -- 제품 바코드 (8806436016712) + product_name TEXT NOT NULL, -- 대표 제품명 (탁센캡슐) + manufacturer TEXT, -- 제조사 (동아제약) + + -- 분류 + drug_classification TEXT, -- 일반의약품, 전문의약품, 건강기능식품 + + -- 성분 정보 (JSON) + ingredients_json TEXT, -- [{"name": "나프록센", "amount": "250mg", "role": "주성분"}] + + -- AI 태그 (JSON 배열) + tags_symptoms TEXT, -- ["생리통", "치통", "골관절염", "두통", "근육통"] + tags_ingredients TEXT, -- ["나프록센 250mg", "비스테로이드성 소염진통제"] + tags_effects TEXT, -- ["진통", "소염", "해열"] + + -- 메타데이터 + source_url TEXT, -- 크롤링한 URL (약학정보원, 식약처) + last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + is_verified BOOLEAN DEFAULT 0, -- 약사 검증 여부 + notes TEXT -- 약사 메모 +); + +-- 인덱스 +CREATE INDEX IF NOT EXISTS idx_product_name ON product_master(product_name); +CREATE INDEX IF NOT EXISTS idx_manufacturer ON product_master(manufacturer); +CREATE INDEX IF NOT EXISTS idx_drug_classification ON product_master(drug_classification); +CREATE INDEX IF NOT EXISTS idx_last_updated ON product_master(last_updated); + + +-- ============================================================================ +-- 2. 제품 카테고리 마스터 (Product Categories) +-- ============================================================================ +CREATE TABLE IF NOT EXISTS product_categories ( + category_id INTEGER PRIMARY KEY AUTOINCREMENT, + category_name TEXT UNIQUE NOT NULL, -- 진통제, 소화제, 비타민 등 + parent_category TEXT, -- 상위 카테고리 (옵션) + description TEXT, -- 카테고리 설명 + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 인덱스 +CREATE INDEX IF NOT EXISTS idx_category_name ON product_categories(category_name); +CREATE INDEX IF NOT EXISTS idx_parent_category ON product_categories(parent_category); + + +-- ============================================================================ +-- 3. 제품-카테고리 매핑 (다대다 관계) +-- ============================================================================ +CREATE TABLE IF NOT EXISTS product_category_mapping ( + barcode TEXT NOT NULL, -- 제품 바코드 + category_name TEXT NOT NULL, -- 카테고리명 + relevance_score REAL DEFAULT 1.0, -- 관련도 (0.0 ~ 1.0) + -- 1.0 = 주 카테고리, 0.5 = 부 카테고리 + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + + PRIMARY KEY (barcode, category_name), + FOREIGN KEY (barcode) REFERENCES product_master(barcode) ON DELETE CASCADE, + FOREIGN KEY (category_name) REFERENCES product_categories(category_name) ON DELETE CASCADE +); + +-- 인덱스 +CREATE INDEX IF NOT EXISTS idx_mapping_barcode ON product_category_mapping(barcode); +CREATE INDEX IF NOT EXISTS idx_mapping_category ON product_category_mapping(category_name); +CREATE INDEX IF NOT EXISTS idx_mapping_score ON product_category_mapping(relevance_score); + + +-- ============================================================================ +-- 4. 질병 코드 (ICD-10) +-- ============================================================================ +CREATE TABLE IF NOT EXISTS disease_codes ( + icd_code TEXT PRIMARY KEY, -- K30 (ICD-10 코드) + disease_name TEXT NOT NULL, -- 소화불량 + disease_category TEXT, -- 소화기질환 + description TEXT, -- 기능성 소화불량증 + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 인덱스 +CREATE INDEX IF NOT EXISTS idx_disease_name ON disease_codes(disease_name); +CREATE INDEX IF NOT EXISTS idx_disease_category ON disease_codes(disease_category); + + +-- ============================================================================ +-- 5. 질병군 마스터 +-- ============================================================================ +CREATE TABLE IF NOT EXISTS disease_categories ( + category_id INTEGER PRIMARY KEY AUTOINCREMENT, + category_name TEXT UNIQUE NOT NULL, -- 소화기질환, 근골격계질환 등 + parent_category TEXT, -- 상위 질병군 (옵션) + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 인덱스 +CREATE INDEX IF NOT EXISTS idx_dis_category_name ON disease_categories(category_name); + + +-- ============================================================================ +-- 6. 질병군 - 제품군 매핑 +-- ============================================================================ +CREATE TABLE IF NOT EXISTS disease_product_mapping ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + disease_category TEXT NOT NULL, -- 소화기질환 + product_category TEXT NOT NULL, -- 소화제 + relevance_score REAL DEFAULT 1.0, -- 관련도 (0.0 ~ 1.0) + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + + FOREIGN KEY (disease_category) REFERENCES disease_categories(category_name) ON DELETE CASCADE, + FOREIGN KEY (product_category) REFERENCES product_categories(category_name) ON DELETE CASCADE, + UNIQUE(disease_category, product_category) +); + +-- 인덱스 +CREATE INDEX IF NOT EXISTS idx_dis_prod_disease ON disease_product_mapping(disease_category); +CREATE INDEX IF NOT EXISTS idx_dis_prod_product ON disease_product_mapping(product_category); + + +-- ============================================================================ +-- 초기 데이터: 제품 카테고리 +-- ============================================================================ +INSERT OR IGNORE INTO product_categories (category_name, description) VALUES +-- 진통/소염 +('진통제', '통증 완화'), +('소염제', '염증 완화'), +('해열제', '발열 완화'), +('진통소염제', '통증과 염증 동시 완화'), + +-- 소화기 +('소화제', '소화 기능 개선'), +('위장약', '위장 보호 및 치료'), +('제산제', '위산 중화'), +('정장제', '장 기능 개선'), + +-- 호흡기 +('감기약', '감기 증상 완화'), +('기침약', '기침 완화'), +('거담제', '가래 배출'), + +-- 비타민/영양 +('복합비타민', '종합 비타민'), +('간영양제', '간 기능 개선'), +('피로회복제', '피로 회복'), +('칼슘제', '칼슘 보충'), +('철분제', '철분 보충'), + +-- 외용 +('파스', '외용 진통소염'), +('안약', '안과 질환'), +('연고', '피부 외용'), + +-- 기타 +('항히스타민제', '알레르기 완화'), +('수면제', '수면 유도'), +('변비약', '배변 활동 개선'); + + +-- ============================================================================ +-- 초기 데이터: 질병 카테고리 +-- ============================================================================ +INSERT OR IGNORE INTO disease_categories (category_name, description) VALUES +('소화기질환', '소화기계 관련 질환'), +('근골격계질환', '관절, 근육 관련 질환'), +('신경계질환', '신경계 관련 질환'), +('호흡기질환', '호흡기계 관련 질환'), +('순환기질환', '심혈관계 관련 질환'), +('피부질환', '피부 관련 질환'); + + +-- ============================================================================ +-- 초기 데이터: 주요 질병 코드 (ICD-10) +-- ============================================================================ +INSERT OR IGNORE INTO disease_codes (icd_code, disease_name, disease_category, description) VALUES +-- 소화기 +('K30', '소화불량', '소화기질환', '기능성 소화불량증'), +('K29', '위염', '소화기질환', '만성 위염'), +('K21', '역류성식도염', '소화기질환', 'GERD'), +('K59', '변비', '소화기질환', '기능성 변비'), + +-- 근골격계 +('M25', '관절통', '근골격계질환', '관절 통증'), +('M79', '근육통', '근골격계질환', '근육 통증'), +('M54', '요통', '근골격계질환', '허리 통증'), + +-- 신경계 +('R51', '두통', '신경계질환', '긴장성 두통'), +('G43', '편두통', '신경계질환', '편두통'), + +-- 호흡기 +('J00', '급성비인두염', '호흡기질환', '감기'), +('J06', '급성상기도감염', '호흡기질환', '상기도 감염'), + +-- 기타 +('N94', '생리통', '여성질환', '월경통'); + + +-- ============================================================================ +-- 초기 데이터: 질병군 - 제품군 매핑 +-- ============================================================================ +INSERT OR IGNORE INTO disease_product_mapping (disease_category, product_category, relevance_score) VALUES +-- 소화기질환 +('소화기질환', '소화제', 1.0), +('소화기질환', '위장약', 0.9), +('소화기질환', '제산제', 0.8), +('소화기질환', '정장제', 0.7), + +-- 근골격계질환 +('근골격계질환', '진통소염제', 1.0), +('근골격계질환', '파스', 0.8), +('근골격계질환', '진통제', 0.9), + +-- 신경계질환 +('신경계질환', '진통제', 1.0), +('신경계질환', '수면제', 0.5), + +-- 호흡기질환 +('호흡기질환', '감기약', 1.0), +('호흡기질환', '기침약', 0.9), +('호흡기질환', '거담제', 0.8); + + +-- ============================================================================ +-- 뷰: 제품 상세 정보 (카테고리 포함) +-- ============================================================================ +CREATE VIEW IF NOT EXISTS v_product_details AS +SELECT + p.barcode, + p.product_name, + p.manufacturer, + p.drug_classification, + GROUP_CONCAT(m.category_name, ', ') as categories, + AVG(m.relevance_score) as avg_relevance, + p.tags_symptoms, + p.tags_effects, + p.is_verified, + p.last_updated +FROM product_master p +LEFT JOIN product_category_mapping m ON p.barcode = m.barcode +GROUP BY p.barcode; + + +-- ============================================================================ +-- 뷰: 질병별 추천 제품 카테고리 +-- ============================================================================ +CREATE VIEW IF NOT EXISTS v_disease_recommendations AS +SELECT + d.icd_code, + d.disease_name, + d.disease_category, + dp.product_category, + dp.relevance_score +FROM disease_codes d +JOIN disease_product_mapping dp ON d.disease_category = dp.disease_category +ORDER BY d.icd_code, dp.relevance_score DESC; + + +-- ============================================================================ +-- 완료 +-- ============================================================================ +-- 테이블 생성 완료 +-- 초기 카테고리, 질병 코드, 매핑 데이터 삽입 완료 diff --git a/backend/insert_sample_products.py b/backend/insert_sample_products.py new file mode 100644 index 0000000..b1a8501 --- /dev/null +++ b/backend/insert_sample_products.py @@ -0,0 +1,137 @@ +""" +샘플 제품 데이터 추가 +""" + +import sqlite3 +import os +import json + +def insert_sample_products(): + """실제 바코드로 샘플 제품 추가""" + db_path = os.path.join(os.path.dirname(__file__), 'db', 'mileage.db') + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # 샘플 제품 데이터 + products = [ + { + "barcode": "8806436016712", + "product_name": "탁센캡슐", + "manufacturer": "동아제약", + "drug_classification": "일반의약품", + "ingredients": [ + {"name": "나프록센", "amount": "250mg", "role": "주성분"} + ], + "tags_symptoms": ["생리통", "치통", "골관절염", "두통", "근육통"], + "tags_ingredients": ["나프록센 250mg", "비스테로이드성 소염진통제"], + "tags_effects": ["진통", "소염", "해열"], + "categories": [ + {"name": "진통소염제", "score": 1.0}, + {"name": "진통제", "score": 0.9}, + {"name": "해열제", "score": 0.3} + ] + }, + { + "barcode": "8806606002231", + "product_name": "베아제정(10정)", + "manufacturer": "대웅제약", + "drug_classification": "일반의약품", + "ingredients": [ + {"name": "판크레아틴", "amount": "150mg", "role": "주성분"} + ], + "tags_symptoms": ["소화불량", "복부팽만", "가스"], + "tags_ingredients": ["판크레아틴 150mg", "소화효소"], + "tags_effects": ["소화촉진", "가스제거"], + "categories": [ + {"name": "소화제", "score": 1.0}, + {"name": "위장약", "score": 0.8} + ] + }, + { + "barcode": "8806265019618", + "product_name": "마그비맥스", + "manufacturer": "일양약품", + "drug_classification": "일반의약품", + "ingredients": [ + {"name": "메코발라민", "amount": "1mg", "role": "비타민B12"}, + {"name": "UDCA", "amount": "60mg", "role": "간기능개선"}, + {"name": "타우린", "amount": "100mg", "role": "피로회복"} + ], + "tags_symptoms": ["피로", "구내염", "신경통", "근육통"], + "tags_ingredients": ["메코발라민 1mg", "UDCA 60mg", "타우린 100mg"], + "tags_effects": ["피로회복", "간기능개선", "신경계보호"], + "categories": [ + {"name": "복합비타민", "score": 1.0}, + {"name": "간영양제", "score": 0.9}, + {"name": "피로회복제", "score": 1.0} + ] + } + ] + + try: + for product in products: + # 1. product_master 삽입 + cursor.execute(""" + INSERT OR REPLACE INTO product_master + (barcode, product_name, manufacturer, drug_classification, + ingredients_json, tags_symptoms, tags_effects, tags_ingredients, + is_verified) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1) + """, ( + product["barcode"], + product["product_name"], + product["manufacturer"], + product["drug_classification"], + json.dumps(product["ingredients"], ensure_ascii=False), + json.dumps(product["tags_symptoms"], ensure_ascii=False), + json.dumps(product["tags_effects"], ensure_ascii=False), + json.dumps(product["tags_ingredients"], ensure_ascii=False) + )) + + # 2. product_category_mapping 삽입 + for cat in product["categories"]: + cursor.execute(""" + INSERT OR REPLACE INTO product_category_mapping + (barcode, category_name, relevance_score) + VALUES (?, ?, ?) + """, ( + product["barcode"], + cat["name"], + cat["score"] + )) + + conn.commit() + print("[OK] 샘플 제품 3개 추가 완료!") + + # 결과 확인 + cursor.execute("SELECT COUNT(*) FROM product_master") + count = cursor.fetchone()[0] + print(f"\n전체 제품 수: {count}개") + + print("\n추가된 제품:") + cursor.execute(""" + SELECT barcode, product_name, drug_classification + FROM product_master + """) + for row in cursor.fetchall(): + print(f" - {row[0]}: {row[1]} ({row[2]})") + + # 카테고리 매핑 확인 + print("\n제품-카테고리 매핑:") + cursor.execute(""" + SELECT p.product_name, m.category_name, m.relevance_score + FROM product_master p + JOIN product_category_mapping m ON p.barcode = m.barcode + ORDER BY p.product_name, m.relevance_score DESC + """) + for row in cursor.fetchall(): + print(f" {row[0]:20} -> {row[1]:15} (관련도: {row[2]:.1f})") + + except Exception as e: + print(f"[ERROR] {e}") + conn.rollback() + finally: + conn.close() + +if __name__ == '__main__': + insert_sample_products() diff --git a/docs/PRODUCT_TAGGING_SYSTEM.md b/docs/PRODUCT_TAGGING_SYSTEM.md new file mode 100644 index 0000000..1245838 --- /dev/null +++ b/docs/PRODUCT_TAGGING_SYSTEM.md @@ -0,0 +1,396 @@ +# 바코드 기반 제품 태깅 시스템 설계 + +## 목표 + +바코드를 기준으로 약품 정보를 AI로 자동 태깅하여 온톨로지 기반 추천 시스템 구축 + +## 배경 + +- **문제**: 약국마다 동일 제품의 이름이 다르게 저장됨 (예: "탁센", "탁센정", "Taxen") +- **해결**: 바코드는 제품 껍데기에 인쇄되어 전국 동일 → 바코드 기준 마스터 데이터 구축 +- **현황**: SALE_SUB.BARCODE 컬럼 존재, 95.79% 판매 건수에 바코드 있음 + +## 시스템 구조 + +### 1. 데이터 구조 + +#### SQLite - product_master 테이블 + +```sql +CREATE TABLE product_master ( + barcode TEXT PRIMARY KEY, -- 바코드 (8806449141111) + product_name TEXT NOT NULL, -- 대표 제품명 (마그비맥스) + + -- 기본 정보 + category TEXT, -- 분류 (일반의약품, 전문의약품, 건강기능식품) + manufacturer TEXT, -- 제조사 + ingredients_json TEXT, -- 성분 정보 (JSON) + + -- AI 태깅 (JSON) + tags_symptoms TEXT, -- 증상 태그 ["피로회복", "구내염", "육체피로"] + tags_ingredients TEXT, -- 성분 태그 ["메코발라민 1mg", "UDCA 60mg", "타우린 100mg"] + tags_effects TEXT, -- 효능 태그 ["활성비타민 5종", "간 기능 개선"] + + -- 온톨로지 (계층 구조) + ontology_json TEXT, -- 온톨로지 구조 (JSON) + + -- 메타데이터 + source_url TEXT, -- 크롤링한 URL + last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + is_verified BOOLEAN DEFAULT 0 -- 약사 검증 여부 +); + +-- 인덱스 +CREATE INDEX idx_product_name ON product_master(product_name); +CREATE INDEX idx_category ON product_master(category); +``` + +#### JSON 구조 예시 + +**탁센 (Taxen)** +```json +{ + "barcode": "8806436016712", + "product_name": "탁센캡슐", + "category": "일반의약품", + "manufacturer": "동아제약", + + "ingredients": [ + {"name": "나프록센", "amount": "250mg", "role": "주성분"} + ], + + "tags_symptoms": ["생리통", "치통", "골관절염", "두통", "근육통"], + "tags_ingredients": ["나프록센 250mg", "비스테로이드성 소염진통제"], + "tags_effects": ["진통", "소염", "해열"], + + "ontology": { + "class": "진통소염제", + "subclass": "비스테로이드성 소염진통제", + "mechanism": "COX 억제", + "targets": ["관절염", "통증", "염증"], + "relations": { + "similar_to": ["이부프로펜", "디클로페낙"], + "alternative_for": ["생리통약", "두통약"], + "contraindications": ["위장장애", "임신말기", "아스피린 알레르기"] + } + } +} +``` + +**마그비맥스** +```json +{ + "barcode": "8806265019618", + "product_name": "마그비맥스", + "category": "일반의약품", + "manufacturer": "일양약품", + + "ingredients": [ + {"name": "메코발라민", "amount": "1mg", "role": "비타민B12"}, + {"name": "UDCA", "amount": "60mg", "role": "간 기능 개선"}, + {"name": "타우린", "amount": "100mg", "role": "피로회복"}, + {"name": "피리독신", "amount": "50mg", "role": "비타민B6"}, + {"name": "티아민", "amount": "50mg", "role": "비타민B1"} + ], + + "tags_symptoms": ["피로회복", "구내염", "육체피로", "신경통", "근육통"], + "tags_ingredients": [ + "메코발라민 1mg", + "UDCA 60mg", + "타우린 100mg", + "활성비타민 5종" + ], + "tags_effects": ["간 기능 개선", "신경계 보호", "에너지 대사"], + + "ontology": { + "class": "비타민제", + "subclass": "복합비타민", + "mechanism": "비타민B군 보충", + "targets": ["피로", "간기능", "신경계"], + "relations": { + "similar_to": ["박카스", "활명수", "우루사"], + "alternative_for": ["피로회복제", "간 영양제"], + "synergistic_with": ["밀크씨슬", "실리마린"], + "contraindications": [] + } + } +} +``` + +### 2. AI 자동 태깅 시스템 + +#### 2.1 데이터 소스 + +1. **약학정보원 (KPIS)**: https://www.health.kr + - 의약품 검색 → 바코드로 검색 + - 효능, 성분, 용법 크롤링 + +2. **식약처 의약품안전나라**: https://nedrug.mfds.go.kr + - 허가 정보, 성분, 효능효과 + - API 사용 가능 (공공데이터포털) + +3. **건강보험심사평가원**: https://www.hira.or.kr + - 약가 정보, 성분코드 + +4. **제품 인서트 이미지 OCR** + - PIL + Tesseract OCR + - 제품 박스 사진에서 성분 추출 + +#### 2.2 AI 태깅 프로세스 + +```python +def ai_tag_product(barcode): + """ + 바코드로 제품 자동 태깅 + + 1. 웹 크롤링으로 제품 정보 수집 + 2. OpenAI GPT-4o로 정보 분석 및 태깅 + 3. 온톨로지 구조 생성 + 4. SQLite에 저장 + """ + + # Step 1: 웹 크롤링 + product_info = crawl_product_info(barcode) + + # Step 2: OpenAI 프롬프트 + prompt = f""" + 다음 약품 정보를 분석하여 JSON 형식으로 태깅해주세요. + + 제품명: {product_info['name']} + 성분: {product_info['ingredients']} + 효능: {product_info['effects']} + + 다음 형식으로 반환: + {{ + "tags_symptoms": ["증상1", "증상2", ...], + "tags_ingredients": ["성분명 용량", ...], + "tags_effects": ["효능1", "효능2", ...], + "ontology": {{ + "class": "약품 분류", + "subclass": "세부 분류", + "mechanism": "작용 기전", + "targets": ["대상 증상", ...], + "relations": {{ + "similar_to": ["유사 제품", ...], + "alternative_for": ["대체 가능 용도", ...], + "synergistic_with": ["시너지 제품", ...], + "contraindications": ["금기사항", ...] + }} + }} + }} + """ + + # Step 3: OpenAI 호출 + analysis = call_openai(prompt) + + # Step 4: DB 저장 + save_to_product_master(barcode, analysis) +``` + +#### 2.3 크롤링 구현 + +```python +import requests +from bs4 import BeautifulSoup + +def crawl_kpis(barcode): + """약학정보원에서 제품 정보 크롤링""" + url = f"https://www.health.kr/searchDrug/result_drug.asp?barcode={barcode}" + response = requests.get(url) + soup = BeautifulSoup(response.text, 'html.parser') + + return { + "name": soup.select_one('.drug-name').text, + "ingredients": soup.select('.ingredient'), + "effects": soup.select_one('.effect').text, + "manufacturer": soup.select_one('.maker').text + } + +def crawl_nedrug(barcode): + """식약처 의약품안전나라 API""" + api_key = os.getenv('NEDRUG_API_KEY') + url = f"https://apis.data.go.kr/1471000/DrugPrdtPrmsnInfoService05/getDrugPrdtPrmsnInq05" + params = { + "serviceKey": api_key, + "bar_code": barcode, + "type": "json" + } + response = requests.get(url, params=params) + return response.json() +``` + +### 3. API 엔드포인트 + +#### 3.1 제품 태깅 API + +```python +@app.route('/api/product/tag/', methods=['POST']) +def tag_product(barcode): + """ + 바코드로 제품 자동 태깅 + + Returns: + { + "success": true, + "product": {...}, + "tags": {...}, + "ontology": {...} + } + """ +``` + +#### 3.2 제품 조회 API + +```python +@app.route('/api/product/', methods=['GET']) +def get_product(barcode): + """ + 바코드로 제품 정보 조회 + + Returns: + { + "success": true, + "product": {...}, + "recommendations": [...] # 온톨로지 기반 추천 + } + """ +``` + +#### 3.3 유사 제품 추천 API + +```python +@app.route('/api/product//recommendations', methods=['GET']) +def get_recommendations(barcode): + """ + 온톨로지 기반 유사/대체 제품 추천 + + Returns: + { + "similar_products": [...], + "alternative_products": [...], + "synergistic_products": [...] + } + """ +``` + +### 4. 온톨로지 기반 추천 로직 + +```python +def recommend_products(user_id, current_barcode): + """ + 온톨로지 기반 제품 추천 + + 1. 사용자 구매 이력 분석 + 2. 현재 제품의 온톨로지 확인 + 3. 유사/대체/시너지 제품 검색 + 4. 사용자 맞춤 추천 + """ + + # 사용자 구매 패턴 + user_purchases = get_user_purchase_history(user_id) + + # 현재 제품 온톨로지 + product = get_product_master(current_barcode) + ontology = product['ontology'] + + # 추천 알고리즘 + recommendations = [] + + # 1. 유사 제품 (similar_to) + for similar_barcode in ontology['relations']['similar_to']: + recommendations.append({ + "barcode": similar_barcode, + "reason": "유사 제품", + "score": 0.8 + }) + + # 2. 대체 제품 (alternative_for) + for alt_category in ontology['relations']['alternative_for']: + alt_products = search_by_category(alt_category) + recommendations.extend(alt_products) + + # 3. 시너지 제품 (synergistic_with) + for synergy_barcode in ontology['relations']['synergistic_with']: + recommendations.append({ + "barcode": synergy_barcode, + "reason": "함께 복용 시 효과적", + "score": 0.9 + }) + + return recommendations +``` + +### 5. Vector Database 활용 (선택) + +고급 검색을 위해 Vector DB(Pinecone, Weaviate, ChromaDB) 사용 가능: + +```python +# 제품 임베딩 생성 +from openai import OpenAI + +def create_product_embedding(product): + """제품 정보를 벡터로 변환""" + text = f"{product['name']} {' '.join(product['tags_symptoms'])} {' '.join(product['tags_ingredients'])}" + + client = OpenAI() + response = client.embeddings.create( + input=text, + model="text-embedding-3-small" + ) + + return response.data[0].embedding + +# 유사 제품 검색 +def search_similar_products(query_embedding, top_k=10): + """벡터 유사도 기반 제품 검색""" + # ChromaDB 사용 예시 + results = collection.query( + query_embeddings=[query_embedding], + n_results=top_k + ) + return results +``` + +## 구현 단계 + +### Phase 1: 데이터 수집 (1주) +- [ ] product_master 테이블 생성 +- [ ] 약학정보원 크롤러 구현 +- [ ] 식약처 API 연동 +- [ ] 100개 인기 제품 수동 태깅 + +### Phase 2: AI 태깅 (1주) +- [ ] OpenAI 태깅 API 구현 +- [ ] 배치 태깅 스크립트 +- [ ] 전체 제품 자동 태깅 + +### Phase 3: 온톨로지 구축 (1주) +- [ ] 제품 분류 체계 구축 +- [ ] 관계 정의 (유사/대체/시너지) +- [ ] 추천 알고리즘 구현 + +### Phase 4: 검증 및 개선 (1주) +- [ ] 약사 검증 UI +- [ ] 오류 수정 시스템 +- [ ] 성능 최적화 + +## 예상 비용 + +- **OpenAI API**: GPT-4o-mini 사용 시 3,000개 제품 × $0.002 = $6 +- **웹 크롤링**: 무료 (공공데이터) +- **Vector DB**: ChromaDB (로컬) 사용 시 무료 + +## 기대 효과 + +1. **개인화 추천**: 고객 구매 패턴 + 온톨로지 → 정확한 제품 추천 +2. **재구매 유도**: "지난번 구매한 탁센과 유사한 제품" +3. **크로스셀**: "마그비맥스와 함께 복용하면 좋은 밀크씨슬" +4. **약사 상담 지원**: 대체 제품 즉시 검색 +5. **재고 관리**: 유사 제품 재고 확인 + +## 참고 자료 + +- 약학정보원: https://www.health.kr +- 식약처 의약품안전나라: https://nedrug.mfds.go.kr +- 공공데이터포털 API: https://www.data.go.kr +- OpenAI Embeddings: https://platform.openai.com/docs/guides/embeddings