The problem this solves

Oracle EPM Planning stores the financial truth for a mid-to-large enterprise β€” income statement, balance sheet, cash flow β€” updated on a daily or weekly cycle by planning and close operations. The data is correct. The problem is retrieval.

A finance analyst who wants "Berlin Mitte's Q3 FY26 net revenue vs Plan" opens Oracle SmartView, authenticates, navigates to the right cube, sets five POV dimensions, builds or finds a row set, pulls the data, and copies it to a deck. On a good day that is 6 minutes. On a day with a VPN problem or an expired token it is 25 minutes and two IT tickets.

The NLQ layer changes the retrieval to a typed sentence. The analyst types "Show me Berlin Mitte Q3 FY26 revenue vs plan" and sees the table in under 4 seconds. The insight work β€” the comparison, the narrative, the flag to the CFO β€” starts immediately.

Audience

FP&A analysts, finance managers, and CFOs who run Oracle EPM Planning and spend meaningful time each period on data retrieval rather than analysis. Revenue management teams tracking actuals vs plan by entity and period. EPM architects evaluating whether to add an NLQ layer to an existing PBCS or EPBCS deployment.

Natural language financial queries

Input is a plain-text string entered by the finance user. No syntax to learn. The parser resolves the query against five EPM dimension axes:

DimensionWhat the parser recognisesExample
Statement typeincome statement, IS, P&L, profit and loss, balance sheet, BS, cash flow, CF"income statement"
Entity44 stores/entities, 4 region rollups, digital + franchise channels"Berlin Mitte", "APAC", "Toronto"
Scenarioplan, actual, budget, forecast, LE, latest estimate"vs plan", "actual"
YearFY22–FY26, "fiscal 2026", "next year""FY26", "2026"
PeriodQ1–Q4, H1/H2, months, YTD+period, full year, annual"Q3", "H2 FY26", "YTD Q3"

Minimum viable query: statement type + entity. All other dimensions default: Scenario β†’ Plan, Year β†’ FY26, Period β†’ all four quarters.

A structured financial table with BvA variance

The output is a rendered financial table in the browser:

  • Row dimension: Account members grouped by statement section (Revenue, COGS, Gross Profit, OpEx, EBIT…)
  • Column dimension: Period(s) Γ— Scenario(s), up to 8 columns
  • BvA column: Actual βˆ’ Plan, colour-coded green/red with ↑↓ directional arrows
  • Chart: bar, line, doughnut, radar, waterfall, stacked, ranking, or combo β€” selected by LLM or overridden by user

Secondary outputs

  • Downloadable Excel file with Oracle SmartView HsGetValue formulas pre-populated and a Connection Guide sheet
  • JSON export of the raw data object (for pipeline integration)

In your EPM workflow

This use case lives between data entry / close operations (which write to EPM) and management reporting (which reads from EPM). It does not replace Oracle Analytics Cloud for enterprise BI β€” it replaces the ad-hoc retrieval that currently falls to SmartView or manual PBCS grid navigation.

Recommended integration points

  • CFO review prep: Controller types 5 queries in 2 minutes to populate a review deck, replacing 30 minutes of SmartView grids
  • Variance triage: Business partner types "Show Toronto actual vs plan Q2 FY26" the moment a variance alert fires
  • Month-end close: Accountant verifies a line item against plan before posting a journal entry
  • Board pack prep: IR team generates 44 store-level IS tables in one session without touching SmartView

The numbers

$0.0003
Cost per query
10,500+
EPM members
55
Account members
44
Entity members
< 4s
p95 latency
8
Chart types
ComponentChoice
LLMDeepSeek V3 β€” $0.14/1M input Β· $0.28/1M output tokens
Data layer10,500+ pre-loaded EPM member values (demo); EPM REST API in production
RuntimeCloudflare Pages Functions β€” edge, zero cold start
Auth30-day cookie gate, D1-backed access log with name/email/company/role
Offline fallbackRule-based parser β€” no LLM cost, fixed-format queries only

The honest checklist

  • βœ“You have Oracle EPM Planning (PBCS, EPBCS, or Planning Modules) in production with a financial data model
  • βœ“Your finance team does more than 20 ad-hoc data retrievals per person per week
  • βœ“You can expose a read-only EPM REST API endpoint β€” or you want to start with the demo data layer first
  • βœ“Your users are comfortable with a browser UI (no SmartView client install required)
  • βœ—You need real-time writeback or plan input β€” NLQ is read-only by design
  • βœ—You have fewer than 5 EPM data consumers β€” the LLM overhead isn't justified at that scale
  • βœ—You need row-level security per user without building an EPM REST API auth layer

