# Design Spec — Report ETF/Fondi **Data:** 2026-06-08 **Scope:** Nuovo report PDF a 2 pagine per ETF/fondi, con endpoint dedicati `/api/report/fund/` e `/api/chart/fund/`. Indipendente dal flusso certificati. --- ## 1. Endpoints ### Report PDF | Metodo | Route | Comportamento | |--------|-------|---------------| | GET | `/api/report/fund/by-isin/{isin}` | PDF inline | | GET | `/api/report/fund?p={isin_cifrato}` | PDF inline (ISIN cifrato, stesso CryptoHelper) | | GET | `/api/report/fund?alias={id}` | PDF inline (alias → ISIN via `FindIsinByAliasIdAsync`) | | GET | `/api/report/fund/download?p={...}` | PDF come allegato (`Content-Disposition: attachment`) | ### Grafico standalone | Metodo | Route | Comportamento | |--------|-------|---------------| | GET | `/api/chart/fund/{isin}` | PNG/PDF inline (vedi §4) | ### Parametri opzionali (tutti gli endpoint report) | Parametro | Default | Effetto | |-----------|---------|---------| | `?branding=true` | `false` | Footer "Powered by [Smart Roots](https://www.smart-roots.net)" — riusa `PdfTheme.DrawFooter` già esistente | ### Parametri opzionali (endpoint chart) | Parametro | Default | Effetto | |-----------|---------|---------| | `?format=png\|pdf\|jpg\|jpeg` | `png` | Formato output | | `?save=true` | `false` | Salva JPEG su disco (`ChartSettings:SavePath`) | --- ## 2. Architettura ``` HTTP → FundReportController ├── FundDataService sfih_GetOptDettagli @ISIN → FundInfo ├── FundReportOrchestrator │ ├── FundAnagraficaRenderer → PdfDocument (Pagina 1) │ ├── FundChartRenderer → PdfDocument (Pagina 2, placeholder) │ └── PdfMergerService → byte[] (riusato) └── PdfCacheService → cache chiave "fund:{isin}[:branded]" HTTP → FundChartController ├── FundChartDataService SP da definire → FundChartData └── FundSkiaChartRenderer → PNG/JPEG/PDF ``` **Riuso componenti esistenti:** `PdfMergerService`, `PdfCacheService`, `PdfTheme` (incluso `DrawFooter`), `CryptoHelper`. **Nessuna modifica** ai controller/renderer/service certificati esistenti. --- ## 3. Pagina 1 — Anagrafica (Layout C) ### Struttura visiva ``` ┌─────────────────────────────────────────────────────────┐ │ [BLUE BAR] {typ} — {str} — {isn} │ ├──────────────────────────────────────────────────────────┤ │ [RANK strip] rnk │ [PREZZO strip] prz val │ dpz │ ├──────────────────────────────────────────────────────────┤ │ Dati Anagrafici │ ESG Score │ Perf·Vol·R/R grid │ │ (flex 1.3) │ (flex 0.9) │ (flex 1.4) │ └──────────────────────────────────────────────────────────┘ │ footer: [Powered by Smart Roots]? pagina N │ ``` ### Mapping campi SP → sezioni **Titolo:** - `{typ} — {str} — {isn}` (es. `ETF — WisdomTree … — IE00BDVPNG13`) **Strip Rank/Prezzo:** - Rank: `rnk` - Prezzo: `prz` + `val` - Data aggiornamento: `dpz` (formato `dd/MM/yyyy`) **Dati Anagrafici** (tabella label/valore): | Label | Campo SP | |-------|----------| | Società | `soc` | | Categoria MS | `msc` | | Tipo | `typ` | | Valuta | `val` | | Hedged | `hed` (→ `"sì"` / `"no"`) | | Benchmark | `bmk` | | Spese correnti | `spc` (→ formato `0.##%`) | | Catalogo | `itr` | | Proventi | `prv` | | Data lancio | `daf` (formato `dd/MM/yyyy`) | | Patrimonio | `pat` (formato `#,##0 EUR`) | **ESG Score** (4 card verdi verticali): - Sustainability: `sus` - Environmental: `env` - Social: `ssc` - Governance: `gov` - Se un singolo valore è `0` (AMC/fallback), viene mostrato come `"—"`. La sezione ESG è sempre visibile. **Griglia Performance/Volatilità/RendRisk** (3×2): | Periodo | Perf | Vol | R/R | |---------|------|-----|-----| | 3 Mesi | `p3M` | `v3M` | `r3M` | | 6 Mesi | `p6M` | `v6M` | `r6M` | | Da inizio anno | `pYD` | `vYD` | `rYD` | | 1 Anno | `p1Y` | `v1Y` | `r1Y` | | 3 Anni | `p3Y` | `v3Y` | `r3Y` | | 5 Anni | `p5Y` | `v5Y` | `r5Y` | Colori: Perf positiva → `PositiveGreenBrush`, negativa → `NegativeRedBrush`, Vol e R/R → testo normale. --- ## 4. Pagina 2 — Grafico (placeholder) Il rendering della pagina 2 è un **placeholder** in questa fase. La SP del grafico verrà fornita in seguito. `FundChartRenderer` restituisce per ora una pagina con il testo "Grafico non disponibile" oppure viene omessa se la SP non è configurata. La struttura è pronta per accogliere la SP senza modifiche all'orchestratore. `FundSkiaChartRenderer` (endpoint `/api/chart/fund/{isin}`): - Singola linea: close price storico (`Px_Close` da SP da definire) - Supporta `?format=png|jpg|jpeg|pdf` e `?save=true` - Nessuna logica WorstOf/barriere/label complesse --- ## 5. Modelli **File:** `Models/FundModels.cs` ```csharp // Mapping 1:1 con result set di sfih_GetOptDettagli public class FundInfo { public string Isin { get; set; } // isn public string Strumento { get; set; } // str public string Tipo { get; set; } // typ public string Societa { get; set; } // soc public string CategoriaMorningstar { get; set; } // msc public string Valuta { get; set; } // val public string Hedged { get; set; } // hed public string Benchmark { get; set; } // bmk public string Catalogo { get; set; } // itr public string Proventi { get; set; } // prv public DateTime? DataLancio { get; set; } // daf public decimal? Patrimonio { get; set; } // pat public decimal? SpeseCorrenti { get; set; } // spc public decimal? Prezzo { get; set; } // prz public DateTime? DataPrezzo { get; set; } // dpz public decimal? Rank { get; set; } // rnk // Performance public decimal? P3M { get; set; } public decimal? P6M { get; set; } public decimal? PYD { get; set; } public decimal? P1Y { get; set; } public decimal? P3Y { get; set; } public decimal? P5Y { get; set; } // Volatilità public decimal? V3M { get; set; } public decimal? V6M { get; set; } public decimal? VYD { get; set; } public decimal? V1Y { get; set; } public decimal? V3Y { get; set; } public decimal? V5Y { get; set; } // RendRisk public decimal? R3M { get; set; } public decimal? R6M { get; set; } public decimal? RYD { get; set; } public decimal? R1Y { get; set; } public decimal? R3Y { get; set; } public decimal? R5Y { get; set; } // ESG public decimal? Sustainability { get; set; } // sus public decimal? Environmental { get; set; } // env public decimal? Social { get; set; } // ssc public decimal? Governance { get; set; } // gov } public class FundReportData { public FundInfo Info { get; set; } public bool ShowBranding { get; set; } } ``` --- ## 6. Interfacce ```csharp // IFundDataService Task GetFundInfoAsync(string isin); Task FindIsinByAliasIdAsync(string aliasId); // riusa stessa logica // IFundReportOrchestrator Task GenerateReportAsync(string isin, bool showBranding = false); ``` --- ## 7. Registrazioni Program.cs ```csharp builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // placeholder builder.Services.AddScoped(); // per /api/chart/fund/ ``` --- ## 8. Cache Chiave pattern: `fund:{isin}` + suffisso `:branded` se `showBranding=true`. Riusa il `PdfCacheService` esistente (stesso TTL da `appsettings.json`). --- ## 9. File da creare | File | Descrizione | |------|-------------| | `Models/FundModels.cs` | `FundInfo`, `FundReportData`, `FundChartData` (placeholder) | | `Services/Interfaces/IFundServices.cs` | `IFundDataService`, `IFundReportOrchestrator` | | `Services/Implementations/FundDataService.cs` | Chiama `sfih_GetOptDettagli` | | `Services/Implementations/FundAnagraficaRenderer.cs` | Pagina 1 layout C | | `Services/Implementations/FundChartRenderer.cs` | Pagina 2 placeholder | | `Services/Implementations/FundReportOrchestrator.cs` | Coordina anagrafica + chart + merge + cache | | `Services/Implementations/FundSkiaChartRenderer.cs` | Rendering grafico (placeholder, SP da aggiungere) | | `Controllers/FundReportController.cs` | 4 endpoint `/api/report/fund/` | | `Controllers/FundChartController.cs` | 1 endpoint `/api/chart/fund/` | --- ## 10. Fuori scope (questa iterazione) - SP grafico: `FundChartDataService` e `FundSkiaChartRenderer` rimangono placeholder fino alla fornitura della SP - Parametri `?natixis`, `?dividend` — non applicabili ai fondi - Test automatici (nessun test nel progetto al momento)