234 lines
14 KiB
Markdown
234 lines
14 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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 = rosso `#CC0000` 2.5px, WorstOf = blu `#1565C0` 2px, altri UL = grigi 1px
|
|
|
|
**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` |
|
|
|
|
**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
|
|
|
|
## 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 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 |
|