import os import uuid from datetime import datetime from flask import Response, current_app from PIL import Image from reportlab.lib.pagesizes import letter from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.units import inch from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle from reportlab.lib import colors from io import BytesIO ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} def allowed_file(filename): """Check if the uploaded file has an allowed extension.""" return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS def save_uploaded_image(file): """Save an uploaded image file with a unique filename.""" if not file: return None # Generate unique filename filename = str(uuid.uuid4()) + '.' + file.filename.rsplit('.', 1)[1].lower() filepath = os.path.join(current_app.config['UPLOAD_FOLDER'], filename) # Save and optimize the image try: image = Image.open(file.stream) # Convert RGBA to RGB if necessary if image.mode in ('RGBA', 'LA'): background = Image.new('RGB', image.size, (255, 255, 255)) background.paste(image, mask=image.split()[-1] if image.mode == 'RGBA' else None) image = background # Resize if image is too large (max 1920px on longest side) max_size = 1920 if max(image.size) > max_size: image.thumbnail((max_size, max_size), Image.Resampling.LANCZOS) # Save with optimization image.save(filepath, optimize=True, quality=85) except Exception as e: current_app.logger.error(f"Error processing image: {e}") # Fallback to direct save file.seek(0) file.save(filepath) return filename def generate_treatment_pdf(treatment_plan): """Generate a PDF for the treatment plan.""" buffer = BytesIO() doc = SimpleDocTemplate(buffer, pagesize=letter) # Styles styles = getSampleStyleSheet() title_style = ParagraphStyle( 'CustomTitle', parent=styles['Heading1'], fontSize=18, spaceAfter=30, textColor=colors.HexColor('#0F766E') ) heading_style = ParagraphStyle( 'CustomHeading', parent=styles['Heading2'], fontSize=14, spaceBefore=20, spaceAfter=10, textColor=colors.HexColor('#1E40AF') ) # Build the PDF content content = [] # Header content.append(Paragraph("Doctor's Sidekick - Treatment Plan", title_style)) content.append(Spacer(1, 20)) # Patient and plan information patient_info = [ ['Patient:', treatment_plan.patient.full_name], ['Patient ID:', treatment_plan.patient.patient_id], ['Date of Birth:', treatment_plan.patient.date_of_birth.strftime('%B %d, %Y')], ['Plan Date:', treatment_plan.created_at.strftime('%B %d, %Y')], ['Created By:', treatment_plan.created_by.full_name] ] patient_table = Table(patient_info, colWidths=[1.5*inch, 4*inch]) patient_table.setStyle(TableStyle([ ('ALIGN', (0, 0), (-1, -1), 'LEFT'), ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), ('FONTSIZE', (0, 0), (-1, -1), 12), ('BOTTOMPADDING', (0, 0), (-1, -1), 6), ])) content.append(patient_table) content.append(Spacer(1, 30)) # Diagnosis content.append(Paragraph("Diagnosis", heading_style)) content.append(Paragraph(treatment_plan.diagnosis or "Not specified", styles['Normal'])) content.append(Spacer(1, 15)) # Medications if treatment_plan.medications: content.append(Paragraph("Medications", heading_style)) content.append(Paragraph(treatment_plan.medications, styles['Normal'])) content.append(Spacer(1, 15)) # Instructions if treatment_plan.instructions: content.append(Paragraph("Instructions", heading_style)) content.append(Paragraph(treatment_plan.instructions, styles['Normal'])) content.append(Spacer(1, 15)) # Duration and Follow-up if treatment_plan.duration or treatment_plan.follow_up_date: content.append(Paragraph("Treatment Details", heading_style)) if treatment_plan.duration: content.append(Paragraph(f"Duration: {treatment_plan.duration}", styles['Normal'])) if treatment_plan.follow_up_date: content.append(Paragraph( f"Follow-up Date: {treatment_plan.follow_up_date.strftime('%B %d, %Y')}", styles['Normal'] )) content.append(Spacer(1, 15)) # Additional Notes if treatment_plan.notes: content.append(Paragraph("Additional Notes", heading_style)) content.append(Paragraph(treatment_plan.notes, styles['Normal'])) # Footer content.append(Spacer(1, 50)) footer_style = ParagraphStyle( 'Footer', parent=styles['Normal'], fontSize=10, textColor=colors.grey, alignment=1 # Center alignment ) content.append(Paragraph( f"Generated on {datetime.now().strftime('%B %d, %Y at %I:%M %p')}", footer_style )) # Build PDF doc.build(content) # Return PDF as response buffer.seek(0) return Response( buffer.getvalue(), mimetype='application/pdf', headers={ 'Content-Disposition': f'attachment; filename=treatment_plan_{treatment_plan.id}.pdf' } ) def calculate_age(birth_date): """Calculate age from birth date.""" today = datetime.now().date() return today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day)) def format_phone_number(phone): """Format phone number for display.""" if not phone: return "" # Remove all non-digit characters digits = ''.join(filter(str.isdigit, phone)) # Format as (XXX) XXX-XXXX for 10-digit numbers if len(digits) == 10: return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}" return phone def get_dashboard_stats(): """Get statistics for the dashboard.""" from models import Patient, Visit, Inventory from datetime import date, timedelta today = date.today() week_ago = today - timedelta(days=7) stats = { 'total_patients': Patient.query.count(), 'new_patients_week': Patient.query.filter(Patient.created_at >= week_ago).count(), 'visits_today': Visit.query.filter(Visit.visit_date >= today).count(), 'low_stock_items': Inventory.query.filter( Inventory.current_stock <= Inventory.min_threshold ).count() } return stats