-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreport_generator.py
More file actions
122 lines (103 loc) · 4.5 KB
/
Copy pathreport_generator.py
File metadata and controls
122 lines (103 loc) · 4.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import os
from fpdf import FPDF
def generate_esg_pdf(df, cs, date_range_str):
"""Generates an enterprise-grade, Unicode-safe ESG PDF report."""
pdf = FPDF()
pdf.add_page()
# 1. FAIL-SAFE FONT REGISTRATION
base_font = "Helvetica" # Default safe core font
font_path = "fonts/DejaVuSans.ttf"
if os.path.exists(font_path):
try:
pdf.add_font("DejaVu", "", font_path)
pdf.add_font("DejaVu", "B", font_path)
pdf.add_font("DejaVu", "I", font_path)
base_font = "DejaVu"
except Exception as e:
print(f"Font load warning: {e}")
# --- THE FIX: DYNAMIC TEXT SANITIZER ---
def safe_text(text):
"""Forces latin-1 compliance only if the fallback font is active."""
if base_font == "Helvetica":
return str(text).encode('latin-1', errors='replace').decode('latin-1')
return str(text)
# 2. BRANDED HEADER
pdf.set_fill_color(16, 45, 29) # GreenOps Emerald
pdf.rect(0, 0, 210, 25, 'F')
pdf.set_y(8)
pdf.set_text_color(255, 255, 255)
pdf.set_font(base_font, "B", 18)
pdf.cell(0, 10, "GreenOps ESG Compliance Report", border=0, align="C")
pdf.set_text_color(15, 23, 42) # Slate text
pdf.ln(20)
# 3. ENTERPRISE METADATA (Grid Layout)
pdf.set_font(base_font, "B", 11)
pdf.cell(0, 8, "Corporate Profile", ln=1)
pdf.line(10, pdf.get_y(), 200, pdf.get_y())
pdf.ln(2)
pdf.set_font(base_font, "B", 10)
pdf.cell(35, 6, "Entity Name:")
pdf.set_font(base_font, "", 10)
pdf.cell(70, 6, safe_text(cs.get('company_name', 'Enterprise')))
pdf.set_font(base_font, "B", 10)
pdf.cell(35, 6, "Reporting Period:")
pdf.set_font(base_font, "", 10)
pdf.cell(0, 6, safe_text(date_range_str), ln=1)
pdf.set_font(base_font, "B", 10)
pdf.cell(35, 6, "Location:")
pdf.set_font(base_font, "", 10)
pdf.cell(70, 6, safe_text(cs.get('location', 'Not Specified')))
# --- EXPORT MARKETS FIX ---
pdf.set_font(base_font, "B", 10)
pdf.cell(30, 6, "Export Markets:")
pdf.set_font(base_font, "", 10)
markets_list = cs.get('export_markets', [])
if not markets_list:
markets_str = "None"
elif len(markets_list) == 1:
markets_str = markets_list[0]
else:
markets_str = f"{markets_list[0]} (+{len(markets_list) - 1} more)"
pdf.cell(0, 6, safe_text(markets_str), ln=1)
pdf.ln(8)
# 4. EXECUTIVE SUMMARY
total_emissions = df['emissions_kgCO2e'].sum()
pdf.set_font(base_font, "B", 12)
pdf.set_fill_color(241, 255, 249) # Light Slate
pdf.cell(0, 8, " 1. Executive Footprint Summary", border=1, fill=True, ln=1)
pdf.set_font(base_font, "", 11)
pdf.cell(0, 8, f" Total Validated Carbon Footprint: {total_emissions:,.2f} kgCO2e", border=1, ln=1)
pdf.ln(5)
# 5. SCOPE BREAKDOWN (Tabular Format)
pdf.set_font(base_font, "B", 12)
pdf.cell(0, 8, " 2. Emissions by Domain (Scope)", border=1, fill=True, ln=1)
pdf.set_font(base_font, "B", 10)
pdf.cell(95, 8, " Domain Category", border=1)
pdf.cell(50, 8, " Impact (kgCO2e)", border=1, align="R")
pdf.cell(45, 8, " % of Total", border=1, align="R", ln=1)
pdf.set_font(base_font, "", 10)
scope_data = df.groupby('scope')['emissions_kgCO2e'].sum()
for scope, val in scope_data.items():
percentage = (val / total_emissions * 100) if total_emissions > 0 else 0
pdf.cell(95, 8, f" {safe_text(scope)}", border=1)
pdf.cell(50, 8, f" {val:,.2f}", border=1, align="R")
pdf.cell(45, 8, f" {percentage:.1f}%", border=1, align="R", ln=1)
pdf.ln(5)
# 6. CARBON BOTTLENECKS
pdf.set_font(base_font, "B", 12)
pdf.cell(0, 8, " 3. Primary Carbon Bottlenecks (Top Emitters)", border=1, fill=True, ln=1)
pdf.set_font(base_font, "", 10)
top_offenders = df.groupby('activity')['emissions_kgCO2e'].sum().nlargest(5)
if not top_offenders.empty:
for idx, (act, val) in enumerate(top_offenders.items(), 1):
pdf.cell(145, 8, f" {idx}. {safe_text(act)}", border=1)
pdf.cell(45, 8, f" {val:,.2f} kgCO2e", border=1, align="R", ln=1)
else:
pdf.cell(0, 8, " No quantifiable bottleneck data available.", border=1, ln=1)
# 7. FOOTER
pdf.set_y(-25)
pdf.set_font(base_font, "I", 8)
pdf.set_text_color(100, 100, 100)
disclaimer = "Generated by GreenOps | Calculations based on user telemetry and standard GHG Protocol emission factors."
pdf.multi_cell(0, 4, disclaimer, align="C")
return bytes(pdf.output())