Initial commit: 동물약 복약안내 PDF API
This commit is contained in:
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.egg-info/
|
||||||
|
.eggs/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
venv/
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Output
|
||||||
|
output/
|
||||||
|
*.pdf
|
||||||
|
*.png
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
3
animal_med/__init__.py
Normal file
3
animal_med/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .renderer import AnimalMedRenderer
|
||||||
|
|
||||||
|
__all__ = ['AnimalMedRenderer']
|
||||||
205
animal_med/renderer.py
Normal file
205
animal_med/renderer.py
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
동물약 복약안내 PDF 렌더러
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Dict, Optional
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
|
|
||||||
|
class AnimalMedRenderer:
|
||||||
|
"""동물약 복약안내 PDF 렌더링 API"""
|
||||||
|
|
||||||
|
def __init__(self, data_path: str = None, template_path: str = None):
|
||||||
|
base_dir = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
self.data_path = data_path or os.path.join(base_dir, 'data', 'mock_drugs.json')
|
||||||
|
self.template_dir = template_path or os.path.join(base_dir, 'templates')
|
||||||
|
|
||||||
|
# Jinja2 템플릿 환경
|
||||||
|
self.jinja_env = Environment(
|
||||||
|
loader=FileSystemLoader(self.template_dir),
|
||||||
|
autoescape=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# 약품 데이터 로드
|
||||||
|
self._load_drug_data()
|
||||||
|
|
||||||
|
def _load_drug_data(self):
|
||||||
|
"""약품 데이터 로드"""
|
||||||
|
with open(self.data_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
self.drugs = {drug['apc_code']: drug for drug in data['drugs']}
|
||||||
|
self.metadata = data.get('metadata', {})
|
||||||
|
|
||||||
|
def get_drug(self, apc_code: str) -> Optional[Dict]:
|
||||||
|
"""APC 코드로 약품 조회"""
|
||||||
|
return self.drugs.get(apc_code)
|
||||||
|
|
||||||
|
def get_drugs(self, apc_codes: List[str]) -> List[Dict]:
|
||||||
|
"""여러 APC 코드로 약품 조회"""
|
||||||
|
result = []
|
||||||
|
for code in apc_codes:
|
||||||
|
drug = self.get_drug(code)
|
||||||
|
if drug:
|
||||||
|
result.append(drug)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def render_html(
|
||||||
|
self,
|
||||||
|
apc_codes: List[str],
|
||||||
|
patient_name: str = "홍길동",
|
||||||
|
pet_name: str = "뽀삐",
|
||||||
|
pet_species: str = "개",
|
||||||
|
pet_age: str = "3세",
|
||||||
|
pharmacy_name: str = "청춘약국",
|
||||||
|
pharmacy_tel: str = "033-481-7390",
|
||||||
|
pharmacy_address: str = "강원 양구군 양구읍 양구새싹로 7-3"
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
APC 코드 배열을 받아 HTML 복약안내문 렌더링
|
||||||
|
|
||||||
|
Args:
|
||||||
|
apc_codes: 약품 APC 코드 배열
|
||||||
|
patient_name: 보호자명
|
||||||
|
pet_name: 반려동물 이름
|
||||||
|
pet_species: 동물 종류
|
||||||
|
pet_age: 나이
|
||||||
|
pharmacy_name: 약국명
|
||||||
|
pharmacy_tel: 전화번호
|
||||||
|
pharmacy_address: 주소
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
렌더링된 HTML 문자열
|
||||||
|
"""
|
||||||
|
drugs = self.get_drugs(apc_codes)
|
||||||
|
|
||||||
|
if not drugs:
|
||||||
|
raise ValueError(f"유효한 약품을 찾을 수 없습니다: {apc_codes}")
|
||||||
|
|
||||||
|
template = self.jinja_env.get_template('medication_guide.html')
|
||||||
|
|
||||||
|
html = template.render(
|
||||||
|
drugs=drugs,
|
||||||
|
patient_name=patient_name,
|
||||||
|
pet_name=pet_name,
|
||||||
|
pet_species=pet_species,
|
||||||
|
pet_age=pet_age,
|
||||||
|
pharmacy_name=pharmacy_name,
|
||||||
|
pharmacy_tel=pharmacy_tel,
|
||||||
|
pharmacy_address=pharmacy_address,
|
||||||
|
issue_date=datetime.now().strftime('%Y년 %m월 %d일')
|
||||||
|
)
|
||||||
|
|
||||||
|
return html
|
||||||
|
|
||||||
|
def render_to_pdf(
|
||||||
|
self,
|
||||||
|
apc_codes: List[str],
|
||||||
|
output_path: str = None,
|
||||||
|
**kwargs
|
||||||
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
APC 코드 배열을 받아 PDF 복약안내문 생성
|
||||||
|
|
||||||
|
Args:
|
||||||
|
apc_codes: 약품 APC 코드 배열
|
||||||
|
output_path: 출력 PDF 경로
|
||||||
|
**kwargs: render_html에 전달할 추가 인자
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{
|
||||||
|
'success': True,
|
||||||
|
'pdf_path': '/path/to/file.pdf',
|
||||||
|
'drug_count': 3,
|
||||||
|
'drugs': [약품명 목록]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
# HTML 렌더링
|
||||||
|
html_content = self.render_html(apc_codes, **kwargs)
|
||||||
|
|
||||||
|
# 출력 경로
|
||||||
|
if not output_path:
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
output_path = os.path.join(tempfile.gettempdir(), f'animal_med_{int(time.time())}.pdf')
|
||||||
|
|
||||||
|
# HTML → PDF
|
||||||
|
async def _convert():
|
||||||
|
from playwright.async_api import async_playwright
|
||||||
|
|
||||||
|
async with async_playwright() as p:
|
||||||
|
browser = await p.chromium.launch(headless=True)
|
||||||
|
|
||||||
|
# A4 크기 viewport (210mm x 297mm @ 96dpi)
|
||||||
|
page = await browser.new_page(viewport={'width': 794, 'height': 1123})
|
||||||
|
|
||||||
|
await page.set_content(html_content, wait_until='networkidle')
|
||||||
|
|
||||||
|
# 콘텐츠 크기 확인
|
||||||
|
rect = await page.evaluate('''() => {
|
||||||
|
const el = document.querySelector('.container');
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
return { width: rect.width, height: rect.height };
|
||||||
|
}''')
|
||||||
|
|
||||||
|
# A4 (595 x 842 pt)에 맞는 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) # 최대 1.0
|
||||||
|
|
||||||
|
# PDF 생성
|
||||||
|
await page.pdf(
|
||||||
|
path=output_path,
|
||||||
|
format='A4',
|
||||||
|
print_background=True,
|
||||||
|
margin={'top': '0', 'right': '0', 'bottom': '0', 'left': '0'},
|
||||||
|
scale=scale
|
||||||
|
)
|
||||||
|
|
||||||
|
await browser.close()
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
except RuntimeError:
|
||||||
|
loop = None
|
||||||
|
|
||||||
|
if loop and loop.is_running():
|
||||||
|
import concurrent.futures
|
||||||
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
|
future = executor.submit(asyncio.run, _convert())
|
||||||
|
future.result()
|
||||||
|
else:
|
||||||
|
asyncio.run(_convert())
|
||||||
|
|
||||||
|
drugs = self.get_drugs(apc_codes)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'pdf_path': output_path,
|
||||||
|
'drug_count': len(drugs),
|
||||||
|
'drugs': [d['name'] for d in drugs]
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {'success': False, 'error': str(e)}
|
||||||
|
|
||||||
|
def list_drugs(self) -> List[Dict]:
|
||||||
|
"""모든 약품 목록 반환"""
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'apc_code': d['apc_code'],
|
||||||
|
'name': d['name'],
|
||||||
|
'category': d['category'],
|
||||||
|
'target_animal': d['target_animal']
|
||||||
|
}
|
||||||
|
for d in self.drugs.values()
|
||||||
|
]
|
||||||
209
data/mock_drugs.json
Normal file
209
data/mock_drugs.json
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
{
|
||||||
|
"drugs": [
|
||||||
|
{
|
||||||
|
"apc_code": "0519012001",
|
||||||
|
"name": "아목시실린 정",
|
||||||
|
"english_name": "Amoxicillin Tab",
|
||||||
|
"manufacturer": "한국동물약품",
|
||||||
|
"category": "항생제",
|
||||||
|
"target_animal": ["개", "고양이"],
|
||||||
|
"administration": "경구투여",
|
||||||
|
"image_url": "/images/amoxicillin.jpg",
|
||||||
|
"ingredients": "아목시실린트리하이드레이트 250mg",
|
||||||
|
"efficacy": "세균성 감염증 치료. 피부감염, 요로감염, 호흡기감염 등에 효과적입니다.",
|
||||||
|
"dosage": "체중 1kg당 12.5~25mg을 1일 2회, 5~7일간 투여",
|
||||||
|
"precautions": [
|
||||||
|
"페니실린 계열 알러지가 있는 동물에게 투여 금지",
|
||||||
|
"신장 기능 저하 동물은 용량 조절 필요",
|
||||||
|
"임신 중인 동물에게는 수의사와 상담 후 투여"
|
||||||
|
],
|
||||||
|
"storage": "실온(1~30℃) 보관, 직사광선 피할 것",
|
||||||
|
"shelf_life": "제조일로부터 24개월"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"apc_code": "0519023002",
|
||||||
|
"name": "엔로플록사신 정",
|
||||||
|
"english_name": "Enrofloxacin Tab",
|
||||||
|
"manufacturer": "바이엘동물약품",
|
||||||
|
"category": "항생제",
|
||||||
|
"target_animal": ["개", "고양이"],
|
||||||
|
"administration": "경구투여",
|
||||||
|
"image_url": "/images/enrofloxacin.jpg",
|
||||||
|
"ingredients": "엔로플록사신 50mg",
|
||||||
|
"efficacy": "그람양성균 및 그람음성균에 의한 감염증 치료. 피부, 비뇨기, 호흡기 감염에 효과적.",
|
||||||
|
"dosage": "체중 1kg당 5mg을 1일 1회, 7~14일간 투여",
|
||||||
|
"precautions": [
|
||||||
|
"⚠️ 고양이 망막 독성 주의! 권장용량 초과 금지",
|
||||||
|
"성장기 동물에게는 연골 손상 가능성 있음",
|
||||||
|
"간질 병력이 있는 동물 주의"
|
||||||
|
],
|
||||||
|
"storage": "실온 보관, 습기 피할 것",
|
||||||
|
"shelf_life": "제조일로부터 36개월"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"apc_code": "0519034003",
|
||||||
|
"name": "프레드니솔론 정",
|
||||||
|
"english_name": "Prednisolone Tab",
|
||||||
|
"manufacturer": "녹십자수의약품",
|
||||||
|
"category": "스테로이드",
|
||||||
|
"target_animal": ["개", "고양이"],
|
||||||
|
"administration": "경구투여",
|
||||||
|
"image_url": "/images/prednisolone.jpg",
|
||||||
|
"ingredients": "프레드니솔론 5mg",
|
||||||
|
"efficacy": "염증성 질환, 알러지성 질환, 자가면역 질환의 치료",
|
||||||
|
"dosage": "항염증: 체중 1kg당 0.5~1mg, 1일 1~2회\n면역억제: 체중 1kg당 2~4mg, 1일 2회",
|
||||||
|
"precautions": [
|
||||||
|
"장기 투여 시 부신기능 저하 주의",
|
||||||
|
"당뇨병, 쿠싱증후군 동물 금기",
|
||||||
|
"갑자기 중단하지 말고 서서히 감량"
|
||||||
|
],
|
||||||
|
"storage": "실온 보관",
|
||||||
|
"shelf_life": "제조일로부터 24개월"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"apc_code": "0519045004",
|
||||||
|
"name": "세파렉신 캡슐",
|
||||||
|
"english_name": "Cephalexin Cap",
|
||||||
|
"manufacturer": "한국동물약품",
|
||||||
|
"category": "항생제",
|
||||||
|
"target_animal": ["개"],
|
||||||
|
"administration": "경구투여",
|
||||||
|
"image_url": "/images/cephalexin.jpg",
|
||||||
|
"ingredients": "세파렉신모노하이드레이트 500mg",
|
||||||
|
"efficacy": "피부 및 연조직 감염, 요로감염, 골관절 감염 치료",
|
||||||
|
"dosage": "체중 1kg당 22~30mg을 1일 2회, 7~28일간 투여",
|
||||||
|
"precautions": [
|
||||||
|
"세팔로스포린 계열 과민증 동물 금기",
|
||||||
|
"음식과 함께 투여 시 흡수 증가",
|
||||||
|
"신장 기능 저하 시 용량 조절"
|
||||||
|
],
|
||||||
|
"storage": "실온 보관, 개봉 후 습기 주의",
|
||||||
|
"shelf_life": "제조일로부터 24개월"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"apc_code": "0519056005",
|
||||||
|
"name": "메트로니다졸 정",
|
||||||
|
"english_name": "Metronidazole Tab",
|
||||||
|
"manufacturer": "동방동물약품",
|
||||||
|
"category": "항생제/항원충제",
|
||||||
|
"target_animal": ["개", "고양이"],
|
||||||
|
"administration": "경구투여",
|
||||||
|
"image_url": "/images/metronidazole.jpg",
|
||||||
|
"ingredients": "메트로니다졸 250mg",
|
||||||
|
"efficacy": "지아디아증, 혐기성 세균 감염, 염증성 장질환 치료",
|
||||||
|
"dosage": "체중 1kg당 10~25mg을 1일 2회, 5~7일간 투여",
|
||||||
|
"precautions": [
|
||||||
|
"임신 동물 금기 (기형 유발 가능)",
|
||||||
|
"간 질환 동물 용량 감소 필요",
|
||||||
|
"고용량 장기 투여 시 신경독성 주의"
|
||||||
|
],
|
||||||
|
"storage": "차광 보관, 실온",
|
||||||
|
"shelf_life": "제조일로부터 24개월"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"apc_code": "0519067006",
|
||||||
|
"name": "트라마돌 정",
|
||||||
|
"english_name": "Tramadol Tab",
|
||||||
|
"manufacturer": "녹십자수의약품",
|
||||||
|
"category": "진통제",
|
||||||
|
"target_animal": ["개"],
|
||||||
|
"administration": "경구투여",
|
||||||
|
"image_url": "/images/tramadol.jpg",
|
||||||
|
"ingredients": "트라마돌염산염 50mg",
|
||||||
|
"efficacy": "중등도 이상의 통증 관리. 수술 후 통증, 골관절염 통증 완화",
|
||||||
|
"dosage": "체중 1kg당 2~5mg을 1일 2~3회 투여",
|
||||||
|
"precautions": [
|
||||||
|
"⚠️ 고양이에게는 사용 금지 (세로토닌 증후군 위험)",
|
||||||
|
"간질 병력 동물 주의",
|
||||||
|
"MAO 억제제와 병용 금기"
|
||||||
|
],
|
||||||
|
"storage": "실온 보관",
|
||||||
|
"shelf_life": "제조일로부터 36개월"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"apc_code": "0519078007",
|
||||||
|
"name": "오메프라졸 캡슐",
|
||||||
|
"english_name": "Omeprazole Cap",
|
||||||
|
"manufacturer": "한국동물약품",
|
||||||
|
"category": "위장관약",
|
||||||
|
"target_animal": ["개", "고양이"],
|
||||||
|
"administration": "경구투여",
|
||||||
|
"image_url": "/images/omeprazole.jpg",
|
||||||
|
"ingredients": "오메프라졸 20mg",
|
||||||
|
"efficacy": "위산 과다 분비 억제. 위궤양, 역류성 식도염, 스트레스성 위염 치료",
|
||||||
|
"dosage": "체중 1kg당 0.5~1mg을 1일 1회, 공복에 투여",
|
||||||
|
"precautions": [
|
||||||
|
"장기 투여 시 비타민 B12 흡수 저하 가능",
|
||||||
|
"캡슐을 씹거나 부수지 말 것",
|
||||||
|
"식전 30분~1시간 전 투여 권장"
|
||||||
|
],
|
||||||
|
"storage": "실온 보관, 습기 피할 것",
|
||||||
|
"shelf_life": "제조일로부터 24개월"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"apc_code": "0519089008",
|
||||||
|
"name": "아포퀠 정",
|
||||||
|
"english_name": "Apoquel Tab",
|
||||||
|
"manufacturer": "조에티스",
|
||||||
|
"category": "피부과약",
|
||||||
|
"target_animal": ["개"],
|
||||||
|
"administration": "경구투여",
|
||||||
|
"image_url": "/images/apoquel.jpg",
|
||||||
|
"ingredients": "오클라시티닙말레산염 16mg",
|
||||||
|
"efficacy": "아토피성 피부염 및 알러지성 피부염에 의한 가려움증 치료",
|
||||||
|
"dosage": "체중 1kg당 0.4~0.6mg을 1일 2회(14일간), 이후 1일 1회 유지",
|
||||||
|
"precautions": [
|
||||||
|
"12개월 미만 강아지 금기",
|
||||||
|
"심각한 감염증이 있는 경우 주의",
|
||||||
|
"악성 종양 병력 동물 주의"
|
||||||
|
],
|
||||||
|
"storage": "실온 보관",
|
||||||
|
"shelf_life": "제조일로부터 36개월"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"apc_code": "0519090009",
|
||||||
|
"name": "밀베마이신 정",
|
||||||
|
"english_name": "Milbemycin Tab",
|
||||||
|
"manufacturer": "노바티스동물약품",
|
||||||
|
"category": "구충제",
|
||||||
|
"target_animal": ["개"],
|
||||||
|
"administration": "경구투여",
|
||||||
|
"image_url": "/images/milbemycin.jpg",
|
||||||
|
"ingredients": "밀베마이신옥심 12.5mg",
|
||||||
|
"efficacy": "심장사상충 예방, 회충/구충/편충/조충 구제",
|
||||||
|
"dosage": "월 1회, 체중에 따라 1정 또는 분할 투여",
|
||||||
|
"precautions": [
|
||||||
|
"콜리 품종은 MDR1 유전자 검사 후 투여",
|
||||||
|
"심장사상충 감염견에게 투여 시 쇼크 위험",
|
||||||
|
"음식과 함께 투여 권장"
|
||||||
|
],
|
||||||
|
"storage": "실온 보관, 직사광선 피할 것",
|
||||||
|
"shelf_life": "제조일로부터 24개월"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"apc_code": "0519001010",
|
||||||
|
"name": "클로르헥시딘 샴푸",
|
||||||
|
"english_name": "Chlorhexidine Shampoo",
|
||||||
|
"manufacturer": "비르박동물약품",
|
||||||
|
"category": "피부외용제",
|
||||||
|
"target_animal": ["개", "고양이"],
|
||||||
|
"administration": "외용",
|
||||||
|
"image_url": "/images/chlorhexidine_shampoo.jpg",
|
||||||
|
"ingredients": "클로르헥시딘글루콘산염 2%",
|
||||||
|
"efficacy": "세균성/진균성 피부염, 농피증, 피부 감염 예방 및 치료",
|
||||||
|
"dosage": "주 2~3회, 피모를 적신 후 충분히 거품을 내어 5~10분간 방치 후 헹굼",
|
||||||
|
"precautions": [
|
||||||
|
"눈, 귀, 점막 부위 접촉 피할 것",
|
||||||
|
"상처가 깊은 경우 사용 금지",
|
||||||
|
"사용 후 완전히 헹궈낼 것"
|
||||||
|
],
|
||||||
|
"storage": "실온 보관, 직사광선 피할 것",
|
||||||
|
"shelf_life": "제조일로부터 24개월"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"created": "2026-03-18",
|
||||||
|
"description": "동물약 복약안내 목업 데이터"
|
||||||
|
}
|
||||||
|
}
|
||||||
346
templates/medication_guide.html
Normal file
346
templates/medication_guide.html
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>동물약 복약안내문</title>
|
||||||
|
<style>
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap');
|
||||||
|
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
width: 210mm;
|
||||||
|
height: 297mm;
|
||||||
|
font-family: 'Noto Sans KR', sans-serif;
|
||||||
|
font-size: 10pt;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.4;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 210mm;
|
||||||
|
height: 297mm;
|
||||||
|
padding: 8mm;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 헤더 */
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
border-bottom: 3px solid #2E7D32;
|
||||||
|
padding-bottom: 4mm;
|
||||||
|
margin-bottom: 4mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left { flex: 1; }
|
||||||
|
|
||||||
|
.patient-name {
|
||||||
|
font-size: 18pt;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1B5E20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-info {
|
||||||
|
font-size: 9pt;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 2mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-center {
|
||||||
|
text-align: center;
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pharmacy-name {
|
||||||
|
font-size: 16pt;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #2E7D32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pharmacy-slogan {
|
||||||
|
font-size: 9pt;
|
||||||
|
color: #4CAF50;
|
||||||
|
margin-top: 1mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 8pt;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 건강정보 */
|
||||||
|
.health-info {
|
||||||
|
background: linear-gradient(135deg, #E8F5E9, #C8E6C9);
|
||||||
|
border-radius: 2mm;
|
||||||
|
padding: 3mm 4mm;
|
||||||
|
margin-bottom: 4mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.health-title {
|
||||||
|
font-size: 11pt;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1B5E20;
|
||||||
|
margin-bottom: 1mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.health-content {
|
||||||
|
font-size: 8pt;
|
||||||
|
color: #33691E;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 약품 카드 */
|
||||||
|
.drug-cards {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 3mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-card {
|
||||||
|
width: calc(50% - 1.5mm);
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #C8E6C9;
|
||||||
|
border-radius: 2mm;
|
||||||
|
overflow: hidden;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-card.full-width { width: 100%; }
|
||||||
|
|
||||||
|
.drug-header {
|
||||||
|
display: flex;
|
||||||
|
background: #E8F5E9;
|
||||||
|
padding: 2mm;
|
||||||
|
gap: 2mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-image {
|
||||||
|
width: 15mm;
|
||||||
|
height: 15mm;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 1mm;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 20pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-title { flex: 1; }
|
||||||
|
|
||||||
|
.drug-name {
|
||||||
|
font-size: 10pt;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1B5E20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-category {
|
||||||
|
display: inline-block;
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
font-size: 7pt;
|
||||||
|
padding: 0.5mm 2mm;
|
||||||
|
border-radius: 1mm;
|
||||||
|
margin-top: 1mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.target-animals {
|
||||||
|
display: flex;
|
||||||
|
gap: 1mm;
|
||||||
|
margin-top: 1mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animal-badge {
|
||||||
|
background: #81C784;
|
||||||
|
color: white;
|
||||||
|
font-size: 6pt;
|
||||||
|
padding: 0.3mm 1.5mm;
|
||||||
|
border-radius: 0.5mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-english {
|
||||||
|
font-size: 6pt;
|
||||||
|
color: #888;
|
||||||
|
margin-top: 0.5mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-body {
|
||||||
|
padding: 2mm;
|
||||||
|
font-size: 7pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-section { margin-bottom: 1.5mm; }
|
||||||
|
|
||||||
|
.drug-section-title {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2E7D32;
|
||||||
|
font-size: 7pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-section-content {
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 용법용량 */
|
||||||
|
.dosage-highlight {
|
||||||
|
background: #E3F2FD;
|
||||||
|
border: 1px solid #64B5F6;
|
||||||
|
border-radius: 1.5mm;
|
||||||
|
padding: 1.5mm 2mm;
|
||||||
|
margin-top: 1.5mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dosage-title {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1565C0;
|
||||||
|
font-size: 7pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dosage-content {
|
||||||
|
color: #0D47A1;
|
||||||
|
font-size: 7pt;
|
||||||
|
margin-top: 0.5mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 주의사항 */
|
||||||
|
.caution-box {
|
||||||
|
background: #FFF3E0;
|
||||||
|
border: 1px solid #FFB74D;
|
||||||
|
border-radius: 1.5mm;
|
||||||
|
padding: 1.5mm 2mm;
|
||||||
|
margin-top: 1.5mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caution-title {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #E65100;
|
||||||
|
font-size: 7pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caution-list {
|
||||||
|
list-style: none;
|
||||||
|
margin-top: 0.5mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caution-list li {
|
||||||
|
font-size: 6.5pt;
|
||||||
|
color: #BF360C;
|
||||||
|
margin-bottom: 0.3mm;
|
||||||
|
padding-left: 2mm;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caution-list li::before {
|
||||||
|
content: "•";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 푸터 */
|
||||||
|
.footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5mm;
|
||||||
|
left: 8mm;
|
||||||
|
right: 8mm;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 7pt;
|
||||||
|
color: #999;
|
||||||
|
border-top: 1px solid #E0E0E0;
|
||||||
|
padding-top: 2mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-logo {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #2E7D32;
|
||||||
|
font-size: 9pt;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<!-- 헤더 -->
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left">
|
||||||
|
<div class="patient-name">{{ patient_name }} 보호자님</div>
|
||||||
|
<div class="patient-info">
|
||||||
|
환자: {{ pet_name }} ({{ pet_species }}, {{ pet_age }})<br>
|
||||||
|
조제일: {{ issue_date }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="header-center">
|
||||||
|
<div class="pharmacy-name">🐾 {{ pharmacy_name }}</div>
|
||||||
|
<div class="pharmacy-slogan">반려동물의 건강한 삶을 위한 복약안내</div>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
{{ pharmacy_tel }}<br>
|
||||||
|
{{ pharmacy_address }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 건강정보 -->
|
||||||
|
<div class="health-info">
|
||||||
|
<div class="health-title">💊 약사의 복약 안내</div>
|
||||||
|
<div class="health-content">
|
||||||
|
처방된 약품을 정확한 용법에 따라 투여해 주세요.
|
||||||
|
투약 중 이상반응이 나타나면 즉시 투약을 중단하고 수의사 또는 약사에게 상담하세요.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 약품 카드들 -->
|
||||||
|
<div class="drug-cards">
|
||||||
|
{% for drug in drugs %}
|
||||||
|
<div class="drug-card">
|
||||||
|
<div class="drug-header">
|
||||||
|
<div class="drug-image">💊</div>
|
||||||
|
<div class="drug-title">
|
||||||
|
<div class="drug-name">{{ drug.name }}</div>
|
||||||
|
<span class="drug-category">{{ drug.category }}</span>
|
||||||
|
<div class="target-animals">
|
||||||
|
{% for animal in drug.target_animal %}
|
||||||
|
<span class="animal-badge">{{ animal }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="drug-english">{{ drug.english_name }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="drug-body">
|
||||||
|
<div class="drug-section">
|
||||||
|
<div class="drug-section-title">성분</div>
|
||||||
|
<div class="drug-section-content">{{ drug.ingredients }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="drug-section">
|
||||||
|
<div class="drug-section-title">효능효과</div>
|
||||||
|
<div class="drug-section-content">{{ drug.efficacy }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dosage-highlight">
|
||||||
|
<div class="dosage-title">📋 용법용량</div>
|
||||||
|
<div class="dosage-content">{{ drug.dosage }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="caution-box">
|
||||||
|
<div class="caution-title">⚠️ 주의사항</div>
|
||||||
|
<ul class="caution-list">
|
||||||
|
{% for precaution in drug.precautions %}
|
||||||
|
<li>{{ precaution }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 푸터 -->
|
||||||
|
<div class="footer">
|
||||||
|
<div class="footer-logo">🐾 동물약 복약안내 시스템</div>
|
||||||
|
※ 본 복약안내문은 참고용이며, 정확한 투약은 반드시 수의사의 지시에 따라 주세요.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
66
test_render.py
Normal file
66
test_render.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
동물약 복약안내 PDF 테스트
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
|
||||||
|
from animal_med import AnimalMedRenderer
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 60)
|
||||||
|
print("동물약 복약안내 PDF 테스트")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
renderer = AnimalMedRenderer()
|
||||||
|
|
||||||
|
# 약품 목록 확인
|
||||||
|
print("\n[1] 등록된 약품 목록:")
|
||||||
|
for drug in renderer.list_drugs():
|
||||||
|
print(f" {drug['apc_code']} - {drug['name']} ({drug['category']})")
|
||||||
|
|
||||||
|
# PDF 렌더링 테스트 (3개 약품)
|
||||||
|
test_codes = ["0519012001", "0519023002", "0519034003"]
|
||||||
|
|
||||||
|
print(f"\n[2] PDF 렌더링 테스트 ({len(test_codes)}개 약품)")
|
||||||
|
print(f" APC 코드: {test_codes}")
|
||||||
|
|
||||||
|
output_dir = os.path.join(os.path.dirname(__file__), 'output')
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
pdf_path = os.path.join(output_dir, 'animal_medication_guide.pdf')
|
||||||
|
|
||||||
|
result = renderer.render_to_pdf(
|
||||||
|
apc_codes=test_codes,
|
||||||
|
output_path=pdf_path,
|
||||||
|
patient_name="김철수",
|
||||||
|
pet_name="뽀삐",
|
||||||
|
pet_species="포메라니안",
|
||||||
|
pet_age="3세"
|
||||||
|
)
|
||||||
|
|
||||||
|
if result['success']:
|
||||||
|
print(f" ✅ 성공!")
|
||||||
|
print(f" 📄 PDF: {result['pdf_path']}")
|
||||||
|
print(f" 약품: {', '.join(result['drugs'])}")
|
||||||
|
|
||||||
|
# 파일 크기
|
||||||
|
size = os.path.getsize(pdf_path)
|
||||||
|
print(f" 크기: {size / 1024:.1f} KB")
|
||||||
|
|
||||||
|
# PDF 페이지 수
|
||||||
|
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()
|
||||||
Reference in New Issue
Block a user