추가: - 자동인쇄 ON/OFF 토글 (헤더) - ESC/POS 영수증 인쇄 (EUC-KR 인코딩) - ESCPOS_TROUBLESHOOTING.md 트러블슈팅 문서 핵심 변경: - 이미지 방식 -> 텍스트 방식 (socket 직접 전송) - UTF-8 -> EUC-KR 인코딩 - 이모지 제거 ([V], [!], >> 사용) - 48자 기준 줄바꿈 인쇄 흐름: 1. PAAI 분석 완료 2. 자동인쇄 ON이면 /pmr/api/paai/print 호출 3. _format_paai_receipt()로 텍스트 생성 4. _print_escpos_text()로 프린터 전송 참고: docs/ESCPOS_TROUBLESHOOTING.md
202 lines
5.6 KiB
Python
202 lines
5.6 KiB
Python
"""PAAI ESC/POS 프린터 CLI - EUC-KR 텍스트 방식"""
|
|
import sys
|
|
import json
|
|
import socket
|
|
from datetime import datetime
|
|
|
|
# 프린터 설정
|
|
PRINTER_IP = "192.168.0.174"
|
|
PRINTER_PORT = 9100
|
|
|
|
# ESC/POS 명령어
|
|
ESC = b'\x1b'
|
|
INIT = ESC + b'@' # 프린터 초기화
|
|
CUT = ESC + b'd\x03' # 피드 + 커트
|
|
|
|
|
|
def print_raw(data: bytes) -> bool:
|
|
"""바이트 데이터를 프린터로 전송"""
|
|
try:
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
sock.settimeout(10)
|
|
sock.connect((PRINTER_IP, PRINTER_PORT))
|
|
sock.sendall(data)
|
|
sock.close()
|
|
return True
|
|
except Exception as e:
|
|
print(f"프린터 오류: {e}", file=sys.stderr)
|
|
return False
|
|
|
|
|
|
def wrap_text(text: str, width: int = 44) -> list:
|
|
"""텍스트 줄바꿈 (44자 기준, 들여쓰기 고려)"""
|
|
if not text:
|
|
return []
|
|
|
|
lines = []
|
|
words = text.split()
|
|
current = ""
|
|
|
|
for word in words:
|
|
if len(current) + len(word) + 1 <= width:
|
|
current = current + " " + word if current else word
|
|
else:
|
|
if current:
|
|
lines.append(current)
|
|
current = word
|
|
|
|
if current:
|
|
lines.append(current)
|
|
|
|
return lines if lines else [text[:width]]
|
|
|
|
|
|
def center_text(text: str, width: int = 48) -> str:
|
|
"""중앙 정렬"""
|
|
text_len = len(text)
|
|
if text_len >= width:
|
|
return text
|
|
spaces = (width - text_len) // 2
|
|
return " " * spaces + text
|
|
|
|
|
|
def format_paai_receipt(pre_serial: str, patient_name: str,
|
|
analysis: dict, kims_summary: dict) -> str:
|
|
"""PAAI 복약안내 영수증 텍스트 생성 (48자 기준)"""
|
|
|
|
LINE = "=" * 48
|
|
THIN = "-" * 48
|
|
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
|
|
# 헤더
|
|
msg = f"\n{LINE}\n"
|
|
msg += center_text("[ PAAI 복약안내 ]") + "\n"
|
|
msg += f"{LINE}\n"
|
|
|
|
# 환자 정보
|
|
msg += f"환자: {patient_name}\n"
|
|
msg += f"처방번호: {pre_serial}\n"
|
|
msg += f"출력: {now}\n"
|
|
msg += f"{THIN}\n"
|
|
|
|
# 상호작용 요약
|
|
interaction_count = kims_summary.get('interaction_count', 0)
|
|
has_severe = kims_summary.get('has_severe', False)
|
|
|
|
if has_severe:
|
|
msg += "[!!] 중증 상호작용 있음!\n"
|
|
elif interaction_count > 0:
|
|
msg += f"[!] 약물 상호작용: {interaction_count}건\n"
|
|
else:
|
|
msg += "[V] 상호작용 없음\n"
|
|
msg += "\n"
|
|
|
|
# 처방 해석
|
|
insight = analysis.get('prescription_insight', '')
|
|
if insight:
|
|
msg += f"{THIN}\n"
|
|
msg += ">> 처방 해석\n"
|
|
for line in wrap_text(insight, 44):
|
|
msg += f" {line}\n"
|
|
msg += "\n"
|
|
|
|
# 복용 주의사항
|
|
cautions = analysis.get('cautions', [])
|
|
if cautions:
|
|
msg += f"{THIN}\n"
|
|
msg += ">> 복용 주의사항\n"
|
|
for i, caution in enumerate(cautions[:4], 1):
|
|
# 첫 줄
|
|
first_line = True
|
|
for line in wrap_text(f"{i}. {caution}", 44):
|
|
if first_line:
|
|
msg += f" {line}\n"
|
|
first_line = False
|
|
else:
|
|
msg += f" {line}\n"
|
|
msg += "\n"
|
|
|
|
# 상담 포인트
|
|
counseling = analysis.get('counseling_points', [])
|
|
if counseling:
|
|
msg += f"{THIN}\n"
|
|
msg += ">> 상담 포인트\n"
|
|
for i, point in enumerate(counseling[:3], 1):
|
|
first_line = True
|
|
for line in wrap_text(f"{i}. {point}", 44):
|
|
if first_line:
|
|
msg += f" {line}\n"
|
|
first_line = False
|
|
else:
|
|
msg += f" {line}\n"
|
|
msg += "\n"
|
|
|
|
# OTC 추천
|
|
otc_recs = analysis.get('otc_recommendations', [])
|
|
if otc_recs:
|
|
msg += f"{THIN}\n"
|
|
msg += ">> OTC 추천\n"
|
|
for rec in otc_recs[:2]:
|
|
product = rec.get('product', '')
|
|
reason = rec.get('reason', '')
|
|
msg += f" - {product}\n"
|
|
for line in wrap_text(reason, 42):
|
|
msg += f" {line}\n"
|
|
msg += "\n"
|
|
|
|
# 푸터
|
|
msg += f"{LINE}\n"
|
|
msg += center_text("양구청춘약국 PAAI") + "\n"
|
|
msg += center_text("Tel: 033-481-5222") + "\n"
|
|
msg += "\n"
|
|
|
|
return msg
|
|
|
|
|
|
def print_paai_receipt(data: dict) -> bool:
|
|
"""PAAI 영수증 인쇄"""
|
|
try:
|
|
pre_serial = data.get('pre_serial', '')
|
|
patient_name = data.get('patient_name', '')
|
|
analysis = data.get('analysis', {})
|
|
kims_summary = data.get('kims_summary', {})
|
|
|
|
# 텍스트 생성
|
|
message = format_paai_receipt(pre_serial, patient_name, analysis, kims_summary)
|
|
|
|
# EUC-KR 인코딩 (한글 지원)
|
|
text_bytes = message.encode('euc-kr', errors='replace')
|
|
|
|
# 명령어 조합
|
|
command = INIT + text_bytes + b'\n\n\n' + CUT
|
|
|
|
return print_raw(command)
|
|
except Exception as e:
|
|
print(f"인쇄 오류: {e}", file=sys.stderr)
|
|
return False
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print("사용법: python paai_printer_cli.py <json_file>", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
json_path = sys.argv[1]
|
|
|
|
try:
|
|
with open(json_path, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
|
|
if print_paai_receipt(data):
|
|
print("인쇄 완료")
|
|
sys.exit(0)
|
|
else:
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"오류: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|