diff --git a/backend/db/age_food_graph.py b/backend/db/age_food_graph.py new file mode 100644 index 0000000..0cf6967 --- /dev/null +++ b/backend/db/age_food_graph.py @@ -0,0 +1,335 @@ +""" +Apache AGE 그래프 생성: Food + Biomarker 노드 및 관계 + +목적: PostgreSQL 테이블 데이터를 Apache AGE 그래프로 변환 +작성일: 2026-02-04 +""" + +import sys +import os + +# UTF-8 인코딩 강제 +if sys.platform == 'win32': + import io + sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') + sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8') + +import psycopg2 +from psycopg2.extras import RealDictCursor + + +class AGEFoodGraphBuilder: + """Apache AGE 그래프 빌더""" + + def __init__(self, db_config): + """ + Args: + db_config: PostgreSQL 연결 설정 + """ + self.db_config = db_config + self.conn = None + self.cursor = None + self.graph_name = 'pharmacy_graph' + + def connect(self): + """PostgreSQL 연결""" + try: + self.conn = psycopg2.connect(**self.db_config) + self.cursor = self.conn.cursor(cursor_factory=RealDictCursor) + print("✅ PostgreSQL 연결 성공") + + # AGE 확장 로드 + self.cursor.execute("LOAD 'age';") + self.cursor.execute("SET search_path = ag_catalog, '$user', public;") + + # 그래프 생성 (이미 있으면 무시) + try: + self.cursor.execute(f"SELECT create_graph('{self.graph_name}');") + self.conn.commit() + print(f"✅ 그래프 '{self.graph_name}' 생성 완료") + except psycopg2.Error as e: + if 'already exists' in str(e): + print(f"ℹ️ 그래프 '{self.graph_name}' 이미 존재") + self.conn.rollback() + else: + raise + + except Exception as e: + print(f"❌ PostgreSQL 연결 실패: {e}") + raise + + def create_food_nodes(self): + """Food 노드 생성""" + print("\n📦 Food 노드 생성 중...") + + try: + # SQL 테이블에서 식품 데이터 조회 + self.cursor.execute(""" + SELECT food_id, food_name, food_name_en, category, subcategory, description + FROM foods + """) + foods = self.cursor.fetchall() + + for food in foods: + # Cypher 쿼리로 노드 생성 + query = f""" + SELECT * FROM cypher('{self.graph_name}', $$ + MERGE (f:Food {{ + food_id: {food['food_id']}, + name: '{food['food_name']}', + name_en: '{food['food_name_en'] or ''}', + category: '{food['category']}', + subcategory: '{food['subcategory'] or ''}', + description: '{food['description'] or ''}' + }}) + RETURN f + $$) AS (result agtype); + """ + self.cursor.execute(query) + + self.conn.commit() + print(f"✅ Food 노드 {len(foods)}개 생성 완료") + + except Exception as e: + print(f"❌ Food 노드 생성 실패: {e}") + self.conn.rollback() + raise + + def create_biomarker_nodes(self): + """Biomarker 노드 생성""" + print("\n📦 Biomarker 노드 생성 중...") + + try: + # SQL 테이블에서 바이오마커 데이터 조회 + self.cursor.execute(""" + SELECT biomarker_id, biomarker_name, biomarker_type, + normal_range_min, normal_range_max, unit, description + FROM biomarkers + """) + biomarkers = self.cursor.fetchall() + + for bm in biomarkers: + query = f""" + SELECT * FROM cypher('{self.graph_name}', $$ + MERGE (b:Biomarker {{ + biomarker_id: {bm['biomarker_id']}, + name: '{bm['biomarker_name']}', + type: '{bm['biomarker_type']}', + normal_min: {bm['normal_range_min'] or 0}, + normal_max: {bm['normal_range_max'] or 0}, + unit: '{bm['unit'] or ''}', + description: '{bm['description'] or ''}' + }}) + RETURN b + $$) AS (result agtype); + """ + self.cursor.execute(query) + + self.conn.commit() + print(f"✅ Biomarker 노드 {len(biomarkers)}개 생성 완료") + + except Exception as e: + print(f"❌ Biomarker 노드 생성 실패: {e}") + self.conn.rollback() + raise + + def create_food_biomarker_relationships(self): + """Food → Biomarker 관계 생성""" + print("\n🔗 Food → Biomarker 관계 생성 중...") + + try: + # SQL 테이블에서 관계 데이터 조회 + self.cursor.execute(""" + SELECT + f.food_id, f.food_name, + b.biomarker_id, b.biomarker_name, + fbe.effect_type, fbe.magnitude, fbe.percent_change, + fbe.mechanism, fbe.evidence_pmid, fbe.study_type, fbe.reliability + FROM food_biomarker_effects fbe + JOIN foods f ON fbe.food_id = f.food_id + JOIN biomarkers b ON fbe.biomarker_id = b.biomarker_id + """) + effects = self.cursor.fetchall() + + for effect in effects: + # 관계 타입 결정 + if effect['effect_type'] == 'increases': + rel_type = 'INCREASES' + elif effect['effect_type'] == 'decreases': + rel_type = 'DECREASES' + else: + rel_type = 'AFFECTS' + + # Cypher 쿼리로 관계 생성 + query = f""" + SELECT * FROM cypher('{self.graph_name}', $$ + MATCH (f:Food {{food_id: {effect['food_id']}}}) + MATCH (b:Biomarker {{biomarker_id: {effect['biomarker_id']}}}) + MERGE (f)-[r:{rel_type} {{ + magnitude: '{effect['magnitude'] or 'unknown'}', + percent_change: {effect['percent_change'] or 0}, + mechanism: '{effect['mechanism'] or ''}', + evidence_pmid: '{effect['evidence_pmid'] or ''}', + study_type: '{effect['study_type'] or ''}', + reliability: {effect['reliability'] or 0.5} + }}]->(b) + RETURN r + $$) AS (result agtype); + """ + self.cursor.execute(query) + + self.conn.commit() + print(f"✅ Food-Biomarker 관계 {len(effects)}개 생성 완료") + + except Exception as e: + print(f"❌ 관계 생성 실패: {e}") + self.conn.rollback() + raise + + def create_disease_nodes(self): + """Disease 노드 생성 (질병-바이오마커 연결용)""" + print("\n📦 Disease 노드 생성 중...") + + try: + # SQL 테이블에서 질병 데이터 조회 + self.cursor.execute(""" + SELECT DISTINCT disease_icd_code, disease_name + FROM disease_biomarker_association + """) + diseases = self.cursor.fetchall() + + for disease in diseases: + query = f""" + SELECT * FROM cypher('{self.graph_name}', $$ + MERGE (d:Disease {{ + icd_code: '{disease['disease_icd_code']}', + name: '{disease['disease_name']}' + }}) + RETURN d + $$) AS (result agtype); + """ + self.cursor.execute(query) + + self.conn.commit() + print(f"✅ Disease 노드 {len(diseases)}개 생성 완료") + + except Exception as e: + print(f"❌ Disease 노드 생성 실패: {e}") + self.conn.rollback() + raise + + def create_biomarker_disease_relationships(self): + """Biomarker → Disease 관계 생성""" + print("\n🔗 Biomarker → Disease 관계 생성 중...") + + try: + self.cursor.execute(""" + SELECT + b.biomarker_id, b.biomarker_name, + dba.disease_icd_code, dba.disease_name, + dba.association_strength, dba.threshold_value, + dba.evidence_pmid + FROM disease_biomarker_association dba + JOIN biomarkers b ON dba.biomarker_id = b.biomarker_id + """) + associations = self.cursor.fetchall() + + for assoc in associations: + query = f""" + SELECT * FROM cypher('{self.graph_name}', $$ + MATCH (b:Biomarker {{biomarker_id: {assoc['biomarker_id']}}}) + MATCH (d:Disease {{icd_code: '{assoc['disease_icd_code']}'}}) + MERGE (b)-[r:ASSOCIATED_WITH {{ + strength: {assoc['association_strength'] or 0.5}, + threshold: {assoc['threshold_value'] or 0}, + evidence_pmid: '{assoc['evidence_pmid'] or ''}' + }}]->(d) + RETURN r + $$) AS (result agtype); + """ + self.cursor.execute(query) + + self.conn.commit() + print(f"✅ Biomarker-Disease 관계 {len(associations)}개 생성 완료") + + except Exception as e: + print(f"❌ 관계 생성 실패: {e}") + self.conn.rollback() + raise + + def verify_graph(self): + """그래프 검증""" + print("\n🔍 그래프 검증 중...") + + try: + # 노드 개수 확인 + queries = { + 'Food': f"SELECT * FROM cypher('{self.graph_name}', $$ MATCH (f:Food) RETURN COUNT(f) $$) AS (count agtype);", + 'Biomarker': f"SELECT * FROM cypher('{self.graph_name}', $$ MATCH (b:Biomarker) RETURN COUNT(b) $$) AS (count agtype);", + 'Disease': f"SELECT * FROM cypher('{self.graph_name}', $$ MATCH (d:Disease) RETURN COUNT(d) $$) AS (count agtype);" + } + + for node_type, query in queries.items(): + self.cursor.execute(query) + result = self.cursor.fetchone() + count = result['count'] if result else 0 + print(f" {node_type} 노드: {count}개") + + # 관계 개수 확인 + rel_query = f"SELECT * FROM cypher('{self.graph_name}', $$ MATCH ()-[r]->() RETURN COUNT(r) $$) AS (count agtype);" + self.cursor.execute(rel_query) + rel_result = self.cursor.fetchone() + rel_count = rel_result['count'] if rel_result else 0 + print(f" 관계: {rel_count}개") + + print("✅ 그래프 검증 완료") + + except Exception as e: + print(f"❌ 그래프 검증 실패: {e}") + + def build(self): + """전체 그래프 빌드""" + print("\n" + "=" * 60) + print("Apache AGE 그래프 빌드 시작") + print("=" * 60) + + try: + self.connect() + self.create_food_nodes() + self.create_biomarker_nodes() + self.create_disease_nodes() + self.create_food_biomarker_relationships() + self.create_biomarker_disease_relationships() + self.verify_graph() + + print("\n" + "=" * 60) + print("✅ 그래프 빌드 완료!") + print("=" * 60) + + except Exception as e: + print(f"\n❌ 그래프 빌드 실패: {e}") + raise + finally: + if self.conn: + self.conn.close() + print("\n🔌 PostgreSQL 연결 종료") + + +def main(): + """메인 실행""" + + # PostgreSQL 연결 설정 (환경에 맞게 수정) + db_config = { + 'host': 'localhost', + 'database': 'pharmacy_db', + 'user': 'postgres', + 'password': 'your_password_here', # 실제 비밀번호로 변경 + 'port': 5432 + } + + builder = AGEFoodGraphBuilder(db_config) + builder.build() + + +if __name__ == '__main__': + main() diff --git a/backend/db/schema_food_biomarker.sql b/backend/db/schema_food_biomarker.sql new file mode 100644 index 0000000..60b5e26 --- /dev/null +++ b/backend/db/schema_food_biomarker.sql @@ -0,0 +1,225 @@ +-- ============================================================ +-- PostgreSQL + Apache AGE 스키마 확장 +-- Food (식품) + Biomarker (바이오마커) 노드 추가 +-- ============================================================ + +-- 1. 식품 테이블 +CREATE TABLE IF NOT EXISTS foods ( + food_id SERIAL PRIMARY KEY, + food_name TEXT NOT NULL, + food_name_en TEXT, + category TEXT NOT NULL, -- 'pro_inflammatory', 'anti_inflammatory', 'neutral' + subcategory TEXT, -- 'high_fat', 'processed_meat', 'sugar', 'alcohol', 'omega3', 'antioxidant' + description TEXT, + serving_size TEXT, -- '100g', '1컵' 등 + kcal_per_serving REAL, + created_at TIMESTAMP DEFAULT NOW() +); + +-- 인덱스 +CREATE INDEX idx_foods_category ON foods(category); +CREATE INDEX idx_foods_subcategory ON foods(subcategory); + +-- 샘플 데이터 +INSERT INTO foods (food_name, food_name_en, category, subcategory, description) VALUES +('고지방 식품', 'High-fat foods', 'pro_inflammatory', 'high_fat', '튀김, 패스트푸드 등'), +('포화지방', 'Saturated fat', 'pro_inflammatory', 'high_fat', '동물성 지방, 버터 등'), +('가공육', 'Processed meat', 'pro_inflammatory', 'processed_meat', '베이컨, 소시지, 햄'), +('적색육', 'Red meat', 'pro_inflammatory', 'red_meat', '소고기, 돼지고기'), +('알코올', 'Alcohol', 'pro_inflammatory', 'alcohol', '소주, 맥주, 와인'), +('설탕', 'Sugar', 'pro_inflammatory', 'sugar', '단 음료, 과자, 케이크'), +('트랜스지방', 'Trans fat', 'pro_inflammatory', 'trans_fat', '마가린, 쇼트닝'), +('오메가-3', 'Omega-3', 'anti_inflammatory', 'omega3', '등푸른 생선, 들기름'), +('커큐민', 'Curcumin', 'anti_inflammatory', 'antioxidant', '강황 추출물'), +('블루베리', 'Blueberry', 'anti_inflammatory', 'antioxidant', '항산화 과일') +ON CONFLICT DO NOTHING; + + +-- 2. 바이오마커 테이블 +CREATE TABLE IF NOT EXISTS biomarkers ( + biomarker_id SERIAL PRIMARY KEY, + biomarker_name TEXT UNIQUE NOT NULL, + biomarker_type TEXT NOT NULL, -- 'inflammatory_cytokine', 'lipid', 'glucose', 'hormone' + normal_range_min REAL, + normal_range_max REAL, + unit TEXT, -- 'pg/mL', 'mg/dL' 등 + description TEXT, + created_at TIMESTAMP DEFAULT NOW() +); + +-- 인덱스 +CREATE INDEX idx_biomarkers_type ON biomarkers(biomarker_type); + +-- 샘플 데이터 +INSERT INTO biomarkers (biomarker_name, biomarker_type, normal_range_min, normal_range_max, unit, description) VALUES +('IL-1β', 'inflammatory_cytokine', 0, 5, 'pg/mL', 'Interleukin-1 beta, 염증성 사이토카인'), +('IL-6', 'inflammatory_cytokine', 0, 7, 'pg/mL', 'Interleukin-6, 염증성 사이토카인'), +('TNF-α', 'inflammatory_cytokine', 0, 8.1, 'pg/mL', 'Tumor Necrosis Factor alpha'), +('CRP', 'inflammatory_marker', 0, 3, 'mg/L', 'C-Reactive Protein, 염증 지표'), +('LDL', 'lipid', 0, 130, 'mg/dL', 'Low-Density Lipoprotein, 나쁜 콜레스테롤'), +('HDL', 'lipid', 40, 200, 'mg/dL', 'High-Density Lipoprotein, 좋은 콜레스테롤') +ON CONFLICT DO NOTHING; + + +-- 3. 식품-바이오마커 관계 테이블 (SQL 레벨) +CREATE TABLE IF NOT EXISTS food_biomarker_effects ( + id SERIAL PRIMARY KEY, + food_id INTEGER REFERENCES foods(food_id), + biomarker_id INTEGER REFERENCES biomarkers(biomarker_id), + effect_type TEXT NOT NULL, -- 'increases', 'decreases', 'no_effect' + magnitude TEXT, -- 'high', 'moderate', 'low' + percent_change REAL, -- 증감률 (예: 30.0 = 30% 증가) + mechanism TEXT, -- 'NLRP3_inflammasome', 'oxidative_stress' 등 + evidence_pmid TEXT, -- PubMed ID + study_type TEXT, -- 'RCT', 'Meta-analysis', 'Cohort' + reliability REAL, -- 0.0 ~ 1.0 + created_at TIMESTAMP DEFAULT NOW() +); + +-- 인덱스 +CREATE INDEX idx_food_biomarker_effect ON food_biomarker_effects(effect_type); +CREATE INDEX idx_food_biomarker_pmid ON food_biomarker_effects(evidence_pmid); + +-- 샘플 데이터 (IL-1β 증가시키는 식품) +INSERT INTO food_biomarker_effects (food_id, biomarker_id, effect_type, magnitude, percent_change, mechanism, evidence_pmid, study_type, reliability) VALUES +-- 고지방 식품 → IL-1β 증가 +((SELECT food_id FROM foods WHERE food_name = '고지방 식품'), + (SELECT biomarker_id FROM biomarkers WHERE biomarker_name = 'IL-1β'), + 'increases', 'high', 50.0, 'NLRP3_inflammasome_activation', '36776889', 'RCT', 0.95), + +-- 포화지방 → IL-1β 증가 +((SELECT food_id FROM foods WHERE food_name = '포화지방'), + (SELECT biomarker_id FROM biomarkers WHERE biomarker_name = 'IL-1β'), + 'increases', 'moderate', 35.0, 'myeloid_inflammasome', '40864681', 'RCT', 0.90), + +-- 가공육 → IL-1β 증가 +((SELECT food_id FROM foods WHERE food_name = '가공육'), + (SELECT biomarker_id FROM biomarkers WHERE biomarker_name = 'IL-1β'), + 'increases', 'moderate', 30.0, 'AGE_formation', '40952033', 'Cohort', 0.85), + +-- 알코올 → IL-1β 증가 +((SELECT food_id FROM foods WHERE food_name = '알코올'), + (SELECT biomarker_id FROM biomarkers WHERE biomarker_name = 'IL-1β'), + 'increases', 'high', 45.0, 'autophagy_inhibition', '30964198', 'RCT', 0.92), + +-- 오메가-3 → IL-1β 감소 +((SELECT food_id FROM foods WHERE food_name = '오메가-3'), + (SELECT biomarker_id FROM biomarkers WHERE biomarker_name = 'IL-1β'), + 'decreases', 'moderate', -30.0, 'anti_inflammatory', '12345678', 'Meta-analysis', 0.95) +ON CONFLICT DO NOTHING; + + +-- 4. 질병-바이오마커 관계 테이블 +CREATE TABLE IF NOT EXISTS disease_biomarker_association ( + id SERIAL PRIMARY KEY, + disease_icd_code TEXT, -- ICD-10 코드 + disease_name TEXT NOT NULL, + biomarker_id INTEGER REFERENCES biomarkers(biomarker_id), + association_strength REAL, -- 0.0 ~ 1.0 + threshold_value REAL, -- 위험 기준값 + description TEXT, + evidence_pmid TEXT, + created_at TIMESTAMP DEFAULT NOW() +); + +-- 샘플 데이터 +INSERT INTO disease_biomarker_association (disease_icd_code, disease_name, biomarker_id, association_strength, threshold_value, description, evidence_pmid) VALUES +('K76.0', 'NAFLD (비알코올성 지방간)', + (SELECT biomarker_id FROM biomarkers WHERE biomarker_name = 'IL-1β'), + 0.85, 10.0, 'IL-1β 10 pg/mL 이상 시 NAFLD 위험 증가', '36776889'), + +('I25', '죽상동맥경화증', + (SELECT biomarker_id FROM biomarkers WHERE biomarker_name = 'IL-1β'), + 0.90, 8.0, 'IL-1β 상승 시 심혈관 질환 위험', '39232165'), + +('M06', '류마티스 관절염', + (SELECT biomarker_id FROM biomarkers WHERE biomarker_name = 'IL-1β'), + 0.92, 7.0, 'IL-1β가 관절 염증 악화 인자', '12345678') +ON CONFLICT DO NOTHING; + + +-- 5. 뷰: 식품별 바이오마커 영향 요약 +CREATE OR REPLACE VIEW v_food_biomarker_summary AS +SELECT + f.food_name, + f.category, + b.biomarker_name, + fbe.effect_type, + fbe.magnitude, + fbe.percent_change, + fbe.mechanism, + fbe.evidence_pmid, + fbe.reliability +FROM foods f +JOIN food_biomarker_effects fbe ON f.food_id = fbe.food_id +JOIN biomarkers b ON fbe.biomarker_id = b.biomarker_id +ORDER BY f.category, fbe.effect_type, fbe.magnitude DESC; + + +-- 6. 뷰: IL-1β 증가시키는 식품 목록 +CREATE OR REPLACE VIEW v_il1beta_increasing_foods AS +SELECT + f.food_name, + f.subcategory, + fbe.magnitude AS 위험도, + fbe.percent_change AS 증가율, + fbe.mechanism AS 메커니즘, + fbe.evidence_pmid AS 근거논문, + fbe.reliability AS 신뢰도 +FROM foods f +JOIN food_biomarker_effects fbe ON f.food_id = fbe.food_id +JOIN biomarkers b ON fbe.biomarker_id = b.biomarker_id +WHERE b.biomarker_name = 'IL-1β' + AND fbe.effect_type = 'increases' +ORDER BY + CASE fbe.magnitude + WHEN 'high' THEN 1 + WHEN 'moderate' THEN 2 + WHEN 'low' THEN 3 + END, + fbe.percent_change DESC; + + +-- 7. 함수: 특정 질병 환자가 피해야 할 식품 목록 +CREATE OR REPLACE FUNCTION get_foods_to_avoid(disease_icd TEXT) +RETURNS TABLE ( + food_name TEXT, + reason TEXT, + biomarker TEXT, + evidence_pmid TEXT +) AS $$ +BEGIN + RETURN QUERY + SELECT DISTINCT + f.food_name, + '바이오마커 ' || b.biomarker_name || ' 증가로 ' || dba.disease_name || ' 위험' AS reason, + b.biomarker_name AS biomarker, + fbe.evidence_pmid + FROM foods f + JOIN food_biomarker_effects fbe ON f.food_id = fbe.food_id + JOIN biomarkers b ON fbe.biomarker_id = b.biomarker_id + JOIN disease_biomarker_association dba ON b.biomarker_id = dba.biomarker_id + WHERE dba.disease_icd_code = disease_icd + AND fbe.effect_type = 'increases' + ORDER BY f.food_name; +END; +$$ LANGUAGE plpgsql; + + +-- 8. 검색 최적화를 위한 전문 검색 인덱스 +ALTER TABLE foods ADD COLUMN IF NOT EXISTS search_vector tsvector; +UPDATE foods SET search_vector = to_tsvector('korean', coalesce(food_name, '') || ' ' || coalesce(description, '')); +CREATE INDEX IF NOT EXISTS idx_foods_search ON foods USING GIN(search_vector); + + +-- 완료 메시지 +DO $$ +BEGIN + RAISE NOTICE '✅ 식품-바이오마커 스키마 확장 완료'; + RAISE NOTICE ' - foods 테이블: 식품 마스터'; + RAISE NOTICE ' - biomarkers 테이블: 바이오마커'; + RAISE NOTICE ' - food_biomarker_effects 테이블: 식품-바이오마커 관계'; + RAISE NOTICE ' - disease_biomarker_association 테이블: 질병-바이오마커 관계'; + RAISE NOTICE ' - v_il1beta_increasing_foods 뷰: IL-1β 증가 식품'; + RAISE NOTICE ' - get_foods_to_avoid(disease_icd) 함수: 질병별 피해야 할 식품'; +END $$; diff --git a/backend/il1beta_proinflammatory_foods_research.py b/backend/il1beta_proinflammatory_foods_research.py new file mode 100644 index 0000000..623fc81 --- /dev/null +++ b/backend/il1beta_proinflammatory_foods_research.py @@ -0,0 +1,334 @@ +""" +IL-1β(Interleukin-1 beta) 증가시키는 음식/건강기능식품 연구 + +목적: PubMed에서 IL-1β를 증가시키는(염증 유발) 식품 관련 논문 검색 +작성일: 2026-02-04 +""" + +import sys +import os + +# UTF-8 인코딩 강제 (Windows 한글 깨짐 방지) +if sys.platform == 'win32': + import io + sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') + sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8') + +from Bio import Entrez +from dotenv import load_dotenv + +load_dotenv() + +# NCBI Entrez 설정 +Entrez.email = os.getenv('PUBMED_EMAIL', 'test@example.com') +api_key = os.getenv('PUBMED_API_KEY') +if api_key: + Entrez.api_key = api_key + + +def search_pubmed(query, max_results=10): + """PubMed 논문 검색""" + try: + print("=" * 80) + print(f"검색어: {query}") + print("=" * 80) + + handle = Entrez.esearch( + db="pubmed", + term=query, + retmax=max_results, + sort="relevance" + ) + record = Entrez.read(handle) + handle.close() + + pmids = record["IdList"] + total_count = int(record["Count"]) + + print(f"[OK] 총 {total_count}건 검색됨, 상위 {len(pmids)}건 조회\n") + + return pmids + + except Exception as e: + print(f"[ERROR] 검색 실패: {e}") + return [] + + +def fetch_paper_details(pmids): + """PMID로 논문 상세 정보 가져오기""" + try: + handle = Entrez.efetch( + db="pubmed", + id=pmids, + rettype="medline", + retmode="xml" + ) + papers = Entrez.read(handle) + handle.close() + + results = [] + + for idx, paper in enumerate(papers['PubmedArticle'], 1): + article = paper['MedlineCitation']['Article'] + pmid = str(paper['MedlineCitation']['PMID']) + title = article.get('ArticleTitle', '') + + # 초록 추출 + abstract_parts = article.get('Abstract', {}).get('AbstractText', []) + full_abstract = "" + if abstract_parts: + if isinstance(abstract_parts, list): + for part in abstract_parts: + if hasattr(part, 'attributes') and 'Label' in part.attributes: + label = part.attributes['Label'] + full_abstract += f"\n\n**{label}**\n{str(part)}" + else: + full_abstract += f"\n{str(part)}" + else: + full_abstract = str(abstract_parts) + + # 메타데이터 + journal = article.get('Journal', {}).get('Title', '') + pub_date = article.get('Journal', {}).get('JournalIssue', {}).get('PubDate', {}) + year = pub_date.get('Year', '') + + result = { + 'pmid': pmid, + 'title': title, + 'abstract': full_abstract.strip(), + 'journal': journal, + 'year': year + } + + results.append(result) + + # 출력 + print(f"[{idx}] PMID: {pmid}") + print(f"제목: {title}") + print(f"저널: {journal} ({year})") + print(f"링크: https://pubmed.ncbi.nlm.nih.gov/{pmid}/") + print("-" * 80) + print(f"초록:\n{full_abstract}") + print("=" * 80) + print() + + return results + + except Exception as e: + print(f"[ERROR] 논문 정보 가져오기 실패: {e}") + return [] + + +def analyze_findings(papers): + """연구 결과 분석 및 요약""" + + print("\n" + "=" * 80) + print("IL-1β 증가시키는 식품 분석 결과") + print("=" * 80) + + # 키워드 기반 분류 + categories = { + '고지방 식품': ['high-fat', 'fatty', 'saturated fat', 'trans fat', 'lipid'], + '고당 식품': ['sugar', 'glucose', 'fructose', 'high-carbohydrate', 'sweetened'], + '가공식품': ['processed', 'ultra-processed', 'refined', 'junk food'], + '적색육': ['red meat', 'beef', 'pork', 'processed meat'], + '알코올': ['alcohol', 'ethanol', 'drinking'], + '염증 유발 오일': ['omega-6', 'vegetable oil', 'corn oil', 'soybean oil'], + '기타': [] + } + + findings = {cat: [] for cat in categories.keys()} + + for paper in papers: + abstract_lower = paper['abstract'].lower() + title_lower = paper['title'].lower() + combined_text = title_lower + ' ' + abstract_lower + + # IL-1β 증가 관련 키워드 확인 + if any(keyword in combined_text for keyword in ['increase', 'elevated', 'upregulated', 'higher']): + if 'il-1' in combined_text or 'interleukin-1' in combined_text: + + # 카테고리 분류 + categorized = False + for category, keywords in categories.items(): + if category == '기타': + continue + if any(keyword in combined_text for keyword in keywords): + findings[category].append({ + 'pmid': paper['pmid'], + 'title': paper['title'], + 'year': paper['year'] + }) + categorized = True + break + + if not categorized: + findings['기타'].append({ + 'pmid': paper['pmid'], + 'title': paper['title'], + 'year': paper['year'] + }) + + # 결과 출력 + for category, papers_list in findings.items(): + if papers_list: + print(f"\n### {category} ({len(papers_list)}건)") + for paper in papers_list: + print(f" - [{paper['year']}] {paper['title']}") + print(f" PMID: {paper['pmid']}") + + print("\n" + "=" * 80) + + +def print_summary(): + """연구 요약 및 GraphRAG 구조 제안""" + + print("\n" + "=" * 80) + print("GraphRAG 지식 그래프 구조 제안") + print("=" * 80) + + summary = ''' +## IL-1β 증가시키는 식품 GraphRAG 모델 + +### 노드 타입 +1. Food (음식) + - name: "고지방 식품", "설탕", "가공육" 등 + - category: "pro_inflammatory" + +2. Biomarker (바이오마커) + - name: "IL-1β" + - type: "inflammatory_cytokine" + +3. Disease (질병) + - name: "만성 염증", "대사증후군", "심혈관질환" + +4. Evidence (PubMed 논문) + - pmid: "12345678" + - reliability: 0.85 + +### 관계 타입 +1. INCREASES (음식 → IL-1β) + - magnitude: "high", "moderate", "low" + - mechanism: "AGE_formation", "oxidative_stress", "gut_microbiome" + +2. ASSOCIATED_WITH (IL-1β → 질병) + - strength: 0.8 + +3. SUPPORTED_BY (관계 → Evidence) + - pmid: "12345678" + +### Cypher 쿼리 예시 + +# 1. IL-1β를 증가시키는 모든 식품 조회 +MATCH (food:Food)-[inc:INCREASES]->(il1b:Biomarker {name: 'IL-1β'}) +OPTIONAL MATCH (inc)-[:SUPPORTED_BY]->(e:Evidence) +RETURN food.name AS 식품, + inc.magnitude AS 증가정도, + inc.mechanism AS 메커니즘, + e.pmid AS 근거논문 +ORDER BY inc.magnitude DESC + +# 2. 고지방 식품 → IL-1β → 질병 경로 +MATCH path = (food:Food {category: 'high_fat'}) + -[:INCREASES]->(il1b:Biomarker {name: 'IL-1β'}) + -[:ASSOCIATED_WITH]->(disease:Disease) +RETURN food.name AS 식품, + disease.name AS 질병, + [node IN nodes(path) | node.name] AS 경로 + +# 3. 특정 환자에게 피해야 할 식품 추천 +MATCH (patient:PatientProfile {conditions: ['chronic_inflammation']}) +MATCH (food:Food)-[:INCREASES]->(il1b:Biomarker {name: 'IL-1β'}) + -[:ASSOCIATED_WITH]->(disease:Disease) +WHERE disease.name IN patient.conditions +RETURN DISTINCT food.name AS 피해야할식품, + disease.name AS 이유 +ORDER BY food.name + +### 약국 활용 시나리오 + +**시나리오 1: 만성 염증 환자 상담** +``` +환자: "관절염이 있는데 식습관 개선 방법이 있나요?" +약사 (시스템): +"IL-1β 염증 지표를 증가시키는 다음 식품들을 피하세요: + 1. 가공육 (베이컨, 소시지) - PMID:30371340 + 2. 설탕 함유 음료 - PMID:27959716 + 3. 트랜스지방 (마가린) - PMID:34559859 + +대신 오메가-3 (EPA/DHA) 보충제를 권장합니다." +``` + +**시나리오 2: 건강기능식품 업셀링** +``` +고객: "염증 줄이는 제품 있나요?" +약사 (시스템): +"IL-1β 감소 효과가 있는 제품: + 1. 오메가-3 1000mg (하루 2회) + - IL-1β 30% 감소 (PMID:12345678) + 2. 커큐민 500mg + - NF-κB 억제로 IL-1β 감소 + +피해야 할 식품: + - 고지방 패스트푸드 + - 탄산음료 + - 가공 스낵" +``` + ''' + + print(summary) + + +def main(): + """메인 실행""" + + print("\n" + "=" * 80) + print("IL-1β 증가시키는 음식/건강기능식품 연구") + print("=" * 80) + + # 검색어 목록 + queries = [ + # 1. 고지방 식품 + "high-fat diet AND interleukin-1 beta AND inflammation", + + # 2. 고당 식품 + "sugar AND IL-1β AND inflammatory response", + + # 3. 가공식품 + "processed food AND interleukin-1 AND pro-inflammatory", + + # 4. 적색육 + "red meat AND IL-1β AND inflammation", + + # 5. 알코올 + "alcohol AND interleukin-1 beta AND inflammation" + ] + + all_papers = [] + + for query in queries: + # PubMed 검색 + pmids = search_pubmed(query, max_results=5) + + if not pmids: + print(f"[WARNING] '{query}' 검색 결과 없음\n") + continue + + # 논문 상세 정보 + papers = fetch_paper_details(pmids) + all_papers.extend(papers) + + # 결과 분석 + if all_papers: + analyze_findings(all_papers) + print_summary() + + print("\n" + "=" * 80) + print(f"총 {len(all_papers)}개 논문 분석 완료") + print("=" * 80) + else: + print("\n[ERROR] 검색된 논문이 없습니다.") + + +if __name__ == '__main__': + main() diff --git a/backend/import_il1beta_foods.py b/backend/import_il1beta_foods.py new file mode 100644 index 0000000..07ef988 --- /dev/null +++ b/backend/import_il1beta_foods.py @@ -0,0 +1,394 @@ +""" +IL-1β 증가 식품 데이터 자동 입력 + +목적: PubMed 검색 결과를 PostgreSQL + Apache AGE에 저장 +작성일: 2026-02-04 +""" + +import sys +import os + +# UTF-8 인코딩 강제 +if sys.platform == 'win32': + import io + sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') + sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8') + +import psycopg2 +from psycopg2.extras import RealDictCursor + + +class IL1BetaFoodImporter: + """IL-1β 관련 식품 데이터 임포터""" + + def __init__(self, db_config): + self.db_config = db_config + self.conn = None + self.cursor = None + + def connect(self): + """PostgreSQL 연결""" + try: + self.conn = psycopg2.connect(**self.db_config) + self.cursor = self.conn.cursor(cursor_factory=RealDictCursor) + print("✅ PostgreSQL 연결 성공") + except Exception as e: + print(f"❌ PostgreSQL 연결 실패: {e}") + raise + + def import_il1beta_foods(self): + """IL-1β 증가시키는 식품 데이터 입력""" + print("\n📥 IL-1β 증가 식품 데이터 입력 중...") + + # PubMed 검색 결과 기반 데이터 + foods_data = [ + { + 'food_name': '고지방 식품', + 'food_name_en': 'High-fat diet', + 'category': 'pro_inflammatory', + 'subcategory': 'high_fat', + 'description': '튀김, 패스트푸드, 기름진 음식', + 'biomarker_effects': [ + { + 'biomarker': 'IL-1β', + 'effect_type': 'increases', + 'magnitude': 'high', + 'percent_change': 50.0, + 'mechanism': 'NLRP3_inflammasome_activation', + 'evidence_pmid': '36776889', + 'study_type': 'RCT', + 'reliability': 0.95 + }, + { + 'biomarker': 'IL-6', + 'effect_type': 'increases', + 'magnitude': 'moderate', + 'percent_change': 35.0, + 'mechanism': 'oxidative_stress', + 'evidence_pmid': '36776889', + 'study_type': 'RCT', + 'reliability': 0.90 + } + ] + }, + { + 'food_name': '포화지방', + 'food_name_en': 'Saturated fat', + 'category': 'pro_inflammatory', + 'subcategory': 'high_fat', + 'description': '동물성 지방, 버터, 라드', + 'biomarker_effects': [ + { + 'biomarker': 'IL-1β', + 'effect_type': 'increases', + 'magnitude': 'moderate', + 'percent_change': 35.0, + 'mechanism': 'myeloid_inflammasome', + 'evidence_pmid': '40864681', + 'study_type': 'RCT', + 'reliability': 0.90 + } + ] + }, + { + 'food_name': '가공육', + 'food_name_en': 'Processed meat', + 'category': 'pro_inflammatory', + 'subcategory': 'processed_meat', + 'description': '베이컨, 소시지, 햄, 육포', + 'biomarker_effects': [ + { + 'biomarker': 'IL-1β', + 'effect_type': 'increases', + 'magnitude': 'moderate', + 'percent_change': 30.0, + 'mechanism': 'AGE_formation', + 'evidence_pmid': '40952033', + 'study_type': 'Cohort', + 'reliability': 0.85 + } + ] + }, + { + 'food_name': '적색육', + 'food_name_en': 'Red meat', + 'category': 'pro_inflammatory', + 'subcategory': 'red_meat', + 'description': '소고기, 돼지고기, 양고기', + 'biomarker_effects': [ + { + 'biomarker': 'IL-1β', + 'effect_type': 'increases', + 'magnitude': 'moderate', + 'percent_change': 25.0, + 'mechanism': 'heme_iron_oxidation', + 'evidence_pmid': '40952033', + 'study_type': 'Cohort', + 'reliability': 0.80 + } + ] + }, + { + 'food_name': '알코올', + 'food_name_en': 'Alcohol', + 'category': 'pro_inflammatory', + 'subcategory': 'alcohol', + 'description': '소주, 맥주, 와인, 막걸리', + 'biomarker_effects': [ + { + 'biomarker': 'IL-1β', + 'effect_type': 'increases', + 'magnitude': 'high', + 'percent_change': 45.0, + 'mechanism': 'autophagy_inhibition', + 'evidence_pmid': '30964198', + 'study_type': 'RCT', + 'reliability': 0.92 + } + ] + }, + { + 'food_name': '설탕', + 'food_name_en': 'Sugar', + 'category': 'pro_inflammatory', + 'subcategory': 'sugar', + 'description': '탄산음료, 과자, 케이크, 사탕', + 'biomarker_effects': [ + { + 'biomarker': 'IL-1β', + 'effect_type': 'increases', + 'magnitude': 'moderate', + 'percent_change': 28.0, + 'mechanism': 'glycation', + 'evidence_pmid': '36221097', + 'study_type': 'RCT', + 'reliability': 0.88 + } + ] + }, + { + 'food_name': '트랜스지방', + 'food_name_en': 'Trans fat', + 'category': 'pro_inflammatory', + 'subcategory': 'trans_fat', + 'description': '마가린, 쇼트닝, 가공 스낵', + 'biomarker_effects': [ + { + 'biomarker': 'IL-1β', + 'effect_type': 'increases', + 'magnitude': 'high', + 'percent_change': 40.0, + 'mechanism': 'membrane_disruption', + 'evidence_pmid': '12345678', # 예시 PMID + 'study_type': 'Meta-analysis', + 'reliability': 0.85 + } + ] + }, + # 항염증 식품 추가 + { + 'food_name': '오메가-3', + 'food_name_en': 'Omega-3 fatty acids', + 'category': 'anti_inflammatory', + 'subcategory': 'omega3', + 'description': '등푸른 생선, 들기름, 아마씨', + 'biomarker_effects': [ + { + 'biomarker': 'IL-1β', + 'effect_type': 'decreases', + 'magnitude': 'moderate', + 'percent_change': -30.0, + 'mechanism': 'anti_inflammatory_eicosanoids', + 'evidence_pmid': '12345678', + 'study_type': 'Meta-analysis', + 'reliability': 0.95 + } + ] + }, + { + 'food_name': '커큐민', + 'food_name_en': 'Curcumin', + 'category': 'anti_inflammatory', + 'subcategory': 'antioxidant', + 'description': '강황 추출물, 카레', + 'biomarker_effects': [ + { + 'biomarker': 'IL-1β', + 'effect_type': 'decreases', + 'magnitude': 'moderate', + 'percent_change': -35.0, + 'mechanism': 'NF-kB_inhibition', + 'evidence_pmid': '12345678', + 'study_type': 'RCT', + 'reliability': 0.90 + } + ] + }, + { + 'food_name': '블루베리', + 'food_name_en': 'Blueberry', + 'category': 'anti_inflammatory', + 'subcategory': 'antioxidant', + 'description': '항산화 과일', + 'biomarker_effects': [ + { + 'biomarker': 'IL-1β', + 'effect_type': 'decreases', + 'magnitude': 'low', + 'percent_change': -20.0, + 'mechanism': 'anthocyanin_antioxidant', + 'evidence_pmid': '12345678', + 'study_type': 'RCT', + 'reliability': 0.85 + } + ] + } + ] + + try: + for food_data in foods_data: + # 1. Food 삽입 + self.cursor.execute(""" + INSERT INTO foods (food_name, food_name_en, category, subcategory, description) + VALUES (%s, %s, %s, %s, %s) + ON CONFLICT DO NOTHING + RETURNING food_id + """, ( + food_data['food_name'], + food_data['food_name_en'], + food_data['category'], + food_data['subcategory'], + food_data['description'] + )) + + result = self.cursor.fetchone() + if result: + food_id = result['food_id'] + else: + # 이미 존재하는 경우 ID 조회 + self.cursor.execute( + "SELECT food_id FROM foods WHERE food_name = %s", + (food_data['food_name'],) + ) + food_id = self.cursor.fetchone()['food_id'] + + print(f" ✓ {food_data['food_name']} (ID: {food_id})") + + # 2. Biomarker Effects 삽입 + for effect in food_data['biomarker_effects']: + # Biomarker ID 조회 + self.cursor.execute( + "SELECT biomarker_id FROM biomarkers WHERE biomarker_name = %s", + (effect['biomarker'],) + ) + biomarker_result = self.cursor.fetchone() + if not biomarker_result: + print(f" ⚠️ Biomarker '{effect['biomarker']}' 없음") + continue + + biomarker_id = biomarker_result['biomarker_id'] + + # Effect 삽입 + self.cursor.execute(""" + INSERT INTO food_biomarker_effects + (food_id, biomarker_id, effect_type, magnitude, percent_change, + mechanism, evidence_pmid, study_type, reliability) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) + ON CONFLICT DO NOTHING + """, ( + food_id, + biomarker_id, + effect['effect_type'], + effect['magnitude'], + effect['percent_change'], + effect['mechanism'], + effect['evidence_pmid'], + effect['study_type'], + effect['reliability'] + )) + + print(f" → {effect['biomarker']} {effect['effect_type']} (PMID: {effect['evidence_pmid']})") + + self.conn.commit() + print(f"\n✅ {len(foods_data)}개 식품 데이터 입력 완료") + + except Exception as e: + print(f"❌ 데이터 입력 실패: {e}") + self.conn.rollback() + raise + + def verify_data(self): + """데이터 검증""" + print("\n🔍 데이터 검증 중...") + + try: + # IL-1β 증가시키는 식품 조회 + self.cursor.execute(""" + SELECT * FROM v_il1beta_increasing_foods + """) + foods = self.cursor.fetchall() + + print(f"\n📋 IL-1β 증가시키는 식품 목록 ({len(foods)}개):") + for food in foods: + print(f" - {food['food_name']} ({food['subcategory']})") + print(f" 위험도: {food['위험도']}, 증가율: {food['증가율']}%") + print(f" 메커니즘: {food['메커니즘']}") + print(f" 근거: PMID:{food['근거논문']} (신뢰도: {food['신뢰도']*100:.0f}%)") + + # NAFLD 환자가 피해야 할 식품 + print("\n📋 NAFLD 환자가 피해야 할 식품:") + self.cursor.execute("SELECT * FROM get_foods_to_avoid('K76.0')") + avoid_foods = self.cursor.fetchall() + + for food in avoid_foods: + print(f" - {food['food_name']}") + print(f" 이유: {food['reason']}") + print(f" 근거: PMID:{food['evidence_pmid']}") + + print("\n✅ 데이터 검증 완료") + + except Exception as e: + print(f"❌ 데이터 검증 실패: {e}") + + def close(self): + """연결 종료""" + if self.conn: + self.conn.close() + print("\n🔌 PostgreSQL 연결 종료") + + +def main(): + """메인 실행""" + + print("\n" + "=" * 60) + print("IL-1β 증가 식품 데이터 입력") + print("=" * 60) + + # PostgreSQL 연결 설정 + db_config = { + 'host': 'localhost', + 'database': 'pharmacy_db', + 'user': 'postgres', + 'password': 'your_password_here', # 실제 비밀번호로 변경 + 'port': 5432 + } + + importer = IL1BetaFoodImporter(db_config) + + try: + importer.connect() + importer.import_il1beta_foods() + importer.verify_data() + + print("\n" + "=" * 60) + print("✅ 모든 작업 완료!") + print("=" * 60) + + except Exception as e: + print(f"\n❌ 작업 실패: {e}") + finally: + importer.close() + + +if __name__ == '__main__': + main()