Implement Dashboard consolidation + Performance logging

Features:
- Add unified "Dashboard Complet" sheet (Excel) with all 9 sections
- Add unified "Dashboard Complet" page (PDF) with key metrics
- Fix VALOARE_ANTERIOARA NULL bug (use sumar_executiv_yoy directly)
- Add PerformanceLogger class for timing analysis
- Remove redundant consolidated sheets (keep only Dashboard Complet)

Bug fixes:
- Fix Excel formula error (=== interpreted as formula, changed to >>>)
- Fix args.output → args.output_dir in perf.summary()

Performance analysis:
- Add PERFORMANCE_ANALYSIS.md with detailed breakdown
- SQL queries take 94% of runtime (31 min), Excel/PDF only 1%
- Identified slow queries for optimization

Documentation:
- Update CLAUDE.md with new structure
- Add context handover for query optimization task

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-11 13:33:02 +02:00
parent a2ad4c7ed2
commit 9e9ddec014
20 changed files with 2400 additions and 959 deletions

View File

@@ -262,6 +262,156 @@ class ExcelReportGenerator:
ws.freeze_panes = ws.cell(row=5, column=1)
def add_consolidated_sheet(self, name: str, sections: list, sheet_title: str = None,
sheet_description: str = None):
"""
Add a consolidated sheet with multiple sections separated visually.
Args:
name: Sheet name (max 31 chars)
sections: List of dicts with keys:
- 'title': Section title (str)
- 'df': DataFrame with data
- 'description': Optional section description (str)
- 'legend': Optional dict with column explanations
sheet_title: Overall sheet title
sheet_description: Overall sheet description
"""
sheet_name = name[:31]
ws = self.wb.create_sheet(title=sheet_name)
start_row = 1
# Add overall sheet title
if sheet_title:
ws.cell(row=start_row, column=1, value=sheet_title)
ws.cell(row=start_row, column=1).font = Font(bold=True, size=16)
start_row += 1
# Add overall description
if sheet_description:
ws.cell(row=start_row, column=1, value=sheet_description)
ws.cell(row=start_row, column=1).font = Font(italic=True, size=10, color='666666')
start_row += 1
# Add timestamp
ws.cell(row=start_row, column=1, value=f"Generat: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
ws.cell(row=start_row, column=1).font = Font(size=9, color='999999')
start_row += 2
# Process each section
for section in sections:
section_title = section.get('title', '')
df = section.get('df')
description = section.get('description', '')
legend = section.get('legend', {})
# Section separator
separator_fill = PatternFill(start_color='2C3E50', end_color='2C3E50', fill_type='solid')
for col in range(1, 10): # Wide separator
# Use >>> instead of === to avoid Excel formula interpretation
cell = ws.cell(row=start_row, column=col, value='' if col > 1 else f'>>> {section_title}')
cell.fill = separator_fill
cell.font = Font(bold=True, color='FFFFFF', size=11)
start_row += 1
# Section description
if description:
ws.cell(row=start_row, column=1, value=description)
ws.cell(row=start_row, column=1).font = Font(italic=True, size=9, color='666666')
start_row += 1
start_row += 1
# Check for empty data
if df is None or df.empty:
ws.cell(row=start_row, column=1, value="Nu există date pentru această secțiune.")
ws.cell(row=start_row, column=1).font = Font(italic=True, color='999999')
start_row += 3
continue
# Write headers
for col_idx, col_name in enumerate(df.columns, 1):
cell = ws.cell(row=start_row, column=col_idx, value=col_name)
cell.font = self.header_font
cell.fill = self.header_fill
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
cell.border = self.border
# Write data
for row_idx, row in enumerate(df.itertuples(index=False), start_row + 1):
for col_idx, value in enumerate(row, 1):
cell = ws.cell(row=row_idx, column=col_idx, value=value)
cell.border = self.border
# Format numbers
if isinstance(value, (int, float)):
cell.number_format = '#,##0.00' if isinstance(value, float) else '#,##0'
cell.alignment = Alignment(horizontal='right')
# Highlight based on column name
col_name = df.columns[col_idx - 1].lower()
# Status coloring
if col_name == 'status' or col_name == 'acoperire':
if isinstance(value, str):
if value == 'OK':
cell.fill = self.good_fill
elif value in ('ATENTIE', 'NECESAR'):
cell.fill = self.warning_fill
elif value in ('ALERTA', 'DEFICIT', 'RISC MARE'):
cell.fill = self.alert_fill
# Trend coloring
if col_name == 'trend':
if isinstance(value, str):
if value in ('CRESTERE', 'IMBUNATATIRE', 'DIVERSIFICARE'):
cell.fill = self.good_fill
elif value in ('SCADERE', 'DETERIORARE', 'CONCENTRARE', 'PIERDUT'):
cell.fill = self.alert_fill
elif value == 'ATENTIE':
cell.fill = self.warning_fill
# Variatie coloring
if 'variatie' in col_name:
if isinstance(value, (int, float)):
if value > 0:
cell.fill = self.good_fill
elif value < 0:
cell.fill = self.alert_fill
# Margin coloring
if 'procent' in col_name or 'marja' in col_name:
if isinstance(value, (int, float)):
if value < 10:
cell.fill = self.alert_fill
elif value < 15:
cell.fill = self.warning_fill
elif value > 25:
cell.fill = self.good_fill
start_row = start_row + len(df) + 2
# Add legend for this section
if legend:
ws.cell(row=start_row, column=1, value="Legendă:")
ws.cell(row=start_row, column=1).font = Font(bold=True, size=8, color='336699')
start_row += 1
for col_name, explanation in legend.items():
ws.cell(row=start_row, column=1, value=f"{col_name}: {explanation}")
ws.cell(row=start_row, column=1).font = Font(size=8, color='666666')
start_row += 1
# Space between sections
start_row += 2
# Auto-adjust column widths
for col_idx in range(1, 12):
ws.column_dimensions[get_column_letter(col_idx)].width = 18
# Freeze title row
ws.freeze_panes = ws.cell(row=5, column=1)
def save(self):
"""Save the workbook"""
self.wb.save(self.output_path)
@@ -497,6 +647,108 @@ class PDFReportGenerator:
"""Add page break"""
self.elements.append(PageBreak())
def add_consolidated_page(self, page_title: str, sections: list):
"""
Add a consolidated PDF page with multiple sections.
Args:
page_title: Main title for the page
sections: List of dicts with keys:
- 'title': Section title (str)
- 'df': DataFrame with data
- 'columns': List of columns to display (optional)
- 'max_rows': Max rows to display (default 15)
"""
# Page title
self.elements.append(Paragraph(page_title, self.styles['SectionHeader']))
self.elements.append(Spacer(1, 0.3*cm))
for section in sections:
section_title = section.get('title', '')
df = section.get('df')
columns = section.get('columns')
max_rows = section.get('max_rows', 15)
# Sub-section title
subsection_style = ParagraphStyle(
name='SubSection',
parent=self.styles['Heading2'],
fontSize=11,
spaceBefore=10,
spaceAfter=5,
textColor=colors.HexColor('#2C3E50')
)
self.elements.append(Paragraph(section_title, subsection_style))
if df is None or df.empty:
self.elements.append(Paragraph("Nu există date.", self.styles['Normal']))
self.elements.append(Spacer(1, 0.3*cm))
continue
# Select columns
if columns:
cols = [c for c in columns if c in df.columns]
else:
cols = list(df.columns)[:6] # Max 6 columns
if not cols:
continue
# Prepare data
data = [cols]
for _, row in df.head(max_rows).iterrows():
row_data = []
for col in cols:
val = row.get(col, '')
if isinstance(val, float):
row_data.append(f"{val:,.2f}")
elif isinstance(val, int):
row_data.append(f"{val:,}")
else:
row_data.append(str(val)[:30]) # Truncate long strings
data.append(row_data)
# Calculate column widths
n_cols = len(cols)
col_width = 16*cm / n_cols
table = Table(data, colWidths=[col_width] * n_cols)
# Build style with conditional row colors for status
table_style = [
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#366092')),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, -1), 7),
('BOTTOMPADDING', (0, 0), (-1, 0), 6),
('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f5f5f5')])
]
# Color status cells if STATUS column exists
if 'STATUS' in cols:
status_col_idx = cols.index('STATUS')
for row_idx, row in enumerate(df.head(max_rows).itertuples(index=False), 1):
status_val = str(row[df.columns.get_loc('STATUS')]) if 'STATUS' in df.columns else ''
if status_val == 'ALERTA':
table_style.append(('BACKGROUND', (status_col_idx, row_idx), (status_col_idx, row_idx), colors.HexColor('#FF6B6B')))
elif status_val == 'ATENTIE':
table_style.append(('BACKGROUND', (status_col_idx, row_idx), (status_col_idx, row_idx), colors.HexColor('#FFE66D')))
elif status_val == 'OK':
table_style.append(('BACKGROUND', (status_col_idx, row_idx), (status_col_idx, row_idx), colors.HexColor('#4ECDC4')))
table.setStyle(TableStyle(table_style))
self.elements.append(table)
if len(df) > max_rows:
self.elements.append(Paragraph(
f"... și încă {len(df) - max_rows} înregistrări",
self.styles['SmallText']
))
self.elements.append(Spacer(1, 0.4*cm))
def add_recommendations_section(self, recommendations_df: pd.DataFrame):
"""Add recommendations section with status colors"""
self.elements.append(Paragraph("Recomandari Cheie", self.styles['SectionHeader']))