Initial commit: Data Intelligence Report Generator
- Oracle ERP ROA integration with sales analytics and margin analysis - Excel multi-sheet reports with conditional formatting - PDF executive summaries with charts via ReportLab - Optimized SQL queries (no cartesian products) - Docker support for cross-platform deployment - Configurable alert thresholds for business intelligence 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
670
main.py
Normal file
670
main.py
Normal file
@@ -0,0 +1,670 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Data Intelligence Report Generator
|
||||
===================================
|
||||
Generates comprehensive Excel and PDF reports with financial analytics
|
||||
for ERP data stored in Oracle Database.
|
||||
|
||||
Usage:
|
||||
python main.py [--months 12] [--output-dir ./output]
|
||||
|
||||
Author: Claude AI for ROMFAST SRL
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import warnings
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
# Check dependencies
|
||||
def check_dependencies():
|
||||
"""Check if all required packages are installed"""
|
||||
missing = []
|
||||
packages = {
|
||||
'oracledb': 'oracledb',
|
||||
'pandas': 'pandas',
|
||||
'openpyxl': 'openpyxl',
|
||||
'matplotlib': 'matplotlib',
|
||||
'reportlab': 'reportlab',
|
||||
'dotenv': 'python-dotenv'
|
||||
}
|
||||
|
||||
for import_name, pip_name in packages.items():
|
||||
try:
|
||||
__import__(import_name)
|
||||
except ImportError:
|
||||
missing.append(pip_name)
|
||||
|
||||
if missing:
|
||||
print("❌ Pachete lipsă. Rulează:")
|
||||
print(f" pip install {' '.join(missing)} --break-system-packages")
|
||||
sys.exit(1)
|
||||
|
||||
check_dependencies()
|
||||
|
||||
import oracledb
|
||||
import pandas as pd
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from config import (
|
||||
ORACLE_CONFIG, get_dsn, OUTPUT_DIR, COMPANY_NAME,
|
||||
ANALYSIS_MONTHS, MIN_SALES_FOR_ANALYSIS, LOW_MARGIN_THRESHOLD,
|
||||
RECOMMENDATION_THRESHOLDS
|
||||
)
|
||||
from queries import QUERIES
|
||||
from report_generator import (
|
||||
ExcelReportGenerator, PDFReportGenerator,
|
||||
create_monthly_chart, create_client_concentration_chart, create_production_chart,
|
||||
create_cash_cycle_chart
|
||||
)
|
||||
from recommendations import RecommendationsEngine
|
||||
|
||||
|
||||
class OracleConnection:
|
||||
"""Context manager for Oracle database connection"""
|
||||
|
||||
def __init__(self):
|
||||
self.connection = None
|
||||
|
||||
def __enter__(self):
|
||||
try:
|
||||
print(f"🔌 Conectare la Oracle: {get_dsn()}...")
|
||||
self.connection = oracledb.connect(
|
||||
user=ORACLE_CONFIG['user'],
|
||||
password=ORACLE_CONFIG['password'],
|
||||
dsn=get_dsn()
|
||||
)
|
||||
print("✓ Conectat cu succes!")
|
||||
return self.connection
|
||||
except oracledb.Error as e:
|
||||
print(f"❌ Eroare conexiune Oracle: {e}")
|
||||
raise
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.connection:
|
||||
self.connection.close()
|
||||
print("✓ Conexiune închisă.")
|
||||
|
||||
|
||||
def execute_query(connection, query_name: str, query_info: dict) -> pd.DataFrame:
|
||||
"""Execute a query and return results as DataFrame"""
|
||||
try:
|
||||
sql = query_info['sql']
|
||||
params = query_info.get('params', {})
|
||||
|
||||
print(f" 📊 Executare: {query_name}...", end=" ")
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(sql, params)
|
||||
columns = [col[0] for col in cursor.description]
|
||||
rows = cursor.fetchall()
|
||||
|
||||
df = pd.DataFrame(rows, columns=columns)
|
||||
print(f"✓ ({len(df)} rânduri)")
|
||||
return df
|
||||
|
||||
except oracledb.Error as e:
|
||||
print(f"❌ Eroare: {e}")
|
||||
return pd.DataFrame()
|
||||
|
||||
|
||||
def generate_reports(args):
|
||||
"""Main function to generate all reports"""
|
||||
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
excel_path = OUTPUT_DIR / f"data_intelligence_report_{timestamp}.xlsx"
|
||||
pdf_path = OUTPUT_DIR / f"data_intelligence_report_{timestamp}.pdf"
|
||||
|
||||
print("\n" + "="*60)
|
||||
print(" DATA INTELLIGENCE REPORT GENERATOR")
|
||||
print("="*60)
|
||||
print(f" Perioada: Ultimele {args.months} luni")
|
||||
print(f" Output: {OUTPUT_DIR}")
|
||||
print("="*60 + "\n")
|
||||
|
||||
# Update parameters with command line arguments
|
||||
for query_info in QUERIES.values():
|
||||
if 'months' in query_info.get('params', {}):
|
||||
query_info['params']['months'] = args.months
|
||||
|
||||
# Calculate reporting period string
|
||||
end_date = datetime.now()
|
||||
start_date = end_date - relativedelta(months=args.months)
|
||||
period_str = f"Perioada: {start_date.strftime('%d.%m.%Y')} - {end_date.strftime('%d.%m.%Y')}"
|
||||
|
||||
# Add period to descriptions for queries with months parameter
|
||||
for query_name, query_info in QUERIES.items():
|
||||
if 'months' in query_info.get('params', {}):
|
||||
original_desc = query_info.get('description', '')
|
||||
query_info['description'] = f"{original_desc}\n{period_str}"
|
||||
|
||||
# Connect and execute queries
|
||||
results = {}
|
||||
|
||||
with OracleConnection() as conn:
|
||||
print("\n📥 Extragere date din Oracle:\n")
|
||||
|
||||
for query_name, query_info in QUERIES.items():
|
||||
df = execute_query(conn, query_name, query_info)
|
||||
results[query_name] = df
|
||||
|
||||
# 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...")
|
||||
recommendations_engine = RecommendationsEngine(RECOMMENDATION_THRESHOLDS)
|
||||
recommendations_df = recommendations_engine.analyze_all(results)
|
||||
results['recomandari'] = recommendations_df
|
||||
print(f"✓ {len(recommendations_df)} recomandări generate")
|
||||
|
||||
# Add sheets in logical order (updated per PLAN_INDICATORI_LICHIDITATE_YOY.md)
|
||||
sheet_order = [
|
||||
# SUMAR EXECUTIV
|
||||
'sumar_executiv',
|
||||
'sumar_executiv_yoy',
|
||||
'recomandari',
|
||||
|
||||
# INDICATORI AGREGATI (MUTATI SUS - imagine de ansamblu)
|
||||
'indicatori_agregati_venituri',
|
||||
'indicatori_agregati_venituri_yoy',
|
||||
'portofoliu_clienti',
|
||||
'concentrare_risc',
|
||||
'concentrare_risc_yoy',
|
||||
'sezonalitate_lunara',
|
||||
|
||||
# INDICATORI GENERALI & LICHIDITATE
|
||||
'indicatori_generali',
|
||||
'indicatori_lichiditate',
|
||||
'clasificare_datorii',
|
||||
'grad_acoperire_datorii',
|
||||
'proiectie_lichiditate',
|
||||
|
||||
# ALERTE
|
||||
'vanzari_sub_cost',
|
||||
'clienti_marja_mica',
|
||||
|
||||
# CICLU CASH
|
||||
'ciclu_conversie_cash',
|
||||
|
||||
# ANALIZA CLIENTI
|
||||
'marja_per_client',
|
||||
'clienti_ranking_profit',
|
||||
'frecventa_clienti',
|
||||
'concentrare_clienti',
|
||||
'trending_clienti',
|
||||
'marja_client_categorie',
|
||||
|
||||
# PRODUSE
|
||||
'top_produse',
|
||||
'marja_per_categorie',
|
||||
'marja_per_gestiune',
|
||||
'articole_negestionabile',
|
||||
'productie_vs_revanzare',
|
||||
|
||||
# PRETURI
|
||||
'dispersie_preturi',
|
||||
'clienti_sub_medie',
|
||||
'evolutie_discount',
|
||||
|
||||
# FINANCIAR
|
||||
'dso_dpo',
|
||||
'dso_dpo_yoy',
|
||||
'solduri_clienti',
|
||||
'aging_creante',
|
||||
'facturi_restante',
|
||||
'solduri_furnizori',
|
||||
'aging_datorii',
|
||||
'facturi_restante_furnizori',
|
||||
'pozitia_cash',
|
||||
|
||||
# ISTORIC
|
||||
'vanzari_lunare',
|
||||
|
||||
# STOC
|
||||
'stoc_curent',
|
||||
'stoc_lent',
|
||||
'rotatie_stocuri',
|
||||
|
||||
# PRODUCTIE
|
||||
'analiza_prajitorie',
|
||||
]
|
||||
|
||||
# Legends for each sheet explaining column calculations
|
||||
legends = {
|
||||
'recomandari': {
|
||||
'CATEGORIE': 'Domeniu: Marja, Clienti, Stoc, Financiar',
|
||||
'STATUS': 'OK = bine, ATENTIE = necesita atentie, ALERTA = actiune urgenta',
|
||||
'VEZI_DETALII': 'Sheet-ul cu date detaliate pentru acest indicator'
|
||||
},
|
||||
'marja_per_client': {
|
||||
'VANZARI_FARA_TVA': 'SUM(cantitate × preț) din fact_vfacturi_detalii',
|
||||
'COST_TOTAL': 'SUM(cantitate × pret_achizitie)',
|
||||
'MARJA_BRUTA': 'Vânzări - Cost = SUM(cantitate × (preț - pret_achizitie))',
|
||||
'PROCENT_MARJA': 'Marja Brută / Vânzări × 100'
|
||||
},
|
||||
'clienti_marja_mica': {
|
||||
'VANZARI_FARA_TVA': 'SUM(cantitate × preț) pentru client',
|
||||
'MARJA_BRUTA': 'Vânzări - Cost',
|
||||
'PROCENT_MARJA': 'Marja / Vânzări × 100 (sub 15% = alertă)'
|
||||
},
|
||||
'vanzari_sub_cost': {
|
||||
'PRET_VANZARE': 'Preț unitar din factură (fact_vfacturi_detalii.pret)',
|
||||
'COST': 'Preț achiziție (pret_achizitie)',
|
||||
'PIERDERE': '(Preț vânzare - Cost) × Cantitate (negativ = pierdere)'
|
||||
},
|
||||
'stoc_curent': {
|
||||
'TIP_GESTIUNE': 'Preț vânzare (nr_pag=7) sau Preț achiziție',
|
||||
'VALOARE_STOC_ACHIZITIE': '(cants+cant-cante) × pret din vstoc',
|
||||
'VALOARE_STOC_VANZARE': 'Doar pentru gestiuni preț vânzare, altfel gol'
|
||||
},
|
||||
'stoc_lent': {
|
||||
'CANTITATE': 'Stoc final = cants + cant - cante',
|
||||
'VALOARE': 'Cantitate × preț achiziție',
|
||||
'ZILE_FARA_MISCARE': 'Zile de la ultima ieșire (dataout) sau intrare'
|
||||
},
|
||||
'rotatie_stocuri': {
|
||||
'VALOARE_STOC': 'Stoc curent (cants+cant-cante) × preț achiziție',
|
||||
'VANZARI_12_LUNI': 'Doar vânzări (nu transferuri/consumuri) din ultimele 12 luni',
|
||||
'ROTATIE': 'Vânzări / Stoc (de câte ori s-a rotit stocul)',
|
||||
'ZILE_STOC': 'La ritmul actual, în câte zile se epuizează'
|
||||
},
|
||||
'dispersie_preturi': {
|
||||
'NR_TRANZACTII': 'Număr total linii factură pentru acest produs',
|
||||
'VARIATIE_PROCENT': '(Preț Max - Preț Min) / Preț Mediu × 100',
|
||||
'NR_LA_PRET_MIN': 'Câte tranzacții au fost la prețul minim',
|
||||
'CLIENT_PRET_MIN': 'Primul client care a cumpărat la preț minim'
|
||||
},
|
||||
'top_produse': {
|
||||
'VALOARE_VANZARI': 'SUM(cantitate × preț)',
|
||||
'MARJA_BRUTA': 'SUM(cantitate × (preț - pret_achizitie))',
|
||||
'PROCENT_MARJA': 'Marja / Vânzări × 100'
|
||||
},
|
||||
'marja_per_categorie': {
|
||||
'VANZARI_FARA_TVA': 'Total vânzări pe subgrupă',
|
||||
'COST_TOTAL': 'Total cost achiziție pe subgrupă',
|
||||
'PROCENT_MARJA': 'Marja / Vânzări × 100'
|
||||
},
|
||||
'marja_per_gestiune': {
|
||||
'VANZARI_FARA_TVA': 'Total vânzări pe gestiune (doar articole gestionabile)',
|
||||
'MARJA_BRUTA': 'Total marjă pe gestiune',
|
||||
'PROCENT_MARJA': 'Marja / Vânzări × 100'
|
||||
},
|
||||
'articole_negestionabile': {
|
||||
'DENUMIRE': 'Nume articol negestionabil (in_stoc=0)',
|
||||
'VANZARI_FARA_TVA': 'Total vânzări pentru articole care nu se țin pe stoc',
|
||||
'MARJA_BRUTA': 'Vânzări - Cost',
|
||||
'PROCENT_MARJA': 'Marja / Vânzări × 100'
|
||||
},
|
||||
'vanzari_lunare': {
|
||||
'VANZARI_FARA_TVA': 'Total vânzări în lună',
|
||||
'MARJA_BRUTA': 'Total marjă în lună',
|
||||
'NR_FACTURI': 'Număr facturi emise',
|
||||
'NR_CLIENTI': 'Clienți unici activi'
|
||||
},
|
||||
# NEW legends for financial and aggregated sheets
|
||||
'indicatori_agregati_venituri': {
|
||||
'LINIE_BUSINESS': 'Producție proprie / Materii prime / Marfă revândută',
|
||||
'PROCENT_VENITURI': 'Contribuția la totalul vânzărilor',
|
||||
'CONTRIBUTIE_PROFIT': 'Contribuția la profitul total (%)'
|
||||
},
|
||||
'sezonalitate_lunara': {
|
||||
'MEDIE_VANZARI': 'Media vânzărilor pe 24 luni pentru această lună',
|
||||
'DEVIERE_PROCENT': 'Cât de mult deviază de la media globală',
|
||||
'CLASIFICARE': 'LUNĂ PUTERNICĂ / LUNĂ SLABĂ / NORMAL'
|
||||
},
|
||||
'portofoliu_clienti': {
|
||||
'VALOARE': 'Numărul de clienți în fiecare categorie',
|
||||
'EXPLICATIE': 'Definiția categoriei de clienți'
|
||||
},
|
||||
'concentrare_risc': {
|
||||
'PROCENT': 'Procentul din vânzări pentru Top N clienți',
|
||||
'STATUS': 'OK / ATENTIE / RISC MARE'
|
||||
},
|
||||
'ciclu_conversie_cash': {
|
||||
'INDICATOR': 'DIO (zile stoc) + DSO (zile încasare) - DPO (zile plată)',
|
||||
'ZILE': 'Numărul de zile pentru fiecare component',
|
||||
'EXPLICATIE': 'Ce reprezintă fiecare indicator'
|
||||
},
|
||||
'clienti_ranking_profit': {
|
||||
'RANG_PROFIT': 'Poziția clientului după profit (nu vânzări)',
|
||||
'RANG_VANZARI': 'Poziția clientului după vânzări',
|
||||
'PROFIT_BRUT': 'Vânzări - Cost = profitul efectiv adus'
|
||||
},
|
||||
'frecventa_clienti': {
|
||||
'COMENZI_PE_LUNA': 'Media comenzilor pe lună',
|
||||
'VALOARE_MEDIE_COMANDA': 'Valoarea medie per comandă',
|
||||
'EVOLUTIE_FRECVENTA_YOY': 'Schimbarea frecvenței față de anul trecut'
|
||||
},
|
||||
'marja_client_categorie': {
|
||||
'STATUS_MARJA': 'OK / MARJĂ MICĂ (<15%) / PIERDERE (negativă)',
|
||||
'CATEGORIA': 'Grupa de produse',
|
||||
'PROCENT_MARJA': 'Marja pentru acest client la această categorie'
|
||||
},
|
||||
'evolutie_discount': {
|
||||
'PRET_INITIAL': 'Prețul mediu în primele 6 luni',
|
||||
'PRET_ACTUAL': 'Prețul mediu în ultimele 6 luni',
|
||||
'VARIATIE_PRET_PROCENT': 'Scăderea/creșterea prețului (negativ = discount)'
|
||||
},
|
||||
'dso_dpo': {
|
||||
'DSO': 'Days Sales Outstanding - zile medii încasare clienți',
|
||||
'DPO': 'Days Payables Outstanding - zile medii plată furnizori',
|
||||
'STATUS': 'OK / ATENTIE / ALERTA'
|
||||
},
|
||||
'solduri_clienti': {
|
||||
'SOLD_CURENT': 'Suma de încasat de la client (din cont 4111)',
|
||||
'TIP_SOLD': 'Creanță (ne datorează) sau Avans client (am încasat în avans)'
|
||||
},
|
||||
'aging_creante': {
|
||||
'NEAJUNS_SCADENTA': 'Facturi nescadente încă',
|
||||
'ZILE_1_30': 'Restanțe 1-30 zile',
|
||||
'PESTE_90_ZILE': 'Restanțe critice >90 zile - risc de neîncasare'
|
||||
},
|
||||
'facturi_restante': {
|
||||
'ZILE_INTARZIERE': 'Zile de la scadență',
|
||||
'SUMA_RESTANTA': 'Valoarea rămasă de încasat'
|
||||
},
|
||||
'aging_datorii': {
|
||||
'NEAJUNS_SCADENTA': 'Datorii neajunse la scadență',
|
||||
'ZILE_1_30': 'Restanțe 1-30 zile',
|
||||
'ZILE_31_60': 'Restanțe 31-60 zile',
|
||||
'ZILE_61_90': 'Restanțe 61-90 zile',
|
||||
'PESTE_90_ZILE': 'Restanțe critice >90 zile',
|
||||
'TOTAL_SOLD': 'Total datorii către furnizor'
|
||||
},
|
||||
'facturi_restante_furnizori': {
|
||||
'ZILE_INTARZIERE': 'Zile de la scadență',
|
||||
'SUMA_RESTANTA': 'Valoarea rămasă de plătit'
|
||||
},
|
||||
'solduri_furnizori': {
|
||||
'SOLD_CURENT': 'Suma de plătit furnizorului (din cont 401)',
|
||||
'TIP_SOLD': 'Datorie (trebuie să plătim) sau Avans (am plătit în avans)'
|
||||
},
|
||||
'pozitia_cash': {
|
||||
'SOLD_CURENT': 'Disponibilul curent în cont/casă',
|
||||
'DESCRIERE': 'Tipul contului (bancă/casă, lei/valută)'
|
||||
},
|
||||
# =====================================================================
|
||||
# NEW: Legends for Indicatori Generali, Lichiditate, YoY sheets
|
||||
# =====================================================================
|
||||
'indicatori_generali': {
|
||||
'INDICATOR': 'Grad îndatorare, autonomie financiară, ROA, marjă netă',
|
||||
'VALOARE': 'Valoarea calculată a indicatorului',
|
||||
'STATUS': 'OK / ATENȚIE / ALERTĂ bazat pe praguri standard',
|
||||
'RECOMANDARE': 'Acțiune sugerată pentru îmbunătățire'
|
||||
},
|
||||
'indicatori_lichiditate': {
|
||||
'INDICATOR': 'Lichiditate curentă, rapidă, cash ratio, fond de rulment',
|
||||
'VALOARE': 'Valoarea calculată (rată sau sumă RON)',
|
||||
'STATUS': 'OK / ATENȚIE / ALERTĂ',
|
||||
'INTERPRETARE': 'Ce înseamnă valoarea pentru business'
|
||||
},
|
||||
'clasificare_datorii': {
|
||||
'CATEGORIE': 'Termen scurt (<30z) / mediu (31-90z) / lung (>90z)',
|
||||
'VALOARE': 'Suma datoriilor în categoria respectivă',
|
||||
'NR_FACTURI': 'Numărul facturilor în acea categorie'
|
||||
},
|
||||
'grad_acoperire_datorii': {
|
||||
'VALOARE': 'Cash disponibil + încasări așteptate vs plăți scadente',
|
||||
'ACOPERIRE': 'OK / ATENȚIE / DEFICIT - dacă puteți plăti datoriile',
|
||||
'EXPLICATIE': 'Ce înseamnă pentru fluxul de numerar'
|
||||
},
|
||||
'proiectie_lichiditate': {
|
||||
'PERIOADA': 'Azi / 30 zile / 60 zile / 90 zile',
|
||||
'SOLD_PROIECTAT': 'Cash estimat la sfârșitul perioadei',
|
||||
'FLUX_NET': 'Încasări - Plăți pentru perioada respectivă',
|
||||
'STATUS': 'OK dacă sold pozitiv, ALERTĂ dacă negativ'
|
||||
},
|
||||
'sumar_executiv_yoy': {
|
||||
'VALOARE_CURENTA': 'Valoarea din ultimele 12 luni',
|
||||
'VALOARE_ANTERIOARA': 'Valoarea din anul anterior (12-24 luni)',
|
||||
'VARIATIE_PROCENT': 'Creștere/scădere procentuală',
|
||||
'TREND': 'CREȘTERE / SCĂDERE / STABIL'
|
||||
},
|
||||
'dso_dpo_yoy': {
|
||||
'VALOARE_CURENTA': 'Zile încasare/plată actuale',
|
||||
'VALOARE_ANTERIOARA': 'Zile în perioada anterioară',
|
||||
'VARIATIE_ZILE': 'Diferența în zile (+ = mai rău pentru DSO, mai bine pentru DPO)',
|
||||
'TREND': 'ÎMBUNĂTĂȚIRE / DETERIORARE / STABIL'
|
||||
},
|
||||
'concentrare_risc_yoy': {
|
||||
'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'
|
||||
},
|
||||
'indicatori_agregati_venituri_yoy': {
|
||||
'LINIE_BUSINESS': 'Producție proprie / Materii prime / Marfă',
|
||||
'VANZARI_CURENTE': 'Vânzări în ultimele 12 luni',
|
||||
'VANZARI_ANTERIOARE': 'Vânzări în perioada anterioară',
|
||||
'VARIATIE_PROCENT': 'Creștere/scădere procentuală',
|
||||
'TREND': 'CREȘTERE / SCĂDERE / STABIL'
|
||||
},
|
||||
'analiza_prajitorie': {
|
||||
'CANTITATE_INTRARI': 'Cantitate intrata (cant > 0, cante = 0)',
|
||||
'VALOARE_INTRARI': 'Valoare intrari = cantitate x pret',
|
||||
'CANTITATE_IESIRI': 'Cantitate iesita (cant = 0, cante > 0)',
|
||||
'VALOARE_IESIRI': 'Valoare iesiri = cantitate x pret',
|
||||
'CANTITATE_TRANSFORMARI_IN': 'Cantitate intrata in transformari',
|
||||
'CANTITATE_TRANSFORMARI_OUT': 'Cantitate iesita din transformari',
|
||||
'SOLD_NET_CANTITATE': 'Sold net = Total intrari - Total iesiri',
|
||||
'SOLD_NET_VALOARE': 'Valoare neta a soldului'
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
excel_gen.save()
|
||||
|
||||
# Generate PDF Report
|
||||
print("\n📄 Generare raport PDF...")
|
||||
pdf_gen = PDFReportGenerator(pdf_path, company_name=COMPANY_NAME)
|
||||
|
||||
# Title page
|
||||
pdf_gen.add_title_page()
|
||||
|
||||
# KPIs
|
||||
pdf_gen.add_kpi_section(results.get('sumar_executiv'))
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
# 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
|
||||
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())
|
||||
})
|
||||
|
||||
pdf_gen.add_page_break()
|
||||
|
||||
# Monthly chart
|
||||
if 'vanzari_lunare' in results and not results['vanzari_lunare'].empty:
|
||||
fig = create_monthly_chart(results['vanzari_lunare'])
|
||||
pdf_gen.add_chart_image(fig, "Evoluția Vânzărilor și Marjei")
|
||||
|
||||
# Client concentration
|
||||
if 'concentrare_clienti' in results and not results['concentrare_clienti'].empty:
|
||||
fig = create_client_concentration_chart(results['concentrare_clienti'])
|
||||
pdf_gen.add_chart_image(fig, "Concentrare Clienți")
|
||||
|
||||
pdf_gen.add_page_break()
|
||||
|
||||
# NEW: Cash Conversion Cycle chart
|
||||
if 'ciclu_conversie_cash' in results and not results['ciclu_conversie_cash'].empty:
|
||||
fig = create_cash_cycle_chart(results['ciclu_conversie_cash'])
|
||||
pdf_gen.add_chart_image(fig, "Ciclu Conversie Cash (DIO + DSO - DPO)")
|
||||
|
||||
# Production vs Resale
|
||||
if 'productie_vs_revanzare' in results and not results['productie_vs_revanzare'].empty:
|
||||
fig = create_production_chart(results['productie_vs_revanzare'])
|
||||
pdf_gen.add_chart_image(fig, "Producție Proprie vs Revânzare")
|
||||
|
||||
# Top clients table
|
||||
pdf_gen.add_table_section(
|
||||
"Top 15 Clienți după Vânzări",
|
||||
results.get('marja_per_client'),
|
||||
columns=['CLIENT', 'VANZARI_FARA_TVA', 'MARJA_BRUTA', 'PROCENT_MARJA'],
|
||||
max_rows=15
|
||||
)
|
||||
|
||||
pdf_gen.add_page_break()
|
||||
|
||||
# Top products
|
||||
pdf_gen.add_table_section(
|
||||
"Top 15 Produse după Vânzări",
|
||||
results.get('top_produse'),
|
||||
columns=['PRODUS', 'VALOARE_VANZARI', 'MARJA_BRUTA', 'PROCENT_MARJA'],
|
||||
max_rows=15
|
||||
)
|
||||
|
||||
# Trending clients
|
||||
pdf_gen.add_table_section(
|
||||
"Trending Clienți (YoY)",
|
||||
results.get('trending_clienti'),
|
||||
columns=['CLIENT', 'VANZARI_12_LUNI', 'VANZARI_AN_ANTERIOR', 'VARIATIE_PROCENT', 'TREND'],
|
||||
max_rows=15
|
||||
)
|
||||
|
||||
# NEW: Aging Creanțe table
|
||||
if 'aging_creante' in results and not results['aging_creante'].empty:
|
||||
pdf_gen.add_page_break()
|
||||
pdf_gen.add_table_section(
|
||||
"Aging Creanțe (Vechime Facturi Neîncasate)",
|
||||
results.get('aging_creante'),
|
||||
columns=['CLIENT', 'NEAJUNS_SCADENTA', 'ZILE_1_30', 'ZILE_31_60', 'PESTE_90_ZILE', 'TOTAL_SOLD'],
|
||||
max_rows=15
|
||||
)
|
||||
|
||||
# Stoc lent
|
||||
if 'stoc_lent' in results and not results['stoc_lent'].empty:
|
||||
pdf_gen.add_page_break()
|
||||
pdf_gen.add_table_section(
|
||||
"Stoc Lent (>90 zile fără mișcare)",
|
||||
results.get('stoc_lent'),
|
||||
columns=['PRODUS', 'NUME_GESTIUNE', 'CANTITATE', 'VALOARE', 'ZILE_FARA_MISCARE'],
|
||||
max_rows=20
|
||||
)
|
||||
|
||||
pdf_gen.save()
|
||||
|
||||
# Summary
|
||||
print("\n" + "="*60)
|
||||
print(" ✅ RAPOARTE GENERATE CU SUCCES!")
|
||||
print("="*60)
|
||||
print(f"\n 📊 Excel: {excel_path}")
|
||||
print(f" 📄 PDF: {pdf_path}")
|
||||
print("\n" + "="*60)
|
||||
|
||||
return excel_path, pdf_path
|
||||
|
||||
|
||||
def main():
|
||||
"""Entry point"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Data Intelligence Report Generator',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Exemple:
|
||||
python main.py # Raport pentru ultimele 12 luni
|
||||
python main.py --months 6 # Raport pentru ultimele 6 luni
|
||||
python main.py --output-dir /tmp # Salvare în alt director
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--months', '-m',
|
||||
type=int,
|
||||
default=ANALYSIS_MONTHS,
|
||||
help=f'Numărul de luni pentru analiză (default: {ANALYSIS_MONTHS})'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--output-dir', '-o',
|
||||
type=Path,
|
||||
default=OUTPUT_DIR,
|
||||
help=f'Directorul pentru output (default: {OUTPUT_DIR})'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Ensure output directory exists
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
try:
|
||||
generate_reports(args)
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n⚠️ Întrerupt de utilizator.")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n❌ Eroare: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user