-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreport_corp.py
More file actions
5093 lines (4397 loc) · 247 KB
/
Copy pathreport_corp.py
File metadata and controls
5093 lines (4397 loc) · 247 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import io
import uuid
import copy
import zipfile
import pandas as pd
import numpy as np
import docx
import os
import re
from docxtpl import DocxTemplate
import matplotlib.pyplot as plt
from docxtpl import InlineImage
import warnings
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.shared import Pt, Mm
import tempfile
import matplotlib.image as mpimg
import matplotlib.patches as patches
warnings.filterwarnings("ignore", category=UserWarning, module="matplotlib")
PERCORSO_LOGO_WATERMARK = "logofv.png" # WM
def aggiungi_watermark_fig(fig, applica):
"""Sovrappone logo e testo opachi, MANTENENDO intatte le dimensioni originali"""
if not applica:
return
# Peschiamo l'asse originale del grafico per ancorarci a lui (evita sfasamenti e resize!)
ax = fig.gca()
# 1. 🛡️ VETRO SATINATO (alpha=0.96) ancorato all'area interna del grafico
rect = patches.Rectangle((-0.1, -0.1), 1.2, 1.2, transform=ax.transAxes,
linewidth=0, edgecolor='none', facecolor='white',
alpha=0.96, zorder=998, clip_on=False)
ax.add_patch(rect)
# 2. Creiamo un asse "fantasma" tenendolo rigorosamente DENTRO i margini (non allarga il grafico)
ax_bg = fig.add_axes([0.1, 0.1, 0.8, 0.8], zorder=999)
ax_bg.axis('off')
try:
# Inserisce il Logo a sinistra (Opacità 100%, Colori Vividi)
logo = mpimg.imread(PERCORSO_LOGO_WATERMARK)
ax_bg.imshow(logo, extent=[0.0, 0.40, 0.10, 0.90], alpha=1.0, aspect='auto')
# Inserisce il Testo a destra (Opacità 100%, Colori Vividi)
ax_bg.text(0.75, 0.5, "Finance & Value\nREPORT PREMIUM", fontsize=24, color='#2B3A67',
alpha=1.0, ha='center', va='center', rotation=15, weight='bold')
except:
ax_bg.text(0.5, 0.5, "Finance & Value\nDATI BLOCCATI", fontsize=32, color='#2B3A67',
alpha=1.0, ha='center', va='center', rotation=15, weight='bold')
def format_euro(numero, decimali=2):
if pd.isna(numero): return "n.d."
formato = f"{{:,.{decimali}f}}".format(numero)
return formato.replace(',', 'X').replace('.', ',').replace('X', '.')
# =================================================================
# 🟢 INDICATORI ECONOMICI (100% SEPARATI)
# =================================================================
def get_intro_margini(descr_settore):
return f"Le risultanze relative al tessuto competitivo del mercato ({descr_settore}) evidenziano che:"
def get_analisi_ebitda(az_ebitda, set_ebitda):
if az_ebitda < set_ebitda:
return f"• Il Margine EBITDA ({format_euro(az_ebitda)}%) risulta inferiore alla mediana settoriale ({format_euro(set_ebitda)}%). Valori più contenuti segnalano una minore capacità di trasformare i ricavi in margine operativo lordo, denotando una minore efficienza della gestione caratteristica prima degli ammortamenti e delle svalutazioni."
else:
return f"• Il Margine EBITDA ({format_euro(az_ebitda)}%) supera la mediana settoriale ({format_euro(set_ebitda)}%). Valori più elevati indicano una maggiore capacità dell'impresa di generare reddito dalla gestione caratteristica prima di ammortamenti e svalutazioni, evidenziando una superiore efficienza operativa."
def get_analisi_ebit(az_ebit, set_ebit):
if az_ebit < set_ebit:
return f"• Il Margine EBIT ({format_euro(az_ebit)}%) si colloca al di sotto del target mediano ({format_euro(set_ebit)}%). Tale dato segnala una minore capacità di generare reddito operativo in relazione ai ricavi conseguiti a valle dell'assorbimento dei costi fissi operativi e degli ammortamenti."
else:
return f"• Il Margine EBIT ({format_euro(az_ebit)}%) si posiziona al di sopra della mediana di settore ({format_euro(set_ebit)}%). Questo livello indica una maggiore capacità di conseguire un risultato operativo soddisfacente dopo aver considerato gli ammortamenti e le svalutazioni."
def get_analisi_margine_profitto_tag(az_prof, set_prof):
if az_prof < set_prof:
return f"• Il Margine di Profitto ({format_euro(az_prof)}%) risulta inferiore alla mediana settoriale ({format_euro(set_prof)}%). Tale andamento denota una minore capacità di trasformare i ricavi in utile netto, evidenziando criticità nell'assorbimento della gestione straordinaria, degli oneri finanziari o del carico fiscale."
else:
return f"• Il Margine di Profitto ({format_euro(az_prof)}%) supera il parametro mediano del settore ({format_euro(set_prof)}%). Valori più elevati indicano una maggiore capacità dell'impresa di convertire i ricavi in risultato netto finale, confermando un'efficace gestione degli oneri extra-caratteristici."
# =================================================================
# 🟠 INDICATORI PATRIMONIALI (100% SEPARATI)
# =================================================================
def get_analisi_struttura1(az_str1):
if az_str1 >= 1:
return f"• L'Indice primario di struttura ({format_euro(az_str1)}), superiore o uguale all'unità, indica che il capitale proprio, il quale non ha vincoli di scadenza, ha finanziato interamente le immobilizzazioni, caratterizzate da tempi di disinvestimento medio-lunghi."
else:
return f"• L'Indice primario di struttura ({format_euro(az_str1)}), risultando inferiore ad uno, segnala che una parte delle immobilizzazioni è stata finanziata mediante capitale di terzi, con potenziale obbligo di rimborso nel breve termine."
def get_analisi_struttura2(az_str2):
if az_str2 >= 1:
return f"• L'Indice secondario di struttura ({format_euro(az_str2)}), superiore o uguale all'unità, conferma che il capitale permanente, costituito dal capitale proprio e dai debiti a medio-lunga scadenza, ha finanziato interamente gli asset immobilizzati."
else:
return f"• L'Indice secondario di struttura ({format_euro(az_str2)}), essendo inferiore ad uno, indica che una parte dell'attivo immobilizzato è finanziata attraverso capitale di terzi a breve scadenza, determinando uno squilibrio temporale tra fonti e impieghi."
def get_analisi_gearing_tag(az_gear, set_gear):
if az_gear <= set_gear:
return f"• Il Gearing ({format_euro(az_gear)}%) si attesta al di sotto del parametro mediano del comparto ({format_euro(set_gear)}%). Tali valori contenuti indicano una limitata dipendenza dall'indebitamento oneroso e una solida autonomia rispetto ai creditori."
else:
return f"• Il Gearing ({format_euro(az_gear)}%) supera la mediana di settore ({format_euro(set_gear)}%). Valori più elevati segnalano un maggiore ricorso al capitale di terzi per il finanziamento aziendale, determinando un incremento del rischio finanziario e una minore autonomia."
# =================================================================
# 🔵 INDICATORI FINANZIARI (100% SEPARATI)
# =================================================================
def get_analisi_current_ratio_tag(az_cr, set_cr):
if az_cr >= 1:
return f"• Il Current Ratio ({format_euro(az_cr)}), superiore o uguale all'unità, indica che le attività a breve termine sono sufficienti a coprire integralmente i debiti esigibili nel breve periodo, evidenziando una situazione di equilibrio d'esercizio."
else:
return f"• Il Current Ratio ({format_euro(az_cr)}), inferiore ad uno, segnala l'incapacità delle attività correnti di far fronte alle passività correnti, configurando una potenziale tensione di liquidità all'interno della struttura d'esercizio."
def get_analisi_quick_ratio_tag(az_qr, set_qr):
if az_qr >= 1:
return f"• Il Quick Ratio ({format_euro(az_qr)}), superiore o uguale all'unità, indica che le risorse prontamente liquidabili sono sufficienti a garantire la copertura dei debiti a breve termine senza ricorrere alla vendita delle rimanenze di magazzino."
else:
return f"• Il Quick Ratio ({format_euro(az_qr)}), essendo inferiore ad uno, evidenzia una dipendenza, almeno parziale, dalla monetizzazione delle scorte o da ulteriori fonti di finanziamento esterne per soddisfare gli impegni immediati."
def get_analisi_rotazione_tag(az_rot, set_rot):
if az_rot < set_rot:
return f"• L'Indice di rotazione del capitale investito ({format_euro(az_rot)}) risulta inferiore alla mediana del comparto ({format_euro(set_rot)}). Valori più contenuti segnalano una minore capacità del capitale investito di tradursi in ricavi, denotando un impiego meno efficiente degli asset operativi."
else:
return f"• L'Indice di rotazione del capitale investito ({format_euro(az_rot)}) supera la mediana settoriale ({format_euro(set_rot)}). Valori più elevati indicano una maggiore capacità dell'impresa di generare ricavi attraverso le risorse investite, evidenziando un efficiente utilizzo del capitale."
# =====================================================================
# ☢️ LA LAVATRICE NUCLEARE (Ricostruisce l'XML di Word da zero)
# =====================================================================
def lavatrice_nucleare(template_path):
doc_temp = docx.Document(template_path)
def ripara_paragrafo(p):
testo = p.text
# Se trova un tag di tabella o grafico
if '{{' in testo and '}}' in testo and any(tag in testo for tag in ['{{ tabella_', '{{ grafico_', '{{tabella_', '{{grafico_']):
# Taglia il testo usando i tag come forbici
frammenti = re.split(r'(\{\{.*?\}\})', testo)
for frammento in frammenti:
pulito = frammento.strip()
if pulito: # Se non è vuoto
# Crea un paragrafo puro e totalmente indipendente
nuovo_p = p.insert_paragraph_before('')
nuovo_p.clear()
nuovo_p.add_run(pulito)
try:
nuovo_p.style = p.style
except:
pass
# Svuota il paragrafo originale corrotto, lasciando un a capo pulito obbligatorio
p._p.getparent().remove(p._p)
# 1. Scansiona tutto il documento
for p in doc_temp.paragraphs:
ripara_paragrafo(p)
# 2. Scansiona le vecchie tabelle (se ci hai lasciato tag dentro per sbaglio)
for table in doc_temp.tables:
for row in table.rows:
for cell in row.cells:
for p in cell.paragraphs:
ripara_paragrafo(p)
# Obbliga ogni cella a finire con un a capo (Regola d'oro di Word)
try:
cell.add_paragraph("")
except:
pass
temp_path = os.path.join(tempfile.gettempdir(), f"template_nucleare_{uuid.uuid4().hex[:8]}.docx")
doc_temp.save(temp_path)
return temp_path
# =====================================================================
# MOTORE DI CALCOLO E IMPAGINAZIONE
# =====================================================================
def genera_report_word(zip_buffer, template_path, azienda_target, df_orbis, settore_nace, num_max_soc_orbis, modalita_teaser=False):
# 🛡️ ANTIDOTO: Disintegra i caratteri invisibili di Excel che corrompono Word
caratteri_proibiti = dict.fromkeys(range(0, 32))
caratteri_proibiti.pop(9, None); caratteri_proibiti.pop(10, None); caratteri_proibiti.pop(13, None)
azienda_target = str(azienda_target).translate(caratteri_proibiti)
settore_nace = str(settore_nace).translate(caratteri_proibiti)
df_orbis = df_orbis.replace(r'[\x00-\x08\x0b\x0c\x0e-\x1f]', '', regex=True)
df_orbis = df_orbis.copy()
# --- PULIZIA DATI INIZIALE (Elimina tutti gli n.d. testuali) ---
df_orbis = df_orbis.replace(['n.d.', 'n.a.', 'n.s.', 'N.D.', 'N.A.', 'N.S.', 'n.d', ' '], np.nan)
# ---------------------------------------------------------------
colonne_da_convertire = [
'Totale valore della produzione migl EUR 2024', 'Totale Attivo migl EUR 2024',
'Numero dipendenti 2024', 'Margine di Profitto (*) % 2024', 'Margine EBITDA (*) % 2024',
'Margine EBIT (*) % 2024', 'Indice di Struttura 1° livello (*) 2024',
'Indice di Struttura 2° livello (*) 2024', 'Gearing (*) % 2024',
'Indice di Rotazione del Capitale Investito (*) 2024', 'Current Ratio (*) 2024', 'Quick Ratio (*) 2024'
]
for c in colonne_da_convertire:
if c in df_orbis.columns:
df_orbis[c] = pd.to_numeric(df_orbis[c], errors='coerce')
col_ragione = [c for c in df_orbis.columns if 'ragione' in str(c).lower()][0]
col_nuts = [c for c in df_orbis.columns if 'nuts2' in str(c).lower() or 'nuts 2' in str(c).lower()]
col_regione = col_nuts[0] if col_nuts else None
df_orbis['Forma Giuridica Pulita'] = df_orbis['Forma giuridica nazionale'].astype(str).str.replace(r'\s*\(Italia\)', '', regex=True).str.strip()
# 🔠 DIZIONARIO ABBREVIAZIONI FORME GIURIDICHE
mappatura_fg = {
'società a responsabilità limitata semplificata': 'S.r.l.s.',
"societa' a responsabilita' limitata semplificata": 'S.r.l.s.',
"societa a responsabilita limitata semplificata": 'S.r.l.s.',
'società a responsabilità limitata': 'S.r.l.',
"societa' a responsabilita' limitata": 'S.r.l.',
"societa a responsabilita limitata": 'S.r.l.',
'società per azioni': 'S.p.A.',
"societa' per azioni": 'S.p.A.',
"societa per azioni": 'S.p.A.',
'società in nome collettivo': 'S.n.c.',
"societa' in nome collettivo": 'S.n.c.',
"societa in nome collettivo": 'S.n.c.',
'società in accomandita semplice': 'S.a.s.',
"societa' in accomandita semplice": 'S.a.s.',
"societa in accomandita semplice": 'S.a.s.',
'società in accomandita per azioni': 'S.a.p.a.',
"societa' in accomandita per azioni": 'S.a.p.a.',
"societa in accomandita per azioni": 'S.a.p.a.',
'società cooperativa': 'Soc. Coop.',
"societa' cooperativa": 'Soc. Coop.',
"societa cooperativa": 'Soc. Coop.',
'società semplice': 'S.s.',
"societa' semplice": 'S.s.',
"societa semplice": 'S.s.',
'società consortile': 'Soc. Cons.',
"societa' consortile": 'Soc. Cons.',
"societa consortile": 'Soc. Cons.'
}
def abbrevia_forma_giuridica(fg_estesa):
if pd.isna(fg_estesa): return "N.D."
fg_lower = str(fg_estesa).lower().strip()
for chiave, abbreviazione in mappatura_fg.items():
if chiave in fg_lower: return abbreviazione
if "s.r.l." in fg_lower or "srl" in fg_lower: return "S.r.l."
if "s.p.a." in fg_lower or "spa" in fg_lower: return "S.p.A."
return str(fg_estesa).title()
df_orbis['Forma Giuridica Pulita'] = df_orbis['Forma Giuridica Pulita'].apply(abbrevia_forma_giuridica)
def get_macro(nuts2):
code = str(nuts2)[:3]
if code == 'ITC': return 'Nord Ovest'
elif code == 'ITH': return 'Nord Est'
elif code == 'ITI': return 'Centro'
elif code in ['ITF', 'ITG']: return 'Sud e Isole'
else: return 'Altro'
if col_regione: df_orbis['Macroregione'] = df_orbis[col_regione].apply(get_macro)
else: df_orbis['Macroregione'] = 'Altro'
tot_imprese_settore = len(df_orbis)
tot_ricavi_settore = df_orbis['Totale valore della produzione migl EUR 2024'].sum()
tot_attivo_settore = df_orbis['Totale Attivo migl EUR 2024'].sum()
tot_dipendenti_settore = df_orbis['Numero dipendenti 2024'].sum()
# 🟢 LOGICA DETTAGLIATA FORME GIURIDICHE (Top 1, Top 2 e Altre)
fg_counts = df_orbis['Forma Giuridica Pulita'].value_counts()
# Forma Giuridica #1 (Maggioranza assoluta)
fg_1_name = str(fg_counts.index[0]).strip() if len(fg_counts) > 0 else "N.D."
fg_1_num = int(fg_counts.iloc[0]) if len(fg_counts) > 0 else 0
fg_1_perc = (fg_1_num / tot_imprese_settore) * 100 if tot_imprese_settore > 0 else 0
# Variabili per retro-compatibilità con il resto del tuo codice
fg_maggioranza = fg_1_name
num_fg_maggioranza = fg_1_num
perc_fg_maggioranza = fg_1_perc
# Forma Giuridica #2 (La seconda forza del mercato)
fg_2_name = str(fg_counts.index[1]).strip() if len(fg_counts) > 1 else None
fg_2_num = int(fg_counts.iloc[1]) if len(fg_counts) > 1 else 0
fg_2_perc = (fg_2_num / tot_imprese_settore) * 100 if tot_imprese_settore > 0 else 0
# Forme Giuridiche #3+ (Tutte le "briciole" sommate insieme, se esistono)
fg_altre_num = sum(fg_counts.iloc[2:]) if len(fg_counts) > 2 else 0
fg_altre_perc = (fg_altre_num / tot_imprese_settore) * 100 if tot_imprese_settore > 0 else 0
df_target = df_orbis[df_orbis[col_ragione].astype(str).str.lower().str.contains(azienda_target.lower().strip(), regex=False, na=False)]
def get_val_and_rank(col_name, is_lower_better=False):
if col_name not in df_orbis.columns or df_target.empty: return "n.d.", "n.d.", "n.d."
target_idx = df_target.index[0]
valore = df_target.at[target_idx, col_name]
rank_naz = df_orbis[col_name].rank(ascending=is_lower_better, method='min')
pos_naz = int(rank_naz.loc[target_idx]) if pd.notna(rank_naz.loc[target_idx]) else "n.d."
if col_regione:
rank_reg = df_orbis.groupby(col_regione)[col_name].rank(ascending=is_lower_better, method='min')
pos_reg = int(rank_reg.loc[target_idx]) if pd.notna(rank_reg.loc[target_idx]) else "n.d."
else: pos_reg = "n.d."
return format_euro(valore), pos_naz, pos_reg
mg_prof, rnk_naz_prof, rnk_reg_prof = get_val_and_rank('Margine di Profitto (*) % 2024', False)
mg_ebitda, rnk_naz_ebitda, rnk_reg_ebitda = get_val_and_rank('Margine EBITDA (*) % 2024', False)
mg_ebit, rnk_naz_ebit, rnk_reg_ebit = get_val_and_rank('Margine EBIT (*) % 2024', False)
ind_str1, rnk_naz_strut1, rnk_reg_strut1 = get_val_and_rank('Indice di Struttura 1° livello (*) 2024', False)
ind_str2, rnk_naz_strut2, rnk_reg_strut2 = get_val_and_rank('Indice di Struttura 2° livello (*) 2024', False)
gearing, rnk_naz_gear, rnk_reg_gear = get_val_and_rank('Gearing (*) % 2024', True)
ind_rot, rnk_naz_rot, rnk_reg_rot = get_val_and_rank('Indice di Rotazione del Capitale Investito (*) 2024', False)
ind_cr, rnk_naz_cr, rnk_reg_cr = get_val_and_rank('Current Ratio (*) 2024', False)
ind_qr, rnk_naz_qr, rnk_reg_qr = get_val_and_rank('Quick Ratio (*) 2024', False)
if not df_target.empty:
riga = df_target.iloc[0]
p_iva = riga.get('Codice fiscale/Partita IVA', 'N.D.')
forma_giuridica = riga.get('Forma Giuridica Pulita', 'N.D.')
macroregione_target = riga.get('Macroregione', 'N.D.')
num_fg_target = fg_counts.get(forma_giuridica, 0)
perc_fg_target = (num_fg_target / tot_imprese_settore) * 100 if tot_imprese_settore > 0 else 0
ricavi_mgl = riga.get('Totale valore della produzione migl EUR 2024')
attivo_mgl = riga.get('Totale Attivo migl EUR 2024')
dipendenti = riga.get('Numero dipendenti 2024')
ricavi_mln = ricavi_mgl / 1000 if pd.notna(ricavi_mgl) else 0
attivo_mln = attivo_mgl / 1000 if pd.notna(attivo_mgl) else 0
perc_ricavi_panel = (ricavi_mgl / tot_ricavi_settore * 100) if tot_ricavi_settore > 0 and pd.notna(ricavi_mgl) else 0
perc_attivo_panel = (attivo_mgl / tot_attivo_settore * 100) if tot_attivo_settore > 0 and pd.notna(attivo_mgl) else 0
tot_dip_area = df_orbis[df_orbis['Macroregione'] == macroregione_target]['Numero dipendenti 2024'].sum()
perc_dip_area = (dipendenti / tot_dip_area * 100) if tot_dip_area > 0 and pd.notna(dipendenti) else 0
# --- NUOVI CALCOLI TERRITORIALI ---
regione_grezza = str(riga.get(col_regione, 'N.D.'))
regione_target_pulita = regione_grezza.split(' - ')[1] if ' - ' in regione_grezza else regione_grezza
tot_imprese_regione = len(df_orbis[df_orbis[col_regione] == riga.get(col_regione)]) if col_regione else 0
perc_imprese_regione = (tot_imprese_regione / tot_imprese_settore * 100) if tot_imprese_settore > 0 else 0
tot_ricavi_macro_mgl = df_orbis[df_orbis['Macroregione'] == macroregione_target]['Totale valore della produzione migl EUR 2024'].sum()
tot_ricavi_macro_mln = tot_ricavi_macro_mgl / 1000
perc_ricavi_macroregione = (tot_ricavi_macro_mgl / tot_ricavi_settore * 100) if tot_ricavi_settore > 0 else 0
perc_ricavi_target_su_macro = (ricavi_mgl / tot_ricavi_macro_mgl * 100) if tot_ricavi_macro_mgl > 0 and pd.notna(ricavi_mgl) else 0
# =======================================================
# --- NUOVI CALCOLI CATEGORIA (PER FORMA GIURIDICA) ---
# =======================================================
# Filtriamo il database prendendo SOLO le aziende con la stessa forma giuridica (es. solo le S.p.A.)
df_categoria = df_orbis[df_orbis['Forma Giuridica Pulita'] == forma_giuridica]
tot_ricavi_categoria = df_categoria['Totale valore della produzione migl EUR 2024'].sum()
tot_attivo_categoria = df_categoria['Totale Attivo migl EUR 2024'].sum()
perc_ricavi_categoria = (ricavi_mgl / tot_ricavi_categoria * 100) if tot_ricavi_categoria > 0 and pd.notna(ricavi_mgl) else 0
perc_attivo_categoria = (attivo_mgl / tot_attivo_categoria * 100) if tot_attivo_categoria > 0 and pd.notna(attivo_mgl) else 0
try: quartile_ricavi_target = pd.qcut(df_orbis['Totale valore della produzione migl EUR 2024'].dropna(), 4, labels=[1, 2, 3, 4]).loc[riga.name]
except: quartile_ricavi_target = "N.D."
try: quartile_attivo_target = pd.qcut(df_orbis['Totale Attivo migl EUR 2024'].dropna(), 4, labels=[1, 2, 3, 4]).loc[riga.name]
except: quartile_attivo_target = "N.D."
else:
p_iva, forma_giuridica, macroregione_target, num_fg_target, perc_fg_target, ricavi_mln, attivo_mln, dipendenti = "N.D.", "N.D.", "N.D.", 0, 0, 0, 0, 0
perc_ricavi_panel, perc_attivo_panel, perc_dip_area, quartile_ricavi_target, quartile_attivo_target = 0, 0, 0, "N.D.", "N.D."
regione_target_pulita = "N.D."
perc_imprese_regione, tot_ricavi_macro_mln, perc_ricavi_macroregione, perc_ricavi_target_su_macro = 0, 0, 0, 0
perc_ricavi_categoria, perc_attivo_categoria = 0, 0 # <--- AGGIUNTA QUESTA RIGA!
# 🧽 PULITURA RAGIONE SOCIALE
ragione_sociale_pulita = str(azienda_target)
sostituzioni_nomi = {
"SOCIETA' A RESPONSABILITA' LIMITATA SEMPLIFICATA": "S.r.l.s.",
"SOCIETÀ A RESPONSABILITÀ LIMITATA SEMPLIFICATA": "S.r.l.s.",
"SOCIETA A RESPONSABILITA LIMITATA SEMPLIFICATA": "S.r.l.s.",
"SOCIETA' A RESPONSABILITA' LIMITATA": "S.r.l.",
"SOCIETÀ A RESPONSABILITÀ LIMITATA": "S.r.l.",
"SOCIETA A RESPONSABILITA LIMITATA": "S.r.l.",
"SOCIETA' PER AZIONI": "S.p.A.",
"SOCIETÀ PER AZIONI": "S.p.A.",
"SOCIETA PER AZIONI": "S.p.A.",
"SOCIETA' IN NOME COLLETTIVO": "S.n.c.",
"SOCIETÀ IN NOME COLLETTIVO": "S.n.c.",
"SOCIETA IN NOME COLLETTIVO": "S.n.c.",
"SOCIETA' IN ACCOMANDITA SEMPLICE": "S.a.s.",
"SOCIETÀ IN ACCOMANDITA SEMPLICE": "S.a.s.",
"SOCIETA IN ACCOMANDITA SEMPLICE": "S.a.s.",
"SOCIETA' IN ACCOMANDITA PER AZIONI": "S.a.p.a.",
"SOCIETÀ IN ACCOMANDITA PER AZIONI": "S.a.p.a.",
"SOCIETA IN ACCOMANDITA PER AZIONI": "S.a.p.a.",
"SOCIETA' COOPERATIVA": "Soc. Coop.",
"SOCIETÀ COOPERATIVA": "Soc. Coop.",
"SOCIETA COOPERATIVA": "Soc. Coop.",
"SOCIETA' SEMPLICE": "S.s.",
"SOCIETÀ SEMPLICE": "S.s.",
"SOCIETA SEMPLICE": "S.s.",
"SOCIETA' CONSORTILE": "Soc. Cons.",
"SOCIETÀ CONSORTILE": "Soc. Cons.",
"SOCIETA CONSORTILE": "Soc. Cons."
}
for lungo, corto in sostituzioni_nomi.items():
ragione_sociale_pulita = re.sub(re.escape(lungo), corto, ragione_sociale_pulita, flags=re.IGNORECASE)
# 🏷️ PULITURA NACE MULTIPLI
settore_nace_str = str(settore_nace).strip()
codici_estratti = []
descrizioni_estratte = []
matches = list(re.finditer(r'(\d{3,4})\s*-\s*(.*?)(?=\s*(?:[,;|]|\n)?\s*\d{3,4}\s*-|$)', settore_nace_str))
if matches:
for match in matches:
cod_grezzo = match.group(1).strip()
desc_testo = match.group(2).strip().rstrip(",;| ")
cod_pulito = f"{cod_grezzo[:2]}.{cod_grezzo[2:]}"
codici_estratti.append(cod_pulito)
descrizioni_estratte.append(desc_testo)
def formatta_italiano(lista):
if len(lista) == 1: return lista[0]
return ", ".join(lista[:-1]) + " e " + lista[-1]
cod_nace_pulito = formatta_italiano(codici_estratti)
desc_nace_pulita = formatta_italiano(descrizioni_estratte)
else:
cod_nace_pulito = "N.D."
desc_nace_pulita = settore_nace_str
# 🏛️ ESPANSIONE FORMA GIURIDICA (Per il testo discorsivo)
dizionario_espansioni = {
'S.r.l.s.': 'Società a Responsabilità Limitata Semplificata',
'S.r.l.': 'Società a Responsabilità Limitata',
'S.p.A.': 'Società per Azioni',
'S.n.c.': 'Società in Nome Collettivo',
'S.a.s.': 'Società in Accomandita Semplice',
'S.a.p.a.': 'Società in Accomandita per Azioni',
'Soc. Coop.': 'Società Cooperativa',
'S.s.': 'Società Semplice',
'Soc. Cons.': 'Società Consortile'
}
fg_espansa = dizionario_espansioni.get(forma_giuridica, forma_giuridica)
# 🟢 FIX P.IVA: Ripristina gli zeri iniziali "mangiati" da Excel/Pandas
if pd.notna(p_iva) and str(p_iva).strip().lower() not in ['n.d.', 'nan', '']:
try:
# 1. Lo passiamo a float e poi a int per eliminare eventuali ".0" finali
p_iva_pulita = str(int(float(p_iva)))
# 2. Aggiunge gli zeri a sinistra finché la lunghezza non torna a 11 cifre
p_iva = p_iva_pulita.zfill(11)
except ValueError:
# Se dovesse contenere lettere (es. codici esteri o errori), lo lascia a testo
p_iva = str(p_iva)
else:
p_iva = "n.d."
# 🟢 MOTORE NARRATIVO: COMPOSIZIONE SOCIETARIA DETTAGLIATA (Versione Ultra-Resistente)
target_fg = str(forma_giuridica).strip()
if target_fg.lower() == fg_1_name.lower():
# Caso A: L'azienda in analisi fa parte della Maggioranza #1
testo_fg = f"rappresenta la veste giuridica dominante assoluta del comparto, costituendo da sola il {format_euro(fg_1_perc)}% delle realtà censite ({f'{fg_1_num:,}'.replace(',', '.')} unità)."
# Se esiste una seconda forma giuridica, continuiamo la frase
if fg_2_name:
testo_fg = testo_fg.rstrip(".") + f", mentre la restante parte del mercato è composta in larga misura da {fg_2_name} ({format_euro(fg_2_perc)}%, {f'{fg_2_num:,}'.replace(',', '.')} unità)"
if fg_altre_num > 0:
testo_fg += f" e, in minor parte, da altre configurazioni societarie miste ({format_euro(fg_altre_perc)}%, {f'{fg_altre_num:,}'.replace(',', '.')} unità)."
else:
testo_fg += "."
else:
# Caso B: L'azienda in analisi NON è la Maggioranza #1
testo_fg = f"si inserisce in un comparto caratterizzato in larga parte da {fg_1_name}, struttura che controlla il {format_euro(fg_1_perc)}% delle componenti societarie ({f'{fg_1_num:,}'.replace(',', '.')} unità). "
if fg_2_name and target_fg.lower() == fg_2_name.lower():
# Il target è esattamente il #2
testo_fg += f"A seguire si posiziona proprio la veste legale dell'azienda in analisi ({target_fg}), che rappresenta il {format_euro(fg_2_perc)}% del panel ({f'{fg_2_num:,}'.replace(',', '.')} unità)"
if fg_altre_num > 0:
testo_fg += f", affiancata in minor misura da altre configurazioni societarie miste ({format_euro(fg_altre_perc)}%, {f'{fg_altre_num:,}'.replace(',', '.')} unità)."
else:
testo_fg += "."
elif fg_2_name:
# Il target è dal #3 in giù
testo_fg += f"A seguire troviamo una forte presenza di {fg_2_name} ({format_euro(fg_2_perc)}%, {f'{fg_2_num:,}'.replace(',', '.')} unità), mentre la veste societaria dell'azienda in analisi si colloca nel restante {format_euro(fg_altre_perc)}% del mercato, insieme ad altre configurazioni minoritarie."
else:
# Fail-safe nel caso ci fossero anomalie strane
testo_fg += f"L'azienda in analisi si inserisce in questo contesto con una quota del {format_euro(perc_fg_target)}%."
# ----------------------------------------------------
# COSTRUZIONE DEL DIZIONARIO (Dati per il Word)
context = {
'ragione_sociale': ragione_sociale_pulita,
'codice_nace': cod_nace_pulito,
'descr_settore': desc_nace_pulita,
'partita_iva': p_iva,
'forma_giuridica': forma_giuridica,
'forma_giuridica_espansa': fg_espansa,
'testo_forma_giuridica_strutturato': testo_fg,
'perc_fg': format_euro(perc_fg_target), 'num_fg': f"{num_fg_target:,}".replace(',', '.'),
'fg_maggioranza': fg_maggioranza, 'num_fg_maggioranza': f"{num_fg_maggioranza:,}".replace(',', '.'),
'perc_fg_maggioranza': format_euro(perc_fg_maggioranza),
'classe_dimensionale': 'Grande Impresa' if ricavi_mln > 50 else ('Media Impresa' if ricavi_mln > 10 else 'Piccola Impresa'),
'ricavi_mln': format_euro(ricavi_mln),
'perc_ricavi_panel': format_euro(perc_ricavi_panel),
'perc_ricavi_categoria': format_euro(perc_ricavi_categoria), # <-- Ora calcola il VERO dato!
'quartile_ricavi': quartile_ricavi_target,
'attivo_mln': format_euro(attivo_mln),
'perc_attivo_panel': format_euro(perc_attivo_panel),
'perc_attivo_categoria': format_euro(perc_attivo_categoria), # <-- Aggiunto anche per l'Attivo!
'quartile_attivo': quartile_attivo_target,
'perc_dip_area': format_euro(perc_dip_area), 'macroregione': macroregione_target,
'regione_target': regione_target_pulita,
'perc_imprese_regione': format_euro(perc_imprese_regione),
'perc_ricavi_macroregione': format_euro(perc_ricavi_macroregione),
'tot_ricavi_macro_mln': format_euro(tot_ricavi_macro_mln),
'perc_ricavi_target_su_macro': format_euro(perc_ricavi_target_su_macro),
'mg_ebitda': mg_ebitda, 'rnk_naz_ebitda': rnk_naz_ebitda, 'rnk_reg_ebitda': rnk_reg_ebitda,
'mg_ebit': mg_ebit, 'rnk_naz_ebit': rnk_naz_ebit, 'rnk_reg_ebit': rnk_reg_ebit,
'mg_prof': mg_prof, 'rnk_naz_prof': rnk_naz_prof, 'rnk_reg_prof': rnk_reg_prof,
'ind_str1': ind_str1, 'rnk_naz_strut1': rnk_naz_strut1, 'rnk_reg_strut1': rnk_reg_strut1,
'ind_str2': ind_str2, 'rnk_naz_strut2': rnk_naz_strut2, 'rnk_reg_strut2': rnk_reg_strut2,
'gearing': gearing, 'rnk_naz_gear': rnk_naz_gear, 'rnk_reg_gear': rnk_reg_gear,
'ind_rot_cap': ind_rot, 'rnk_naz_rot': rnk_naz_rot, 'rnk_reg_rot': rnk_reg_rot,
'ind_cr': ind_cr, 'rnk_naz_cr': rnk_naz_cr, 'rnk_reg_cr': rnk_reg_cr,
'ind_qr': ind_qr, 'rnk_naz_qr': rnk_naz_qr, 'rnk_reg_qr': rnk_reg_qr,
'tot_imprese': f"{tot_imprese_settore:,}".replace(',', '.'), 'tot_ricavi': format_euro(tot_ricavi_settore),
'tot_attivo': format_euro(tot_attivo_settore), 'tot_dipendenti': format_euro(tot_dipendenti_settore, 0),
'impatto_territoriale': 'N.D.',
# Variabili di default (sovrascritte dal motore sottostante)
'rating_eco': 'N.D.', 'rating_patr': 'N.D.', 'rating_fin': 'N.D.', 'rating_tot': 'N.D.', 'rating_comb': 'N.D.',
'rating_piu_presente': 'N.D.', 'assoc_desc_rating_lett': 'N.D.',
'rat1_piu_pres_num': 'N.D.', 'rat1_piu_pres_categ': 'N.D.',
'rat2_piu_pres_num': 'N.D.', 'rat2_piu_pres_categ': 'N.D.',
'rat3_piu_pres_num': 'N.D.', 'rat3_piu_pres_categ': 'N.D.',
'rating_piu_pres': 'N.D.', 'rating_piu_pres_num_tot': 'N.D.',
'num_max_soc': 'N.D.', 'num_soc_valide': 'N.D.', 'perc_su_istat': '100', 'max_soc_istat': 'N.D.',
'tab_territorio': [], 'tab_bench_territorio': []
}
# =================================================================
# 📊 MOTORE DI CALCOLO NATIVO DEI RATING (Senza Excel!)
# =================================================================
# *** MODIFICA RISPETTO ALL'ORIGINALE ***
# Il punteggio ora rispecchia il rango del terzile:
# 1 = primo terzile = valori più alti (MIGLIORI) per metriche dirette
# 2 = secondo terzile = valori medi
# 3 = terzo terzile = valori più bassi (PEGGIORI)
# Nell'originale era invertito: 3 = migliore, 1 = peggiore.
# Il default NaN diventa 3 (peggiore) invece di 1.
def punteggio_diretto(val, t1, t2):
if pd.isna(val): return 3 # MODIFICATO: era return 1
return 1 if val >= t2 else (2 if val >= t1 else 3) # MODIFICATO: era 3/.../1
def punteggio_inverso(val, t1, t2):
if pd.isna(val): return 3 # MODIFICATO: era return 1
return 1 if val <= t1 else (2 if val <= t2 else 3) # MODIFICATO: era 3/.../1
# MODIFICATO: due funzioni distinte perché assegna_lettera è usata in due contesti:
# 1. assegna_lettera_area → per pts_eco/fin/pat (somma di 1/2/3 con 1=best, basso=buono)
# 2. assegna_lettera → per pts_totali/Benchmark Totale (valori_lettere A=3, alto=buono)
def assegna_lettera_area(punti): # NUOVO: soglia invertita, solo per somme di terzili area
if pd.isna(punti): return 'C'
return 'A' if punti <= 4 else ('B' if punti <= 7 else 'C')
def assegna_lettera(punti): # INVARIATA rispetto all'originale: usata per Benchmark Totale
if pd.isna(punti): return 'C'
return 'A' if punti >= 8 else ('B' if punti >= 5 else 'C')
# Identificatori metriche
c_prof = 'Margine di Profitto (*) % 2024'
c_ebitda = 'Margine EBITDA (*) % 2024'
c_ebit = 'Margine EBIT (*) % 2024'
c_rot = 'Indice di Rotazione del Capitale Investito (*) 2024'
c_quick = 'Quick Ratio (*) 2024'
c_curr = 'Current Ratio (*) 2024'
c_str1 = 'Indice di Struttura 1° livello (*) 2024'
c_str2 = 'Indice di Struttura 2° livello (*) 2024'
c_gear = 'Gearing (*) % 2024'
metriche_dirette = [c_prof, c_ebitda, c_ebit, c_rot, c_quick, c_curr, c_str1, c_str2]
metriche_inverse = [c_gear]
df_rating = df_orbis.copy()
# 1. Calcolo matematico dei Terzili e Assegnazione Punti
for m in metriche_dirette + metriche_inverse:
if m in df_rating.columns:
t1 = df_rating[m].quantile(1/3)
t2 = df_rating[m].quantile(2/3)
if m in metriche_inverse:
df_rating[f'pts_{m}'] = df_rating[m].apply(lambda x: punteggio_inverso(x, t1, t2))
else:
df_rating[f'pts_{m}'] = df_rating[m].apply(lambda x: punteggio_diretto(x, t1, t2))
else:
df_rating[f'pts_{m}'] = 3 # Fallback: colonna assente → terzo terzile (MODIFICATO: era 1)
# 2. Somma Punti per Area (Economico, Finanziario, Patrimoniale)
df_rating['pts_eco'] = df_rating[f'pts_{c_prof}'] + df_rating[f'pts_{c_ebitda}'] + df_rating[f'pts_{c_ebit}']
df_rating['pts_fin'] = df_rating[f'pts_{c_rot}'] + df_rating[f'pts_{c_quick}'] + df_rating[f'pts_{c_curr}']
df_rating['pts_pat'] = df_rating[f'pts_{c_str1}'] + df_rating[f'pts_{c_str2}'] + df_rating[f'pts_{c_gear}']
# 3. Assegnazione Lettere (A, B, C) — usa assegna_lettera_area (soglia invertita)
df_rating['Rating Economico'] = df_rating['pts_eco'].apply(assegna_lettera_area)
df_rating['Rating Finanziario'] = df_rating['pts_fin'].apply(assegna_lettera_area)
df_rating['Rating Patrimoniale'] = df_rating['pts_pat'].apply(assegna_lettera_area)
# 4. Rating Combinato Testuale (es: AAA, BBB, ABC)
df_rating['Rating Combinato'] = df_rating['Rating Economico'] + df_rating['Rating Patrimoniale'] + df_rating['Rating Finanziario']
# 5. Benchmark Totale (Media delle Lettere)
valori_lettere = {'A': 3, 'B': 2, 'C': 1}
df_rating['pts_totali'] = df_rating['Rating Economico'].map(valori_lettere) + \
df_rating['Rating Finanziario'].map(valori_lettere) + \
df_rating['Rating Patrimoniale'].map(valori_lettere)
df_rating['Benchmark Totale'] = df_rating['pts_totali'].apply(assegna_lettera)
# =================================================================
# 🎯 ESTRAZIONE E INIEZIONE NEL TEMPLATE WORD
# =================================================================
# Preleviamo la riga esatta dell'azienda target
df_target_rating = df_rating[df_rating[col_ragione].astype(str).str.lower().str.contains(azienda_target.lower().strip(), regex=False, na=False)]
if not df_target_rating.empty:
riga_t = df_target_rating.iloc[0]
context['rating_eco'] = riga_t['Rating Economico']
context['rating_fin'] = riga_t['Rating Finanziario']
context['rating_patr'] = riga_t['Rating Patrimoniale']
context['rating_tot'] = riga_t['Rating Combinato']
context['rating_comb'] = riga_t['Benchmark Totale']
context['dip_target'] = f"{int(dipendenti):,}".replace(',', '.') if pd.notna(dipendenti) else "n.d."
# Calcoli matematici reali per il Box Economico
r_eco_target = riga_t['Rating Economico']
num_eco_fascia_real = len(df_rating[df_rating['Rating Economico'] == r_eco_target])
perc_eco_fascia_real = (num_eco_fascia_real / tot_imprese_settore) * 100 if tot_imprese_settore > 0 else 0
context['nr_rating_eco'] = f"{num_eco_fascia_real:,}".replace(',', '.')
context['perc_rating_eco'] = format_euro(perc_eco_fascia_real)
context['perc_rating_eco_parte'] = format_euro((1 / num_eco_fascia_real) * 100) if num_eco_fascia_real > 0 else "0,00"
# Calcoli matematici reali per il Box Patrimoniale
r_patr_target = riga_t['Rating Patrimoniale']
num_patr_fascia_real = len(df_rating[df_rating['Rating Patrimoniale'] == r_patr_target])
perc_patr_fascia_real = (num_patr_fascia_real / tot_imprese_settore) * 100 if tot_imprese_settore > 0 else 0
context['nr_rating_patr'] = f"{num_patr_fascia_real:,}".replace(',', '.')
context['perc_rating_patr'] = format_euro(perc_patr_fascia_real)
context['perc_rating_patr_parte'] = format_euro((1 / num_patr_fascia_real) * 100) if num_patr_fascia_real > 0 else "0,00"
# Calcoli matematici reali per il Box Finanziario
r_fin_target = riga_t['Rating Finanziario']
num_fin_fascia_real = len(df_rating[df_rating['Rating Finanziario'] == r_fin_target])
perc_fin_fascia_real = (num_fin_fascia_real / tot_imprese_settore) * 100 if tot_imprese_settore > 0 else 0
context['nr_rating_fin'] = f"{num_fin_fascia_real:,}".replace(',', '.')
context['perc_rating_fin'] = format_euro(perc_fin_fascia_real)
context['perc_rating_fin_parte'] = format_euro((1 / num_fin_fascia_real) * 100) if num_fin_fascia_real > 0 else "0,00"
# --- 🤖 MOTORE NARRATIVO (Testi Dinamici e Tecnici in base ai Dati Reali) ---
def get_impatto_territoriale(perc, nome, ricavi_formattati):
if perc >= 5.0:
return f"Con ricavi pari a {ricavi_formattati} mln di EUR, {nome} incide in maniera determinante sulla creazione di ricchezza locale, confermandosi un player di assoluto riferimento sul piano territoriale grazie a un impatto pari al {format_euro(perc)}% rispetto al totale dei ricavi dell'area."
elif perc >= 1.0:
return f"Con ricavi pari a {ricavi_formattati} mln di EUR, {nome} fornisce un contributo significativo alla creazione di ricchezza locale, consolidando una posizione di rilievo sul piano territoriale con un'incidenza pari al {format_euro(perc)}% rispetto ai ricavi complessivi dell'area."
elif perc >= 0.1:
return f"Con ricavi pari a {ricavi_formattati} mln di EUR, {nome} partecipa attivamente al tessuto economico locale, rappresentando una stabile realtà territoriale con un'incidenza pari al {format_euro(perc)}% rispetto ai ricavi complessivi dell'area."
else:
return f"Con ricavi pari a {ricavi_formattati} mln di EUR, {nome} opera all'interno di un mercato territoriale ampio e competitivo, contribuendo al tessuto economico locale con un'incidenza pari al {format_euro(perc)}% rispetto ai ricavi complessivi dell'area."
def get_testo_totale(rating):
if rating == 'A':
return "Questo risultato riflette un profilo economico-patrimoniale solido, con indicatori di redditività, indipendenza finanziaria ed equilibrio di breve termine superiori ai parametri mediani del settore."
elif rating == 'B':
return "Questo risultato riflette un profilo di sostanziale stabilità complessiva, evidenziando un assetto economico-patrimoniale solido pur con margini di miglioramento specifici nell'ottimizzazione degli asset e dei margini operativi."
elif rating == 'C':
return "Questo risultato evidenzia elementi di vulnerabilità nella struttura economica, patrimoniale e finanziaria, suggerendo l'opportunità di interventi mirati per stabilizzare i flussi di cassa, riequilibrare le fonti di finanziamento o recuperare marginalità operativa."
return "Non sono disponibili dati sufficienti per elaborare un giudizio complessivo accurato."
def get_testo_eco(rating):
if rating == 'A':
return "I margini operativi (EBITDA ed EBIT) e il margine di profitto si collocano stabilmente al di sopra dei parametri mediani di settore. L'impresa mostra una buona efficienza nel generare reddito dalla gestione caratteristica e nel trasformare i ricavi in risultato netto."
elif rating == 'B':
return "La redditività operativa e netta risulta adeguata alle dinamiche di settore. L'azienda presenta una buona capacità di generare reddito operativo, seppur con spazi di ottimizzazione nell'assorbimento dei costi di gestione e degli oneri accessori per incrementare l'efficienza della struttura economica."
elif rating == 'C':
return "L'analisi dei margini segnala una minore capacità di trasformare i ricavi in risultato operativo e netto. Il posizionamento al di sotto dei parametri settoriali denota una minore efficienza della struttura economica e un'elevata incidenza dei costi di gestione."
return "Dati economici non disponibili o insufficienti."
def get_testo_patr(rating):
if rating == 'A':
return "La struttura patrimoniale rappresenta un punto di solidità dell'azienda. Il capitale proprio copre interamente gli investimenti a lungo termine (immobilizzazioni), garantendo una buona indipendenza dai vincoli di rimborso a breve termine."
elif rating == 'B':
return "La struttura patrimoniale appare equilibrata. L'azienda finanzia una parte adeguata delle proprie immobilizzazioni attraverso capitale permanente, sebbene il ricorso al capitale di terzi per sostenere gli impieghi a medio-lungo termine debba essere costantemente monitorato."
elif rating == 'C':
return "La struttura patrimoniale evidenzia uno squilibrio nella correlazione temporale tra fonti e impieghi. Una porzione significativa delle immobilizzazioni risulta finanziata mediante capitale di terzi con obbligo di rimborso nel breve termine, esponendo l'azienda a potenziali rischi di rifinanziamento."
return "Dati patrimoniali non disponibili o insufficienti."
def get_testo_fin(rating):
if rating == 'A':
return "L'equilibrio della struttura finanziaria di breve termine è solido. Le attività correnti coprono integralmente i debiti esigibili nell'esercizio e le risorse prontamente liquidabili assicurano una buona solvibilità immediata, senza necessità di smobilizzare le scorte."
elif rating == 'B':
return "L'equilibrio della struttura finanziaria di breve termine è sufficiente a coprire gli impieghi correnti. Tuttavia, la liquidità immediata potrebbe presentare una parziale dipendenza dalla monetizzazione delle scorte o dall'incasso dei crediti per soddisfare tutti gli impegni a breve."
elif rating == 'C':
return "La struttura finanziaria registra potenziali tensioni di liquidità. L'incapacità delle attività correnti o prontamente liquidabili di far fronte agevolmente alle passività correnti segnala il rischio di dover ricorrere a ulteriori fonti di finanziamento esterne."
return "Dati finanziari non disponibili o insufficienti."
def get_testo_sintesi(rating):
if rating == 'A':
return "In sintesi, l'integrazione di questi equilibri mostra un'impresa con una struttura economica, patrimoniale e finanziaria solida. L'efficienza nell'impiego del capitale investito e la solidità delle fonti di finanziamento supportano i futuri percorsi di crescita."
elif rating == 'B':
return "In sintesi, il coordinamento tra struttura patrimoniale e liquidità finanziaria assicura all'impresa la continuità operativa. Risulta tuttavia opportuno monitorare la capacità del capitale investito di tradursi in ricavi, al fine di non erodere i margini di sicurezza nel lungo periodo."
elif rating == 'C':
return "In sintesi, la combinazione dei tre equilibri rivela inefficienze nella struttura economica, patrimoniale e finanziaria che penalizzano l'impiego del capitale e l'autonomia monetaria. È opportuno intervenire per ripristinare un migliore allineamento temporale tra le fonti di finanziamento e gli impieghi."
return ""
def get_intro_benchmark_eco(rating):
if rating == 'A':
return f'Rispetto al Benchmark Economico, la valutazione "{rating}" evidenzia una redditività operativa e netta superiore ai parametri mediani del comparto di riferimento:'
elif rating == 'B':
return f'Rispetto al Benchmark Economico, la valutazione "{rating}" evidenzia una capacità di generare reddito adeguata e allineata ai valori mediani del comparto di riferimento, pur con specifici margini di intervento:'
elif rating == 'C':
return f'Rispetto al Benchmark Economico, la valutazione "{rating}" sottolinea una marginalità operativa e netta contratta, al di sotto dei parametri mediani espressi dal comparto di riferimento:'
return "Rispetto al Benchmark Economico, i dati a disposizione non consentono di esprimere una valutazione completa, come riassunto di seguito:"
# =================================================================
# 🟢 INDICATORI ECONOMICI (Valore vs Mediana) - Formattati a Bullet Points
# =================================================================
def get_analisi_margini_operativi(az_ebitda, set_ebitda, az_ebit, set_ebit, descr_settore):
if az_ebitda < set_ebitda:
txt_ebitda = f"• Il **Margine EBITDA** ({format_euro(az_ebitda)}%) risulta inferiore alla mediana settoriale ({format_euro(set_ebitda)}%). Valori più contenuti segnalano una minore capacità di trasformare i ricavi in margine operativo lordo, denotando una minore efficienza della gestione caratteristica prima degli ammortamenti e delle svalutazioni.\n"
else:
txt_ebitda = f"• Il **Margine EBITDA** ({format_euro(az_ebitda)}%) supera la mediana settoriale ({format_euro(set_ebitda)}%). Valori più elevati indicano una maggiore capacità dell'impresa di generare reddito dalla gestione caratteristica prima di ammortamenti e svalutazioni, evidenziando una superiore efficienza operativa.\n"
if az_ebit < set_ebit:
txt_ebit = f"• Il **Margine EBIT** ({format_euro(az_ebit)}%) si colloca al di sotto del target mediano ({format_euro(set_ebit)}%). Tale dato segnala una minore capacità di generare reddito operativo in relazione ai ricavi conseguiti a valle dell'assorbimento dei costi e degli ammortamenti."
else:
txt_ebit = f"• Il **Margine EBIT** ({format_euro(az_ebit)}%) si posiziona al di sopra della mediana di settore ({format_euro(set_ebit)}%). Questo livello indica una maggiore capacità di conseguire un risultato operativo soddisfacente dopo aver considerato gli ammortamenti e le svalutazioni."
return f"Le risultanze relative al mercato ({descr_settore}) evidenziano che:\n{txt_ebitda}{txt_ebit}"
def get_analisi_margine_profitto(az_prof, set_prof, descr_settore):
if az_prof < set_prof:
return f"\n• Il **Margine di Profitto** ({format_euro(az_prof)}%) risulta inferiore alla mediana settoriale ({format_euro(set_prof)}%). Tale andamento denota una minore capacità di trasformare i ricavi in utile netto, evidenziando criticità nell'assorbimento della gestione straordinaria, degli oneri finanziari o del carico fiscale."
else:
return f"\n• Il **Margine di Profitto** ({format_euro(az_prof)}%) supera il parametro mediano del settore ({format_euro(set_prof)}%). Valori più elevati indicano una maggiore capacità dell'impresa di convertire i ricavi in risultato netto finale, confermando una gestione ottimizzata degli oneri extra-caratteristici."
# =================================================================
# 🟠 INDICATORI PATRIMONIALI (Valore vs 1 e Gearing vs Mediana)
# =================================================================
def get_intro_benchmark_patr(rating):
if rating == 'A':
return "L'analisi attesta una buona robustezza nella copertura degli investimenti, posizionando gli indici strutturali dell'azienda al di sopra delle soglie di sicurezza:"
elif rating == 'B':
return "L'analisi evidenzia un rapporto tra capitale, debiti e immobilizzazioni adeguato e in linea con i parametri mediani del mercato di riferimento:"
elif rating == 'C':
return "L'analisi segnala squilibri nella correlazione temporale tra le fonti di copertura e le immobilizzazioni aziendali rispetto ai parametri di sicurezza:"
return "L'analisi non consente di esprimere una valutazione completa sui rischi a lungo termine a causa di dati insufficienti:"
def get_analisi_indici_struttura(az_str1, set_str1, az_str2, set_str2):
if az_str1 >= 1:
txt_s1 = f"• L'**Indice primario di struttura** ({format_euro(az_str1)}), superiore o uguale all'unità, indica che il capitale proprio, il quale non ha vincoli di scadenza, ha finanziato interamente le immobilizzazioni, caratterizzate da tempi di disinvestimento medio-lunghi.\n"
else:
txt_s1 = f"• L'**Indice primario di struttura** ({format_euro(az_str1)}), risultando inferiore ad uno, segnala che una parte delle immobilizzazioni è stata finanziata mediante capitale di terzi, con potenziale obbligo di rimborso nel breve termine.\n"
if az_str2 >= 1:
txt_s2 = f"• L'**Indice secondario di struttura** ({format_euro(az_str2)}), superiore o uguale all'unità, conferma che il capitale permanente, costituito dal capitale proprio e dai debiti a medio-lunga scadenza, ha finanziato interamente gli asset immobilizzati."
else:
txt_s2 = f"• L'**Indice secondario di struttura** ({format_euro(az_str2)}), essendo inferiore ad uno, indica che una parte dell'attivo immobilizzato è finanziata attraverso capitale di terzi a breve scadenza, determinando uno squilibrio temporale tra fonti e impieghi."
return f"Analizzando la provvista allargata:\n{txt_s1}{txt_s2}"
def get_analisi_gearing(az_gear, set_gear):
if az_gear <= set_gear:
return f"\n• Il **Gearing** ({format_euro(az_gear)}%) si attesta al di sotto del parametro mediano del comparto ({format_euro(set_gear)}%). Tali valori più contenuti indicano una limitata dipendenza dell'impresa dall'indebitamento oneroso e una solida autonomia rispetto ai creditori."
else:
return f"\n• Il **Gearing** ({format_euro(az_gear)}%) supera la mediana di settore ({format_euro(set_gear)}%). Valori più elevati segnalano un maggiore ricorso al capitale di terzi per il finanziamento aziendale, determinando un incremento del rischio finanziario e una minore autonomia."
# =================================================================
# 🔵 INDICATORI FINANZIARI (Valore vs 1 e Rotazione vs Mediana)
# =================================================================
def get_intro_benchmark_fin(rating):
if rating == 'A':
return "L'analisi degli indicatori correnti evidenzia una condizione di stabilità nel breve periodo e una buona capacità di generare ricavi tramite l'impiego delle risorse, superiore ai parametri mediani del settore:"
elif rating == 'B':
return "L'analisi degli indicatori correnti evidenzia un equilibrio finanziario di breve termine adeguato a coprire le passività correnti, in linea con gli standard del settore:"
elif rating == 'C':
return "L'analisi degli indicatori correnti segnala potenziali tensioni di liquidità e un impiego meno efficiente delle risorse rispetto alle soglie di sicurezza:"
return "L'analisi non consente di esprimere una valutazione completa sulla gestione della liquidità:"
def get_analisi_current_ratio(az_cr, set_cr):
if az_cr >= 1:
return f"• Il **Current Ratio** ({format_euro(az_cr)}), essendo superiore o uguale all'unità, indica che le attività a breve termine sono sufficienti a coprire integralmente i debiti esigibili nel breve periodo, evidenziando una situazione di equilibrio d'esercizio.\n"
else:
return f"• Il **Current Ratio** ({format_euro(az_cr)}), risultando inferiore ad uno, segnala l'incapacità delle attività correnti di far fronte alle passività correnti, configurando una potenziale tensione di liquidità all'interno della struttura d'esercizio.\n"
def get_analisi_quick_ratio(az_qr, set_qr):
if az_qr >= 1:
return f"• Il **Quick Ratio** ({format_euro(az_qr)}), superiore o uguale all'unità, indica che le risorse prontamente liquidabili sono sufficienti a garantire la copertura dei debiti a breve termine senza ricorrere alla vendita forzata delle rimanenze di magazzino.\n"
else:
return f"• Il **Quick Ratio** ({format_euro(az_qr)}), essendo inferiore ad uno, evidenzia una dipendenza, almeno parziale, dalla monetizzazione delle scorte o da ulteriori fonti di finanziamento esterne per soddisfare gli impegni immediati.\n"
def get_analisi_rotazione(az_rot, set_rot, descr_settore):
if az_rot < set_rot:
return f"• L'**Indice di rotazione del capitale investito** ({format_euro(az_rot)}) risulta inferiore alla mediana del comparto ({format_euro(set_rot)}). Valori più contenuti segnalano una minore capacità del capitale investito di tradursi in ricavi, denotando un impiego meno efficiente degli asset operativi."
else:
return f"• L'**Indice di rotazione del capitale investito** ({format_euro(az_rot)}) supera la mediana settoriale ({format_euro(set_rot)}). Valori più elevati indicano una maggiore capacità dell'impresa di generare ricavi attraverso le risorse investite, evidenziando un efficiente utilizzo del capitale."
def get_analisi_posizionamento_fin(az_cr, az_qr, set_cr, set_qr):
# La frase conclusiva sotto ai 3 bullet point finanziari
if az_cr >= set_cr and az_qr >= set_qr:
return "Il posizionamento finale nelle griglie distributive consolida un profilo di elevata affidabilità finanziaria, registrando indicatori correnti sistematicamente allineati o superiori ai parametri dei competitor caratteristici."
elif az_cr < set_cr and az_qr < set_qr:
return "Il posizionamento finale colloca la società nelle fasce contratte della distribuzione settoriale. Gli indicatori riflettono la necessità di velocizzare la rotazione degli asset circolanti per alleviare il potenziale rischio di insolvenza nel breve periodo."
else:
return "Il posizionamento finale riflette risultanze asimmetriche nel panel di settore. Sebbene la solvibilità generale risulti presidiata in linea con i valori mediani, permangono mirati elementi di sfasamento monetario sul ciclo di liquidazione immediata delle rimanenze."
def get_analisi_combinata(eco, patr, fin):
dict_eco = {'A': "un'ottima marginalità operativa", 'B': "una redditività caratteristica in linea col mercato", 'C': "una debole capacità di trasformare i ricavi in margini"}
dict_patr = {'A': "un'indipendenza finanziaria che copre le immobilizzazioni", 'B': "un capitale permanente adeguato agli asset", 'C': "un forte squilibrio nell'indebitamento a sostegno degli impieghi"}
dict_fin = {'A': "una rotazione ottimale per coprire le passività a breve", 'B': "un Current Ratio adeguato a onorare i debiti esigibili", 'C': "un Quick Ratio dipendente dallo smobilizzo delle rimanenze"}
if eco in ['A','B','C'] and patr in ['A','B','C'] and fin in ['A','B','C']:
return f"riflette un'azienda che poggia su {dict_patr[patr]}, associata a {dict_eco[eco]} e {dict_fin[fin]}."
return "riflette un'azienda per la quale non è possibile tracciare un profilo combinato completo a causa di dati mancanti."
def get_descr_fascia_appartenenza(rating):
if rating == 'A': return "la solidità reddituale, patrimoniale e finanziaria"
elif rating == 'B': return "l'adeguatezza agli indici di stabilità settoriale"
elif rating == 'C': return "la fascia che necessita di consolidamento per le tensioni su margini e liquidità"
return "una porzione non classificabile"
def get_intro_divario_strutturale(az_ebitda, set_ebitda):
if az_ebitda >= set_ebitda:
return "un posizionamento favorevole in termini di ottimizzazione della gestione operativa e di struttura economica da parte di"
else:
return "un significativo divario prestazionale tra la mediana del comparto e l'efficienza della struttura economica di"
# =========================================================================================
# LE ALTRE FUNZIONI NARRATIVE PER LA PARTE DI TREND STORICO (Restano invariate o snellite)
# =========================================================================================
def get_analisi_trend_ebitda(az_ebitda, set_ebitda):
if az_ebitda >= set_ebitda:
return f"ha registrato performance favorevoli, con un valore al 2024 pari al {format_euro(az_ebitda)}%. Questo risultato dimostra una maggiore capacità della struttura economica di generare reddito dalla gestione caratteristica, superando la mediana del settore ({format_euro(set_ebitda)}%)."
else:
return f"ha registrato evidenti segnali di contrazione, con un valore al 2024 pari al {format_euro(az_ebitda)}%. Questo risultato si colloca al di sotto della mediana settoriale ({format_euro(set_ebitda)}%), segnalando una minore efficienza nell'assorbimento dei costi correnti."
def get_asimmetria_ebitda(az_ebitda, set_ebitda):
if az_ebitda >= set_ebitda: return "una spiccata forza del Margine EBITDA dell'azienda rispetto ai parametri mediani di settore."
else: return "una chiara criticità nella tenuta della struttura economica aziendale rispetto al benchmark mediano del comparto."
def get_confronto_costi_settore(az_ebitda, set_ebitda):
if az_ebitda >= set_ebitda: return "ottimizzata e strutturalmente protetta rispetto ai parametri economici medi del mercato di riferimento"
else: return "maggiormente gravosa e penalizzante rispetto ai parametri economici mediani del mercato di riferimento"
def get_impatto_costi_su_margine(az_ebitda, set_ebitda):
if az_ebitda >= set_ebitda: return "evidenzia una spiccata efficienza nell'assorbimento dei costi operativi correnti a salvaguardia del margine lordo caratteristico"
else: return "segnala una minore efficienza nell'assorbimento dei costi industriali interni, comprimendo il potenziale residuo del Margine EBITDA"
def get_analisi_trend_ebit(az_ebit, set_ebit):
if az_ebit >= set_ebit: return f"Questo dato dimostra un'elevata redditività dopo aver considerato gli ammortamenti e le svalutazioni, con un valore pari al {format_euro(az_ebit)}% che supera stabilmente la mediana."
else: return f"Questo dato riflette una minore capacità di conseguire un risultato operativo soddisfacente in rapporto ai ricavi di vendita conseguiti, posizionandosi sotto la mediana settoriale al {format_euro(az_ebit)}%."
def get_confronto_ebit_settore(az_ebit, set_ebit):
if az_ebit >= set_ebit: return "L'efficienza nell'impiego operativo delle risorse aziendali è confermata dal posizionamento nettamente superiore alla mediana dell'EBIT."
else: return "La capacità di generare reddito operativo a valle dei costi fissi si attesta al di sotto dei livelli di riferimento mediani espressi dal settore in esame."
def get_sintesi_ebit_mediana(az_ebit, set_ebit):
if az_ebit >= set_ebit: return f"al di sopra della mediana di settore (pari a {format_euro(set_ebit)}%) per quanto concerne l'utile operativo EBIT"
else: return f"al di sotto della mediana di settore (pari a {format_euro(set_ebit)}%) per quanto concerne l'utile operativo EBIT"
def get_analisi_verticale_ebit(az_ebitda, az_ebit, set_ebitda, set_ebit):
spread_azienda = az_ebitda - az_ebit
spread_settore = set_ebitda - set_ebit
if spread_azienda > spread_settore + 3.0: return "un cosiddetto 'effetto forbice' più marcato rispetto al settore: le quote di ammortamento e svalutazione erodono la redditività generata dai ricavi in misura superiore alla media di comparto, appesantendo la struttura economica a valle della gestione lorda caratteristica"
else: return "un bilanciamento della struttura economica in linea con il settore: l'incidenza degli ammortamenti sul Valore della Produzione risulta paragonabile a quella dei competitor, senza un ulteriore assottigliamento della redditività nel passaggio da EBITDA a EBIT"
def get_efficienza_struttura_asset(az_ebit, set_ebit):
if az_ebit >= set_ebit: return "efficiente nell'impiego, nel rinnovo e nella valorizzazione delle risorse e della struttura a disposizione"
else: return "caratterizzata da una minore efficienza nell'utilizzo strutturale delle proprie immobilizzazioni operative"
def get_analisi_trend_profitto(az_prof, set_prof):
if az_prof >= set_prof: return f"Questo dato, pari al {format_euro(az_prof)}%, dimostra un'elevata efficacia nella gestione dei costi accessori, degli oneri finanziari e del carico fiscale complessivo."
else: return f"Questo dato ({format_euro(az_prof)}%) riflette una redditività netta complessivamente più limitata, risentendo del peso degli oneri extra-caratteristici."
def get_confronto_profitto_settore(az_prof, set_prof):
if az_prof >= set_prof: return f"La struttura dell'impresa supera la performance mediana dei concorrenti (pari a {format_euro(set_prof)}%), distinguendosi per una forte propensione alla generazione di utile netto di periodo."
else: return f"La capacità di convertire i ricavi in risultato netto si colloca al di sotto della performance mediana espressa dal settore ({format_euro(set_prof)}%)."
def get_prospettiva_redditivita_futura(az_prof, set_prof):
if az_prof >= set_prof: return "dimostra di poter sostenere le dinamiche gestionali future grazie all'ottimizzazione del proprio ciclo dell'utile netto."
else: return "deve focalizzarsi sul recupero dell'efficienza sui costi complessivi per migliorare la trasformazione del fatturato in utile netto."
def get_posizionamento_margine_profitto_fine(az_prof, set_prof):
if az_prof >= set_prof: return "su livelli superiori alla mediana di settore, confermando una buona capacità di trasformare i ricavi in risultato netto"
else: return "su livelli compressi che limitano la redditività finale in confronto al comparto"
def get_sintesi_bilancio_finale(az_prof, set_prof):
if az_prof >= set_prof: return "una più elevata efficacia nella gestione dei costi lungo tutta la catena del valore aziendale"
else: return "il forte peso delle gestioni accessorie, degli oneri del debito o del carico fiscale rispetto alla ricchezza economica operativa generata"
def get_impatto_gestione_caratteristica(az_ebitda, set_ebitda):
if az_ebitda >= set_ebitda: return "pienamente efficiente rispetto ai costi diretti correnti"
else: return "debole, disperdendo gran parte del valore operativo in costi di produzione e spese d'esercizio"
def get_impatto_oneri_accessori(az_ebit, az_prof):
if (az_ebit - az_prof) > 4.0: return "assorbe le residue disponibilità operative, contraendo la marginalità netta a causa del peso della gestione extra-operativa"
else: return "impatta in modo proporzionato, consentendo di registrare un utile finale in linea con i target attesi"
def get_interpretazione_risultato_eco(az_ebitda, set_ebitda, az_prof, set_prof):
if az_ebitda >= set_ebitda and az_prof >= set_prof: return "conferma in maniera analitica la piena solidità del modello e la gestione ottimale dei costi lungo tutta la catena del valore."
elif az_ebitda < set_ebitda and az_prof < set_prof: return "indica uno squilibrio nell'area economica per la debole conversione dei ricavi lungo la filiera produttiva aziendale."
else: return "restituisce una dinamica asimmetrica tra l'efficienza industriale caratteristica (EBITDA) e il rendimento netto complessivo."
def get_stato_struttura_operativa(az_ebit, set_ebit):
if az_ebit >= set_ebit: return "altamente performante nella massimizzazione dei ricavi e del reddito caratteristico"
else: return "vulnerabile a causa di un'efficienza non ottimizzata sul rendimento degli asset caratteristici"
def get_gestione_costi_ricavi_core(az_ebitda, set_ebitda):
if az_ebitda >= set_ebitda: return "gestisce in modo efficiente i costi operativi esterni, generando un surplus soddisfacente rispetto ai ricavi di vendita"
else: return "subisce un forte assorbimento dei ricavi a causa di una rigida incidenza dei costi di gestione operativa correnti"
def get_implicazione_finale_eco(az_prof, set_prof):
if az_prof >= set_prof: return "Mantenendo tale assetto organizzativo, l'impresa garantisce una solida autonomia nel lungo periodo grazie alla propria capacità di trasformazione dei ricavi in utile netto."
else: return "La società necessita di interventi mirati per ottimizzare l'incidenza dei costi operativi e extra-caratteristici, al fine di recuperare redditività complessiva e autonomia economica."
def get_sintesi_quadriennio_patr(az_str1, az_str2):
if az_str1 >= 1.0 and az_str2 >= 1.0: return "un'elevata stabilità della struttura patrimoniale e l'assenza di rischi derivanti dal disequilibrio tra investimenti e capitale permanente"
elif az_str1 < 1.0 and az_str2 < 1.0: return "uno squilibrio nella correlazione temporale tra fonti e impieghi, con potenziale immobilizzazione di capitale circolante a copertura di asset duraturi"
else: return "un posizionamento asimmetrico e parzialmente sbilanciato nell'assetto di medio-lungo termine della struttura patrimoniale"
def get_analisi_copertura_attivo_fisso(az_str1):
if az_str1 >= 1.0: return "il capitale proprio finanzia integralmente gli investimenti a lungo periodo, non presentando vincoli di scadenza e riducendo l'esposizione verso terzi"
else: return "si evidenzia una parziale copertura degli asset fissi, dipendendo in larga misura dal debito verso terzi, il che obbliga a un puntuale rimborso a breve"
def get_confronto_struttura_settore(az_str1, set_str1):
if az_str1 >= set_str1: return "L'assetto patrimoniale dell'azienda si distacca in modo favorevole rispetto alla mediana settoriale, limitando l'uso della leva finanziaria."
else: return "L'assetto patrimoniale sconta un divario rispetto al posizionamento mediano dei competitor in termini di capitalizzazione autonoma."
def get_analisi_soglia_struttura1(az_str1):
if az_str1 >= 1.0: return "Il capitale proprio ha finanziato interamente le immobilizzazioni, garantendo la sicurezza degli investimenti."
else: return "Una parte delle immobilizzazioni è stata finanziata mediante capitale di terzi, erodendo i parametri di sicurezza."
def get_confronto_mediana_struttura1(az_str1, set_str1):
if az_str1 >= set_str1: return f"Il posizionamento dell'Indice risulta nettamente superiore alla mediana del comparto di riferimento (pari a {format_euro(set_str1)})."
else: return f"L'azienda sconta un ritardo nella solidità del proprio Indice rispetto ai parametri mediani di settore (attestati a {format_euro(set_str1)})."
def get_implicazione_copertura_attivo(az_str1):
if az_str1 >= 1.0: return "gode di un assetto bilanciato e immune ai tempi di disinvestimento medio-lunghi delle immobilizzazioni."
else: return "è soggetta a forte pressione temporale sui rimborsi dei capitali prestati da terzi a causa dello squilibrio d'impiego."
def get_analisi_trend_struttura2(az_str2):
if az_str2 >= 1.0: return "Il capitale permanente ha finanziato interamente le immobilizzazioni, assicurando correlazione temporale."
else: return "Una parte delle immobilizzazioni è stata finanziata attraverso capitale a breve termine, determinando uno squilibrio temporale."
def get_confronto_settore_struttura2(az_str2, set_str2):
if az_str2 >= set_str2: return "Tale dinamica risulta superiore alle performance di copertura strutturale dei competitor."
else: return "Il trend sconta uno svantaggio in confronto alla correlazione temporale mediana del mercato."
def get_conclusione_struttura2(az_str2):
if az_str2 >= 1.0: return "riduce sensibilmente il rischio legato a rinegoziazioni del debito per la copertura delle immobilizzazioni fisse."
else: return "comporta il rischio di tensioni finanziarie al momento del rinnovo degli affidamenti bancari a breve scadenza."
def get_evoluzione_vantaggio_competitivo(az_str1, set_str1):
if az_str1 >= set_str1: return "riesce a consolidare un vantaggio patrimoniale di rilievo all'interno del proprio segmento competitivo."
else: return "necessita di colmare il divario patrimoniale rispetto ai principali operatori del comparto per preservare il rating."
def get_resilienza_credit_crunch(az_str2):
if az_str2 >= 1.0: return "è fortemente tutelato contro eventuali restrizioni creditizie grazie alla completa copertura fornita dal capitale permanente"
else: return "risulta maggiormente esposto agli effetti delle restrizioni creditizie a causa dello squilibrio tra fonti correnti e immobilizzazioni"
def get_flessibilita_strategica_asset(az_str1):
if az_str1 >= 1.0: return "una buona flessibilità per nuovi piani di sviluppo strategico e investimenti durevoli."
else: return "una rigidità nel finanziare nuovi progetti di rinnovamento degli asset e degli impianti fisici."
def get_intro_analisi_gearing(az_gear, set_gear):
if az_gear <= set_gear: return "L'analisi conferma un contenimento del Gearing coerente con la solidità della struttura patrimoniale."
else: return "L'analisi evidenzia una marcata esposizione verso il capitale di terzi per il finanziamento aziendale."
def get_andamento_storico_gearing(az_gear, set_gear):
if az_gear <= set_gear: return "Valori contenuti indicano una limitata dipendenza dall'indebitamento bancario e una maggiore solidità complessiva."
else: return "Valori più alti segnalano un forte ricorso ai terzi, determinando un incremento del rischio finanziario."
def get_confronto_gearing_settore(az_gear, set_gear):
if az_gear <= set_gear: return f"l'esposizione debitoria risulta nettamente inferiore rispetto alla mediana del comparto ({format_euro(set_gear)}%), riducendo l'indice di rischio aziendale."
else: return f"l'esposizione debitoria risulta del {format_euro(az_gear)}%, sensibilmente superiore rispetto al valore mediano di settore ({format_euro(set_gear)}%), incrementando il rischio d'insolvenza."
def get_reazione_contesto_gearing(az_gear, set_gear):
if az_gear <= set_gear: return "garantisce all'impresa una buona autonomia dai vincoli creditizi, riducendo l'indebitamento oneroso"
else: return "richiede attenzione costante alla sostenibilità degli oneri, la cui leva agisce incrementando il rischio sui margini"
def get_sintesi_valore_gearing(az_gear, set_gear):
if az_gear <= set_gear: return "conferma la solidità aziendale e il limitato ricorso al capitale di terzi."
else: return "rappresenta l'elemento di maggiore leva finanziaria che incide sull'autonomia economica complessiva."