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:
471
main.py
471
main.py
@@ -15,6 +15,7 @@ import sys
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import time
|
||||
import warnings
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
@@ -62,6 +63,72 @@ from report_generator import (
|
||||
from recommendations import RecommendationsEngine
|
||||
|
||||
|
||||
class PerformanceLogger:
|
||||
"""Tracks execution time for each operation to identify bottlenecks."""
|
||||
|
||||
def __init__(self):
|
||||
self.timings = []
|
||||
self.start_time = time.perf_counter()
|
||||
self.phase_start = None
|
||||
self.phase_name = None
|
||||
|
||||
def start(self, name: str):
|
||||
"""Start timing a named operation."""
|
||||
self.phase_name = name
|
||||
self.phase_start = time.perf_counter()
|
||||
print(f"⏱️ [{self._timestamp()}] START: {name}")
|
||||
|
||||
def stop(self, rows: int = None):
|
||||
"""Stop timing and record duration."""
|
||||
if self.phase_start is None:
|
||||
return
|
||||
duration = time.perf_counter() - self.phase_start
|
||||
self.timings.append({
|
||||
'name': self.phase_name,
|
||||
'duration': duration,
|
||||
'rows': rows
|
||||
})
|
||||
rows_info = f" ({rows} rows)" if rows else ""
|
||||
print(f"✅ [{self._timestamp()}] DONE: {self.phase_name} - {duration:.2f}s{rows_info}")
|
||||
self.phase_start = None
|
||||
|
||||
def _timestamp(self):
|
||||
return datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
def summary(self, output_path: str = None):
|
||||
"""Print summary sorted by duration (slowest first)."""
|
||||
total = time.perf_counter() - self.start_time
|
||||
|
||||
print("\n" + "="*70)
|
||||
print("📊 PERFORMANCE SUMMARY (sorted by duration, slowest first)")
|
||||
print("="*70)
|
||||
|
||||
sorted_timings = sorted(self.timings, key=lambda x: x['duration'], reverse=True)
|
||||
|
||||
lines = []
|
||||
for t in sorted_timings:
|
||||
pct = (t['duration'] / total) * 100 if total > 0 else 0
|
||||
rows_info = f" [{t['rows']} rows]" if t['rows'] else ""
|
||||
line = f"{t['duration']:8.2f}s ({pct:5.1f}%) - {t['name']}{rows_info}"
|
||||
print(line)
|
||||
lines.append(line)
|
||||
|
||||
print("-"*70)
|
||||
print(f"TOTAL: {total:.2f}s ({total/60:.1f} minutes)")
|
||||
|
||||
# Save to file
|
||||
if output_path:
|
||||
log_file = f"{output_path}/performance_log.txt"
|
||||
with open(log_file, 'w', encoding='utf-8') as f:
|
||||
f.write(f"Performance Log - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
f.write("="*70 + "\n\n")
|
||||
for line in lines:
|
||||
f.write(line + "\n")
|
||||
f.write("\n" + "-"*70 + "\n")
|
||||
f.write(f"TOTAL: {total:.2f}s ({total/60:.1f} minutes)\n")
|
||||
print(f"\n📝 Log saved to: {log_file}")
|
||||
|
||||
|
||||
class OracleConnection:
|
||||
"""Context manager for Oracle database connection"""
|
||||
|
||||
@@ -142,47 +209,112 @@ def generate_reports(args):
|
||||
|
||||
# Connect and execute queries
|
||||
results = {}
|
||||
|
||||
perf = PerformanceLogger() # Initialize performance logger
|
||||
|
||||
with OracleConnection() as conn:
|
||||
print("\n📥 Extragere date din Oracle:\n")
|
||||
|
||||
|
||||
for query_name, query_info in QUERIES.items():
|
||||
perf.start(f"QUERY: {query_name}")
|
||||
df = execute_query(conn, query_name, query_info)
|
||||
results[query_name] = df
|
||||
|
||||
perf.stop(rows=len(df) if df is not None and not df.empty else 0)
|
||||
|
||||
# Generate Excel Report
|
||||
print("\n📝 Generare raport Excel...")
|
||||
excel_gen = ExcelReportGenerator(excel_path)
|
||||
|
||||
|
||||
# Generate recommendations based on all data
|
||||
print("\n🔍 Generare recomandări automate...")
|
||||
perf.start("RECOMMENDATIONS: analyze_all")
|
||||
recommendations_engine = RecommendationsEngine(RECOMMENDATION_THRESHOLDS)
|
||||
recommendations_df = recommendations_engine.analyze_all(results)
|
||||
results['recomandari'] = recommendations_df
|
||||
perf.stop(rows=len(recommendations_df))
|
||||
print(f"✓ {len(recommendations_df)} recomandări generate")
|
||||
|
||||
# Add sheets in logical order (updated per PLAN_INDICATORI_LICHIDITATE_YOY.md)
|
||||
# =========================================================================
|
||||
# CONSOLIDARE DATE PENTRU VEDERE DE ANSAMBLU
|
||||
# =========================================================================
|
||||
print("\n📊 Consolidare date pentru vedere de ansamblu...")
|
||||
|
||||
# --- Consolidare 1: Vedere Executivă (KPIs + YoY) ---
|
||||
perf.start("CONSOLIDATION: kpi_consolidated")
|
||||
# Folosim direct sumar_executiv_yoy care are deja toate coloanele necesare:
|
||||
# INDICATOR, VALOARE_CURENTA, VALOARE_ANTERIOARA, VARIATIE_PROCENT, TREND
|
||||
if 'sumar_executiv_yoy' in results and not results['sumar_executiv_yoy'].empty:
|
||||
df_kpi = results['sumar_executiv_yoy'].copy()
|
||||
# Adaugă coloana UM bazată pe tipul indicatorului
|
||||
df_kpi['UM'] = df_kpi['INDICATOR'].apply(lambda x:
|
||||
'%' if '%' in x or 'marja' in x.lower() else
|
||||
'buc' if 'numar' in x.lower() else 'RON'
|
||||
)
|
||||
results['kpi_consolidated'] = df_kpi
|
||||
else:
|
||||
# Fallback la sumar_executiv simplu (fără YoY)
|
||||
results['kpi_consolidated'] = results.get('sumar_executiv', pd.DataFrame())
|
||||
perf.stop()
|
||||
|
||||
# --- Consolidare 2: Indicatori Venituri (Current + YoY) ---
|
||||
perf.start("CONSOLIDATION: venituri_consolidated")
|
||||
if 'indicatori_agregati_venituri' in results and 'indicatori_agregati_venituri_yoy' in results:
|
||||
df_venituri = results['indicatori_agregati_venituri'].copy()
|
||||
df_venituri_yoy = results['indicatori_agregati_venituri_yoy'].copy()
|
||||
|
||||
if not df_venituri.empty and not df_venituri_yoy.empty:
|
||||
# Merge pe LINIE_BUSINESS
|
||||
df_venituri_yoy = df_venituri_yoy.rename(columns={
|
||||
'VANZARI': 'VANZARI_ANTERIOARE',
|
||||
'MARJA': 'MARJA_ANTERIOARA'
|
||||
})
|
||||
df_venituri_combined = pd.merge(
|
||||
df_venituri,
|
||||
df_venituri_yoy[['LINIE_BUSINESS', 'VANZARI_ANTERIOARE', 'VARIATIE_PROCENT', 'TREND']],
|
||||
on='LINIE_BUSINESS',
|
||||
how='left'
|
||||
)
|
||||
df_venituri_combined = df_venituri_combined.rename(columns={'VANZARI': 'VANZARI_CURENTE'})
|
||||
results['venituri_consolidated'] = df_venituri_combined
|
||||
else:
|
||||
results['venituri_consolidated'] = df_venituri
|
||||
else:
|
||||
results['venituri_consolidated'] = results.get('indicatori_agregati_venituri', pd.DataFrame())
|
||||
perf.stop()
|
||||
|
||||
# --- Consolidare 3: Clienți și Risc (Portofoliu + Concentrare + YoY) ---
|
||||
perf.start("CONSOLIDATION: risc_consolidated")
|
||||
if 'concentrare_risc' in results and 'concentrare_risc_yoy' in results:
|
||||
df_risc = results['concentrare_risc'].copy()
|
||||
df_risc_yoy = results['concentrare_risc_yoy'].copy()
|
||||
|
||||
if not df_risc.empty and not df_risc_yoy.empty:
|
||||
# Merge pe INDICATOR
|
||||
df_risc = df_risc.rename(columns={'PROCENT': 'PROCENT_CURENT'})
|
||||
df_risc_combined = pd.merge(
|
||||
df_risc,
|
||||
df_risc_yoy[['INDICATOR', 'PROCENT_ANTERIOR', 'VARIATIE', 'TREND']],
|
||||
on='INDICATOR',
|
||||
how='left'
|
||||
)
|
||||
results['risc_consolidated'] = df_risc_combined
|
||||
else:
|
||||
results['risc_consolidated'] = df_risc
|
||||
else:
|
||||
results['risc_consolidated'] = results.get('concentrare_risc', pd.DataFrame())
|
||||
perf.stop()
|
||||
|
||||
print("✓ Consolidări finalizate")
|
||||
|
||||
# Add sheets in logical order - CONSOLIDAT primul, apoi detalii
|
||||
sheet_order = [
|
||||
# SUMAR EXECUTIV
|
||||
'sumar_executiv',
|
||||
'sumar_executiv_yoy',
|
||||
'recomandari',
|
||||
# CONSOLIDAT - Vedere de Ansamblu (înlocuiește sheet-urile individuale)
|
||||
'vedere_ansamblu', # KPIs + YoY + Recomandări
|
||||
'indicatori_venituri', # Venituri Current + YoY merged
|
||||
'clienti_risc', # Portofoliu + Concentrare + YoY
|
||||
'tablou_financiar', # 5 secțiuni financiare
|
||||
|
||||
# INDICATORI AGREGATI (MUTATI SUS - imagine de ansamblu)
|
||||
'indicatori_agregati_venituri',
|
||||
'indicatori_agregati_venituri_yoy',
|
||||
'portofoliu_clienti',
|
||||
'concentrare_risc',
|
||||
'concentrare_risc_yoy',
|
||||
# DETALII - Sheet-uri individuale pentru analiză profundă
|
||||
'sezonalitate_lunara',
|
||||
|
||||
# INDICATORI GENERALI & LICHIDITATE
|
||||
'indicatori_generali',
|
||||
'indicatori_lichiditate',
|
||||
'clasificare_datorii',
|
||||
'grad_acoperire_datorii',
|
||||
'proiectie_lichiditate',
|
||||
|
||||
# ALERTE
|
||||
'vanzari_sub_cost',
|
||||
'clienti_marja_mica',
|
||||
@@ -452,118 +584,248 @@ def generate_reports(args):
|
||||
'CANTITATE_TRANSFORMARI_OUT': 'Cantitate iesita din transformari',
|
||||
'SOLD_NET_CANTITATE': 'Sold net = Total intrari - Total iesiri',
|
||||
'SOLD_NET_VALOARE': 'Valoare neta a soldului'
|
||||
},
|
||||
# =====================================================================
|
||||
# LEGENDS FOR CONSOLIDATED SHEETS
|
||||
# =====================================================================
|
||||
'vedere_ansamblu': {
|
||||
'INDICATOR': 'Denumirea indicatorului de business',
|
||||
'VALOARE_CURENTA': 'Valoare în perioada curentă (ultimele 12 luni)',
|
||||
'UM': 'Unitate de măsură',
|
||||
'VALOARE_ANTERIOARA': 'Valoare în perioada anterioară (12-24 luni)',
|
||||
'VARIATIE_PROCENT': 'Variație procentuală YoY',
|
||||
'TREND': 'CREȘTERE/SCĂDERE/STABIL',
|
||||
'STATUS': 'OK = bine, ATENȚIE = necesită atenție, ALERTĂ = acțiune urgentă',
|
||||
'CATEGORIE': 'Domeniu: Marja, Clienți, Stoc, Financiar',
|
||||
'RECOMANDARE': 'Acțiune sugerată'
|
||||
},
|
||||
'indicatori_venituri': {
|
||||
'LINIE_BUSINESS': 'Producție proprie / Materii prime / Marfă revândută',
|
||||
'VANZARI_CURENTE': 'Vânzări în ultimele 12 luni',
|
||||
'PROCENT_VENITURI': 'Contribuția la totalul vânzărilor (%)',
|
||||
'MARJA': 'Marja brută pe linia de business',
|
||||
'PROCENT_MARJA': 'Marja procentuală',
|
||||
'VANZARI_ANTERIOARE': 'Vânzări în perioada anterioară',
|
||||
'VARIATIE_PROCENT': 'Creștere/scădere procentuală YoY',
|
||||
'TREND': 'CREȘTERE / SCĂDERE / STABIL'
|
||||
},
|
||||
'clienti_risc': {
|
||||
'CATEGORIE': 'Tipul de categorie clienți',
|
||||
'VALOARE': 'Numărul de clienți sau valoarea',
|
||||
'EXPLICATIE': 'Explicația categoriei',
|
||||
'INDICATOR': 'Top 1/5/10 clienți',
|
||||
'PROCENT_CURENT': '% vânzări la Top N clienți - an curent',
|
||||
'PROCENT_ANTERIOR': '% vânzări la Top N clienți - an trecut',
|
||||
'VARIATIE': 'Schimbarea în puncte procentuale',
|
||||
'TREND': 'DIVERSIFICARE (bine) / CONCENTRARE (risc) / STABIL',
|
||||
'STATUS': 'OK / ATENTIE / RISC MARE'
|
||||
},
|
||||
'tablou_financiar': {
|
||||
'INDICATOR': 'Denumirea indicatorului financiar',
|
||||
'VALOARE': 'Valoarea calculată',
|
||||
'STATUS': 'OK / ATENȚIE / ALERTĂ',
|
||||
'RECOMANDARE': 'Acțiune sugerată pentru îmbunătățire',
|
||||
'INTERPRETARE': 'Ce înseamnă valoarea pentru business'
|
||||
}
|
||||
}
|
||||
|
||||
# =========================================================================
|
||||
# GENERARE SHEET-URI CONSOLIDATE EXCEL
|
||||
# =========================================================================
|
||||
|
||||
# --- Sheet 0: DASHBOARD COMPLET (toate secțiunile într-o singură vedere) ---
|
||||
perf.start("EXCEL: Dashboard Complet sheet (9 sections)")
|
||||
excel_gen.add_consolidated_sheet(
|
||||
name='Dashboard Complet',
|
||||
sheet_title='Dashboard Executiv - Vedere Completă',
|
||||
sheet_description='Toate indicatorii cheie consolidați într-o singură vedere rapidă',
|
||||
sections=[
|
||||
# KPIs și Recomandări
|
||||
{
|
||||
'title': 'KPIs cu Comparație YoY',
|
||||
'df': results.get('kpi_consolidated', pd.DataFrame()),
|
||||
'description': 'Indicatori cheie de performanță - curent vs anterior'
|
||||
},
|
||||
{
|
||||
'title': 'Recomandări Prioritare',
|
||||
'df': results.get('recomandari', pd.DataFrame()).head(10),
|
||||
'description': 'Top 10 acțiuni sugerate bazate pe analiză'
|
||||
},
|
||||
# Venituri
|
||||
{
|
||||
'title': 'Venituri per Linie Business',
|
||||
'df': results.get('venituri_consolidated', pd.DataFrame()),
|
||||
'description': 'Producție proprie, Materii prime, Marfă revândută'
|
||||
},
|
||||
# Clienți și Risc
|
||||
{
|
||||
'title': 'Portofoliu Clienți',
|
||||
'df': results.get('portofoliu_clienti', pd.DataFrame()),
|
||||
'description': 'Structura și segmentarea clienților'
|
||||
},
|
||||
{
|
||||
'title': 'Concentrare Risc YoY',
|
||||
'df': results.get('risc_consolidated', pd.DataFrame()),
|
||||
'description': 'Dependența de clienții mari - curent vs anterior'
|
||||
},
|
||||
# Tablou Financiar
|
||||
{
|
||||
'title': 'Indicatori Generali',
|
||||
'df': results.get('indicatori_generali', pd.DataFrame()),
|
||||
'description': 'Sold clienți, furnizori, cifra afaceri'
|
||||
},
|
||||
{
|
||||
'title': 'Indicatori Lichiditate',
|
||||
'df': results.get('indicatori_lichiditate', pd.DataFrame()),
|
||||
'description': 'Zile rotație stoc, creanțe, datorii'
|
||||
},
|
||||
{
|
||||
'title': 'Clasificare Datorii',
|
||||
'df': results.get('clasificare_datorii', pd.DataFrame()),
|
||||
'description': 'Datorii pe intervale de întârziere'
|
||||
},
|
||||
{
|
||||
'title': 'Proiecție Lichiditate',
|
||||
'df': results.get('proiectie_lichiditate', pd.DataFrame()),
|
||||
'description': 'Previziune încasări și plăți pe 30 zile'
|
||||
}
|
||||
]
|
||||
)
|
||||
perf.stop()
|
||||
|
||||
# NOTE: Sheet-urile individuale (Vedere Ansamblu, Indicatori Venituri, Clienti si Risc,
|
||||
# Tablou Financiar) au fost eliminate - toate datele sunt acum în Dashboard Complet
|
||||
|
||||
# --- Adaugă restul sheet-urilor de detaliu ---
|
||||
# Skip sheet-urile care sunt acum în view-urile consolidate
|
||||
consolidated_sheets = {
|
||||
'vedere_ansamblu', 'indicatori_venituri', 'clienti_risc', 'tablou_financiar',
|
||||
# Sheet-uri incluse în consolidări (nu mai sunt separate):
|
||||
'sumar_executiv', 'sumar_executiv_yoy', 'recomandari',
|
||||
'indicatori_agregati_venituri', 'indicatori_agregati_venituri_yoy',
|
||||
'portofoliu_clienti', 'concentrare_risc', 'concentrare_risc_yoy',
|
||||
'indicatori_generali', 'indicatori_lichiditate', 'clasificare_datorii',
|
||||
'grad_acoperire_datorii', 'proiectie_lichiditate'
|
||||
}
|
||||
|
||||
for query_name in sheet_order:
|
||||
if query_name in results:
|
||||
# Tratare speciala pentru 'sumar_executiv' - adauga recomandari sub KPIs
|
||||
if query_name == 'sumar_executiv':
|
||||
query_info = QUERIES.get(query_name, {})
|
||||
excel_gen.add_sheet_with_recommendations(
|
||||
name='Sumar Executiv',
|
||||
df=results['sumar_executiv'],
|
||||
recommendations_df=results.get('recomandari'),
|
||||
title=query_info.get('title', 'Sumar Executiv'),
|
||||
description=query_info.get('description', ''),
|
||||
legend=legends.get('sumar_executiv'),
|
||||
top_n_recommendations=5
|
||||
)
|
||||
# Pastreaza sheet-ul complet de recomandari
|
||||
elif query_name == 'recomandari':
|
||||
excel_gen.add_sheet(
|
||||
name='RECOMANDARI',
|
||||
df=results['recomandari'],
|
||||
title='Recomandari Automate (Lista Completa)',
|
||||
description='Toate insight-urile si actiunile sugerate bazate pe analiza datelor',
|
||||
legend=legends.get('recomandari')
|
||||
)
|
||||
elif query_name in QUERIES:
|
||||
query_info = QUERIES[query_name]
|
||||
# Create short sheet name from query name
|
||||
sheet_name = query_name.replace('_', ' ').title()[:31]
|
||||
excel_gen.add_sheet(
|
||||
name=sheet_name,
|
||||
df=results[query_name],
|
||||
title=query_info.get('title', query_name),
|
||||
description=query_info.get('description', ''),
|
||||
legend=legends.get(query_name)
|
||||
)
|
||||
|
||||
# Skip consolidated sheets and their source sheets
|
||||
if query_name in consolidated_sheets:
|
||||
continue
|
||||
|
||||
if query_name in results and query_name in QUERIES:
|
||||
query_info = QUERIES[query_name]
|
||||
# Create short sheet name from query name
|
||||
sheet_name = query_name.replace('_', ' ').title()[:31]
|
||||
perf.start(f"EXCEL: {query_name} detail sheet")
|
||||
excel_gen.add_sheet(
|
||||
name=sheet_name,
|
||||
df=results[query_name],
|
||||
title=query_info.get('title', query_name),
|
||||
description=query_info.get('description', ''),
|
||||
legend=legends.get(query_name)
|
||||
)
|
||||
df_rows = len(results[query_name]) if results[query_name] is not None else 0
|
||||
perf.stop(rows=df_rows)
|
||||
|
||||
perf.start("EXCEL: Save workbook")
|
||||
excel_gen.save()
|
||||
perf.stop()
|
||||
|
||||
# Generate PDF Report
|
||||
# =========================================================================
|
||||
# GENERARE PDF - PAGINI CONSOLIDATE
|
||||
# =========================================================================
|
||||
print("\n📄 Generare raport PDF...")
|
||||
pdf_gen = PDFReportGenerator(pdf_path, company_name=COMPANY_NAME)
|
||||
|
||||
# Title page
|
||||
# Pagina 1: Titlu
|
||||
perf.start("PDF: Title page")
|
||||
pdf_gen.add_title_page()
|
||||
perf.stop()
|
||||
|
||||
# KPIs
|
||||
pdf_gen.add_kpi_section(results.get('sumar_executiv'))
|
||||
# Pagina 2-3: DASHBOARD COMPLET (toate secțiunile într-o vedere unificată)
|
||||
perf.start("PDF: Dashboard Complet page (4 sections)")
|
||||
pdf_gen.add_consolidated_page(
|
||||
'Dashboard Complet',
|
||||
sections=[
|
||||
{
|
||||
'title': 'KPIs cu Comparație YoY',
|
||||
'df': results.get('kpi_consolidated', pd.DataFrame()),
|
||||
'columns': ['INDICATOR', 'VALOARE_CURENTA', 'UM', 'VALOARE_ANTERIOARA', 'VARIATIE_PROCENT', 'TREND'],
|
||||
'max_rows': 6
|
||||
},
|
||||
{
|
||||
'title': 'Recomandări Prioritare',
|
||||
'df': results.get('recomandari', pd.DataFrame()),
|
||||
'columns': ['STATUS', 'CATEGORIE', 'INDICATOR', 'RECOMANDARE'],
|
||||
'max_rows': 5
|
||||
},
|
||||
{
|
||||
'title': 'Venituri per Linie Business',
|
||||
'df': results.get('venituri_consolidated', pd.DataFrame()),
|
||||
'columns': ['LINIE_BUSINESS', 'VANZARI_CURENTE', 'PROCENT_VENITURI', 'VARIATIE_PROCENT', 'TREND'],
|
||||
'max_rows': 5
|
||||
},
|
||||
{
|
||||
'title': 'Concentrare Risc YoY',
|
||||
'df': results.get('risc_consolidated', pd.DataFrame()),
|
||||
'columns': ['INDICATOR', 'PROCENT_CURENT', 'PROCENT_ANTERIOR', 'TREND'],
|
||||
'max_rows': 4
|
||||
}
|
||||
]
|
||||
)
|
||||
perf.stop()
|
||||
|
||||
# NEW: Indicatori Generali section
|
||||
if 'indicatori_generali' in results and not results['indicatori_generali'].empty:
|
||||
pdf_gen.add_table_section(
|
||||
"Indicatori Generali de Business",
|
||||
results.get('indicatori_generali'),
|
||||
columns=['INDICATOR', 'VALOARE', 'STATUS', 'RECOMANDARE'],
|
||||
max_rows=10
|
||||
)
|
||||
# NOTE: Paginile individuale (Vedere Executivă, Indicatori Venituri, Clienți și Risc,
|
||||
# Tablou Financiar) au fost eliminate - toate datele sunt acum în Dashboard Complet
|
||||
|
||||
# NEW: Indicatori Lichiditate section
|
||||
if 'indicatori_lichiditate' in results and not results['indicatori_lichiditate'].empty:
|
||||
pdf_gen.add_table_section(
|
||||
"Indicatori de Lichiditate",
|
||||
results.get('indicatori_lichiditate'),
|
||||
columns=['INDICATOR', 'VALOARE', 'STATUS', 'RECOMANDARE'],
|
||||
max_rows=10
|
||||
)
|
||||
pdf_gen.add_page_break()
|
||||
|
||||
# NEW: Proiecție Lichiditate
|
||||
if 'proiectie_lichiditate' in results and not results['proiectie_lichiditate'].empty:
|
||||
pdf_gen.add_table_section(
|
||||
"Proiecție Cash Flow 30/60/90 zile",
|
||||
results.get('proiectie_lichiditate'),
|
||||
columns=['PERIOADA', 'SOLD_PROIECTAT', 'INCASARI', 'PLATI', 'STATUS'],
|
||||
max_rows=5
|
||||
)
|
||||
|
||||
# NEW: Recommendations section (top priorities)
|
||||
if 'recomandari' in results and not results['recomandari'].empty:
|
||||
pdf_gen.add_recommendations_section(results['recomandari'])
|
||||
|
||||
# Alerts
|
||||
# Alerte (vânzări sub cost, clienți marjă mică)
|
||||
perf.start("PDF: Alerts section")
|
||||
pdf_gen.add_alerts_section({
|
||||
'vanzari_sub_cost': results.get('vanzari_sub_cost', pd.DataFrame()),
|
||||
'clienti_marja_mica': results.get('clienti_marja_mica', pd.DataFrame())
|
||||
})
|
||||
perf.stop()
|
||||
|
||||
pdf_gen.add_page_break()
|
||||
|
||||
# Monthly chart
|
||||
# =========================================================================
|
||||
# PAGINI DE GRAFICE ȘI DETALII
|
||||
# =========================================================================
|
||||
|
||||
# Grafic: Evoluția Vânzărilor Lunare
|
||||
if 'vanzari_lunare' in results and not results['vanzari_lunare'].empty:
|
||||
perf.start("PDF: Chart - vanzari_lunare")
|
||||
fig = create_monthly_chart(results['vanzari_lunare'])
|
||||
pdf_gen.add_chart_image(fig, "Evoluția Vânzărilor și Marjei")
|
||||
perf.stop()
|
||||
|
||||
# Client concentration
|
||||
# Grafic: Concentrare Clienți
|
||||
if 'concentrare_clienti' in results and not results['concentrare_clienti'].empty:
|
||||
perf.start("PDF: Chart - concentrare_clienti")
|
||||
fig = create_client_concentration_chart(results['concentrare_clienti'])
|
||||
pdf_gen.add_chart_image(fig, "Concentrare Clienți")
|
||||
perf.stop()
|
||||
|
||||
pdf_gen.add_page_break()
|
||||
|
||||
# NEW: Cash Conversion Cycle chart
|
||||
# Grafic: Ciclu Conversie Cash
|
||||
if 'ciclu_conversie_cash' in results and not results['ciclu_conversie_cash'].empty:
|
||||
perf.start("PDF: Chart - ciclu_conversie_cash")
|
||||
fig = create_cash_cycle_chart(results['ciclu_conversie_cash'])
|
||||
pdf_gen.add_chart_image(fig, "Ciclu Conversie Cash (DIO + DSO - DPO)")
|
||||
perf.stop()
|
||||
|
||||
# Production vs Resale
|
||||
# Grafic: Producție vs Revânzare
|
||||
if 'productie_vs_revanzare' in results and not results['productie_vs_revanzare'].empty:
|
||||
perf.start("PDF: Chart - productie_vs_revanzare")
|
||||
fig = create_production_chart(results['productie_vs_revanzare'])
|
||||
pdf_gen.add_chart_image(fig, "Producție Proprie vs Revânzare")
|
||||
perf.stop()
|
||||
|
||||
# Top clients table
|
||||
# Tabel: Top clienți
|
||||
pdf_gen.add_table_section(
|
||||
"Top 15 Clienți după Vânzări",
|
||||
results.get('marja_per_client'),
|
||||
@@ -573,7 +835,7 @@ def generate_reports(args):
|
||||
|
||||
pdf_gen.add_page_break()
|
||||
|
||||
# Top products
|
||||
# Tabel: Top produse
|
||||
pdf_gen.add_table_section(
|
||||
"Top 15 Produse după Vânzări",
|
||||
results.get('top_produse'),
|
||||
@@ -581,7 +843,7 @@ def generate_reports(args):
|
||||
max_rows=15
|
||||
)
|
||||
|
||||
# Trending clients
|
||||
# Tabel: Trending clienți
|
||||
pdf_gen.add_table_section(
|
||||
"Trending Clienți (YoY)",
|
||||
results.get('trending_clienti'),
|
||||
@@ -589,7 +851,7 @@ def generate_reports(args):
|
||||
max_rows=15
|
||||
)
|
||||
|
||||
# NEW: Aging Creanțe table
|
||||
# Tabel: Aging Creanțe
|
||||
if 'aging_creante' in results and not results['aging_creante'].empty:
|
||||
pdf_gen.add_page_break()
|
||||
pdf_gen.add_table_section(
|
||||
@@ -599,7 +861,7 @@ def generate_reports(args):
|
||||
max_rows=15
|
||||
)
|
||||
|
||||
# Stoc lent
|
||||
# Tabel: Stoc lent
|
||||
if 'stoc_lent' in results and not results['stoc_lent'].empty:
|
||||
pdf_gen.add_page_break()
|
||||
pdf_gen.add_table_section(
|
||||
@@ -609,8 +871,13 @@ def generate_reports(args):
|
||||
max_rows=20
|
||||
)
|
||||
|
||||
perf.start("PDF: Save document")
|
||||
pdf_gen.save()
|
||||
|
||||
perf.stop()
|
||||
|
||||
# Performance Summary
|
||||
perf.summary(output_path=str(args.output_dir))
|
||||
|
||||
# Summary
|
||||
print("\n" + "="*60)
|
||||
print(" ✅ RAPOARTE GENERATE CU SUCCES!")
|
||||
@@ -618,7 +885,7 @@ def generate_reports(args):
|
||||
print(f"\n 📊 Excel: {excel_path}")
|
||||
print(f" 📄 PDF: {pdf_path}")
|
||||
print("\n" + "="*60)
|
||||
|
||||
|
||||
return excel_path, pdf_path
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user