250 lines
9.2 KiB
Markdown
250 lines
9.2 KiB
Markdown
# 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
|
||
|
||
**SP:** `sfih_GetChartPrices @ISIN VARCHAR(20)`
|
||
Restituisce `Px_Close (decimal)` e `Px_Date (date)` ordinati ASC, solo valori non NULL.
|
||
|
||
`FundChartRenderer` (pagina 2 nel PDF report):
|
||
- Singola linea nera sul close price storico
|
||
- Asse X: date (intervalli mensili adattivi come chart V2)
|
||
- Asse Y: scala automatica su min/max dei prezzi
|
||
- Titolo: `"Andamento Prezzo — {str}"` + sottotitolo con valuta
|
||
- Se la SP restituisce < 2 punti, la pagina mostra un messaggio "Dati insufficienti"
|
||
|
||
`FundSkiaChartRenderer` (endpoint standalone `/api/chart/fund/{isin}`):
|
||
- Stessa logica di rendering, output PNG/JPEG/PDF
|
||
- Supporta `?format=png|jpg|jpeg|pdf`, `?width=`, `?height=`, `?save=true`
|
||
- `?save=true` salva JPEG in `ChartSettings:SavePath\{isin}_fund.jpg`
|
||
- Nessuna logica WorstOf/barriere/label complesse (renderer dedicato, non V2)
|
||
|
||
---
|
||
|
||
## 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<FundInfo?> GetFundInfoAsync(string isin);
|
||
Task<string?> FindIsinByAliasIdAsync(string aliasId); // riusa stessa logica
|
||
|
||
// IFundReportOrchestrator
|
||
Task<byte[]> GenerateReportAsync(string isin, bool showBranding = false);
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Registrazioni Program.cs
|
||
|
||
```csharp
|
||
builder.Services.AddScoped<IFundDataService, FundDataService>();
|
||
builder.Services.AddScoped<IFundReportOrchestrator, FundReportOrchestrator>();
|
||
builder.Services.AddScoped<FundAnagraficaRenderer>();
|
||
builder.Services.AddScoped<FundChartRenderer>(); // placeholder
|
||
builder.Services.AddScoped<FundSkiaChartRenderer>(); // 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)
|
||
|
||
- Parametri `?natixis`, `?dividend` — non applicabili ai fondi
|
||
- Test automatici (nessun test nel progetto al momento)
|