feat: 바코드 기반 제품 태깅 시스템 구축

- product_master 테이블: 제품 마스터 (바코드, 이름, 성분, 태그)
- product_categories: 제품 카테고리 22개 (진통제, 소화제 등)
- product_category_mapping: 다대다 매핑 (하나의 제품이 여러 카테고리)
- disease_codes: 질병 코드 ICD-10 12개
- disease_product_mapping: 질병-제품 매핑
- 샘플 제품 3개 추가 (탁센, 베아제, 마그비맥스)
- BARCODE 컬럼 95.79% 보유율 확인
- 온톨로지 기반 추천 시스템 설계 문서

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-23 23:28:59 +09:00
parent a3252f7f17
commit 39539639b7
7 changed files with 953 additions and 0 deletions

View File

@@ -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()

70
backend/check_barcodes.py Normal file
View File

@@ -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()

View File

@@ -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;
-- ============================================================================
-- 완료
-- ============================================================================
-- 테이블 생성 완료
-- 초기 카테고리, 질병 코드, 매핑 데이터 삽입 완료

View File

@@ -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()