ProfessorLeVesseur's picture
Update report.py
1ee1fe7 verified
# import io
# from reportlab.lib.pagesizes import letter
# from reportlab.platypus import SimpleDocTemplate, Image, Paragraph, Spacer
# from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
# from reportlab.lib.enums import TA_JUSTIFY
# from reportlab.lib.units import inch
# import matplotlib.pyplot as plt
# import markdown
# from xml.etree import ElementTree as ET
# from PIL import Image as PILImage
# from xml.parsers.expat import ExpatError
# from html import escape
# class ReportGenerator:
# def __init__(self):
# self.styles = getSampleStyleSheet()
# self.styles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY))
# def create_combined_pdf(self, intervention_fig, student_metrics_fig, recommendations):
# buffer = io.BytesIO()
# doc = SimpleDocTemplate(buffer, pagesize=letter)
# elements = []
# elements.extend(self._add_chart(intervention_fig, "Intervention Dosage"))
# elements.extend(self._add_chart(student_metrics_fig, "Student Attendance and Engagement"))
# elements.extend(self._add_recommendations(recommendations))
# doc.build(elements)
# buffer.seek(0)
# return buffer
# def _add_chart(self, fig, title):
# elements = []
# elements.append(Paragraph(title, self.styles['Heading2']))
# img_buffer = io.BytesIO()
# if hasattr(fig, 'write_image'): # Plotly figure
# fig.write_image(img_buffer, format="png", width=700, height=400)
# elif isinstance(fig, plt.Figure): # Matplotlib figure
# fig.set_size_inches(10, 6) # Set a consistent size
# fig.savefig(img_buffer, format='png', dpi=100, bbox_inches='tight')
# plt.close(fig)
# else:
# raise ValueError(f"Unsupported figure type: {type(fig)}")
# img_buffer.seek(0)
# # Use PIL to get image dimensions
# with PILImage.open(img_buffer) as img:
# img_width, img_height = img.size
# # Calculate width and height to maintain aspect ratio
# max_width = 6.5 * inch # Maximum width (letter width is 8.5 inches, leaving margins)
# max_height = 4 * inch # Maximum height
# aspect = img_width / float(img_height)
# if img_width > max_width:
# img_width = max_width
# img_height = img_width / aspect
# if img_height > max_height:
# img_height = max_height
# img_width = img_height * aspect
# # Reset buffer position
# img_buffer.seek(0)
# # Create ReportLab Image with calculated dimensions
# img = Image(img_buffer, width=img_width, height=img_height)
# elements.append(img)
# elements.append(Spacer(1, 12))
# return elements
# def _add_recommendations(self, recommendations):
# elements = []
# elements.append(Paragraph("MTSS.ai Analysis", self.styles['Heading1']))
# # Convert markdown to HTML
# html = markdown.markdown(recommendations)
# # Wrap the HTML in a root element to ensure valid XML
# wrapped_html = f"<root>{html}</root>"
# try:
# root = ET.fromstring(wrapped_html)
# except ExpatError:
# # If parsing fails, fallback to treating the entire content as plain text
# elements.append(Paragraph(escape(recommendations), self.styles['BodyText']))
# return elements
# for elem in root:
# if elem.tag == 'h3':
# elements.append(Paragraph(elem.text or "", self.styles['Heading3']))
# elif elem.tag == 'h4':
# elements.append(Paragraph(elem.text or "", self.styles['Heading4']))
# elif elem.tag == 'p':
# text = ''.join(elem.itertext())
# elements.append(Paragraph(text, self.styles['Justify']))
# elif elem.tag == 'ul':
# for li in elem.findall('li'):
# bullet_text = '• ' + ''.join(li.itertext()).strip()
# elements.append(Paragraph(bullet_text, self.styles['BodyText']))
# else:
# # For any other tags, just extract the text
# text = ''.join(elem.itertext())
# if text.strip():
# elements.append(Paragraph(text, self.styles['BodyText']))
# return elements
import io
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Image, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_JUSTIFY, TA_LEFT
from reportlab.lib.units import inch
import matplotlib.pyplot as plt
import markdown
from bs4 import BeautifulSoup
from PIL import Image as PILImage
from reportlab.lib.colors import black
class ReportGenerator:
def __init__(self):
self.styles = getSampleStyleSheet()
self.styles['BodyText'].alignment = TA_JUSTIFY
self.styles['Bullet'].leftIndent = 20
self.styles['Bullet'].firstLineIndent = 0
self.styles['Bullet'].alignment = TA_LEFT
self.styles['Heading1'].fontSize = 18
self.styles['Heading2'].fontSize = 16
self.styles['Heading3'].fontSize = 14
self.styles['Heading4'].fontSize = 12
# Add a new style for bold text
self.styles.add(ParagraphStyle(name='Bold', parent=self.styles['BodyText'], fontName='Helvetica-Bold'))
def create_combined_pdf(self, intervention_fig, student_metrics_fig, recommendations):
buffer = io.BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=letter, topMargin=0.5*inch, bottomMargin=0.5*inch)
elements = []
elements.extend(self._add_chart(intervention_fig, "Intervention Dosage"))
elements.extend(self._add_chart(student_metrics_fig, "Student Attendance and Engagement"))
elements.extend(self._add_recommendations(recommendations))
doc.build(elements)
buffer.seek(0)
return buffer
def _add_chart(self, fig, title):
elements = []
elements.append(Paragraph(title, self.styles['Heading2']))
img_buffer = io.BytesIO()
if hasattr(fig, 'write_image'): # Plotly figure
fig.write_image(img_buffer, format="png", width=700, height=400)
elif isinstance(fig, plt.Figure): # Matplotlib figure
fig.set_size_inches(10, 6) # Set a consistent size
fig.savefig(img_buffer, format='png', dpi=100, bbox_inches='tight')
plt.close(fig)
else:
raise ValueError(f"Unsupported figure type: {type(fig)}")
img_buffer.seek(0)
# Use PIL to get image dimensions
with PILImage.open(img_buffer) as img:
img_width, img_height = img.size
# Calculate width and height to maintain aspect ratio
max_width = 6.5 * inch # Maximum width (letter width is 8.5 inches, leaving margins)
max_height = 4 * inch # Maximum height
aspect = img_width / float(img_height)
if img_width > max_width:
img_width = max_width
img_height = img_width / aspect
if img_height > max_height:
img_height = max_height
img_width = img_height * aspect
# Reset buffer position
img_buffer.seek(0)
# Create ReportLab Image with calculated dimensions
img = Image(img_buffer, width=img_width, height=img_height)
elements.append(img)
elements.append(Spacer(1, 12))
return elements
def _add_recommendations(self, recommendations):
elements = []
elements.append(Paragraph("MTSS.ai Analysis", self.styles['Heading1']))
html = markdown.markdown(recommendations)
soup = BeautifulSoup(html, 'html.parser')
for element in soup.find_all(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'ul']):
if element.name.startswith('h'):
level = int(element.name[1])
style = f'Heading{min(level, 4)}'
elements.append(Paragraph(element.text, self.styles[style]))
elif element.name == 'p':
elements.append(self._create_paragraph_with_inline_styles(element))
elif element.name == 'ul':
for li in element.find_all('li'):
bullet_text = '• ' + self._get_text_with_inline_styles(li)
elements.append(Paragraph(bullet_text, self.styles['Bullet']))
elements.append(Spacer(1, 6))
return elements
def _create_paragraph_with_inline_styles(self, element):
text = self._get_text_with_inline_styles(element)
return Paragraph(text, self.styles['BodyText'])
def _get_text_with_inline_styles(self, element):
text = ""
for child in element.children:
if isinstance(child, str):
text += child
elif child.name in ['strong', 'b']:
text += f'<b>{child.text}</b>'
elif child.name in ['em', 'i']:
text += f'<i>{child.text}</i>'
elif child.name == 'u':
text += f'<u>{child.text}</u>'
return text