diff --git a/animal_med/renderer_v2.py b/animal_med/renderer_v2.py index 1f9d68f..39011d7 100644 --- a/animal_med/renderer_v2.py +++ b/animal_med/renderer_v2.py @@ -129,13 +129,8 @@ class AnimalMedRendererV2: return { width: rect.width, height: rect.height }; }''') - # A4에 맞는 scale 계산 - content_width_pt = rect['width'] * 0.75 - content_height_pt = rect['height'] * 0.75 - - scale_x = 595 / content_width_pt if content_width_pt > 0 else 1 - scale_y = 842 / content_height_pt if content_height_pt > 0 else 1 - scale = min(scale_x, scale_y, 1.0) + # scale 고정 (자연스러운 페이지 분할) + scale = 1.0 await page.pdf( path=output_path, diff --git a/data/master/chlorhexidine_shampoo.json b/data/master/chlorhexidine_shampoo.json new file mode 100644 index 0000000..2ba7970 --- /dev/null +++ b/data/master/chlorhexidine_shampoo.json @@ -0,0 +1,39 @@ +{ + "product_id": "MASTER-009", + "apc_code": "0519-CHLORHEX-SHAMPOO", + "name": "클로르헥시딘 샴푸", + "english_name": "Chlorhexidine Shampoo 2%", + "manufacturer": "비르박", + "category": "topical", + "category_display": "피부외용제 (샴푸)", + "target_animal": ["개", "고양이"], + "administration": "외용 (샴푸)", + + "indication": "세균성/진균성 피부염, 농피증, 피부 감염 예방", + + "mechanism": { + "drug_class": "비구아나이드계 소독제", + "action": "세균 세포막 파괴 → 살균 작용", + "spectrum": "그람양성균, 그람음성균, 효모균" + }, + + "dosage": { + "frequency": "주 2~3회", + "contact_time": "5~10분간 방치 후 헹굼", + "duration": "증상 호전 시까지 (보통 2~4주)" + }, + + "warnings": [ + "⚠️ 눈, 귀, 점막 접촉 피할 것", + "⚠️ 사용 후 완전히 헹굴 것", + "⚠️ 깊은 상처에는 사용 금지" + ], + + "clinical_notes": [ + "경구 항생제와 병용 시 효과 증가", + "거품을 충분히 내어 피부에 접촉시키는 것이 중요", + "피부 건조 시 보습제 병용 권장" + ], + + "storage": "실온 보관, 직사광선 피할 것" +} diff --git a/data/master/fronilspot.json b/data/master/fronilspot.json new file mode 100644 index 0000000..57700c2 --- /dev/null +++ b/data/master/fronilspot.json @@ -0,0 +1,48 @@ +{ + "product_id": "MASTER-006", + "apc_code": "0519-FRONILSPOT", + "name": "프로닐스팟", + "english_name": "Fronil Spot (Fipronil)", + "manufacturer": "한국동물약품", + "category": "antiparasitic", + "category_display": "외부구충제 (스팟온)", + "target_animal": ["개", "고양이"], + "administration": "경피 도포 (스팟온)", + + "indication": "벼룩, 진드기 구제", + + "coverage": { + "external": { + "fleas": {"covered": true, "note": "4~8시간 내 사멸"}, + "ticks": {"covered": true, "note": "접촉 사멸"} + }, + "heartworm": {"covered": false}, + "intestinal": { + "roundworm": {"covered": false}, + "hookworm": {"covered": false}, + "whipworm": {"covered": false}, + "tapeworm": {"covered": false} + } + }, + + "coverage_summary": { + "covered": ["벼룩", "진드기"], + "not_covered": ["심장사상충", "내부기생충"], + "gap_solution": "심장사상충: 하트세이버 / 내부기생충: 안텔민 추가" + }, + + "dosing": { + "interval": "매월 1회", + "interval_reason": "효능 지속 4주", + "minimum_age": "8주 이상", + "minimum_weight": "제한 없음" + }, + + "warnings": [ + "⚠️ 도포 후 2일간 목욕/수영 금지", + "⚠️ 토끼에게는 독성 (사용 금지)", + "⚠️ 피부 상처 부위 도포 금지" + ], + + "storage": "실온 보관, 직사광선 피할 것" +} diff --git a/data/master/heartsaver.json b/data/master/heartsaver.json new file mode 100644 index 0000000..723e8f3 --- /dev/null +++ b/data/master/heartsaver.json @@ -0,0 +1,51 @@ +{ + "product_id": "MASTER-005", + "apc_code": "0519-HEARTSAVER", + "name": "하트세이버 츄어블", + "english_name": "Heartsaver (Ivermectin + Pyrantel)", + "manufacturer": "중앙바이오텍", + "category": "antiparasitic", + "category_display": "심장사상충 예방", + "target_animal": ["개"], + "administration": "경구 (츄어블)", + + "indication": "심장사상충 예방 + 회충/구충 구제", + + "coverage": { + "external": { + "fleas": {"covered": false, "note": ""}, + "ticks": {"covered": false, "note": ""} + }, + "heartworm": { + "covered": true, + "note": "L3/L4 유충 구제" + }, + "intestinal": { + "roundworm": {"covered": true, "note": "회충"}, + "hookworm": {"covered": true, "note": "구충"}, + "whipworm": {"covered": false, "note": ""}, + "tapeworm": {"covered": false, "note": ""} + } + }, + + "coverage_summary": { + "covered": ["심장사상충", "회충", "구충"], + "not_covered": ["벼룩", "진드기", "편충", "조충"], + "gap_solution": "외부구충: 프로닐스팟 / 편충·조충: 안텔민 추가" + }, + + "dosing": { + "interval": "매월 1회", + "interval_reason": "심장사상충 윈도우 30일", + "minimum_age": "6주 이상", + "minimum_weight": "제한 없음" + }, + + "warnings": [ + "⚠️ 콜리 품종: MDR1 유전자 확인 필요", + "⚠️ 투약 전 심장사상충 검사 필수", + "⚠️ 감염견에 투약 시 쇼크 위험" + ], + + "storage": "실온 보관" +} diff --git a/data/master/oridermyl.json b/data/master/oridermyl.json new file mode 100644 index 0000000..42e0951 --- /dev/null +++ b/data/master/oridermyl.json @@ -0,0 +1,39 @@ +{ + "product_id": "MASTER-007", + "apc_code": "0519-ORIDERMYL", + "name": "오리더밀", + "english_name": "Oridermyl (Nystatin+Neomycin+Triamcinolone)", + "manufacturer": "TVM", + "category": "otic", + "category_display": "귀염증 치료제", + "target_animal": ["개", "고양이"], + "administration": "점이 (귀에 투약)", + + "indication": "외이염, 귀진드기 감염, 세균성/진균성 귀감염", + + "mechanism": { + "nystatin": "항진균 (칸디다, 말라세지아)", + "neomycin": "항균 (그람음성균)", + "triamcinolone": "항염증 (가려움, 부종 완화)", + "permethrin": "귀진드기 구제" + }, + + "dosage": { + "standard": "1일 1~2회", + "amount": "귀당 5~10방울", + "duration": "7~14일" + }, + + "warnings": [ + "⚠️ 고막 천공 시 사용 금지", + "⚠️ 투약 전 귀 세정 권장", + "⚠️ 장기 사용 시 내성균 주의" + ], + + "contraindications": [ + "고막 천공/손상", + "아미노글리코사이드 과민증" + ], + + "storage": "실온 보관" +} diff --git a/data/master/terbiderm.json b/data/master/terbiderm.json new file mode 100644 index 0000000..da983fb --- /dev/null +++ b/data/master/terbiderm.json @@ -0,0 +1,38 @@ +{ + "product_id": "MASTER-008", + "apc_code": "0519-TERBIDERM", + "name": "터비덤 스프레이", + "english_name": "Terbiderm (Terbinafine)", + "manufacturer": "동방동물약품", + "category": "antifungal", + "category_display": "항진균제 (피부)", + "target_animal": ["개", "고양이"], + "administration": "외용 (스프레이)", + + "indication": "피부사상균증 (백선), 말라세지아 피부염", + + "mechanism": { + "drug_class": "알릴아민계 항진균제", + "action": "스쿠알렌 에폭시다제 억제 → 진균 세포막 합성 차단" + }, + + "dosage": { + "frequency": "1일 1~2회", + "duration": "2~4주 (완치 후 1주 추가)", + "application": "병변 및 주변 2cm까지 도포" + }, + + "warnings": [ + "⚠️ 눈, 점막 접촉 피할 것", + "⚠️ 핥지 못하게 엘리자베스 칼라 권장", + "⚠️ 경구 항진균제 병용 시 효과 증가" + ], + + "clinical_notes": [ + "피부사상균 배양 검사 권장 (치료 전)", + "환경 소독 병행 필수 (재감염 방지)", + "장모종은 병변 주변 제모 권장" + ], + + "storage": "실온 보관, 화기 주의" +} diff --git a/templates/medication_guide_v2.html b/templates/medication_guide_v2.html index 118c726..f86a592 100644 --- a/templates/medication_guide_v2.html +++ b/templates/medication_guide_v2.html @@ -544,6 +544,31 @@ {% endif %} + {% elif drug.category in ['otic', 'antifungal', 'topical'] %} + +
+
📋 적응증
+
{{ drug.indication }}
+
+ +
+
💊 용법
+
+ {% if drug.dosage.frequency %} +
빈도: {{ drug.dosage.frequency }}
+ {% endif %} + {% if drug.dosage.duration %} +
기간: {{ drug.dosage.duration }}
+ {% endif %} + {% if drug.dosage.contact_time %} +
접촉시간: {{ drug.dosage.contact_time }}
+ {% endif %} + {% if drug.dosage.amount %} +
용량: {{ drug.dosage.amount }}
+ {% endif %} +
+
+ {% else %}
diff --git a/test_v2_8drugs.py b/test_v2_8drugs.py new file mode 100644 index 0000000..17c6f52 --- /dev/null +++ b/test_v2_8drugs.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +""" +v2 API 테스트 - 8개 약품 (2페이지) +""" + +import os +import sys +sys.path.insert(0, os.path.dirname(__file__)) + +from animal_med import AnimalMedRendererV2 + + +def main(): + print("=" * 60) + print("v2 API 테스트 - 8개 약품 (2페이지)") + print("=" * 60) + + renderer = AnimalMedRendererV2() + + # 마스터 약품 목록 + print("\n[1] 마스터 약품 목록:") + for drug in renderer.list_drugs(): + print(f" {drug['product_id']} - {drug['name']}") + + # 8개 약품 사용 (2x2 x 2페이지) + test_ids = [ + "MASTER-001", "MASTER-002", "MASTER-003", "MASTER-004", + "MASTER-005", "MASTER-006", "MASTER-007", "MASTER-008" + ] + + print(f"\n[2] PDF 렌더링 ({len(test_ids)}개 약품)") + + # PDF 생성 + output_dir = os.path.join(os.path.dirname(__file__), 'output') + os.makedirs(output_dir, exist_ok=True) + + pdf_path = os.path.join(output_dir, 'v2_8drugs_2pages.pdf') + + print(f"\n[3] PDF 생성 중...") + + result = renderer.render_to_pdf( + product_ids=test_ids, + output_path=pdf_path, + patient_name="박보호자", + pet_name="콩이", + pet_species="말티즈", + pet_age="5세" + ) + + if result['success']: + print(f" ✅ 성공!") + print(f" 📄 PDF: {result['pdf_path']}") + print(f" 약품: {len(result['drugs'])}개") + + size = os.path.getsize(pdf_path) + print(f" 크기: {size / 1024:.1f} KB") + + import fitz + doc = fitz.open(pdf_path) + print(f" 페이지 수: {len(doc)}") + doc.close() + else: + print(f" ❌ 실패: {result.get('error')}") + + print("\n" + "=" * 60) + + +if __name__ == "__main__": + main()