Files
SmartReports/CLAUDE.md
2026-05-28 14:41:22 +02:00

15 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project

SmartReports / CertReports.Syncfusion — ASP.NET Core 8 REST API che genera PDF di certificati finanziari strutturati, sostituendo il vecchio progetto WebForms DevExpress. Usa Syncfusion PDF (Community License) e SkiaSharp per i grafici.

Progetto unico nella solution: CertReports.Syncfusion/.

Build & Run

# Restore + build
dotnet restore
dotnet build

# Run locale → https://localhost:{porta}/api/report/by-isin/{ISIN}
dotnet run --project CertReports.Syncfusion

# Docker
docker-compose up --build
# → http://localhost:5080/api/report/by-isin/{ISIN}

Non esistono test automatici al momento.

Architettura

Il flusso di generazione è orchestrato da ReportOrchestrator, che sceglie il template in base al campo Stato del certificato:

HTTP (ISIN) → ReportController → ReportOrchestrator
    ├── CertificateDataService  (4 SP → anagrafica, sottostanti, eventi, scenario)
    │
    ├── [Stato == "Quotazione"] — report certificati in quotazione (4 sezioni)
    │   ├── AnagraficaSectionRenderer       → PdfDocument (Sezione 1)
    │   ├── EventiSectionRenderer           → PdfDocument (Sezione 2)
    │   ├── ScenarioSectionRenderer         → PdfDocument (Sezione 3, opzionale)
    │   └── ChartSectionRenderer            → PdfDocument (Sezione 4)
    │
    ├── [Stato != "Quotazione"] — report certificati non in quotazione (3 sezioni)
    │   ├── ExpiredAnagraficaSectionRenderer → PdfDocument (Sezione 1)
    │   ├── EventiSectionRenderer            → PdfDocument (Sezione 2, colonne adattate)
    │   └── ChartSectionRenderer             → PdfDocument (Sezione 3)
    │
    ├── ChartDataService       (3 SP → dati grafico)
    ├── SkiaChartRenderer      → PNG in memoria
    └── PdfMergerService       → byte[] → Response

Stato certificato: il campo Stato viene dalla SP rpt_Master_CFT_ISIN. Valori: "Quotazione" (report attivo), "Scaduto" / "Rimborsato" / "Revocato" (report expired). Se Stato è vuoto, viene usato il flusso attivo come fallback.

Aggiungere una nuova sezione PDF al flusso attivo: implementare IPdfSectionRenderer, impostare SectionName e Order, registrare in Program.cs come AddScoped<IPdfSectionRenderer, NuovaSezione>(). L'orchestratore la include automaticamente ordinandola per Order.

ExpiredAnagraficaSectionRenderer: registrata come AddScoped<ExpiredAnagraficaSectionRenderer>() (senza interfaccia) e iniettata direttamente nell'orchestratore — non partecipa al ciclo IEnumerable<IPdfSectionRenderer>.

Tema: tutto in PdfTheme.cs — colori, font, layout, brush/pen. Modificare lì per aggiornare tutti i renderer.

Palette colori principale (stile "Ibrido elegante"):

  • AccentBlue #1565C0 — titoli, header tabelle, valori chiave
  • NegativeRed #CC0000 — valori negativi (performance, rendimento)
  • PositiveGreen #2E7D32 — valori positivi (performance)
  • Brush corrispondenti: AccentBlueBrush, NegativeRedBrush, PositiveGreenBrush, TableHeaderBrush (alias di AccentBlueBrush)

Configurazione chiave

  • appsettings.json: connection string (CertDb), Syncfusion license key, CryptoSettings passphrase, cache TTL
  • appsettings.Development.json: non deve contenere ConnectionStrings, altrimenti sovrascrive appsettings.json
  • Connection string: usare Data Source + Initial Catalog + Encrypt=False; per Microsoft.Data.SqlClient v5
  • Se la connessione fallisce via Named Pipes, aggiungere prefisso tcp: all'indirizzo IP

Gotcha Syncfusion v33 & SkiaSharp