Try it now

The live demo runs on pre-loaded EPM demo data with a real DeepSeek LLM call for every query.

Launch FP&A NLQ demo β†’ Start a Lab engagement

Sample queries to try

  • "Show income statement for Berlin Mitte FY26"
  • "Toronto Q3 actual vs plan"
  • "APAC balance sheet FY25"
  • "Cash flow forecast FY26 waterfall chart"
HOW WE BUILT IT

The value calculation

A 6-minute retrieval task done 20 times per analyst per week = 2 hours/analyst/week. For a 10-person FP&A team that is 20 hours/week β€” half an FTE per period spent on getting data rather than using it.

At a loaded cost of $120K/year for a mid-level FP&A analyst, the retrieval overhead costs $60K/year for that team. The NLQ layer at DeepSeek pricing costs ~$108/year at 100 queries per day β€” a 555:1 cost ratio in favour of the AI layer.

The secondary business case is accuracy. Manual SmartView retrieval introduces two error modes: wrong POV selection (wrong entity or period) and copy-paste errors into decks. NLQ-generated output is deterministic for a given input β€” the same query always returns the same table. The only remaining error mode is query ambiguity, which surfaces as an explicit error state rather than a silent wrong number.

Three-layer pipeline

User query (plain text)
1
NLQ Parser
DeepSeek V3
{ statementType, entity, scenario, year, periods[], chartType }
  • Input: system prompt + user text · Output: structured JSON
  • Runtime: Cloudflare Pages Function (edge)
2
Data Resolver
epm-data.js
  • Input: parsed JSON from Layer 1
  • Lookup: DIMENSION_MEMBERS[entity][scenario][year][period]
  • Output: Account × Period value matrix
  • Demo: 10,500+ pre-loaded values · Prod: EPM REST API (same JSON contract)
3
Renderer
finance.html
  • IS / BS / CF tabbed table with BvA
  • Chart.js (8 chart types)
  • Excel download (SheetJS + HsGetValue)

The key design decision: Layer 2 defines a clean contract that works identically whether the data comes from the demo corpus or a live EPM REST API call. Switching to production means replacing one function β€” the data resolver β€” without touching the parser or renderer.

App UI β€” Component breakdown

ComponentBehaviour
Query barFull-width text input, auto-focused on load, Enter to submit, 500-char max
Sample chips6 click-to-run query chips below the input; click populates and fires immediately
LLM status tagsDATA: DEMO (orange) Β· LLM: LIVE β†’ LLM: Processing… (indigo) β†’ LLM: LIVE
Result panelAppears after first query; tabs: IS / BS / CF; persists across queries
TableSticky header, alternating row shading, BvA column with ↑↓ colour coding
Chart panelChart.js canvas, 8 chart type buttons (bar/line/doughnut/radar/waterfall/stacked/ranking/combo)
Action barDownload .xlsx with HsGetValue formulas Β· Copy raw JSON to clipboard

UI state machine

Idle β†’ Loading (LLM call in flight, submit disabled, spinner visible) β†’ Result (table + chart rendered) β†’ Error (scoped message + 4 sample query links). Error state never shows a raw stack trace β€” only a user-readable message pointing to valid query patterns.

EPM dimension corpus

Account hierarchy β€” 55 members

StatementParent membersLeaf members
Income StatementNet Revenue, Total COGS, Gross Profit, Total OpEx, EBIT, EBT, Net IncomeStore Revenue, Digital Revenue, Wholesale & Franchise Revenue, Merchandise Cost, Buying & Distribution, Store Labor, Store Occupancy, Marketing & Digital Ads, Technology & G&A, D&A, Interest Expense, Tax Provision
Balance SheetTotal Assets, Total Liabilities, Total EquityCash, AR, Merchandise Inventory, Prepaid, PP&E & Store Fixtures, Right-of-Use Assets, Intangibles, AP, Accrued Liab, Deferred Revenue (Gift Cards), LT Debt, LT Lease Liab, Common Stock, Retained Earnings
Cash FlowOperating CF, Investing CF, Financing CFNet Income (CF), D&A (CF), Inventory & WC Changes, Capex β€” New & Renovated Stores, Acquisitions, Debt Issuance, Dividends & Buybacks, Net Change in Cash

