9.2 KiB
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" — 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(formatodd/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=truesalva JPEG inChartSettings:SavePath\{isin}_fund.jpg- Nessuna logica WorstOf/barriere/label complesse (renderer dedicato, non V2)
5. Modelli
File: Models/FundModels.cs
// 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
// 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
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)