Files
SmartReports/docs/superpowers/specs/2026-07-21-underlying-only-chart-design.md
SmartRootsSrl d15af22a2a docs: fix underlying name/ticker column in chart spec
Underlyings.Name + Underlyings.Ticker_bbg, composed as "{Name} (Ticker)".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 10:23:14 +02:00

5.3 KiB

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

public class UnderlyingChartData
{
    public int IDUnderlyings { get; set; }
    public string Nome { get; set; } = string.Empty; // ticker/nome per il titolo
    public List<UnderlyingChartPoint> 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<IUnderlyingChartDataService, UnderlyingChartDataService>().

3. Stored Procedure — docs/sql/cedlab_Chart_UnderlyingOnly.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<IActionResult> GenerateChartUnderlying(
    int idUnderlyings,
    [FromQuery] int width = 1100,
    [FromQuery] int height = 700,
    [FromQuery] string format = "png")
  • idUnderlyings <= 0400 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).