From 7b71ea0179daf6aa70c3b4f380faef6d5e9c0086 Mon Sep 17 00:00:00 2001 From: thug0bin Date: Wed, 11 Mar 2026 22:13:08 +0900 Subject: [PATCH] =?UTF-8?q?feat(pmr):=20=EB=9D=BC=EB=B2=A8=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EA=B0=9C=EC=84=A0=20+=20=EC=95=BD?= =?UTF-8?q?=ED=92=88=EB=AA=85=20=EC=A0=95=EA=B7=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 라벨 미리보기: - 지그재그 테두리 (가위로 자른 느낌) - 환자명 공백 + 폰트 확대 (44px) - 복용량 박스 + 총량 표시 - 시그니처 박스 (청 춘 약 국) - 조제일 표시 약품명 정규화: - 밀리그램/밀리그람 → mg - 마이크로그램 → μg - 그램/그람 → g - 밀리리터 → mL - 언더스코어(_) 뒤 내용 제거 - 대괄호 내용 제거 프론트엔드: - data-med-name 속성으로 순수 약품명 전달 - 번호/뱃지 제외된 이름 사용 --- backend/pmr_api.py | 182 +++++++++++++++++++++++++++++++------ backend/templates/pmr.html | 6 +- 2 files changed, 157 insertions(+), 31 deletions(-) diff --git a/backend/pmr_api.py b/backend/pmr_api.py index 084ec43..8fb65c6 100644 --- a/backend/pmr_api.py +++ b/backend/pmr_api.py @@ -577,10 +577,82 @@ def preview_label(): return jsonify({'success': False, 'error': str(e)}), 500 +def normalize_medication_name(med_name): + """ + 약품명 정제 - 밀리그램 등을 mg로 변환, 불필요한 부분 제거 + """ + import re + if not med_name: + return med_name + + # 언더스코어 뒤 내용 제거 (예: 휴니즈레바미피드정_ → 휴니즈레바미피드정) + med_name = re.sub(r'_.*$', '', med_name) + + # 대괄호 및 내용 제거 + med_name = re.sub(r'\[.*?\]', '', med_name) + med_name = re.sub(r'\[.*$', '', med_name) + + # 밀리그램 변환 + med_name = re.sub(r'밀리그램|밀리그람|미리그램|미리그람', 'mg', med_name) + # 마이크로그램 변환 + med_name = re.sub(r'마이크로그램|마이크로그람', 'μg', med_name) + # 그램 변환 (mg/μg 제외) + med_name = re.sub(r'(?= min_font_size: + try: + test_font = ImageFont.truetype(font_path, current_size) + except: + return ImageFont.load_default() + bbox = draw.textbbox((0, 0), text, font=test_font) + text_width = bbox[2] - bbox[0] + if text_width <= max_width: + return test_font + current_size -= 2 + try: + return ImageFont.truetype(font_path, min_font_size) + except: + return ImageFont.load_default() # 중앙 정렬 텍스트 함수 def draw_centered(text, y, font, fill="black"): @@ -650,33 +745,37 @@ def create_label_image(patient_name, med_name, add_info='', dosage=0, frequency= for line in name_lines: y = draw_centered(line, y, drug_font) - # 효능효과 (add_info) + # 효능효과 (add_info) - 동적 폰트 크기 적용 if add_info: - y = draw_centered(f"({add_info})", y, small_font, fill="gray") + efficacy_text = f"({add_info})" + adaptive_efficacy_font = get_adaptive_font(efficacy_text, label_width - 40, 30, 20) + y = draw_centered(efficacy_text, y, adaptive_efficacy_font, fill="black") y += 5 - # 총량 계산 + # 총량 계산 및 표시 if dosage > 0 and frequency > 0 and duration > 0: total = dosage * frequency * duration - total_str = str(int(total)) if total == int(total) else f"{total:.1f}" - total_text = f"총 {total_str}{unit} / {duration}일분" - y = draw_centered(total_text, y, info_font) + total_str = str(int(total)) if total == int(total) else f"{total:.2f}".rstrip('0').rstrip('.') + total_text = f"총{total_str}{unit}/{duration}일분" + y = draw_centered(total_text, y, additional_font) y += 5 - # 용법 박스 + # 용법 박스 (테두리 있는 박스) box_margin = 20 + box_height = 75 box_top = y - box_bottom = y + 70 + box_bottom = y + box_height + box_width = label_width - 2 * box_margin draw.rectangle( [(box_margin, box_top), (label_width - box_margin, box_bottom)], outline="black", width=2 ) - # 박스 내용 - dosage_str = str(int(dosage)) if dosage == int(dosage) else f"{dosage:.2f}".rstrip('0').rstrip('.') + # 박스 내용 - 1회 복용량 + dosage_str = str(int(dosage)) if dosage == int(dosage) else f"{dosage:.4f}".rstrip('0').rstrip('.') dosage_text = f"{dosage_str}{unit}" # 복용 시간 @@ -689,24 +788,51 @@ def create_label_image(patient_name, med_name, add_info='', dosage=0, frequency= else: time_text = f"1일 {frequency}회" - box_center_y = (box_top + box_bottom) // 2 - draw_centered(dosage_text, box_center_y - 20, info_font) - draw_centered(time_text, box_center_y + 5, info_font) + # 박스 내 텍스트 중앙 배치 (수직 중앙 정렬) + line_spacing = 5 + bbox1 = draw.textbbox((0, 0), dosage_text, font=info_font) + text1_height = bbox1[3] - bbox1[1] + bbox2 = draw.textbbox((0, 0), time_text, font=info_font) + text2_height = bbox2[3] - bbox2[1] + total_text_height = text1_height + line_spacing + text2_height + + center_y = (box_top + box_bottom) // 2 + start_y = center_y - (total_text_height // 2) - 5 # 약간 위로 조정 + + draw_centered(dosage_text, start_y, info_font) + draw_centered(time_text, start_y + text1_height + line_spacing, info_font) y = box_bottom + 10 - # 조제일 + # 조제일 (시그니처 위쪽에 배치) today = datetime.now().strftime('%Y-%m-%d') - y = draw_centered(f"조제일: {today}", y, small_font) + print_date_text = f"조제일 : {today}" + bbox = draw.textbbox((0, 0), print_date_text, font=small_font) + date_w, date_h = bbox[2] - bbox[0], bbox[3] - bbox[1] + print_date_y = label_height - date_h - 70 # 시그니처 위쪽 + draw.text(((label_width - date_w) / 2, print_date_y), print_date_text, font=small_font, fill="black") - # 약국명 (하단) - pharmacy_y = label_height - 40 - draw.rectangle( - [(50, pharmacy_y - 5), (label_width - 50, pharmacy_y + 25)], - outline="black", - width=1 - ) - draw_centered("청 춘 약 국", pharmacy_y, info_font) + # 시그니처 박스 (하단 - 약국명) + signature_text = "청 춘 약 국" + bbox = draw.textbbox((0, 0), signature_text, font=signature_font) + w_sig, h_sig = bbox[2] - bbox[0], bbox[3] - bbox[1] + + # 시그니처 박스 패딩 및 위치 계산 + padding_top = int(h_sig * 0.1) + padding_bottom = int(h_sig * 0.5) + padding_sides = int(h_sig * 0.2) + + box_x = (label_width - w_sig) / 2 - padding_sides + box_y = label_height - h_sig - padding_top - padding_bottom - 10 + box_x2 = box_x + w_sig + 2 * padding_sides + box_y2 = box_y + h_sig + padding_top + padding_bottom + + # 시그니처 테두리 및 텍스트 + draw.rectangle([(box_x, box_y), (box_x2, box_y2)], outline="black", width=1) + draw.text(((label_width - w_sig) / 2, box_y + padding_top), signature_text, font=signature_font, fill="black") + + # 지그재그 테두리 (가위로 자른 느낌) + draw_scissor_border(draw, label_width, label_height, edge_size=10, steps=20) return image diff --git a/backend/templates/pmr.html b/backend/templates/pmr.html index abd6ade..fb02369 100644 --- a/backend/templates/pmr.html +++ b/backend/templates/pmr.html @@ -1562,7 +1562,7 @@ ${data.medications.map((m, i) => ` - +
@@ -2600,8 +2600,8 @@ const tr = checkbox.closest('tr'); const cells = tr.querySelectorAll('td'); - // 약품명: 두 번째 셀의 .med-name - const medName = tr.querySelector('.med-name')?.textContent?.trim() || ''; + // 약품명: data-med-name 속성에서 (번호/뱃지 제외된 순수 약품명) + const medName = tr.dataset.medName || ''; const addInfo = tr.dataset.addInfo || ''; // 용량: 세 번째 셀 (index 2) - 제형 컬럼 제거됨 const dosageText = cells[2]?.textContent?.replace(/[^0-9.]/g, '') || '0';