Files
AgenticCode/openspec/exports/nlxva-pricer-openspec.md
local d46b179221 feat: add porting-guide skill and NL XVA Pricer export bundle
Add /opsx:porting-guide skill that generates detailed human-readable
implementation guides as a companion to /opsx:export-spec. The AI spec
targets the agent; the porting guide targets the human developer with
design rationale, task-by-task notes, troubleshooting tables, and
rollback plans.

Generate the full NL XVA Pricer export bundle for CRC:
- nlxva-pricer-spec.md (AI-targeted portable spec)
- nlxva-pricer-openspec.md (OpenSpec proposal/design/tasks)
- nlxva-pricer-porting-guide.md (human implementation guide)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 00:45:47 +01:00

197 lines
10 KiB
Markdown

# Natural Language XVA Pricer — OpenSpec Bundle for CRC
## Setup Instructions
On the CRC target machine:
1. **Create the change directory:**
```
mkdir -p openspec/changes/nlxva-pricer
```
2. **Save these files** into `openspec/changes/nlxva-pricer/`:
- `proposal.md` (below)
- `design.md` (below)
- `tasks.md` (below)
- `.openspec.yaml` (below)
3. **Copy the portable spec** `nlxva-pricer-spec.md` somewhere accessible
(e.g., `openspec/changes/nlxva-pricer/reference-spec.md`)
4. **Copy the examples folder** to CRC.Server project root:
```
examples/extraction/
├── instruction-template.txt
└── few-shot/01/ 02/ 03/ (each with input.html + output.json)
```
5. **Run:** `/opsx:apply nlxva-pricer` — reference the portable spec when implementing each task
---
## .openspec.yaml
```yaml
name: nlxva-pricer
title: Natural Language XVA Pricer
status: active
created: 2026-04-07
specs:
- nlxva-pricer
```
---
## proposal.md
```markdown
# Add Natural Language XVA Pricer
## What
Add an AI-powered chat page to CRC that can:
1. Accept natural language queries about XVA pricing
2. Accept email uploads (.html) and autonomously extract structured trade data (TradeItem JSON)
3. Validate extracted data via external API tool calls (counterparty lookup, trade/currency validation)
4. Handle disambiguation (multiple counterparty matches) via conversational follow-up
5. Stream responses token-by-token with markdown rendering
## Why
Sales/CVA desk currently manually reads pricing request emails and re-types trade data.
This feature automates extraction with AI + tool-calling validation, reducing errors and time.
## Scope
- New page at /nlxva-pricer, new MudNavLink in existing NavMenu
- New controller with 2 endpoints (chat + extract), same SSE streaming contract
- Semantic Kernel integration with OpenAI-compatible proxy
- Few-shot prompting infrastructure (instruction template + 3 examples)
- External API clients for counterparty/trade/currency validation
- Client-side markdown rendering with XSS sanitization
- Email drag-and-drop and file picker upload
- System prompt and model settings tabs for iteration
## Non-goals
- No Fluxor state management (local component state is sufficient for this isolated page)
- No conversation persistence (in-memory only, lost on refresh)
- No auth changes (inherits CRC's existing auth)
```
---
## design.md
```markdown
# NL XVA Pricer — Design
## Architecture Decision: Semantic Kernel over raw HttpClient
**Why:** SK provides automatic function calling — the LLM can invoke validation tools
(lookup_counterparty, validate_trade, etc.) autonomously and reason about results.
With raw HttpClient we'd need to manually parse tool-call JSON, dispatch functions,
and feed results back. SK handles this loop automatically via FunctionChoiceBehavior.Auto().
## Architecture Decision: SSE streaming over WebSocket
**Why:** SSE is simpler (unidirectional server→client), works through HTTP proxies,
and matches the OpenAI API's native streaming format. WebSocket would add complexity
(connection management, reconnection logic) with no benefit for this use case.
The client only sends complete requests via POST; streaming is server→client only.
## Architecture Decision: Typed HttpClient per external API
**Why:** Each external API (counterparty, trade, currency) gets its own typed HttpClient
registered via AddHttpClient<T>(). This gives each client its own base URL, keeps
concerns separated, and makes testing easy (mock one client without affecting others).
IHttpClientFactory manages socket lifetimes and avoids exhaustion.
## Architecture Decision: Per-request plugin import (not global registration)
**Why:** ExtractionPlugin depends on scoped services (typed HttpClients).
Registering it globally on the Kernel at startup would capture stale references.
Instead, we resolve from DI per-request and import into the Kernel.
## Architecture Decision: FewShotService as singleton with clone-on-use
**Why:** Loading examples from disk is IO-bound and slow. Do it once at startup,
cache the assembled ChatHistory prefix, and clone per request. The clone is a
shallow copy (ChatHistory messages are immutable value objects) so it's fast.
## Architecture Decision: Markdown render caching
**Why:** During streaming, StateHasChanged() fires on every token. Without caching,
Markdig re-processes ALL prior messages each time. With a Dictionary<Message, string>
cache, only the actively streaming message re-renders. Completed messages serve cached HTML.
## Architecture Decision: Component-local state (no Fluxor)
**Why:** This is a self-contained page with no cross-page state sharing needs.
Adding Fluxor actions/reducers/effects would be overengineering. The conversation
list, streaming flag, extraction mode, and settings all live as private fields
in the Razor component. CRC's existing Fluxor infrastructure is untouched.
## Risk: CORS
CRC.Server may need its CORS policy updated to allow SSE streaming (Content-Type: text/event-stream)
to the CRC.Client origin. Verify existing policy covers this.
## Risk: Semantic Kernel version compatibility
CRC targets .NET 8.0. Ensure the SK NuGet package version is compatible with .NET 8.
Current stable SK packages support .NET 8+.
## Risk: Large file uploads
Email HTML files are read entirely into memory (max 10MB guard). For typical sales emails
(< 100KB) this is fine. The guard prevents accidental large file uploads.
```
---
## tasks.md
```markdown
# NL XVA Pricer — Implementation Tasks
## Phase 1: Foundation (Server)
- [ ] **T1: Add NuGet packages** — Add `Microsoft.SemanticKernel` to CRC.Server. Add `Markdig` 1.1.1 to CRC.Client (if not already present). Verify .NET 8 compatibility.
- [ ] **T2: Add shared DTOs** — Create in CRC.Shared: `NlxvaChatMessage`, `NlxvaChatRequest`, `NlxvaModelSettings`, `NlxvaExtractionRequest`, `NlxvaExtractionResult`, `TradeItem` (with `[JsonPropertyName]` snake_case), `NlxvaValidationResult`, `NlxvaCandidateMatch`. See Contracts section in reference spec for exact shapes.
- [ ] **T3: Add external API clients** — Create in CRC.Server/Services: `CounterpartyApiClient`, `TradeApiClient`, `CurrencyApiClient`. Each is a typed HttpClient with a single async method. Register via `AddHttpClient<T>()` in Program.cs/Startup.cs with base URLs from appsettings.json `ExternalApis` section.
- [ ] **T4: Add ExtractionPlugin** — Create in CRC.Server/Plugins: `ExtractionPlugin` with 4 `[KernelFunction]` methods: `lookup_counterparty`, `validate_trade`, `validate_currency`, `validate_schema`. Each returns serialized JSON string. Register as Scoped in DI. See Critical Patterns #5 and #6 in reference spec.
- [ ] **T5: Add FewShotService** — Create in CRC.Server/Services: `FewShotService` that loads instruction template + few-shot examples from disk. Caches ChatHistory prefix. Methods: `CloneWithEmail()`, `CloneWithEmailAndMessages()`. Register as Singleton. Copy examples/ folder to CRC.Server root.
- [ ] **T6: Register Semantic Kernel** — In CRC.Server DI: `AddOpenAIChatCompletion()` + `AddKernel()`. Base URL MUST include `/v1`. Config from `NlxvaPricer:*` keys in appsettings.json. See Critical Pattern #2.
- [ ] **T7: Add NlxvaPricerController** — Create controller with `POST /api/nlxva-pricer/chat` and `POST /api/nlxva-pricer/extract`. Both stream SSE. Chat endpoint: builds ChatHistory from messages + optional system prompt + model settings. Extract endpoint: uses FewShotService prefix. Both import ExtractionPlugin per-request and enable `FunctionChoiceBehavior.Auto()`. See Critical Pattern #6.
## Phase 2: Client
- [ ] **T8: Add MarkdownService** — Create in CRC.Client/Services: `MarkdownService` with Markdig pipeline + HTML sanitization (tag/attribute allowlist). Register as Singleton.
- [ ] **T9: Add NlxvaPricerApiClient** — Create in CRC.Client/Services: typed HttpClient with `SendChatStreamingAsync()` and `SendExtractionStreamingAsync()`. MUST use `SetBrowserResponseStreamingEnabled(true)` + `HttpCompletionOption.ResponseHeadersRead` + ReadLineAsync loop (NOT EndOfStream). See Critical Pattern #1 and #7.
- [ ] **T10: Add file-drop.js** — Create `CRC.Client/wwwroot/js/file-drop.js`. Registers drag/drop handlers on a CSS-selector target, reads file as text, calls back to .NET via DotNetObjectReference. Add `<script>` reference in index.html.
- [ ] **T11: Add NlxvaPricer.razor page** — Create page at `@page "/nlxva-pricer"`. MudTabs with 3 panels (Chat, System Prompt, Model Settings). Message list with user/assistant bubbles, streaming indicator, markdown rendering. Email upload via drag-drop + InputFile. Extraction mode tracking and routing. See reference spec for full component behavior. CSS: use `calc(100vh - 64px)` for CRC's regular AppBar (NOT 48px). See Critical Pattern #3.
- [ ] **T12: Add NlxvaPricer.razor.css** — Scoped styles: tab-container, chat-container, message-list, message bubbles, input area, markdown-body styles, drag-over feedback, extraction indicator. All use `::deep` where targeting MudBlazor child markup.
- [ ] **T13: Add NavMenu link** — Add `<MudNavLink Href="/nlxva-pricer" Icon="@Icons.Material.Filled.SmartToy">NL XVA Pricer</MudNavLink>` to CRC's existing NavMenu component.
## Phase 3: Verify
- [ ] **T14: Config** — Add `NlxvaPricer` and `ExternalApis` sections to CRC.Server appsettings.json. Ensure CORS allows CRC.Client origin for SSE responses.
- [ ] **T15: Smoke test** — Build both projects. Navigate to /nlxva-pricer. Send a chat message → verify streaming. Upload an example email HTML → verify extraction streams. Verify New Chat resets. Verify drag-drop visual feedback.
## Implementation Notes
- Reference the portable spec (`nlxva-pricer-spec.md`) for exact contracts, critical patterns, and CSS values
- Adapt all new code to CRC naming conventions (E-prefix enums, I-prefix interfaces, *Dto suffixes)
- Do NOT modify any existing CRC files except: NavMenu (add one link), index.html (add one script tag), Program.cs/Startup.cs (add DI registrations), appsettings.json (add config sections)
- If any task conflicts with existing CRC patterns, STOP and consult the user
```