Other dimensions

  • Store/Entity (44): Total Company β†’ North America (12 stores incl. NYC Flagship, LA Beverly Hills, Toronto) Β· EMEA (10 stores incl. London Oxford St, Berlin Mitte, Dubai Mall) Β· APAC (9 stores incl. Tokyo Shibuya, Shanghai Plaza, Mumbai BKC) Β· LAD (5 stores incl. Sao Paulo, Buenos Aires) Β· Digital (5: US/EMEA/APAC/LAD Digital + Global Marketplace) Β· Corporate (3: HQ, Distribution, Franchise)
  • Scenario (6): Plan, Actual, Forecast, Budget, Latest Estimate, Prior Year Actual
  • Year (5): FY23, FY24, FY25, FY26, FY27
  • Period (4): Q1, Q2, Q3, Q4 β€” plus synthetic full-year aggregation, seasonally weighted for Q4 holiday peak

System prompt structure

The system prompt has four parts:

  1. Role definition: "You are an Oracle EPM Planning NLQ parser. Return ONLY valid JSON…"
  2. Output schema: Strict JSON definition with field names, types, and enum values for every dimension
  3. Dimension enums: Exhaustive lists of valid entity names, scenarios, years, periods, statement types, and chart types β€” prevents hallucination outside the known corpus
  4. Few-shot examples: 12 query β†’ JSON pairs covering IS/BS/CF, multi-period (H2, YTD), multi-scenario (plan vs actual), region rollup, and chart type selection

Guard clause

If the query cannot be mapped to an EPM query, the LLM returns {"error": "out_of_scope"}. The app renders a specific error message with sample queries β€” it never tries to answer a non-EPM question with EPM data.

// Truncated system prompt excerpt
You are an Oracle EPM Planning NLQ parser.
Return ONLY this JSON (no markdown, no prose):
{
  "statementType": "IS" | "BS" | "CF",
  "entity": <one of: NYC Flagship, LA Beverly Hills, Toronto,
              London Oxford St, Berlin Mitte, Dubai Mall,
              Tokyo Shibuya, Shanghai Plaza, Mumbai BKC,
              Sao Paulo, Buenos Aires, US Digital, ... (44 total)>,
  "scenario": "Plan" | "Actual" | "Forecast" | "Budget" | "LE",
  "year": "FY22" | "FY23" | "FY24" | "FY25" | "FY26",
  "periods": [ "Q1" | "Q2" | "Q3" | "Q4" ],
  "chartType": "bar" | "line" | "doughnut" | "radar"
               | "waterfall" | "stacked" | "ranking" | "combo"
}

How we measure accuracy

Eval dimensionWhat it testsTarget accuracy
Statement typeIS / BS / CF classification from free textβ‰₯ 98%
Entity nameFuzzy match: "Berlin" β†’ Berlin Mitte, "NYC" β†’ NYC Flagship, "APAC region" β†’ APACβ‰₯ 96%
Period parsing"Q3", "H2" β†’ [Q3,Q4], "YTD Q3" β†’ [Q1,Q2,Q3], "full year" β†’ [Q1–Q4]β‰₯ 94%
Year parsing"FY26", "2026", "this year", "next year" β†’ correct FY enumβ‰₯ 97%
Scenario parsing"plan", "budget", "actual", "forecast", "LE" β†’ correct enumβ‰₯ 99%
Out-of-scope detectionNon-EPM queries return error, not a hallucinated EPM responseβ‰₯ 99%

Eval set: 120 hand-written query β†’ expected JSON pairs covering all dimension combinations, edge cases (two scenarios in one query, implicit entity via region, month names), and out-of-scope adversarial inputs.

Cost per query

ComponentDetailCost
LLM input tokens~900 tokens (system prompt ~700 + user query ~200)$0.000126
LLM output tokens~150 tokens (structured JSON response)$0.000042
Cloudflare Pages FunctionFree tier: 100K req/day included$0.000000
D1 databaseFree tier: 5M rows/day included$0.000000
Total per query~$0.000168
With 2Γ— safety marginAccounting for retries and longer queries~$0.0003

DeepSeek V3 pricing as of Aug 2026: $0.14 per 1M input tokens, $0.28 per 1M output tokens. Prices subject to change β€” verify at platform.deepseek.com/docs/pricing.

Cost model β€” At-scale projections

ScaleDaily queriesDaily costMonthly costvs SmartView licences
Pilot (5 users)50$0.015$0.45$0.45 vs $3,000+
Team (20 users)200$0.06$1.80$1.80 vs $12,000+
Department (100 users)1,000$0.30$9$9 vs $60,000+
Enterprise (1,000 users)10,000$3$90$90 vs $600,000+

