""" Report generator for YourCarbonFootprint application. Generates PDF reports and visualizations. """ import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import plotly.express as px import plotly.graph_objects as go from fpdf import FPDF import os from datetime import datetime import base64 from io import BytesIO class ReportGenerator: def __init__(self, data_handler, translations=None): """Initialize the ReportGenerator class.""" self.data_handler = data_handler # Use provided translations or default self.translations = translations or { 'English': { 'title': 'Carbon Emissions Report', 'company': 'Company', 'industry': 'Industry', 'location': 'Location', 'reporting_period': 'Reporting Period', 'generated_on': 'Generated on', 'summary': 'Summary', 'total_emissions': 'Total Emissions', 'emissions_by_scope': 'Emissions by Scope:', 'top_categories': 'Top Categories:', 'emissions_data': 'Emissions Data', 'date': 'Date', 'scope': 'Scope', 'category': 'Category', 'activity': 'Activity', 'quantity': 'Quantity', 'unit': 'Unit', 'factor': 'Factor', 'emissions_kgco2e': 'Emissions (kgCO2e)', 'reg_compliance': 'Regulatory Compliance', 'cbam': 'EU CBAM: This report can be used as supporting documentation for EU CBAM compliance.', 'gx_league': 'Japan GX League: This report follows the GX League reporting format.', 'ets': 'Indonesia ETS/ETP: This report can be used for Indonesia ETS/ETP compliance.', 'recommendations': 'Recommendations', 'rec1': '1. Focus on reducing emissions from the top categories identified in this report.', 'rec2': '2. Consider implementing energy efficiency measures for Scope 2 emissions.', 'rec3': '3. Explore renewable energy options to reduce your carbon footprint.', 'rec4': '4. Engage with suppliers to address Scope 3 emissions in your value chain.', }, 'Vietnamese': { 'title': 'Báo cáo Phát thải Carbon', 'company': 'Công ty', 'industry': 'Ngành nghề', 'location': 'Địa điểm', 'reporting_period': 'Kỳ báo cáo', 'generated_on': 'Ngày tạo', 'summary': 'Tóm tắt', 'total_emissions': 'Tổng phát thải', 'emissions_by_scope': 'Phát thải theo phạm vi:', 'top_categories': 'Danh mục hàng đầu:', 'emissions_data': 'Dữ liệu phát thải', 'date': 'Ngày', 'scope': 'Phạm vi', 'category': 'Danh mục', 'activity': 'Hoạt động', 'quantity': 'Số lượng', 'unit': 'Đơn vị', 'factor': 'Hệ số', 'emissions_kgco2e': 'Phát thải (kgCO2e)', 'reg_compliance': 'Tuân thủ quy định', 'cbam': 'EU CBAM: Báo cáo này có thể dùng làm tài liệu hỗ trợ tuân thủ EU CBAM.', 'gx_league': 'Japan GX League: Báo cáo này tuân theo định dạng báo cáo GX League.', 'ets': 'Indonesia ETS/ETP: Báo cáo này có thể dùng cho tuân thủ Indonesia ETS/ETP.', 'recommendations': 'Khuyến nghị', 'rec1': '1. Tập trung giảm phát thải từ các danh mục hàng đầu trong báo cáo này.', 'rec2': '2. Xem xét thực hiện các biện pháp tiết kiệm năng lượng cho phát thải phạm vi 2.', 'rec3': '3. Khám phá các lựa chọn năng lượng tái tạo để giảm dấu chân carbon.', 'rec4': '4. Hợp tác với nhà cung cấp để giải quyết phát thải phạm vi 3 trong chuỗi giá trị.', } } def t(self, key, language): return self.translations.get(language, self.translations['English']).get(key, key) def generate_pdf_report(self, file_path=None, start_date=None, end_date=None, company_info=None, language='English'): """ Generate PDF report. Args: file_path (str, optional): Path to save PDF file start_date (datetime, optional): Start date for filtering end_date (datetime, optional): End date for filtering company_info (dict, optional): Company information language (str, optional): Language for the report text Returns: bytes or bool: PDF bytes if file_path is None, otherwise True if successful """ try: # Get filtered data data = self.data_handler.get_filtered_data(start_date, end_date) if len(data) == 0: return False, "No data available for the selected period." # Create PDF pdf = FPDF() pdf.add_page() pdf.set_font("Arial", "B", 16) pdf.cell(0, 10, self.t('title', language), 0, 1, "C") pdf.set_font("Arial", "", 12) if company_info: pdf.cell(0, 10, f"{self.t('company', language)}: {company_info.get('name', 'N/A')}", 0, 1) pdf.cell(0, 10, f"{self.t('industry', language)}: {company_info.get('industry', 'N/A')}", 0, 1) pdf.cell(0, 10, f"{self.t('location', language)}: {company_info.get('location', 'N/A')}", 0, 1) pdf.cell(0, 10, f"{self.t('reporting_period', language)}: {start_date.strftime('%Y-%m-%d') if start_date else 'All'} to {end_date.strftime('%Y-%m-%d') if end_date else 'All'}", 0, 1) pdf.cell(0, 10, f"{self.t('generated_on', language)}: {datetime.now().strftime('%Y-%m-%d')}", 0, 1) pdf.ln(10) pdf.set_font("Arial", "B", 14) pdf.cell(0, 10, self.t('summary', language), 0, 1) pdf.set_font("Arial", "", 12) total_emissions = data['emissions_kgCO2e'].sum() pdf.cell(0, 10, f"{self.t('total_emissions', language)}: {total_emissions:.2f} kgCO2e", 0, 1) scope_data = data.groupby('scope')['emissions_kgCO2e'].sum().reset_index() pdf.ln(5) pdf.cell(0, 10, self.t('emissions_by_scope', language), 0, 1) for _, row in scope_data.iterrows(): pdf.cell(0, 10, f"{row['scope']}: {row['emissions_kgCO2e']:.2f} kgCO2e ({row['emissions_kgCO2e'] / total_emissions * 100:.1f}%)", 0, 1) category_data = data.groupby('category')['emissions_kgCO2e'].sum().reset_index() pdf.ln(5) pdf.cell(0, 10, self.t('top_categories', language), 0, 1) for _, row in category_data.nlargest(5, 'emissions_kgCO2e').iterrows(): pdf.cell(0, 10, f"{row['category']}: {row['emissions_kgCO2e']:.2f} kgCO2e ({row['emissions_kgCO2e'] / total_emissions * 100:.1f}%)", 0, 1) pdf.ln(10) pdf.set_font("Arial", "B", 14) pdf.cell(0, 10, self.t('emissions_data', language), 0, 1) pdf.set_font("Arial", "B", 10) col_widths = [25, 25, 30, 30, 20, 15, 25, 30] headers = [self.t('date', language), self.t('scope', language), self.t('category', language), self.t('activity', language), self.t('quantity', language), self.t('unit', language), self.t('factor', language), self.t('emissions_kgco2e', language)] for i, header in enumerate(headers): pdf.cell(col_widths[i], 10, header, 1) pdf.ln() pdf.set_font("Arial", "", 8) for _, row in data.iterrows(): pdf.cell(col_widths[0], 10, row['date'].strftime('%Y-%m-%d') if isinstance(row['date'], pd.Timestamp) else str(row['date']), 1) pdf.cell(col_widths[1], 10, str(row['scope']), 1) pdf.cell(col_widths[2], 10, str(row['category']), 1) pdf.cell(col_widths[3], 10, str(row['activity']), 1) pdf.cell(col_widths[4], 10, f"{row['quantity']:.2f}", 1) pdf.cell(col_widths[5], 10, str(row['unit']), 1) pdf.cell(col_widths[6], 10, f"{row['emission_factor']:.4f}", 1) pdf.cell(col_widths[7], 10, f"{row['emissions_kgCO2e']:.2f}", 1) pdf.ln() pdf.ln(10) pdf.set_font("Arial", "B", 14) pdf.cell(0, 10, self.t('reg_compliance', language), 0, 1) pdf.set_font("Arial", "", 12) pdf.cell(0, 10, self.t('cbam', language), 0, 1) pdf.cell(0, 10, self.t('gx_league', language), 0, 1) pdf.cell(0, 10, self.t('ets', language), 0, 1) pdf.ln(10) pdf.set_font("Arial", "B", 14) pdf.cell(0, 10, self.t('recommendations', language), 0, 1) pdf.set_font("Arial", "", 12) pdf.cell(0, 10, self.t('rec1', language), 0, 1) pdf.cell(0, 10, self.t('rec2', language), 0, 1) pdf.cell(0, 10, self.t('rec3', language), 0, 1) pdf.cell(0, 10, self.t('rec4', language), 0, 1) if file_path: pdf.output(file_path) return True, self.t('title', language) + ' generated successfully.' else: return pdf.output(dest='S').encode('latin1'), self.t('title', language) + ' generated successfully.' except Exception as e: return False, f"Error generating PDF report: {str(e)}" def create_scope_pie_chart(self, data): """ Create pie chart of emissions by scope. Args: data (pandas.DataFrame): Emissions data Returns: plotly.graph_objects.Figure: Pie chart figure """ scope_data = data.groupby('scope')['emissions_kgCO2e'].sum().reset_index() fig = px.pie( scope_data, values='emissions_kgCO2e', names='scope', color='scope', color_discrete_map={ 'Scope 1': '#4CAF50', 'Scope 2': '#2196F3', 'Scope 3': '#FFC107' }, title='Emissions by Scope' ) fig.update_layout( legend_title="Scope", font=dict(size=12), margin=dict(t=50, b=20, l=20, r=20) ) return fig def create_category_bar_chart(self, data): """ Create bar chart of emissions by category. Args: data (pandas.DataFrame): Emissions data Returns: plotly.graph_objects.Figure: Bar chart figure """ category_data = data.groupby('category')['emissions_kgCO2e'].sum().reset_index() category_data = category_data.sort_values('emissions_kgCO2e', ascending=False) fig = px.bar( category_data, x='category', y='emissions_kgCO2e', color='category', title='Emissions by Category' ) fig.update_layout( xaxis_title="Category", yaxis_title="Emissions (kgCO2e)", legend_title="Category", font=dict(size=12), margin=dict(t=50, b=100, l=50, r=20), xaxis_tickangle=-45 ) return fig def create_time_series_chart(self, data): """ Create time series chart of emissions over time. Args: data (pandas.DataFrame): Emissions data Returns: plotly.graph_objects.Figure: Line chart figure """ if 'date' not in data.columns or len(data) == 0: # Create empty figure if no data fig = go.Figure() fig.update_layout( title='Emissions Over Time', xaxis_title="Month", yaxis_title="Emissions (kgCO2e)", font=dict(size=12), margin=dict(t=50, b=50, l=50, r=20) ) return fig # Group by month and scope time_data = data.copy() time_data['month'] = pd.to_datetime(time_data['date']).dt.strftime('%Y-%m') time_data = time_data.groupby(['month', 'scope'])['emissions_kgCO2e'].sum().reset_index() fig = px.line( time_data, x='month', y='emissions_kgCO2e', color='scope', markers=True, title='Emissions Over Time' ) fig.update_layout( xaxis_title="Month", yaxis_title="Emissions (kgCO2e)", legend_title="Scope", font=dict(size=12), margin=dict(t=50, b=50, l=50, r=20) ) return fig def create_activity_treemap(self, data): """ Create treemap of emissions by scope, category, and activity. Args: data (pandas.DataFrame): Emissions data Returns: plotly.graph_objects.Figure: Treemap figure """ fig = px.treemap( data, path=['scope', 'category', 'activity'], values='emissions_kgCO2e', color='scope', color_discrete_map={ 'Scope 1': '#4CAF50', 'Scope 2': '#2196F3', 'Scope 3': '#FFC107' }, title='Emissions Breakdown' ) fig.update_layout( margin=dict(t=50, b=20, l=20, r=20), font=dict(size=12) ) return fig def create_monthly_comparison_chart(self, data): """ Create bar chart comparing emissions by month. Args: data (pandas.DataFrame): Emissions data Returns: plotly.graph_objects.Figure: Bar chart figure """ if 'date' not in data.columns or len(data) == 0: # Create empty figure if no data fig = go.Figure() fig.update_layout( title='Monthly Emissions Comparison', xaxis_title="Month", yaxis_title="Emissions (kgCO2e)", font=dict(size=12), margin=dict(t=50, b=50, l=50, r=20) ) return fig # Group by month monthly_data = data.copy() monthly_data['month'] = pd.to_datetime(monthly_data['date']).dt.strftime('%Y-%m') monthly_data = monthly_data.groupby('month')['emissions_kgCO2e'].sum().reset_index() fig = px.bar( monthly_data, x='month', y='emissions_kgCO2e', title='Monthly Emissions Comparison' ) fig.update_layout( xaxis_title="Month", yaxis_title="Emissions (kgCO2e)", font=dict(size=12), margin=dict(t=50, b=50, l=50, r=20) ) return fig