14 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 chiaveNegativeRed#CC0000— valori negativi (performance, rendimento)PositiveGreen#2E7D32— valori positivi (performance)- Brush corrispondenti:
AccentBlueBrush,NegativeRedBrush,PositiveGreenBrush,TableHeaderBrush(alias diAccentBlueBrush)
Configurazione chiave
appsettings.json: connection string (CertDb), Syncfusion license key, CryptoSettings passphrase, cache TTLappsettings.Development.json: non deve contenereConnectionStrings, altrimenti sovrascriveappsettings.json- Connection string: usare
Data Source+Initial Catalog+Encrypt=False;perMicrosoft.Data.SqlClientv5 - 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.). OrdinataIsWorstOf DESC→ prima riga = worst-of.cedlab_Chart_AllSeriesV2 @isin VARCHAR(15)— tutte le serie (CTF + UL) in un unico round-trip. Da creare: script indocs/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_opendbo.Underlyings:IDUnderlyings,AdjustedPrices,deleted,sospesodbo.CertificatesUnderlyings:CertificatesID,UnderlyingsID,Strike,deleted— lo Strike per UL viene da qui, non daUnderlyingsdbo.Certificates:IDCertificates,ISIN,Nominal_amount,Direction,Domino,BasketTypeIDStartDatenel grafico =MIN(Prices.Px_date) WHERE CertificatesID = @IDCertificates(aggregato, non colonna)
Colori V2: CTF = rosso #CC0000 2.5px, WorstOf = blu #1565C0 2px, altri UL = grigi 1px
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 inlineGET /api/report/download?p={...}/?alias={...}— PDF come allegato- Tutti gli endpoint report accettano
?branding=true(defaultfalse) per abilitare il footer "Powered by Smart Roots" - Tutti gli endpoint report accettano
?dividend=true(defaultfalse) per aggiungere la pagina Sottostanti+Dividendi - Tutti gli endpoint report accettano
?natixis=true(defaultfalse) per mostrareinfo.Nomenel box Tipologia invece diinfo.Categoria GET /api/chart/{isin}[?format=png|pdf&width=&height=]— grafico standalone v1GET /api/chart/v2/{isin}[?format=png|pdf&width=&height=]— grafico standalone v2 (titolo, colori CTF/WorstOf, label linee, legenda in basso)GET /health— health check DB + chart service
Footer branding
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: ReportController → IReportOrchestrator.GenerateReportAsync → CertificateReportData → 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 pagesctx_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)thenctx_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 callctx_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
- GATHER:
ctx_batch_execute(commands, queries)— Primary tool. Runs all commands, auto-indexes output, returns search results. ONE call replaces 30+ individual calls. - FOLLOW-UP:
ctx_search(queries: ["q1", "q2", ...])— Query indexed content. Pass ALL questions as array in ONE call. - PROCESSING:
ctx_execute(language, code)|ctx_execute_file(path, language, code)— Sandbox execution. Only stdout enters context. - WEB:
ctx_fetch_and_index(url, source)thenctx_search(queries)— Fetch, chunk, index, query. Raw HTML never enters context. - 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 |