#!/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()