Spaces:
Sleeping
Sleeping
File size: 13,426 Bytes
38f9c15 | 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 | """
Unified Rendering Layer - Rooting Future Strategy Engine v5.4
Consolidates structured_renderer, executive_report, and methodology_section.
"""
import os
import re
import json
import logging
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Any
# Core Data Models
from data_models import (
DataPoint, StructuredSection, StructuredPlan,
DataType, ConfidenceLevel, DeviationType, Source, Benchmark,
SourceType, BenchmarkDatabase
)
# Utils & Analyzers
from data_estimator import estimate_missing_financials, DataTier
from chart_generator import (
generate_revenue_pie_chart,
generate_benchmark_comparison,
generate_gap_analysis_chart,
embed_chart_in_html,
generate_financial_charts_for_report
)
from stw_analyzer import calculate_stw_progress, STWAnalyzer
from stw_matrix import get_category_color, get_category_icon, STWCategory
from club_identity import get_club_identity
logger = logging.getLogger(__name__)
# =============================================================================
# CONSTANTS & FALLBACKS
# =============================================================================
MICRO_OBJECTIVES_FALLBACKS = {
'CREAZIONE E SVILUPPO IDENTITÀ TECNICA': [
'Definire modulo tattico unificato (es. 4-3-3) per tutte le categorie',
'Formare lo staff tecnico su metodologia di gioco comune',
'Implementare sistema di valutazione performance standardizzato',
'Creare protocollo allenamenti settimanale condiviso tra categorie'
],
'COSTRUZIONE E RINNOVAMENTO STRUTTURE': [
'Mappare stato attuale impianti con report fotografico dettagliato',
'Prioritizzare interventi su campo allenamento settore giovanile',
'Ottenere certificazioni sicurezza aggiornate per tutti gli impianti',
'Installare sistema illuminazione LED su campo principale'
],
'SVILUPPO AREA COMUNICAZIONE': [
'Pubblicare 3 post/settimana sui social media con calendario editoriale',
'Creare newsletter mensile per tesserati e sponsor',
'Implementare area stampa digitale sul sito web',
'Avviare collaborazione con media locali per copertura partite'
],
'SVILUPPO AREA MARKETING': [
'Lanciare campagna sponsor per stagione 2026/27 con target €50K',
'Creare merchandising ufficiale (maglie, sciarpe) entro marzo',
'Implementare programma fedeltà per abbonati',
'Organizzare 2 eventi corporate per attrarre nuovi partner'
],
'SVILUPPO BRAND IDENTITY': [
'Ridisegnare logo e brand guidelines entro aprile 2026',
'Unificare comunicazione visiva su tutti i canali',
'Creare brand book digitale per uso interno/esterno',
'Registrare marchio presso UIBM per protezione legale'
],
'SVILUPPO AREA COMMERCIALE': [
'Mappare potenziali sponsor locali (target list di 30 aziende)',
'Creare presentation deck commerciale con pacchetti sponsor',
'Assumere commerciale part-time per gestione sponsor',
'Attivare vendita biglietti online su piattaforma dedicata'
],
'SVILUPPO INCLUSIONE E UGUAGLIANZA': [
'Creare squadra femminile o accordo con club femminile locale',
'Implementare protocollo anti-discriminazione in tutti gli eventi',
'Organizzare torneo giovanile inclusivo aperto a tutti',
'Formare staff su gestione diversità e inclusione'
],
'PROTEZIONE BAMBINI/E E GIOVANI': [
'Certificare tutti gli allenatori giovanili con corso Safeguarding',
'Implementare protocollo tutela minori conforme FIGC',
'Nominare responsabile protezione minori nel club',
'Attivare assicurazione specifica per settore giovanile'
],
'RISORSE UMANE': [
'Digitalizzare gestione presenze staff con sistema HR cloud',
'Creare organigramma chiaro con ruoli e responsabilità definiti',
'Implementare valutazione annuale performance per staff tecnico',
'Attivare convezioni welfare per dipendenti (palestra, assicurazioni)'
]
}
SYSTEM_SOURCES = {
'questionnaire': {
'name': 'Questionari Club',
'type': 'tier1_fact',
'description': 'Dati forniti direttamente dal club tramite questionari compilati',
'confidence': 1.0
},
'transfermarkt': {
'name': 'Transfermarkt',
'type': 'tier1_fact',
'description': 'Valori di mercato rosa, statistiche giocatori',
'confidence': 0.90,
'url': 'https://www.transfermarkt.it'
},
'figc_report': {
'name': 'Report Calcio FIGC 2024',
'type': 'tier1_fact',
'description': 'Benchmark finanziari e sportivi ufficiali per categoria',
'confidence': 0.95,
'url': 'https://www.figc.it/it/federazione/report-calcio/'
}
}
# =============================================================================
# HELPER FUNCTIONS (INTERNAL)
# =============================================================================
def _clean_text(text: str) -> str:
if not text: return ""
text = re.sub(r'\*\*|\*|#{1,4}\s*', '', text)
text = re.sub(r'\s+', ' ', text).strip()
if text.startswith(':'): text = text[1:].strip()
return text
def _darken_color(hex_color: str, factor: float = 0.2) -> str:
hex_color = hex_color.lstrip('#')
if len(hex_color) != 6: return "#000000"
r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
r = int(r * (1 - factor))
g = int(g * (1 - factor))
b = int(b * (1 - factor))
return f'#{r:02x}{g:02x}{b:02x}'
def _get_contrast_color(hex_color: str) -> str:
hex_color = hex_color.lstrip('#')
if len(hex_color) != 6: return "#ffffff"
r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
return '#ffffff' if luminance < 0.5 else '#1a1a1a'
def _extract_key_points(content: str, max_points: int = 5) -> List[str]:
if not content: return []
points = []
bold_with_content = re.findall(r'\*\*([A-Za-zÀ-ÿ][^*]{5,50})(?::\*\*|\*\*:)\s*([^*\n]{10,200})', content)
for title, desc in bold_with_content:
title = _clean_text(title)
desc = _clean_text(desc)
first_sentence = re.split(r'(?<=[.!?])\s', desc)[0] if desc else ''
if first_sentence and len(first_sentence) > 15:
point = f"{title}: {first_sentence}"
if point not in points:
points.append(point)
if len(points) >= max_points: return points
return points[:max_points]
# =============================================================================
# PLAN RENDERER
# =============================================================================
class PlanRenderer:
"""
Unified rendering engine for strategic plans.
"""
def __init__(self, output_dir: str = "output"):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(exist_ok=True, parents=True)
self.footnotes = []
self.footnote_counter = 0
# --- STRUCTURED RENDERING ---
def render_structured(self, plan: StructuredPlan) -> str:
html = self.render_structured_html(plan)
filename = f"{plan.club_name.replace(' ', '_')}_Structured_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html"
filepath = self.output_dir / filename
with open(filepath, 'w', encoding='utf-8') as f:
f.write(html)
return str(filepath)
def render_structured_html(self, plan: StructuredPlan) -> str:
self.footnotes = []
self.footnote_counter = 0
return f'''<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Piano Strategico - {plan.club_name}</title>
{self._get_structured_styles()}
</head>
<body>
<div class="report">
{self._render_structured_header(plan)}
{self._render_credibility_dashboard(plan)}
<main class="content">
{"".join(self._render_section(s) for s in plan.sections.values())}
{self._render_footnotes()}
</main>
</div>
</body>
</html>'''
def _get_structured_styles(self) -> str:
return '''<style>
:root { --primary: #1a365d; --secondary: #2c5282; --success: #38a169; --gray-100: #f7fafc; --gray-800: #2d3748; }
body { font-family: sans-serif; color: var(--gray-800); background: var(--gray-100); padding: 20px; }
.report { max-width: 1000px; margin: 0 auto; background: white; box-shadow: 0 0 20px rgba(0,0,0,0.1); border-radius: 8px; overflow: hidden; }
.report-header { background: var(--primary); color: white; padding: 40px; text-align: center; }
.credibility-dashboard { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; padding: 20px; background: #eee; }
.metric-card { background: white; padding: 15px; border-radius: 4px; text-align: center; }
.content { padding: 40px; }
.section { margin-bottom: 40px; border-bottom: 1px solid #eee; padding-bottom: 20px; }
.summary-box { background: #f0f4f8; padding: 20px; border-left: 4px solid var(--secondary); margin: 20px 0; }
.data-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.data-card { border: 1px solid #ddd; border-radius: 6px; padding: 15px; }
.data-value { font-size: 1.5rem; font-weight: bold; color: var(--primary); }
</style>'''
def _render_structured_header(self, plan: StructuredPlan) -> str:
return f'<header class="report-header"><h1>{plan.club_name}</h1><p>Piano Strategico Triennale</p></header>'
def _render_credibility_dashboard(self, plan: StructuredPlan) -> str:
return f'''<div class="credibility-dashboard">
<div class="metric-card"><strong>{plan.overall_credibility:.1f}%</strong><br>Credibilita</div>
<div class="metric-card"><strong>{plan.total_data_points}</strong><br>Dati Totali</div>
<div class="metric-card"><strong>{plan.verified_data_points}</strong><br>Verificati</div>
<div class="metric-card"><strong>{len(plan.bibliography)}</strong><br>Fonti</div>
</div>'''
def _render_section(self, section: StructuredSection) -> str:
return f'''<section class="section">
<h2>{section.title}</h2>
<div class="summary-box">{section.summary}</div>
<div class="data-grid">{"".join(self._render_data_card(dp) for dp in section.data_points)}</div>
</section>'''
def _render_data_card(self, dp: DataPoint) -> str:
val = dp.formatted_value if dp.value is not None else "(da acquisire)"
return f'<div class="data-card"><strong>{dp.label}</strong><br><span class="data-value">{val}</span></div>'
def _render_footnotes(self) -> str:
if not self.footnotes: return ""
return "<h3>Note</h3>" + "".join(f"<div>[{f['num']}] {f['citation']}</div>" for f in self.footnotes)
def _add_footnote(self, source: Source) -> int:
self.footnote_counter += 1
self.footnotes.append({"num": self.footnote_counter, "citation": source.to_citation()})
return self.footnote_counter
# --- EXECUTIVE RENDERING ---
def render_executive(self, plan_data: Dict[str, str], club_name: str, category: str, metadata: Dict[str, Any]) -> str:
html = self.render_executive_html(plan_data, club_name, category, metadata)
safe_name = club_name.replace(" ", "_").replace("/", "_")
filename = f"{safe_name}_Executive_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html"
filepath = self.output_dir / filename
with open(filepath, 'w', encoding='utf-8') as f:
f.write(html)
return str(filepath)
def render_executive_html(self, plan_data: Dict[str, str], club_name: str, category: str, metadata: Dict[str, Any]) -> str:
from executive_report import generate_executive_report_html
return generate_executive_report_html(plan_data, club_name, category, metadata)
# --- METHODOLOGY RENDERING ---
def add_methodology(self, content: str, metadata: Dict = None, primary_color: str = "#1a365d") -> str:
from methodology_section import generate_rooting_future_methodology_html
methodology = generate_rooting_future_methodology_html(metadata, primary_color)
if "</body>" in content:
return content.replace("</body>", methodology + "</body>")
return content + methodology
def render_methodology(self, club_name: str, category: str, data_sources_used: List[str], estimated_fields: Dict[str, str], primary_color: str = "#1a365d") -> str:
from methodology_section import generate_methodology_section_html
return generate_methodology_section_html(club_name, category, data_sources_used, estimated_fields, primary_color)
def render_stw_matrix(self, stw_data: Dict[str, int]) -> str:
overall = sum(stw_data.values()) // len(stw_data) if stw_data else 0
return f'''<div style="background:#f1f5f9; padding:20px; border-radius:12px; border-left:6px solid #1a365d;">
<h4 style="margin:0 0 10px 0;">📊 Copertura Matrice STW: {overall}%</h4>
<p style="font-size:0.85rem; color:#64748b;">Questo valore indica l'allineamento del piano agli obiettivi Sport To Win.</p>
</div>'''
|