SmartView licence estimates are illustrative Oracle list prices. NLQ does not replace SmartView for power users who need writeback, zero-suppression, or complex member selection β€” it adds a fast-access retrieval layer for read-heavy users who currently misuse SmartView as a lookup tool.

Key files and their roles

FileRole
functions/api/nlq-query.jsCloudflare Pages Function. Receives POST with query text, calls DeepSeek, validates JSON schema, returns parsed dimensions + data.
epm-nlq-src/assets/epm-data.jsThe 10,500+ member value corpus. Exports DIMENSION_MEMBERS object: [entity][scenario][year][period][account] β†’ number.
epm-nlq-src/pages/finance.htmlSingle-page UI. runNLQ() orchestrates the LLM call, table render, chart render, and Excel download.
functions/api/quick-access.jsLead gate: captures name/email/company/role, writes to D1, sets 30-day cookie. Called before first demo access.

Critical implementation detail

The DeepSeek API is OpenAI-compatible. The nlq-query.js function uses fetch against https://api.deepseek.com/chat/completions with model: "deepseek-chat" and response_format: { type: "json_object" } β€” this forces structured JSON output and eliminates markdown-wrapped responses. Validate the output against the schema before using it; a malformed response triggers the offline fallback.

Tech stack β€” Every tool in this build

LayerToolWhy this choice
LLMDeepSeek V3OpenAI-compatible API, JSON mode, ~10Γ— cheaper than GPT-4o for structured extraction
Edge runtimeCloudflare Pages FunctionsZero cold start, global PoP, free tier covers demo scale, D1 binding built in
DatabaseCloudflare D1 (SQLite)Serverless, zero-ops, sufficient for access log at demo/SMB scale
ChartsChart.js 4.x8 chart types, tree-shakeable, no server required, good accessibility
Excel generationSheetJS (xlsx 0.18.5)Client-side .xlsx with formula support (HsGetValue). No server upload needed.
Static siteEleventy v3.1.5Processes Nunjucks templates and copies epm-nlq-src/ to _site/ verbatim
EmailResend APITransactional email for admin notifications and user approval links
CSSCustom (epm-nlq.css)No framework β€” full control over EPM-specific design tokens

Known attack surfaces

ThreatRiskMitigation in this build
Prompt injectionUser embeds "ignore previous instructions" in query fieldLLM returns structured JSON only β€” injected prose has no output channel. Out-of-scope guard returns error.
Data scope creepUser queries data outside the demo corpusData resolver only serves pre-loaded DIMENSION_MEMBERS values. No dynamic EPM API call in demo mode.
Cost abuseAutomated query loop inflates DeepSeek spendCloudflare rate-limit at edge (100 req/hour/IP). Access gate requires valid cookie. LLM call is server-side only.
Lead data exfiltrationAccess to D1 access_requests tableD1 is not exposed via any public API. Admin access requires ADMIN_KEY secret or Cloudflare Access OTP.

Guardrails β€” What we built to prevent bad outputs

  • Schema validation: LLM JSON output is validated against the expected schema before use. Invalid output (missing fields, wrong enum values) triggers the offline fallback parser β€” not an error state.
  • Dimension enum enforcement: Entity, scenario, year, and period values are checked against their known enum before the data resolver runs. An unknown entity returns "No data found" rather than a wrong number.
  • Out-of-scope detection: Queries that don't resolve to a valid EPM statement return a specific user-readable message with sample queries. The message never says "I don't understand" β€” it says "I help with Oracle EPM data β€” try: …"
  • Offline fallback: If the DeepSeek API is unavailable, a rule-based parser handles a subset of fixed-format queries at zero cost. Clearly labelled as fallback mode in the UI.
  • Input length limit: Query input capped at 500 characters. Longer inputs are truncated before reaching the LLM.

Who is asking, and what are they allowed to see?

The demo answers neither question — it has a cookie gate and no notion of a user. In production these are the two questions everything else rests on, and they have different answers: authentication is who you are, authorization is what you may see. Corporate SSO settles the first. Only Oracle EPM Cloud can settle the second, and the single most important rule in this section is that this application must never become the place where that decision is made.

The rule that governs every choice below: a user must see exactly what they would see by logging into Oracle EPM Cloud directly — no more, and no less. If this tool can surface a number the user could not retrieve themselves, it has become a privilege-escalation path, and it will be found in the first access review.

9.1 · The identity chain, end to end

