How we built the Migration Assessment demo
Given an Oracle EBS footprint — version, modules, data volume, target SAP edition — produce a complexity score, a conservative timeline, named risks, and a recommended approach. This is the most defensive route on the site, and deliberately so: it's reasoning about a client's actual system, not extracting fields from a document they uploaded.
Nothing but enums reaches the prompt
Every input — EBS version, modules, data volume, target edition — is validated against a fixed allowlist before anything is assembled into a prompt:
const VERSIONS = ["R12.1", "R12.2"] as const;
const MODULES = ["FI", "AP", "AR", "GL", "FA", "INV", "PO"] as const;
const VOLUMES = ["1-10GB", "10-100GB", "100GB-1TB", "1TB+"] as const;
const TARGETS = ["S/4HANA Cloud", "S/4HANA Private Cloud",
"S/4HANA On-Premise"] as const;There's no free-text field anywhere in this form. A public demo endpoint that accepts arbitrary text and feeds it to a system prompt is a prompt-injection surface; a form that only accepts membership in a known set removes that surface entirely rather than trying to sanitize around it.
The prompt reasons about the specific selection
The system prompt frames Claude as a senior migration architect and explicitly instructs it to reference the modules actually selected — open AP/AR reconciliation, FA depreciation history conversion, GL chart-of-accounts redesign — rather than returning generic migration advice that would apply to any input. One conditional line does real work: if the selected modules include FI, GL, or AP, the prompt requires the response to explicitly address SOX compliance and audit-trail preservation. Financial-module migrations carry that compliance burden; inventory-only migrations don't, and the prompt reflects that instead of bolting a generic disclaimer onto every response.
The response is validated, not trusted
Structured output from a model is still a string until proven otherwise. Before anything reaches the client:
- ✓Code fences are stripped and the outermost JSON object is extracted defensively — models sometimes wrap output in commentary even under instruction not to.
- ✓complexityScore is clamped into the 1–10 range with Math.min/Math.max, and rejected outright if it isn't a finite number.
- ✓recommendedApproach.approach is lowercased and checked against exactly three allowed values (greenfield / brownfield / selective) — anything else fails the request rather than silently passing through.
- ✓topRisks and nextSteps are truncated to 3 items regardless of how many the model returns, keeping the response shape predictable for the UI.
Why Sonnet, and why 5 requests per 10 minutes
This is the one demo on the site running Claude Sonnet 5 with extended thinking explicitly disabled — the reasoning load (weighing module mix against data volume against target edition to produce a specific, defensible recommendation) is real, but this use case doesn't need multi-step deliberation to get there. It also gets the tightest rate limit on the site — 5 requests per 10 minutes per IP, against 30 requests per day for the extraction demos — because a reasoning-model call costs meaningfully more than a Haiku extraction call, and a public demo endpoint has to assume it will be hit by more than curious visitors.