Please define equipment groups and time slots first.
'; return;
}
// Rebuild header for detailed table
const dhr = detailedHead.insertRow();
dhr.insertCell().textContent = 'Group';
oef_timeSlots.forEach(slot => dhr.insertCell().outerHTML = `
${slot.name} (kWh) | `);
dhr.insertCell().outerHTML = '
Total Daily (kWh) | ';
dhr.insertCell().outerHTML = '
Daily Cost ($) | ';
let overallDailyKwh = 0;
const energyBySlot = {}; oef_timeSlots.forEach(s => energyBySlot[s.id] = 0);
const energyByGroupDaily = {}; oef_equipmentGroups.forEach(g => energyByGroupDaily[g.id] = {name: g.name, totalKwh:0});
oef_equipmentGroups.forEach(group => {
const groupRow = detailedBody.insertRow();
groupRow.insertCell().textContent = group.name;
let groupDailyKwh = 0;
const groupTotalWattage = (group.items || []).reduce((sum, item) => {
const eqType = oef_equipmentTypes.find(et => et.id === item.eqTypeId);
return sum + (eqType ? eqType.wattage * item.quantity : 0);
},0);
oef_timeSlots.forEach(slot => {
const usagePercent = (oef_groupUsagePatterns[group.id] && oef_groupUsagePatterns[group.id][slot.id] !== undefined) ? oef_groupUsagePatterns[group.id][slot.id] : 0;
const slotKwh = (groupTotalWattage / 1000) * (usagePercent / 100) * slot.durationHours;
groupRow.insertCell().textContent = slotKwh.toFixed(3);
groupDailyKwh += slotKwh;
energyBySlot[slot.id] += slotKwh;
});
overallDailyKwh += groupDailyKwh;
energyByGroupDaily[group.id].totalKwh = groupDailyKwh;
groupRow.insertCell().textContent = groupDailyKwh.toFixed(3);
groupRow.insertCell().textContent = (groupDailyKwh * oef_settings.electricityCostPerKwh).toFixed(2);
});
// Overall Totals Display
const dailyCostTotal = overallDailyKwh * oef_settings.electricityCostPerKwh;
const monthlyKwhTotal = overallDailyKwh * oef_settings.workdaysPerMonth;
const monthlyCostTotal = dailyCostTotal * oef_settings.workdaysPerMonth;
const annualKwhTotal = monthlyKwhTotal * 12;
const annualCostTotal = monthlyCostTotal * 12;
const annualCo2Total = oef_settings.co2IntensityFactor > 0 ? annualKwhTotal * oef_settings.co2IntensityFactor : 0;
document.getElementById('oef-totalDailyKwh').textContent = `${overallDailyKwh.toFixed(2)} kWh`;
document.getElementById('oef-totalDailyCost').textContent = `$${dailyCostTotal.toFixed(2)}`;
document.getElementById('oef-totalMonthlyKwh').textContent = `${monthlyKwhTotal.toFixed(2)} kWh`;
document.getElementById('oef-totalMonthlyCost').textContent = `$${monthlyCostTotal.toFixed(2)}`;
document.getElementById('oef-totalAnnualKwh').textContent = `${annualKwhTotal.toFixed(2)} kWh`;
document.getElementById('oef-totalAnnualCost').textContent = `$${annualCostTotal.toFixed(2)}`;
const co2SummaryEl = document.getElementById('oef-co2SummaryLine');
if (oef_settings.co2IntensityFactor > 0) {
document.getElementById('oef-totalAnnualCo2').textContent = `${annualCo2Total.toFixed(2)} kg CO2e`;
co2SummaryEl.style.display = 'block';
} else { co2SummaryEl.style.display = 'none';}
// Charts
const flowCtx = document.getElementById('oef-dailyFlowChart').getContext('2d');
const groupPieCtx = document.getElementById('oef-groupBreakdownPieChart').getContext('2d');
const slotLabels = oef_timeSlots.map(s => `${s.name} (${s.startTime}-${s.endTime})`);
const slotEnergyData = oef_timeSlots.map(s => energyBySlot[s.id].toFixed(2));
const slotColors = oef_timeSlots.map((_, i) => `hsl(${(i * 360 / (oef_timeSlots.length||1)) % 360}, 65%, 60%)`);
if(oef_charts.dailyFlow) oef_charts.dailyFlow.destroy();
if(slotEnergyData.some(d => d > 0)) {
oef_charts.dailyFlow = new Chart(flowCtx, {
type: 'bar', data: { labels: slotLabels, datasets: [{ label: 'kWh per Slot', data: slotEnergyData, backgroundColor: slotColors[1] || '#2ECC71'}]}, // Use a consistent color or map
options: { responsive: true, maintainAspectRatio: false, scales: {y:{beginAtZero:true, title:{display:true, text:'kWh'}}}}
});
} else {flowCtx.clearRect(0,0,flowCtx.canvas.width,flowCtx.canvas.height); flowCtx.textAlign="center"; flowCtx.fillText("No flow data.", flowCtx.canvas.width/2, flowCtx.canvas.height/2);}
const groupLabels = Object.values(energyByGroupDaily).filter(g=>g.totalKwh > 0).map(g => g.name);
const groupEnergyData = Object.values(energyByGroupDaily).filter(g=>g.totalKwh > 0).map(g => g.totalKwh.toFixed(2));
const groupColors = groupLabels.map((_, i) => `hsl(${(i * 360 / (groupLabels.length||1) + 60) % 360}, 75%, 55%)`);
if(oef_charts.groupBreakdownPie) oef_charts.groupBreakdownPie.destroy();
if(groupEnergyData.length > 0) {
oef_charts.groupBreakdownPie = new Chart(groupPieCtx, {
type: 'pie', data: { labels: groupLabels, datasets: [{data: groupEnergyData, backgroundColor: groupColors}]},
options: { responsive: true, maintainAspectRatio: false, plugins: {legend:{position:'right'}, tooltip:{callbacks:{label: (ctx) => `${ctx.label}: ${ctx.raw} kWh (${((ctx.raw/overallDailyKwh)*100).toFixed(1)}%)`}}}}
});
} else {groupPieCtx.clearRect(0,0,groupPieCtx.canvas.width,groupPieCtx.canvas.height); groupPieCtx.textAlign="center"; groupPieCtx.fillText("No group data.", groupPieCtx.canvas.width/2, groupPieCtx.canvas.height/2);}
}
// --- PDF Download ---
document.getElementById('oef-downloadPdfBtn').onclick = async () => {
if (oef_equipmentGroups.length === 0 || oef_timeSlots.length === 0 || document.getElementById('oef-analysisReportArea').style.display === 'none') {
alert("Please setup equipment, time slots, and generate an analysis first."); return;
}
const { jsPDF } = window.jspdf; const pdf = new jsPDF('p', 'mm', 'a4'); // Portrait for this report
let currentY = 15; const margin = 15; const contentWidth = pdf.internal.pageSize.getWidth() - 2*margin;
const accentColor = '#2ECC71'; const textColor = '#1D5E3B';
pdf.setFontSize(18); pdf.setTextColor(accentColor);
pdf.text("Office Energy Flow Analysis Report", pdf.internal.pageSize.getWidth() / 2, currentY, { align: 'center' });
currentY += 8;
pdf.setFontSize(10); pdf.setTextColor(textColor);
pdf.text(`Report Generated: ${OEF_TODAY_CONTEXT.toLocaleDateString()}`, pdf.internal.pageSize.getWidth() / 2, currentY, { align: 'center'});
currentY += 6;
pdf.text(`Settings: ${oef_settings.workdaysPerMonth} workdays/month | $${oef_settings.electricityCostPerKwh.toFixed(3)}/kWh ${oef_settings.co2IntensityFactor > 0 ? `| ${oef_settings.co2IntensityFactor} kgCO2e/kWh` : ''}`, margin, currentY);
currentY += 10;
// Overall Totals
pdf.setFontSize(12); pdf.setTextColor(accentColor);
pdf.text("Overall Estimated Totals (Typical Day):", margin, currentY); currentY += 6;
pdf.setFontSize(10); pdf.setTextColor(textColor);
pdf.text(`- Total Daily Energy: ${document.getElementById('oef-totalDailyKwh').textContent}`, margin + 5, currentY); currentY += 5;
pdf.text(`- Est. Daily Cost: ${document.getElementById('oef-totalDailyCost').textContent}`, margin + 5, currentY); currentY += 7;
pdf.text(`Est. Monthly: ${document.getElementById('oef-totalMonthlyKwh').textContent} | ${document.getElementById('oef-totalMonthlyCost').textContent}`, margin +5, currentY); currentY += 5;
pdf.text(`Est. Annual: ${document.getElementById('oef-totalAnnualKwh').textContent} | ${document.getElementById('oef-totalAnnualCost').textContent}`, margin+5, currentY); currentY += 5;
if (oef_settings.co2IntensityFactor > 0) {
pdf.text(`- Est. Annual CO2 Emissions: ${document.getElementById('oef-totalAnnualCo2').textContent}`, margin + 5, currentY); currentY += 5;
}
currentY += 5;
// Charts
const chartsToCapture = [
{ canvasId: 'oef-dailyFlowChart', title: "Daily Energy Flow by Time Slot (kWh)" },
{ canvasId: 'oef-groupBreakdownPieChart', title: "Daily Energy Consumption by Group (%)" }
];
for (const chartInfo of chartsToCapture) {
if (currentY > pdf.internal.pageSize.getHeight() - 65 ) { pdf.addPage(); currentY = margin; }
const chartCanvas = document.getElementById(chartInfo.canvasId);
const chartInstance = chartInfo.canvasId === 'oef-dailyFlowChart' ? oef_charts.dailyFlow : oef_charts.groupBreakdownPie;
if (chartInstance && chartInstance.data && chartInstance.data.datasets[0].data.some(d=>parseFloat(d)>0)) {
pdf.setFontSize(11); pdf.setTextColor(accentColor);
pdf.text(chartInfo.title + ":", margin, currentY); currentY += 5;
try {
const chartImgData = chartCanvas.toDataURL('image/png', 1.0);
const imgProps = pdf.getImageProperties(chartImgData);
let pdfImgWidth = contentWidth * 0.8;
let pdfImgHeight = (imgProps.height * pdfImgWidth) / imgProps.width;
const maxChartHeight = 60;
if (pdfImgHeight > maxChartHeight) { pdfImgHeight = maxChartHeight; pdfImgWidth = (imgProps.width * pdfImgHeight) / imgProps.height; }
if (pdfImgWidth > contentWidth) { pdfImgWidth = contentWidth; pdfImgHeight = (imgProps.height * pdfImgWidth) / imgProps.width; if(pdfImgHeight > maxChartHeight) pdfImgHeight = maxChartHeight;}
pdf.addImage(chartImgData, 'PNG', margin + (contentWidth - pdfImgWidth)/2 , currentY, pdfImgWidth, pdfImgHeight, undefined, 'FAST');
currentY += pdfImgHeight + 7;
} catch(e) { console.error("PDF chart error for " + chartInfo.title, e); currentY +=5; }
}
}
// Detailed Breakdown Table for PDF
if (currentY > pdf.internal.pageSize.getHeight() - 50) { pdf.addPage(); currentY = margin; }
pdf.setFontSize(12); pdf.setTextColor(accentColor);
pdf.text("Detailed Consumption (kWh) per Group & Slot:", margin, currentY); currentY += 7;
const tableHead = [['Group']];
oef_timeSlots.forEach(slot => tableHead[0].push(`${slot.name.substring(0,10)}.. kWh`)); // Abbreviate for PDF
tableHead[0].push('Total kWh/Day');
tableHead[0].push('Cost/Day ($)');
const tableBody = [];
const breakdownRows = document.getElementById('oef-detailedFlowTableBody').rows;
for(let i=0; i
0) {
pdf.autoTable({
head: tableHead, body: tableBody, startY: currentY, theme: 'grid',
headStyles: { fillColor: accentColor, textColor: '#FFFFFF', fontSize:7, cellPadding:1 },
styles: { fontSize: 7, cellPadding: 1, overflow:'linebreak' },
columnStyles: { 0:{cellWidth:35}, /* Auto for others or define widths */ }
});
} else { pdf.setFontSize(10); pdf.text("No detailed breakdown data available.", margin, currentY); }
pdf.save(`Office_Energy_Flow_Report_${OEF_TODAY_CONTEXT.toISOString().slice(0,10)}.pdf`);
};
// --- Initial Load ---
document.addEventListener('DOMContentLoaded', () => {
oef_openTab(null, oef_tabs[0]);
const firstTabButton = document.querySelector(`.oef-tab-button[onclick*="'${oef_tabs[0]}'"]`);
if (firstTabButton) firstTabButton.classList.add('active');
else console.error("First tab button not found for initial activation.");
oef_updateNavButtons();
});