import matplotlib.pyplot as plt from fpdf import FPDF import pandas as pd import uuid from langchain_core.output_parsers import StrOutputParser from langchain_core.messages import HumanMessage from langchain_ollama import ChatOllama import matplotlib.pyplot as plt import base64 from io import BytesIO from PIL import Image from datetime import datetime import random class ReportGenerator(FPDF): def __init__(self, start_date, end_date): super().__init__(unit='cm') self.start_date = start_date self.end_date = end_date self.model = ChatOllama(model="llava-phi3:3.8b", temperature=0) self.chain = self.prompt_func | self.model | StrOutputParser() def generate_months(self): # Convert input strings to datetime objects start = datetime.strptime(self.start_date, '%b %Y') end = datetime.strptime(self.end_date, '%b %Y') # Generate the list of month-year strings months = [] current = start while current <= end: months.append(current.strftime('%b %Y')) # Move to the next month if current.month == 12: current = current.replace(year=current.year + 1, month=1) else: current = current.replace(month=current.month + 1) # Generate random numbers between 20 and 100 for each month random_numbers = [random.randint(20, 100) for _ in range(len(months))] return months, random_numbers def convert_to_base64(pil_image): buffered = BytesIO() pil_image.save(buffered, format="PNG") # You can change the format if needed img_str = base64.b64encode(buffered.getvalue()).decode("utf-8") return img_str def create_graph_1(self): # Sample data x, y = self.generate_months() # Create a plot plt.figure(figsize=(10, 3)) # Adjust the figure size to fit the left space plt.gcf().set_facecolor('#9edbff') plt.plot(x, y, marker='s', color='#1b1464') for i, label in enumerate(y): plt.text(x[i], y[i] - 20, str(label) + "%", ha='center', va='bottom', fontsize=12, color='black') plt.ylabel("Helmet Not Use, in %", fontsize=10, weight='bold') plt.ylim(0, 100) # Save the plot as an image plt.savefig("./temp/graph_1.png", bbox_inches='tight') # Save with tight layout plt.close() def create_graph_2(self): data = [30, 20, 24, 26] # Values for the pie chart labels = ['Student', 'Passenger', 'Other', 'Family Member'] # Labels for the slices fig, ax = plt.subplots(figsize=(4, 4), facecolor='#9edbff') # Set canvas background #ax.set_facecolor('white') # Set the pie chart's background to white colors = ['#C96868', '#FADFA1', '#FFF4EA', '#7EACB5'] # Draw the pie chart with labels inside wedges, texts, autotexts = ax.pie( data, labels=None, # Exclude outer labels colors=colors, autopct='%1.1f%%', startangle=90, textprops=dict(color="black", fontsize=12, weight='bold') # Style the text inside ) # Add original labels to the pie wedges for i, autotext in enumerate(autotexts): autotext.set_text(f"{labels[i]}\n{autotext.get_text()}") # Label + percentage # Make sure the pie chart is circular ax.axis('equal') plt.savefig("./temp/graph_2.png", bbox_inches='tight') # Save the chart as an image plt.close() # Close the plot to free memory def bullet_multicell(self, width, height, bullet, text, bullet_indent=0.4): # Add the bullet and first line of text self.cell(bullet_indent, height, bullet) # Bullet with small space self.multi_cell(width - bullet_indent, height, text) # Adjust X position for wrapped lines (subsequent lines) last_y = self.get_y() self.set_xy(self.l_margin, last_y) # Indent wrapped lines return last_y def generate(self): self.set_auto_page_break(auto=True, margin=1.5) self.add_page() primary_color = (56, 182, 255) secondary_color = (27, 20, 100) self.image('./resources/banner.png', x=0, y=0, w=21.0) self.set_text_color(255, 255, 255) self.set_xy(1.0, 1.23) self.set_font('Arial', 'B', 24) self.cell(0, 0, 'Motorcycle Helmet Safety', ln=False, align='L') self.set_xy(1.0, 2.22) self.set_font('Arial', '', 18) self.cell(0, 0, 'Statistics Report', ln=False, align='L') self.set_xy(17.22, 1.73) self.set_font('Arial', '', 12) self.cell(0, 0.5, 'This document', ln=True, align='L') self.set_x(17.22) self.cell(0, 0.5, 'was made', ln=True, align='L') self.set_x(17.22) self.cell(0, 0.5, 'by Knock Knock', ln=True, align='L') self.set_draw_color(*primary_color) self.set_line_width(0.2) self.line(1.0 + 0.2, 3.39 - 0.2, 7.82, 3.39 - 0.2) self.set_text_color(0, 0, 0) date = f'{self.start_date} - {self.end_date}' self.set_xy(1.0, 4.3) self.set_font('Arial', '', 14) self.cell(0, 0.6, str(uuid.uuid4()), ln=False, align='L') self.cell(0, 0.6, date, ln=True, align='R') self.ln(0.25) self.set_font('Arial', 'b', 21) self.cell(0, 1, f'Motorcycle Helmet Non-use', ln=True, align='L') self.cell(0, 0.6, f'in {date} Overall Results', ln=True, align='L') self.ln(0.75) self.set_font('Arial', 'b', 18) self.cell(0, 0, 'Camera Information', ln=True, align='L') self.ln(0.5) self.set_x(1.5) self.set_font('Arial', 'b', 14) self.multi_cell(19, 0.6, f"{'HP 4M Fixed USB Camera'}", align='L') self.set_font('Arial', '', 14) self.set_x(2.0) self.bullet_multicell(19-2, 0.6, "-", f"IP: {'122.22.25.2'}") self.set_x(2.0) #self.multi_cell(19, 0.6, f"- Address: {'1 Chalong Krung 1 Alley, Lat Krabang, Khet Lat Krabang, Krung Thep Maha Nakhon 10520'}", align='J') self.bullet_multicell(19-2, 0.6, "-", f"Address: {'1 Chalong Krung 1 Alley, Lat Krabang, Khet Lat Krabang, Krung Thep Maha Nakhon 10520'}") self.set_x(2.0) self.bullet_multicell(19-2, 0.6, "-", f"Administrator: {'KMITL'}") self.ln(0.75) self.set_font('Arial', 'b', 18) self.cell(0, 0, 'Introduction', ln=True, align='L') self.ln(0.5) query = f"Write me 2-3 paragraphs about the introduction of Motorcycle Helmet Non-use less than 120 words" introduction = " "*4 + self.chain.invoke({"text": query, "image": ''}).strip().replace("\t", "").replace("\n", "\n"+" "*4) #summary = 3*(' '*4 + "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\n\n") self.set_font('Arial', '', 14) self.multi_cell(19, 0.6, introduction, align='J') self.set_draw_color(*secondary_color) self.set_line_width(0.15) self.line(1.0 + 0.15, 29 - 0.15, 20, 29 - 0.15) self.add_page() self.ln(0.25) self.set_font('Arial', '', 14) self.cell(0, 1.2, 'Figure 1', ln=True, align='L') self.set_font('Arial', 'b', 18) self.cell(0, 0, f'Motorcycle Helmet Non-use in {date}', ln=True, align='L') self.ln(0.4) self.create_graph_1() self.image("./temp/graph_1.png", x=1, w=19, h=7) self.ln(0.25) query = f"Analyze the chart depicting the percentage of motorcycle helmet non-use for the month of {date}. Please include key insights such as trends over time, comparisons with previous months or years, any noticeable spikes or drops, and possible reasons for these changes. Summarize your analysis in 1 short paragraph. less than 60 words" description_1 = " "*4 + self.chain.invoke({"text": query, "image": "./temp/graph_1.png"}).strip().replace("\t", "").replace("\n", "\n"+" "*4) #description_1 = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." self.set_font('Arial', '', 14) self.multi_cell(19, 0.6, description_1, align='J') self.ln(0.1) self.set_font('Arial', '', 14) self.cell(0, 1.2, 'Figure 2', ln=True, align='L') self.set_font('Arial', 'b', 18) self.cell(0, 0, f'Motorcyclists, by Helmet Type', ln=True, align='L') y = self.get_y() self.ln(0.4) self.create_graph_2() self.image("./temp/graph_2.png", x=1, w=9, h=9) query = f"Analyze the pie chart illustrating the proportion of different categories of individuals who are not wearing motorcycle helmets Discuss Consider potential implications for safety measures or awareness campaigns. Summarize your insights in 1 short paragraph less than 30 words." description_2 = " "*4 + self.chain.invoke({"text": query, "image": "./temp/graph_2.png"}).strip().replace("\t", "").replace("\n", "\n"+" "*4) #description_2 = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." self.set_font('Arial', '', 14) self.set_xy(10.5, y + 0.5) self.multi_cell(9.5, 0.6, description_2, align='J') self.set_draw_color(*secondary_color) self.set_line_width(0.15) self.line(1.0 + 0.15, 29 - 0.15, 20, 29 - 0.15) filename = f'./temp/motorcycle_helmet_report_{self.start_date}_{self.end_date}.pdf' self.output(filename) return filename def prompt_func(self, data): text = data["text"] image = data["image"] content_parts = [] text_part = {"type": "text", "text": text} content_parts.append(text_part) if image != '': image_part = { "type": "image_url", "image_url": f"data:image/jpeg;base64,{image}", } content_parts.append(image_part) return [HumanMessage(content=content_parts)] def describe_image(self, text, image_path): pil_image = Image.open(image_path) image_b64 = self.convert_to_base64(pil_image) query_chain = self.chain.invoke( {"text": text, "image": image_b64} ) return query_chain if __name__ == '__main__': report = ReportGenerator('NOV 2024', 'NOV 2025') #report.generate() x, y = report.generate_months() print(x, y) pass