# Design: grafico standalone per singolo sottostante (senza certificato) **Data**: 2026-07-21 **Stato**: approvato, in attesa di implementazione ## Obiettivo Aggiungere un endpoint che generi un grafico semplificato mostrando **solo l'andamento storico prezzo** di un sottostante, identificato da `IDUnderlyings` — senza passare da un ISIN certificato e senza alcun riferimento a strike/barriere/trigger (che esistono solo nel contesto di un certificato). Caso d'uso: preview rapida dell'andamento di un sottostante indipendente da qualunque certificato collegato. ## Architettura Stessa pipeline di `/api/chart/{isin}` e `/api/chart/v2/{isin}`, ma senza contesto certificato: ``` GET /api/chart/underlying/{idUnderlyings}?width=&height=&format= → ChartController.GenerateChartUnderlying(idUnderlyings, width, height, format) → IUnderlyingChartDataService.GetChartDataAsync(idUnderlyings) → SP cedlab_Chart_UnderlyingOnly @IDUnderlyings → SkiaChartRendererV2 (nuovo metodo per serie singola) → PNG/JPEG bytes → risposta inline, oppure wrap in PDF (riuso WrapPngInPdf esistente) ``` ## Componenti nuovi ### 1. Modello — `Models/UnderlyingChartModels.cs` ```csharp public class UnderlyingChartData { public int IDUnderlyings { get; set; } public string Nome { get; set; } = string.Empty; // ticker/nome per il titolo public List Points { get; set; } = new(); } public class UnderlyingChartPoint { public DateTime Date { get; set; } public decimal Px { get; set; } } ``` ### 2. Servizio dati — `Services/Implementations/UnderlyingChartDataService.cs` + `IUnderlyingChartDataService` - Chiama la SP `cedlab_Chart_UnderlyingOnly @IDUnderlyings`. - Popola `UnderlyingChartData` con i punti prezzo grezzi (non normalizzati a %, a differenza di v2 che divide per Strike/Nominal — qui non c'è contesto certificato per farlo). - Registrato in `Program.cs` come `AddScoped()`. ### 3. Stored Procedure — `docs/sql/cedlab_Chart_UnderlyingOnly.sql` ```sql CREATE OR ALTER PROCEDURE [dbo].[cedlab_Chart_UnderlyingOnly] @IDUnderlyings INT AS BEGIN SET NOCOUNT ON; DECLARE @AdjustedPrices BIT; SELECT @AdjustedPrices = ISNULL(AdjustedPrices, 0) FROM dbo.Underlyings WHERE IDUnderlyings = @IDUnderlyings AND deleted = 0 AND sospeso = 0; IF @AdjustedPrices IS NULL RETURN; -- non trovato / cancellato / sospeso ;WITH Ranked AS ( SELECT p.Px_date, CASE WHEN @AdjustedPrices = 0 THEN p.Px_close ELSE p.Px_closeadj END AS Px, ROW_NUMBER() OVER (ORDER BY p.Px_date DESC) AS rn FROM dbo.Prices p WHERE p.UnderlyingsID = @IDUnderlyings AND (p.Px_low IS NOT NULL OR p.Px_high IS NOT NULL OR p.Px_open IS NOT NULL OR p.Px_close IS NOT NULL) ) SELECT u.Name + ' (' + u.Ticker_bbg + ')' AS Nome, r.Px_date, r.Px FROM Ranked r CROSS JOIN (SELECT TOP 1 * FROM dbo.Underlyings WHERE IDUnderlyings = @IDUnderlyings) u WHERE r.rn <= 350 ORDER BY r.Px_date ASC; END GO ``` `Name` = nome sottostante, `Ticker_bbg` = ticker Bloomberg (entrambi in `dbo.Underlyings`). Titolo composto: `{Name} ({Ticker_bbg})`. ### 4. Renderer — nuovo metodo in `SkiaChartRendererV2` - Firma indicativa: `RenderSingleSeriesToPng(UnderlyingChartData data, int width, int height, bool jpeg)`. - Riusa griglia, assi X con intervalli mensili adattivi (12m/6m/3m/1m) e stile generale di V2. - Una sola linea (colore nero o `AccentBlue`, spessore 2px). - Titolo in alto = `data.Nome`. - **Nessuna legenda** (una sola serie non la richiede). - Nessuna barriera/strike/trigger disegnati (non esiste contesto certificato). ### 5. Controller — nuova route in `ChartController.cs` esistente ``` [HttpGet("underlying/{idUnderlyings}")] public async Task GenerateChartUnderlying( int idUnderlyings, [FromQuery] int width = 1100, [FromQuery] int height = 700, [FromQuery] string format = "png") ``` - `idUnderlyings <= 0` → `400 BadRequest`. - Nessun punto prezzo trovato → `404 NotFound` con messaggio esplicativo. - Formati supportati: `png` (default), `jpg`/`jpeg`, `pdf` (via `WrapPngInPdf` esistente, riuso invariato). - **Non supportato**: `jpgEnc` e `?save=true` — dipendono dall'alias-certificato (`rpt_CertificatesChartsAlias`) che non esiste per un sottostante puro senza contesto certificato. - Eccezioni → `500` loggato, stesso pattern try/catch di v1/v2. ## Error handling | Caso | Risposta | |------|----------| | `idUnderlyings <= 0` | 400 | | Sottostante non trovato / `deleted=1` / `sospeso=1` | 404 | | Nessun prezzo disponibile | 404 | | Eccezione runtime | 500 (loggato) | ## Cache Nessuna cache introdotta per questo endpoint — coerente con `/api/chart/{isin}` e `/api/chart/v2/{isin}`, che non cachano l'immagine (solo il PDF del report principale usa cache). Fuori scope. ## Fuori scope - Parametro `?save=true` (nessun path di salvataggio dedicato per sottostanti puri). - Formato `jpgEnc` (nessun alias-certificato applicabile). - Barriere/strike/trigger/scenari — richiedono contesto certificato, non disponibile con solo `IDUnderlyings`. - Normalizzazione a percentuale (richiede uno strike/nominal di riferimento, assente qui).