Finance user opens the tool in a browser — no local account, no password held here
1
Corporate identity provider
Entra ID · Okta · OCI IAM
OIDC Authorization Code + PKCE  (or SAML 2.0)
  • MFA and Conditional Access are enforced here — device compliance, location, risk signals
  • Returns an ID token (who the user is) and an access token (what they may call)
  • Group membership arrives as a claim; the application never handles a password
2
Application session
validate, never trust
  • Verify signature, issuer, audience and expiry against the IdP’s published keys
  • Read the group claims — there is no local user table and no local role table
  • Short-lived access token with refresh-token rotation; session timeout set to the data classification
Pattern A — identity propagation
OAuth 2.0 token exchange (on-behalf-of)
  • The API is called as the user
  • EPM enforces its own security natively
  • Audit trail names the real user
  • Preferred where the API supports it
Pattern B — service account + filtering
one read-only integration account
  • The application becomes the enforcement point
  • Entitlements fetched separately, applied in one audited place
  • Simpler and cacheable — and a filtering bug is a data breach
3
EPM identity domain
roles + dimension security
  • Roles: Service Administrator, Power User, User, Viewer — assigned to groups, never to individuals
  • Data level: Planning access permissions on the Entity and Account dimensions — read / write / none per member per group. This is the row-level control that decides whether a store manager sees one store or forty-four
  • Group → role mapping lives in the platform, not in this application
Result, filtered to this user
finance.html
  • The user sees exactly what they would see logging into the source system directly — no more
  • Every query logged against the real end user, never a shared account

9.2 · Federating the corporate identity provider

Oracle EPM Cloud does not replace your directory — it trusts it. The EPM Cloud identity domain is federated with the corporate IdP so authentication happens where it already happens, under policies security has already written.

Identity providerProtocolNotes
Microsoft Entra ID (formerly Azure AD)SAML 2.0 or OIDCThe common case. Conditional Access, MFA and device compliance are enforced at Entra and inherited automatically. On-premises Active Directory federates through Entra Connect rather than being integrated directly.
OktaSAML 2.0 or OIDCSame pattern; Okta groups drive EPM roles through SCIM provisioning.
OCI IAM (identity domains)NativeAlready present with Oracle EPM Cloud. Can be the primary IdP for a smaller estate, or a federated spoke of Entra/Okta for a larger one.
AD FSSAML 2.0Still seen where the estate is not yet cloud-first. Works, but you inherit the on-premises availability of the token service — if AD FS is down, nobody logs in.

For the browser application itself, use OIDC Authorization Code flow with PKCE. Not the implicit flow, which is deprecated and leaks tokens through the URL, and never a resource-owner password grant — a finance tool should not be capable of handling a password at all.

9.3 · From group membership to EPM roles

Roles are granted to groups, never to individuals, and the groups come from the directory. That one discipline is what makes joiner/mover/leaver work without anyone having to remember this application exists.

Entra ID group                    β†’  EPM role / entitlement
──────────────────────────────────────────────────────────────────
FIN-EPM-Analysts                  β†’  Planning User
FIN-EPM-Controllers-EMEA          β†’  Power User + EMEA data scope
FIN-EPM-Admins                    β†’  Service Administrator
──────────────────────────────────────────────────────────────────
Provisioned by SCIM. Remove the user from the group and the
entitlement disappears on the next sync β€” including here.

For this use case the relevant native entitlement is: Planning User, plus access to the Financials cube and the relevant forms.

9.4 · The architectural decision: who enforces?

This is the choice that determines whether the deployment is defensible. Both patterns appear in the diagram above; the difference is where the security boundary actually sits.

Pattern A — identity propagationPattern B — service account + filtering
HowThe user’s token is exchanged (OAuth 2.0 on-behalf-of) for one scoped to the EPM API; calls are made as the userA single read-only integration account calls the API; the application filters the results
Enforcement pointOracle EPM CloudThis application
Audit trail showsThe real end userThe service account — you must log the real user separately
Failure modeToken plumbing is more complex; per-user rate limits applyA filtering bug is a data breach, and the entitlement copy drifts from reality
VerdictPrefer this wherever the API supports user-token authenticationAcceptable with discipline: narrowest possible service account, filtering centralised in one tested place, real user in every log line

The shortcut to refuse. Pattern B built with a Service Administrator account and no filtering at all is the most common way this gets delivered, because it works perfectly in UAT — testers are usually over-entitled, so nobody notices that everyone can see everything. It fails at the first access review, and by then it is in production with real users depending on it.

9.5 · Data-level security is the part that matters