Problema Soluzione
Color(r,g,b) rimosso Color.FromArgb(255, r, g, b)
Syncfusion.Drawing.Net.Core non esiste Inglobato in Syncfusion.Pdf.Net.Core, rimuovere dal .csproj
PdfStandardFont non è IDisposable Non usare using sulla dichiarazione
grid.Headers non iterabile con foreach for (int r = 0; r < grid.Headers.Count; r++)
PdfTextWebLink non trovato Aggiungere using Syncfusion.Pdf.Interactive;
RectangleF non supporta named arguments new RectangleF(x, y, w, h) — no startY: o simili
grid.Draw() restituisce void Stimare altezza con (rows + 1) * RowHeight
PdfMergerService: stream chiuso prima del Save() Tenere tutti gli stream aperti fino al salvataggio finale, poi cleanup in finally
SkiaSharp DrawText obsoleto SKFont + canvas.DrawText(text, x, y, SKTextAlign, font, paint)
Namespace conflict CertReports.Syncfusion.Pdf Aggiungere using Syncfusion.Pdf; esplicito nel file
SkiaSharp SKFont NON è IDisposable Non usare using su SKFont — solo SKPaint, SKPath, SKSurface, SKImage, SKData sono IDisposable
SqlDataReader.GetDecimal su colonna float SQL Usare Convert.ToDecimal(r.GetValue(ord)) — le SP cedlab_ possono restituire float (es. PriceWorst da subquery su Prices.Px_close)

Grafico V2 (/api/chart/v2/{isin})

File: SkiaChartRendererV2.cs, ChartDataServiceV2.cs, ChartModelsV2.cs

SP utilizzate (su FirstSolutionDB):

  • cedlab_Chart_UL1 @isin VARCHAR(15) — metadata sottostanti, già esistente; restituisce tutti i campi V2 (IsWorstOf, PriceWorst, NomeCFT, TriggerAutocallPerc, ecc.). Ordinata IsWorstOf DESC → prima riga = worst-of.
  • cedlab_Chart_AllSeriesV2 @isin VARCHAR(15) — tutte le serie (CTF + UL) in un unico round-trip. Da creare: script in docs/sql/cedlab_Chart_AllSeriesV2.sql.

Schema DB rilevante (per le SP chart):

  • dbo.Prices: CertificatesID, UnderlyingsID, Px_date, PX_LAST_EOD, Px_close, Px_closeadj, Px_low, Px_high, Px_open
  • dbo.Underlyings: IDUnderlyings, AdjustedPrices, deleted, sospeso
  • dbo.CertificatesUnderlyings: CertificatesID, UnderlyingsID, Strike, deleted — lo Strike per UL viene da qui, non da Underlyings
  • dbo.Certificates: IDCertificates, ISIN, Nominal_amount, Direction, Domino, BasketTypeID
  • StartDate nel grafico = MIN(Prices.Px_date) WHERE CertificatesID = @IDCertificates (aggregato, non colonna)

Colori V2: CTF = nero #000000 2.5px, WorstOf = rosso #CC0000 2px, altri UL = palette vivace (teal/amber/viola/celeste/indigo/arancio) 1px, Barriera Capitale = marrone #715548

Formati ?format=:

Valore Output Note
(default) / png PNG inline filename chart_v2_{isin}.png
jpg / jpeg JPEG inline filename chart_v2_{isin}.jpg
jpgEnc JPEG inline filename {AliasID}.jpg da SP rpt_CertificatesChartsAlias @isin; fallback a chart_v2_{isin}.jpg se SP non trova risultati
pdf PDF landscape inline filename chart_v2_{isin}.pdf

Parametro ?save=true (default false): salva il JPEG su disco in aggiunta alla risposta HTTP inline. Solo per format=jpg/jpeg/jpgEnc; ignorato per png e pdf.

format + save=true Percorso di salvataggio
jpg / jpeg ChartSettings:SavePath\{isin}.jpg + ChartSettings:SavePathEnc\{AliasID}.jpg (se AliasID trovato)
jpgEnc solo ChartSettings:SavePathEnc\{AliasID}.jpg

