Spec for a new /api/chart/underlying/{idUnderlyings} endpoint that
renders a single price-history line for one underlying, independent
of any certificate context (no strike/barriers/legend).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
139 lines
5.4 KiB
Markdown
139 lines
5.4 KiB
Markdown
# 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<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`
|
|
|
|
```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
|
|
/* TODO: verificare colonna nome/ticker corretta in dbo.Underlyings prima di eseguire */
|
|
u.Ticker 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
|
|
```
|
|
|
|
> **Nota**: colonna `u.Ticker` è placeholder — il nome effettivo della colonna in `dbo.Underlyings` (Ticker / TickerBB / Nome / Description...) va confermato prima di applicare lo script al DB, come già fatto per `cedlab_Chart_AllSeriesV2.sql`.
|
|
|
|
### 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 <= 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).
|