Role membership decides whether a user can open the application. It does not decide which rows they get back, and confusing the two is the most expensive mistake available here.

  • For this use case: Planning access permissions on the Entity and Account dimensions — read / write / none per member per group. This is the row-level control that decides whether a store manager sees one store or forty-four.
  • Apply it before aggregation, not after. Filtering a total that has already been computed across entities the user cannot see still leaks the total.
  • The NLQ layer needs its own check. Layer 4 already validates that the resolved point of view uses approved members; production adds a second test — that the resolved POV sits inside this user’s scope — and it runs before the data call, not after. A natural-language interface is very good at asking for things politely; the authorization check must not care how the question was phrased.
  • Fail closed. If entitlements cannot be resolved, return nothing and say so. An empty result is a support ticket; a permissive default is an incident.

9.6 · Provisioning, sessions and the leaver problem

  • SCIM provisioning from Entra or Okta into the EPM Cloud identity domain (OCI IAM, formerly IDCS), covering joiner, mover and leaver. The mover is the case people forget — somebody changing region should lose the old scope, not accumulate both.
  • No local user store. If this application keeps its own copy of who may do what, a leaver keeps access until somebody remembers to update it. Nobody ever does.
  • Short-lived access tokens with refresh-token rotation; align session timeout with the data classification rather than with convenience.
  • MFA and Conditional Access at the IdP — not reimplemented here. Device compliance and location policy come free with federation.
  • Quarterly recertification of both the groups that grant access and the service account’s own entitlements, evidenced and signed.
  • Break-glass access is a named, monitored, time-boxed account — never a shared credential in a password manager.

9.7 · What this means for FP&A NLQ

ConcernAnswer for this use case
Native entitlement requiredPlanning User, plus access to the Financials cube and the relevant forms
Data-level controlPlanning access permissions on the Entity and Account dimensions — read / write / none per member per group. This is the row-level control that decides whether a store manager sees one store or forty-four
Use-case-specific sensitivityA regional controller asking for “EMEA revenue” must get EMEA as their security defines it. If the answer is assembled from entities they could not open in Planning, this tool has quietly become a privilege-escalation path.

9.8 · Security configuration checklist

  • ✓Oracle EPM Cloud federated with the corporate IdP over SAML 2.0 or OIDC; the cookie gate removed entirely
  • ✓Browser app uses OIDC Authorization Code + PKCE — no implicit flow, no password grant
  • ✓MFA and Conditional Access enforced at the IdP, not reimplemented in the application
  • ✓Roles granted to directory groups, never to individuals; SCIM covers joiner, mover and leaver
  • ✓Enforcement pattern chosen deliberately — Pattern A where the API supports it, or Pattern B with filtering centralised and tested
  • ✓Data-level security applied before aggregation, and the resolved POV checked against the user’s scope before the data call
  • ✓No local user table and no local role table anywhere in the application
  • ✓Every query logged against the real end user, even when a service account makes the call
  • ✓Authorization failures fail closed and are logged as security events rather than swallowed
  • ✓Quarterly recertification of access groups and of the service account’s own entitlements
Talk through your identity model → Back to the demo

From demo to a governed enterprise deployment

Everything above runs on synthetic data, a public LLM API key, a cookie gate, and no audit trail — deliberately, so the mechanics are inspectable. Taking FP&A NLQ to production is not a rewrite; the 4-layer pipeline and the data-layer contract survive intact. It is a controlled-change program across six workstreams: architecture, LLM platform, security, SOX/audit, environment promotion, and operations. This section is the checklist we run with clients.

The one rule that matters most for this use case: the demo already sends only the schema and the user’s words to the LLM — never the financial values. Production must preserve that boundary exactly: the model resolves what to fetch, the EPM REST API fetches it under the user’s own security, and no cell value is ever part of a prompt.

10.1 · Production reference architecture

Finance user · corporate SSO (OIDC/SAML + MFA) · EPM role claims
1
Edge / API Gateway
WAF · rate limit · identity
  • Terminates SSO, validates the session, attaches the user’s EPM groups to the request
  • Rate limits per user, blocks anonymous access, scrubs PII patterns before anything reaches the orchestrator
2
NLQ Orchestrator
the 4-layer pipeline, hardened
L1 guardrails → L2 grounding → L3 LLM adapter → L4 eval + fallback
  • L2 grounding reads dimension metadata from EPM on a schedule — not a hardcoded schema
  • Only the schema + user query go to the model; financial values never leave the data layer
  • L4 rejects anything outside the approved member lists and falls back to the deterministic parser
Oracle OCI Generative AI
same tenancy as EPM Cloud
  • Data stays inside the OCI boundary
  • Natural fit when EPM is already in OCI