Nota: format=jpg/jpeg&save=true esegue un doppio salvataggio in un'unica chiamata API, rendendo superflua la chiamata separata con format=jpgEnc. Lo script esterno (CEDLAB_ChartsAsync) può usare solo "jpeg" ed eliminare il task jpgEnc dedicato.

Percorsi configurati in appsettings.jsonChartSettings:SavePath e ChartSettings:SavePathEnc. Se il salvataggio fallisce viene loggato come Warning ma la risposta HTTP viene restituita comunque.

Note renderer: CTF non mostrato se NumPrezziCFT < 30 (mostra solo avviso testo); asse X usa intervalli mensili adattivi (12m/6m/3m/1m in base al range).

Database

Tutte le stored procedure sono su FirstSolutionDB. I dati tornano già formattati come stringhe dalle SP (es. FORMAT(value,'P2','it-IT')): i modelli C# usano string per questi campi. Solo NominalValue, PrezzoEmissione, CpnPagati, CpnDaPagare, CpnInMemoria sono decimal? perché servono come valori numerici nel rendering.

API Endpoints

  • GET /api/report?p={isin_cifrato} / ?alias={id} / /by-isin/{isin} — PDF inline
  • GET /api/report/download?p={...} / ?alias={...} — PDF come allegato
  • Tutti gli endpoint report accettano ?branding=true (default false) per abilitare il footer "Powered by Smart Roots"
  • Tutti gli endpoint report accettano ?dividend=true (default false) per aggiungere la pagina Sottostanti+Dividendi
  • Tutti gli endpoint report accettano ?natixis=true (default false) per mostrare info.Nome nel box Tipologia invece di info.Categoria
  • GET /api/chart/{isin}[?format=png|pdf&width=&height=] — grafico standalone v1
  • GET /api/chart/v2/{isin}[?format=png|jpg|jpeg|jpgEnc|pdf&width=&height=] — grafico standalone v2 (titolo, colori CTF/WorstOf, label linee, legenda in basso)
  • GET /health — health check DB + chart service

Il parametro ?branding=true aggiunge al footer di ogni pagina del report:

  • Sinistra: "Powered by " + hyperlink PDF cliccabile "Smart Roots"https://www.smart-roots.net
  • Destra: numero pagina

Con branding=false (default): solo numero pagina centrato.

La cache usa chiavi separate: {isin}:branded vs {isin}. Il flag è propagato come CertificateReportData.ShowBranding e passato a PdfTheme.DrawFooter(g, pageWidth, pageHeight, pageNumber, showBranding). L'hyperlink usa PdfTextWebLink (richiede using Syncfusion.Pdf.Interactive;).

Parametri query report

Tutti i flag booleani hanno default false e seguono lo stesso pattern di propagazione: ReportControllerIReportOrchestrator.GenerateReportAsyncCertificateReportData → renderer.

Parametro Flag su CertificateReportData Effetto
?branding=true ShowBranding Footer con "Powered by Smart Roots" + link
?dividend=true ShowDividend Aggiunge pagina Sottostanti+Dividendi; nasconde tabella Sottostanti dalla Sezione 1
?natixis=true ShowNatixis Box Tipologia mostra info.Nome invece di info.Categoria

Cache key pattern (Approccio A — suffissi concatenati):

  • Suffissi: :branded, :dividend, :natixis — aggiunti in questo ordine se il flag è true
  • Chiave base: {isin}[suffissi]
  • Chiave expired: {isin}:expired[suffissi]
  • Esempio: ?branding=true&natixis=true → chiave {isin}:branded:natixis

Sezione 1 — AnagraficaSectionRenderer

Struttura a 3 sezioni verticali in una singola pagina A4:

