Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- main.py +196 -191
- requirements.txt +1 -2
main.py
CHANGED
|
@@ -18,7 +18,7 @@ import subprocess
|
|
| 18 |
import platform
|
| 19 |
import docx
|
| 20 |
from reportlab.lib.pagesizes import letter
|
| 21 |
-
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image as RLImage
|
| 22 |
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
| 23 |
from reportlab.lib import colors
|
| 24 |
from reportlab.lib.units import inch
|
|
@@ -501,10 +501,7 @@ async def generate_report(analysis_id: str, include_consultation: bool = True):
|
|
| 501 |
doc.add_paragraph(f"Primary Diagnosis: {analysis_data['highest_probability_class']}")
|
| 502 |
doc.add_paragraph(f"Severity Index: {analysis_data['severity_index']}/100")
|
| 503 |
|
| 504 |
-
# Add
|
| 505 |
-
doc.add_page_break()
|
| 506 |
-
|
| 507 |
-
# Detailed classification table
|
| 508 |
doc.add_heading('Detailed Classification', level=2)
|
| 509 |
table = doc.add_table(rows=1, cols=4)
|
| 510 |
table.style = 'Table Grid'
|
|
@@ -560,10 +557,10 @@ async def generate_report(analysis_id: str, include_consultation: bool = True):
|
|
| 560 |
docx_path = os.path.join(temp_dir, f"report_{analysis_id}.docx")
|
| 561 |
doc.save(docx_path)
|
| 562 |
|
|
|
|
| 563 |
try:
|
| 564 |
-
# Now generate PDF directly using ReportLab
|
| 565 |
from reportlab.lib.pagesizes import letter
|
| 566 |
-
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image as RLImage
|
| 567 |
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
| 568 |
from reportlab.lib import colors
|
| 569 |
from reportlab.lib.units import inch
|
|
@@ -572,200 +569,208 @@ async def generate_report(analysis_id: str, include_consultation: bool = True):
|
|
| 572 |
|
| 573 |
# Create PDF file
|
| 574 |
pdf_path = os.path.join(temp_dir, f"report_{analysis_id}.pdf")
|
|
|
|
|
|
|
| 575 |
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
spaceBefore=8
|
| 617 |
-
)
|
| 618 |
-
normal_style = ParagraphStyle(
|
| 619 |
-
'Normal',
|
| 620 |
-
parent=styles["Normal"],
|
| 621 |
-
fontName='Helvetica',
|
| 622 |
-
fontSize=10,
|
| 623 |
-
leading=12
|
| 624 |
-
)
|
| 625 |
-
|
| 626 |
-
# Build PDF content
|
| 627 |
-
elements = []
|
| 628 |
-
|
| 629 |
-
# Title
|
| 630 |
-
elements.append(Paragraph('ClarirAI Medical Report', title_style))
|
| 631 |
-
elements.append(Spacer(1, 0.25*inch))
|
| 632 |
-
|
| 633 |
-
# Metadata
|
| 634 |
-
elements.append(Paragraph(f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", normal_style))
|
| 635 |
-
elements.append(Paragraph(f"Analysis ID: {analysis_id}", normal_style))
|
| 636 |
-
elements.append(Paragraph(f"Original Analysis Date: {analysis_data['timestamp']}", normal_style))
|
| 637 |
-
elements.append(Spacer(1, 0.25*inch))
|
| 638 |
|
| 639 |
-
#
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
# Process the image for ReportLab
|
| 646 |
-
img = PILImage.open(image_path)
|
| 647 |
-
img_width, img_height = img.size
|
| 648 |
-
aspect = img_height / float(img_width)
|
| 649 |
-
rl_img_width = 4 * inch
|
| 650 |
-
rl_img_height = rl_img_width * aspect
|
| 651 |
-
|
| 652 |
-
# Convert PIL Image to ReportLab Image
|
| 653 |
-
img_buffer = BytesIO()
|
| 654 |
-
img.save(img_buffer, format='JPEG')
|
| 655 |
-
img_buffer.seek(0)
|
| 656 |
-
rl_img = RLImage(img_buffer, width=rl_img_width, height=rl_img_height)
|
| 657 |
-
elements.append(rl_img)
|
| 658 |
-
elements.append(Spacer(1, 0.25*inch))
|
| 659 |
-
except Exception as img_err:
|
| 660 |
-
logger.error(f"Error adding image to PDF: {img_err}")
|
| 661 |
-
elements.append(Paragraph('Error loading retinal image', normal_style))
|
| 662 |
-
elements.append(Spacer(1, 0.25*inch))
|
| 663 |
|
| 664 |
-
#
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
|
|
|
|
|
|
| 668 |
elements.append(Spacer(1, 0.25*inch))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 669 |
|
| 670 |
-
|
| 671 |
-
elements.append(
|
| 672 |
-
|
| 673 |
-
# Detailed classification table
|
| 674 |
-
elements.append(Paragraph('Detailed Classification', heading2_style))
|
| 675 |
-
|
| 676 |
-
# Create table data
|
| 677 |
-
table_data = [
|
| 678 |
-
['Classification', 'Description', 'Probability', 'Confidence Level']
|
| 679 |
-
]
|
| 680 |
-
|
| 681 |
-
for item in analysis_data['detailed_classification']:
|
| 682 |
-
table_data.append([
|
| 683 |
-
item['class'],
|
| 684 |
-
item['description'],
|
| 685 |
-
f"{item['percentage']}%",
|
| 686 |
-
item['confidence_level']
|
| 687 |
-
])
|
| 688 |
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
|
| 693 |
-
('TEXTCOLOR', (0, 0), (-1, 0), colors.black),
|
| 694 |
-
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
| 695 |
-
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
| 696 |
-
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
| 697 |
-
('GRID', (0, 0), (-1, -1), 1, colors.black),
|
| 698 |
-
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
| 699 |
-
]))
|
| 700 |
-
elements.append(table)
|
| 701 |
-
elements.append(Spacer(1, 0.25*inch))
|
| 702 |
|
| 703 |
-
|
| 704 |
-
elements.append(Paragraph('
|
|
|
|
| 705 |
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
elements.append(Paragraph(
|
| 709 |
-
f"{item['class']}: {item['percentage']}% ({item['confidence_level']})",
|
| 710 |
-
normal_style
|
| 711 |
-
))
|
| 712 |
elements.append(Spacer(1, 0.25*inch))
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
elements.append(Paragraph(clinical_info.get('risks', 'Not available'), normal_style))
|
| 724 |
-
elements.append(Spacer(1, 0.15*inch))
|
| 725 |
-
|
| 726 |
-
elements.append(Paragraph('Recommendations', heading2_style))
|
| 727 |
-
elements.append(Paragraph(clinical_info.get('recommendations', 'Not available'), normal_style))
|
| 728 |
-
elements.append(Spacer(1, 0.15*inch))
|
| 729 |
-
|
| 730 |
-
elements.append(Paragraph('Follow-up', heading2_style))
|
| 731 |
-
elements.append(Paragraph(clinical_info.get('follow_up', 'Not available'), normal_style))
|
| 732 |
-
elements.append(Spacer(1, 0.25*inch))
|
| 733 |
-
|
| 734 |
-
# AI explanation
|
| 735 |
-
elements.append(Paragraph('AI Analysis Explanation', heading1_style))
|
| 736 |
-
elements.append(Paragraph(clinical_info.get('explanation', 'No explanation available for this classification.'), normal_style))
|
| 737 |
elements.append(Spacer(1, 0.25*inch))
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
return FileResponse(
|
| 763 |
-
path=docx_path,
|
| 764 |
-
filename=f"ClarirAI_Report_{analysis_id}.docx",
|
| 765 |
-
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
| 766 |
-
headers={"Content-Disposition": f"attachment; filename=ClarirAI_Report_{analysis_id}.docx"}
|
| 767 |
-
)
|
| 768 |
-
|
| 769 |
except Exception as e:
|
| 770 |
logger.error(f"Error generating report: {e}")
|
| 771 |
raise HTTPException(status_code=500, detail=f"Error generating report: {str(e)}")
|
|
|
|
| 18 |
import platform
|
| 19 |
import docx
|
| 20 |
from reportlab.lib.pagesizes import letter
|
| 21 |
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image as RLImage
|
| 22 |
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
| 23 |
from reportlab.lib import colors
|
| 24 |
from reportlab.lib.units import inch
|
|
|
|
| 501 |
doc.add_paragraph(f"Primary Diagnosis: {analysis_data['highest_probability_class']}")
|
| 502 |
doc.add_paragraph(f"Severity Index: {analysis_data['severity_index']}/100")
|
| 503 |
|
| 504 |
+
# Add detailed classification table
|
|
|
|
|
|
|
|
|
|
| 505 |
doc.add_heading('Detailed Classification', level=2)
|
| 506 |
table = doc.add_table(rows=1, cols=4)
|
| 507 |
table.style = 'Table Grid'
|
|
|
|
| 557 |
docx_path = os.path.join(temp_dir, f"report_{analysis_id}.docx")
|
| 558 |
doc.save(docx_path)
|
| 559 |
|
| 560 |
+
# Now generate PDF directly using ReportLab
|
| 561 |
try:
|
|
|
|
| 562 |
from reportlab.lib.pagesizes import letter
|
| 563 |
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image as RLImage
|
| 564 |
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
| 565 |
from reportlab.lib import colors
|
| 566 |
from reportlab.lib.units import inch
|
|
|
|
| 569 |
|
| 570 |
# Create PDF file
|
| 571 |
pdf_path = os.path.join(temp_dir, f"report_{analysis_id}.pdf")
|
| 572 |
+
pdf_doc = SimpleDocTemplate(pdf_path, pagesize=letter)
|
| 573 |
+
styles = getSampleStyleSheet()
|
| 574 |
|
| 575 |
+
# Create custom styles
|
| 576 |
+
title_style = ParagraphStyle(
|
| 577 |
+
'Title',
|
| 578 |
+
parent=styles['Title'],
|
| 579 |
+
fontSize=16,
|
| 580 |
+
spaceAfter=12
|
| 581 |
+
)
|
| 582 |
+
heading1_style = ParagraphStyle(
|
| 583 |
+
'Heading1',
|
| 584 |
+
parent=styles['Heading1'],
|
| 585 |
+
fontSize=14,
|
| 586 |
+
spaceAfter=10,
|
| 587 |
+
spaceBefore=10
|
| 588 |
+
)
|
| 589 |
+
heading2_style = ParagraphStyle(
|
| 590 |
+
'Heading2',
|
| 591 |
+
parent=styles['Heading2'],
|
| 592 |
+
fontSize=12,
|
| 593 |
+
spaceAfter=8,
|
| 594 |
+
spaceBefore=8
|
| 595 |
+
)
|
| 596 |
+
normal_style = styles["Normal"]
|
| 597 |
+
|
| 598 |
+
# Build PDF content
|
| 599 |
+
elements = []
|
| 600 |
+
|
| 601 |
+
# Title
|
| 602 |
+
elements.append(Paragraph('ClarirAI Medical Report', title_style))
|
| 603 |
+
elements.append(Spacer(1, 0.25*inch))
|
| 604 |
+
|
| 605 |
+
# Metadata
|
| 606 |
+
elements.append(Paragraph(f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", normal_style))
|
| 607 |
+
elements.append(Paragraph(f"Analysis ID: {analysis_id}", normal_style))
|
| 608 |
+
elements.append(Paragraph(f"Original Analysis Date: {analysis_data['timestamp']}", normal_style))
|
| 609 |
+
elements.append(Spacer(1, 0.25*inch))
|
| 610 |
+
|
| 611 |
+
# Add the analyzed image if available
|
| 612 |
+
if image_path and os.path.exists(image_path):
|
| 613 |
+
elements.append(Paragraph('Analyzed Retinal Image', heading1_style))
|
| 614 |
+
elements.append(Paragraph('Below is the retinal image that was analyzed:', normal_style))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 615 |
|
| 616 |
+
# Process the image for ReportLab
|
| 617 |
+
img = PILImage.open(image_path)
|
| 618 |
+
img_width, img_height = img.size
|
| 619 |
+
aspect = img_height / float(img_width)
|
| 620 |
+
rl_img_width = 4 * inch
|
| 621 |
+
rl_img_height = rl_img_width * aspect
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 622 |
|
| 623 |
+
# Convert PIL Image to ReportLab Image
|
| 624 |
+
img_buffer = BytesIO()
|
| 625 |
+
img.save(img_buffer, format='JPEG')
|
| 626 |
+
img_buffer.seek(0)
|
| 627 |
+
rl_img = RLImage(img_buffer, width=rl_img_width, height=rl_img_height)
|
| 628 |
+
elements.append(rl_img)
|
| 629 |
elements.append(Spacer(1, 0.25*inch))
|
| 630 |
+
|
| 631 |
+
# Diagnosis section
|
| 632 |
+
elements.append(Paragraph('Diabetic Retinopathy Assessment', heading1_style))
|
| 633 |
+
elements.append(Paragraph(f"Primary Diagnosis: {analysis_data['highest_probability_class']}", normal_style))
|
| 634 |
+
elements.append(Paragraph(f"Severity Index: {analysis_data['severity_index']}/100", normal_style))
|
| 635 |
+
elements.append(Spacer(1, 0.25*inch))
|
| 636 |
+
|
| 637 |
+
# Detailed classification table
|
| 638 |
+
elements.append(Paragraph('Detailed Classification', heading2_style))
|
| 639 |
+
|
| 640 |
+
# Create table data
|
| 641 |
+
table_data = [
|
| 642 |
+
['Classification', 'Description', 'Probability', 'Confidence Level']
|
| 643 |
+
]
|
| 644 |
+
|
| 645 |
+
for item in analysis_data['detailed_classification']:
|
| 646 |
+
table_data.append([
|
| 647 |
+
item['class'],
|
| 648 |
+
item['description'],
|
| 649 |
+
f"{item['percentage']}%",
|
| 650 |
+
item['confidence_level']
|
| 651 |
+
])
|
| 652 |
+
|
| 653 |
+
# Create table
|
| 654 |
+
table = Table(table_data, colWidths=[1.5*inch, 2.5*inch, 1*inch, 1.5*inch])
|
| 655 |
+
table.setStyle(TableStyle([
|
| 656 |
+
('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
|
| 657 |
+
('TEXTCOLOR', (0, 0), (-1, 0), colors.black),
|
| 658 |
+
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
| 659 |
+
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
| 660 |
+
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
| 661 |
+
('GRID', (0, 0), (-1, -1), 1, colors.black),
|
| 662 |
+
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
| 663 |
+
]))
|
| 664 |
+
elements.append(table)
|
| 665 |
+
elements.append(Spacer(1, 0.25*inch))
|
| 666 |
+
|
| 667 |
+
# Add bar chart visualization
|
| 668 |
+
elements.append(Paragraph('Classification Probability Chart', heading2_style))
|
| 669 |
+
|
| 670 |
+
# Create the drawing with a proper size
|
| 671 |
+
drawing = Drawing(500, 250)
|
| 672 |
+
|
| 673 |
+
# Create the bar chart
|
| 674 |
+
chart = VerticalBarChart()
|
| 675 |
+
chart.x = 50
|
| 676 |
+
chart.y = 50
|
| 677 |
+
chart.height = 150
|
| 678 |
+
chart.width = 350
|
| 679 |
+
|
| 680 |
+
# Extract data for the chart
|
| 681 |
+
data = []
|
| 682 |
+
categories = []
|
| 683 |
+
for item in analysis_data['detailed_classification']:
|
| 684 |
+
data.append(item['percentage'])
|
| 685 |
+
categories.append(item['class'])
|
| 686 |
+
|
| 687 |
+
# Set chart data
|
| 688 |
+
chart.data = [data]
|
| 689 |
+
chart.categoryAxis.categoryNames = categories
|
| 690 |
+
chart.categoryAxis.labels.boxAnchor = 'ne'
|
| 691 |
+
chart.categoryAxis.labels.dx = -8
|
| 692 |
+
chart.categoryAxis.labels.dy = -2
|
| 693 |
+
chart.categoryAxis.labels.angle = 30
|
| 694 |
+
|
| 695 |
+
# Set value axis properties
|
| 696 |
+
chart.valueAxis.valueMin = 0
|
| 697 |
+
chart.valueAxis.valueMax = 100
|
| 698 |
+
chart.valueAxis.valueStep = 10
|
| 699 |
+
|
| 700 |
+
# Set bar properties
|
| 701 |
+
chart.bars[0].fillColor = colors.skyblue
|
| 702 |
+
chart.bars[0].strokeColor = colors.black
|
| 703 |
+
chart.bars[0].strokeWidth = 0.5
|
| 704 |
+
|
| 705 |
+
# Add a legend
|
| 706 |
+
legend = Legend()
|
| 707 |
+
legend.alignment = 'right'
|
| 708 |
+
legend.x = 400
|
| 709 |
+
legend.y = 150
|
| 710 |
+
legend.colorNamePairs = [(colors.skyblue, 'Probability (%)')]
|
| 711 |
+
|
| 712 |
+
# Add chart and legend to the drawing
|
| 713 |
+
drawing.add(chart)
|
| 714 |
+
drawing.add(legend)
|
| 715 |
+
|
| 716 |
+
# Add the drawing to the PDF
|
| 717 |
+
elements.append(drawing)
|
| 718 |
+
elements.append(Spacer(1, 0.25*inch))
|
| 719 |
+
|
| 720 |
+
# Clinical information
|
| 721 |
+
if clinical_info:
|
| 722 |
+
elements.append(Paragraph('Clinical Information', heading1_style))
|
| 723 |
|
| 724 |
+
elements.append(Paragraph('Findings', heading2_style))
|
| 725 |
+
elements.append(Paragraph(clinical_info.get('findings', 'Not available'), normal_style))
|
| 726 |
+
elements.append(Spacer(1, 0.15*inch))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 727 |
|
| 728 |
+
elements.append(Paragraph('Associated Risks', heading2_style))
|
| 729 |
+
elements.append(Paragraph(clinical_info.get('risks', 'Not available'), normal_style))
|
| 730 |
+
elements.append(Spacer(1, 0.15*inch))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 731 |
|
| 732 |
+
elements.append(Paragraph('Recommendations', heading2_style))
|
| 733 |
+
elements.append(Paragraph(clinical_info.get('recommendations', 'Not available'), normal_style))
|
| 734 |
+
elements.append(Spacer(1, 0.15*inch))
|
| 735 |
|
| 736 |
+
elements.append(Paragraph('Follow-up', heading2_style))
|
| 737 |
+
elements.append(Paragraph(clinical_info.get('follow_up', 'Not available'), normal_style))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 738 |
elements.append(Spacer(1, 0.25*inch))
|
| 739 |
+
|
| 740 |
+
# AI explanation
|
| 741 |
+
elements.append(Paragraph('AI Analysis Explanation', heading1_style))
|
| 742 |
+
elements.append(Paragraph(clinical_info.get('explanation', 'No explanation available for this classification.'), normal_style))
|
| 743 |
+
elements.append(Spacer(1, 0.25*inch))
|
| 744 |
+
|
| 745 |
+
# AI consultation
|
| 746 |
+
if consultation_text:
|
| 747 |
+
elements.append(Paragraph('AI-Generated Medical Consultation', heading1_style))
|
| 748 |
+
elements.append(Paragraph(consultation_text, normal_style))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 749 |
elements.append(Spacer(1, 0.25*inch))
|
| 750 |
+
|
| 751 |
+
# Disclaimer
|
| 752 |
+
elements.append(Paragraph('Disclaimer', heading1_style))
|
| 753 |
+
elements.append(Paragraph('This report is generated using artificial intelligence and should not replace professional medical advice. The analysis and recommendations provided are based on automated image processing and AI consultation. Please consult with a qualified healthcare provider for proper diagnosis, treatment, and follow-up care.', normal_style))
|
| 754 |
+
|
| 755 |
+
# Build the PDF
|
| 756 |
+
pdf_doc.build(elements)
|
| 757 |
+
|
| 758 |
+
# Return the PDF file
|
| 759 |
+
return FileResponse(
|
| 760 |
+
path=pdf_path,
|
| 761 |
+
filename=f"ClarirAI_Report_{analysis_id}.pdf",
|
| 762 |
+
media_type="application/pdf"
|
| 763 |
+
)
|
| 764 |
+
|
| 765 |
+
except Exception as e:
|
| 766 |
+
logger.error(f"Error generating PDF with ReportLab: {e}")
|
| 767 |
+
# Fall back to DOCX if PDF generation fails
|
| 768 |
+
return FileResponse(
|
| 769 |
+
path=docx_path,
|
| 770 |
+
filename=f"ClarirAI_Report_{analysis_id}.docx",
|
| 771 |
+
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
| 772 |
+
)
|
| 773 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 774 |
except Exception as e:
|
| 775 |
logger.error(f"Error generating report: {e}")
|
| 776 |
raise HTTPException(status_code=500, detail=f"Error generating report: {str(e)}")
|
requirements.txt
CHANGED
|
@@ -8,5 +8,4 @@ openai
|
|
| 8 |
python-docx
|
| 9 |
docx
|
| 10 |
python-dotenv
|
| 11 |
-
reportlab
|
| 12 |
-
pypdf2
|
|
|
|
| 8 |
python-docx
|
| 9 |
docx
|
| 10 |
python-dotenv
|
| 11 |
+
reportlab
|
|
|