Azure OpenAI / AWS Bedrock / Vertex AI
private endpoint, zero retention
  • Use the hyperscaler the org already governs
  • Enterprise DPA, no training on prompts
Self-hosted open weights
VPC / air-gapped
  • For regulated or sovereign data
  • Highest control, highest run cost
3
EPM Data Layer
Oracle EPM REST API
  • Oracle EPM Planning (PBCS/EPBCS) — REST API exportDataSlice against the Financials cube; the parsed JSON becomes the grid POV
  • Least-privilege service account (read-only role, one app, one pod) with the token in a vault and rotated
  • Results filtered to the requesting user’s EPM security before rendering
4
Audit & Observability
append-only
  • Every query logged: user, timestamp, raw query, parsed intent JSON, model + prompt version, POV returned, latency, cost
  • Exported to the SIEM; retained per the SOX evidence schedule
  • Dashboards for fallback rate, eval pass rate, guardrail hits, p95 latency, spend
Rendered result + evidence trail
finance.html
  • The parsed JSON is shown to the user as the explanation (“AI: entity · year · scenario — 93% confident”) — the same line the demo prints today
  • Every number on screen traces to an EPM cell intersection an auditor can reproduce

10.2 · Choosing the LLM platform

The demo’s DeepSeek call is a placeholder for a single adapter, callLLM(system, user), behind Layer 3. Swapping the provider changes one function and zero business logic. Pick the platform the organisation already governs — the security and procurement review is the long pole, not the integration.

OptionChoose whenData posture
Oracle OCI Generative AI (Cohere Command, Llama)EPM Cloud already lives in OCI; you want one cloud boundary and one contractPrompts stay in the OCI tenancy; no training on customer data; dedicated AI clusters available for isolation
Azure OpenAI ServiceMicrosoft-first finance estate (Entra ID, Purview, Sentinel already in place)Private endpoint, regional deployment, zero-retention by default under the enterprise agreement
AWS Bedrock (Claude, Titan) / Google Vertex AI (Gemini)The org’s landing zone is AWS or GCP; VPC endpoints and IAM already auditedVPC/PSC private access, no data used for training, CloudTrail/Cloud Audit Logs integration
Direct enterprise API (Anthropic, OpenAI)Fastest model access; acceptable when a zero-data-retention agreement and DPA are signedZDR endpoint, SSO-managed keys, SOC 2 report on file
Self-hosted open weights (Llama, Mistral, Qwen via vLLM)Sovereign or air-gapped requirements; regulated data classification forbids any external inferenceFull control; you own patching, eval, and capacity — budget for an MLOps owner

Put a model gateway in front of whichever you choose (Azure API Management, OCI API Gateway, Kong AI Gateway, LiteLLM, or Portkey): it owns key custody, per-team spend caps, routing and fallback between models, prompt/response logging, and lets you retire a deprecated model without touching the application.

10.3 · Security controls

ControlImplementation
Identity & accessCovered in full in section 09 — corporate SSO, group-to-role mapping, and the decision about who enforces data-level security. Listed here because it is a production gate, not because it is optional.
Service accountOne read-only EPM service account per application per pod, least-privilege role, no interactive login, credential in a vault (OCI Vault, Azure Key Vault, HashiCorp Vault), rotated on a schedule and on staff change.
Secrets & configNo secrets in code or build artifacts; environment-specific config injected at deploy; .dev.vars-style files never leave a developer machine.
NetworkPrivate endpoints to the LLM provider and to EPM where the platform supports them; egress allow-list so the orchestrator can reach exactly two hosts; TLS 1.2+ everywhere.
Prompt-injection & input guardrailsLayer 1 (already in the demo) blocks instruction-override patterns, enforces length and scope; extend with a classifier on the gateway and log every rejection.
Output guardrailsLayer 4 (already in the demo) validates every returned member against the approved lists and strips unexpected keys; production adds a policy check that the resolved POV is inside the user’s security scope before the data call.
Data minimisationPrompts contain metadata and the user’s query only. No cell values, no employee names, no free-text comments from EPM. Logged prompts are classified and retained accordingly.
EncryptionIn transit (TLS) and at rest (provider-managed KMS); audit logs on immutable storage with customer-managed keys where policy requires.

10.4 · SOX, audit, and model-risk controls

A read-only NLQ layer does not change a financial-reporting control, but it is an interface to a SOX-relevant system and lands squarely in ITGC scope. Treat prompts, schemas, and eval sets as code — that single decision satisfies most of what an auditor will ask for.