Sezione Contenuto
Titolo "Scheda Prodotto {ISIN}" blu + Bid/Ask a destra + linea separatrice blu
A — Caratteristiche Prodotto Tabella emittente a sinistra (ISIN, Mercato, Valuta, Date, Autocall) + tabella cedole a destra (CpnPagati/DaPagare/InMemoria, RendimentoAttuale)
B — Analisi 9 KV a sinistra + 9 KV a destra. Leva/Memoria/FattoreAirbag/TriggerOneStar mostrati come "—" anche se vuoti
C — Sottostanti PdfGrid 9 colonne: Nome, Strike, Last, % Perf., Barr.K, Buffer K, Trig.CPN, Buf.CPN, Trig.AC (Dist.AC rimossa)

Se la tabella Sottostanti non entra (y > PageH-80pt), si crea una nuova pagina. Il footer viene disegnato su ogni pagina con PdfTheme.DrawFooter.

context-mode — MANDATORY routing rules

You have context-mode MCP tools available. These rules are NOT optional — they protect your context window from flooding. A single unrouted command can dump 56 KB into context and waste the entire session.

BLOCKED commands — do NOT attempt these

curl / wget — BLOCKED

Any Bash command containing curl or wget is intercepted and replaced with an error message. Do NOT retry. Instead use:

  • ctx_fetch_and_index(url, source) to fetch and index web pages
  • ctx_execute(language: "javascript", code: "const r = await fetch(...)") to run HTTP calls in sandbox

Inline HTTP — BLOCKED

Any Bash command containing fetch('http, requests.get(, requests.post(, http.get(, or http.request( is intercepted and replaced with an error message. Do NOT retry with Bash. Instead use:

  • ctx_execute(language, code) to run HTTP calls in sandbox — only stdout enters context

WebFetch — BLOCKED

WebFetch calls are denied entirely. The URL is extracted and you are told to use ctx_fetch_and_index instead. Instead use:

  • ctx_fetch_and_index(url, source) then ctx_search(queries) to query the indexed content

REDIRECTED tools — use sandbox equivalents

Bash (>20 lines output)

Bash is ONLY for: git, mkdir, rm, mv, cd, ls, npm install, pip install, and other short-output commands. For everything else, use:

  • ctx_batch_execute(commands, queries) — run multiple commands + search in ONE call
  • ctx_execute(language: "shell", code: "...") — run in sandbox, only stdout enters context

Read (for analysis)

If you are reading a file to Edit it → Read is correct (Edit needs content in context). If you are reading to analyze, explore, or summarize → use ctx_execute_file(path, language, code) instead. Only your printed summary enters context. The raw file content stays in the sandbox.

Grep (large results)

Grep results can flood context. Use ctx_execute(language: "shell", code: "grep ...") to run searches in sandbox. Only your printed summary enters context.

Tool selection hierarchy

  1. GATHER: ctx_batch_execute(commands, queries) — Primary tool. Runs all commands, auto-indexes output, returns search results. ONE call replaces 30+ individual calls.
  2. FOLLOW-UP: ctx_search(queries: ["q1", "q2", ...]) — Query indexed content. Pass ALL questions as array in ONE call.
  3. PROCESSING: ctx_execute(language, code) | ctx_execute_file(path, language, code) — Sandbox execution. Only stdout enters context.
  4. WEB: ctx_fetch_and_index(url, source) then ctx_search(queries) — Fetch, chunk, index, query. Raw HTML never enters context.
  5. INDEX: ctx_index(content, source) — Store content in FTS5 knowledge base for later search.

Subagent routing

When spawning subagents (Agent/Task tool), the routing block is automatically injected into their prompt. Bash-type subagents are upgraded to general-purpose so they have access to MCP tools. You do NOT need to manually instruct subagents about context-mode.

Output constraints

  • Keep responses under 500 words.
  • Write artifacts (code, configs, PRDs) to FILES — never return them as inline text. Return only: file path + 1-line description.
  • When indexing content, use descriptive source labels so others can ctx_search(source: "label") later.

ctx commands

Command Action
ctx stats Call the ctx_stats MCP tool and display the full output verbatim
ctx doctor Call the ctx_doctor MCP tool, run the returned shell command, display as checklist
ctx upgrade Call the ctx_upgrade MCP tool, run the returned shell command, display as checklist