RequirementHow it is satisfied
Complete, immutable audit trailAppend-only log of user, timestamp, raw query, parsed JSON, model and prompt version hash, POV returned, and row count — WORM storage, retained for the evidence period (typically 7 years), exported to the SIEM.
Change managementPrompt templates, few-shot examples, approved-member schema, and code are version-controlled; every change follows ticket → peer review → test evidence → CAB approval → deploy. A prompt edit is a code change.
Segregation of dutiesDevelopers cannot deploy to production; the service-account owner is not a developer; production secrets are held by platform operations.
Access recertificationQuarterly review of who can use the tool and of the service account’s EPM roles, evidenced and signed.
Testing evidenceA golden-query regression suite (the few-shot examples plus a larger labelled set) runs in CI before every release; pass rate and diffs are archived as release evidence.
Model risk managementAn inventory entry (intended use, limitations, owner, validation date) in the model-risk register — the SR 11-7 pattern for financial services; periodic re-validation when the model or prompt changes.
Reproducibility & lineageEvery displayed number traces to an EPM POV and a consolidation/calculation timestamp; an auditor can re-query the same intersection in EPM and match it.
ExplainabilityThe parsed intent JSON is the explanation and is shown to the user on every response — no hidden reasoning between the query and the data call.

10.5 · Dev → Test → Prod promotion

EnvironmentEPM targetDataGate to leave
DevEPM Test pod (developer slice)Synthetic or maskedUnit tests on the data layer; lint; eval suite ≥ threshold against the Test LLM deployment
Test / UATEPM Test pod (full refresh)Masked copy of productionBusiness UAT sign-off on the golden queries; security scan; performance run (p95 latency, fallback rate)
ProdEPM Production podLiveChange ticket approved; deploy in window; smoke test; hypercare with rollback ready
  • Promoted artifacts: application build, prompt templates (versioned), approved-member schema snapshot, eval set, infrastructure config (IaC) — all from the same Git tag.
  • Pipeline: branch → PR review → CI (tests + evals) → deploy to Test → UAT sign-off → CAB → deploy to Prod → smoke test. Hosting can stay on Cloudflare Pages/Workers or move to OCI Functions + API Gateway or the org’s standard platform — the code does not care.
  • Configuration: per-environment secrets and endpoints injected at deploy; the same build runs in every environment.
  • Metadata sync: a scheduled job refreshes dimension metadata into Layer 2 grounding with change detection, so a new entity or account appears in the approved lists without a code release.
  • Rollback: previous build and previous prompt version retained; rollback is a redeploy, and because prompts are versioned it also reverts a prompt regression.

10.6 · Operating it

  • SLOs: p95 latency, availability of the read path (the deterministic fallback keeps it alive when the LLM is down — already built), fallback rate as a quality signal, eval pass rate per release.
  • Cost governance: per-user and per-team token budgets at the gateway; alert on anomalies; the unit-cost model earlier in this kit is the baseline.
  • Model lifecycle: providers retire models on a schedule — re-run the eval suite on the successor before switching, and record the switch as a change.
  • Incident runbook: LLM outage → fallback parser; EPM API outage → cached metadata with a stale banner; guardrail spike → review logs for injection attempts.

10.7 · What changes for FP&A NLQ

ConcernProduction answer
System of recordOracle EPM Planning (PBCS/EPBCS) — REST API exportDataSlice against the Financials cube; the parsed JSON becomes the grid POV
Read/write postureRead-only. No writeback, no business-rule launch.
Use-case-specific controlEntity/scenario visibility must follow the user’s own Planning security — pass the user identity through (or map EPM groups application-side); never let a super-user service account widen what a user can see.

10.8 · Production readiness checklist

  • ✓LLM platform selected from the governed list, DPA / zero-retention terms on file, gateway in front of it
  • ✓SSO integrated; authorisation derived from EPM security groups; cookie gate removed
  • ✓Read-only EPM service account per pod, credential in a vault, rotation scheduled
  • ✓Prompts, schema, and eval set version-controlled and under change management
  • ✓Append-only audit log wired to the SIEM with the agreed retention
  • ✓Golden-query eval suite passing in CI; results archived as release evidence
  • ✓Dev / Test / Prod pipeline with gates, IaC, and a rehearsed rollback
  • ✓Model-risk register entry and owner named; first re-validation date set
  • ✓Metadata refresh job scheduled with change detection
  • ✓SLOs, cost caps, and the incident runbook agreed with platform operations
Plan a production rollout with us → Back to the demo