The problem this solves
Financials can hold a single number for capex. Capital models the asset lifecycle โ acquire, depreciate, improve or impair or transfer, retire โ and derives the depreciation, interest, cash flow, and balance sheet consequences that a single number can't. A retailer needs this the moment depreciation schedules, lease accounting, or asset-level tracking start to matter, which for a 36-store chain is immediately: every store has a lease, every store has fixtures and a POS system, and IFRS-16 requires each lease to be capitalized as a Right-of-Use asset.
Retail is the textbook IFRS-16 use case. A chain with dozens or hundreds of store leases used to expense rent flat, above EBITDA. Since IFRS-16, every one of those leases sits on the balance sheet as a Right-of-Use asset and a Lease Liability, with the P&L impact split into depreciation and interest below EBITDA. Get this wrong at scale and EBITDA, gearing, and cash flow classification are all misstated.
Audience
Financial controllers and EPM architects designing the Capital module. FP&A teams who need to understand why the finance team keeps saying "that's a controller question, not an FP&A question" about depreciation conventions and discount rates.
Three tools in one demo
| Tab | Input | Output |
|---|---|---|
| NLQ query | "Show the lease for Tokyo Shibuya" or "Depreciation for POS systems" โ DeepSeek picks the right tab and pre-fills its entity or asset class | Lands directly on the matching tab, already populated |
| Store Lease (IFRS-16) | Pick any of the 36 physical stores in the cube | ROU asset, lease liability, and the full amortization table โ derived from that store's actual revenue seed |
| Asset Depreciation | Asset class (auto-fills method/life/convention), cost, useful life, method | Year-by-year depreciation schedule and net book value chart |
| Capitalize vs Expense | Spend amount, capitalization threshold, does it extend life/capacity? | Routing decision + the financial statement flow that follows from it |
The mechanism made visible
- Lease chart: a stacked bar of Depreciation + Interest against a flat dashed line for the old rent expense โ the IFRS-16 front-loading effect in one picture
- Depreciation chart: net book value declining to zero over the useful life, shaped by the selected method (straight line is a ramp; declining balance curves)
- Routing card: a plain CAPITALIZE / EXPENSE verdict plus the specific reason (below threshold, or doesn't extend life), followed by the balance sheet / income statement / cash flow flow diagram
Between capex requests and the balance sheet
Capital sits downstream of the capex budgeting conversation and upstream of Financials. The FS account mapping โ which balance sheet and P&L accounts receive Capital's output โ must be agreed with the controller, not FP&A. New Financials accounts don't appear in Capital's mapping automatically; forgetting to re-sync after an account change is the quiet failure mode where Capital data stops arriving with no error thrown.
Recommended integration points
- Store opening capex: new-store fixtures, leasehold improvements, and POS systems modeled with class-level depreciation defaults before the store even opens
- Lease renewal: Real Estate re-negotiates a store lease โ recompute the ROU asset and liability at the new rent and term
- Threshold enforcement: a $400 store-fixture purchase is routed to OpEx automatically rather than becoming a tracked asset that never moves the needle
- Board reporting: depreciation and interest below EBITDA vs the old flat-rent P&L, side by side, when explaining why EBITDA improved after IFRS-16 adoption
The numbers
| Component | Choice |
|---|---|
| Lease term | 10 years for Flagship / high-revenue stores, 7 years otherwise โ derived, not separately loaded |
| Depreciation methods | Straight Line, Sum of Years' Digits, Declining Balance Year/Period, No Depreciation |
| Depreciation conventions | ProRate Beginning Period, ProRate Actual Date, Mid Period โ set per asset class |
The honest checklist
- โYou lease real estate (stores, DCs) and need IFRS-16 or ASC 842 lease accounting modeled, not just a rent line
- โYou track named assets individually โ necessary for transfers, improvements, and reconciliation
- โYou need depreciation schedules that vary by asset class and must be confirmed with the controller, not assumed by FP&A
- โYou want a capitalization threshold enforced in the process, not just in a policy document
- โThe client only needs "ยฃ4M capex in Q3" with no depreciation, lease, or asset-level detail โ they don't need this module, a Financials line is enough
- โYou manage a capex pot rather than individual assets โ named-asset tracking is expensive in data volume and not worth it for pooled spend
Try it now
All three tabs run entirely client-side against the retail cube's 36 physical stores and 9 asset classes.
Things to try
- Type "Depreciation for the delivery fleet" into the NLQ bar and watch it jump straight to the depreciation tab with the asset class pre-filled
- Switch the lease tab from a Flagship store to a Standard store and compare lease term and ROU asset size
- Change the Asset Depreciation method from Straight Line to Declining Balance and watch the curve bend
- Set spend to $3,000 with a $5,000 threshold in the routing tab โ watch it expense instead of capitalize
Why this had to tie into the store network
A generic depreciation calculator is a spreadsheet. What makes this a Capital planning demo is that the lease tab doesn't ask for a rent number โ it derives one from each store's actual revenue seed already in the retail cube (roughly 11% of annual store revenue, a realistic occupancy-cost ratio), then runs it through the full IFRS-16 present-value calculation. Pick any of the 36 physical stores and get a real, internally-consistent lease.
This is deliberate: it demonstrates that Capital doesn't live in isolation โ it derives from and feeds back into the same entity data the FP&A module already uses (the BS_ROU and BS_LeaseLiab accounts added to the Account hierarchy for exactly this reason).
Three independent calculators, one data layer
- Derives annualRent, termYears from ENTITY_SEEDS revenue
- genLeaseSchedule() → PV, ROU, liability, year-by-year interest/principal split
- Straight Line / SYD / Declining Balance
- Returns { year, depreciation, NBV }[]
- spend ≥ threshold AND extends life/capacity → capitalize
- otherwise → expense
- Chart.js renders each tab's chart
- The routing tab renders a flow diagram instead of a chart
Each tab is independent โ switching tabs does not re-fetch or share state, matching how a real Capital planner moves between "what's this lease worth" and "what's this asset's schedule" as separate questions.
App UI โ Component breakdown
| Component | Behaviour |
|---|---|
| Tab strip | Store Lease / Asset Depreciation / Capitalize vs Expense โ client-side switch, no reload |
| Rule callout | Each tab opens with a plain-language statement of the accounting mechanism it demonstrates |
| Lease KPI row | ROU Asset, Lease Liability, Year 1 Total Expense, Lease Term โ recomputed per store selection |
| Lease chart | Stacked bar (Depreciation + Interest) vs dashed line (old flat rent) across the full lease term |
| Depreciation panel | Asset class picker auto-fills useful life, method, and convention; all remain user-editable |
| Routing card | Colour-coded verdict (indigo = capitalize, green = expense) with the specific reason and a flow diagram |
Tangible, Intangible, Leased
| Type | Handles | Demo classes |
|---|---|---|
| Tangible | Physical assets, depreciation | Store Fixtures & Fittings, Leasehold Improvements, POS & Retail Technology, IT Infrastructure, Distribution Center Equipment, Delivery Fleet Vehicles, Store Signage |
| Intangible | Amortization, and impairment | Brand & Trademark Intangibles |
| Leased | Operating vs capital lease, PV of lease, interest โ with optional IFRS-16 support | Right-of-Use Assets (Store Leases) |
Each class carries a default useful life, depreciation method, and convention โ chosen to be realistic for retail: POS systems on a 5-year straight line (technology refresh cycle), leasehold improvements over 10 years matching a typical lease term, delivery fleet on declining balance (vehicles lose value fastest early).
Four methods, one function
genDepreciationSchedule(cost, usefulLife, method) implements the methods actually offered in Capital: Straight Line, Sum of Years' Digits, Declining Balance (Year and Period both map to the same double-declining-balance curve in this demo), and No Depreciation.
// Straight Line
depr = cost / usefulLife // every year
// Sum of Years' Digits โ front-loaded
sumDigits = usefulLife * (usefulLife + 1) / 2
depr[yr] = cost * (usefulLife - yr + 1) / sumDigits
// Declining Balance โ front-loaded, rate applied to NBV
rate = 2 / usefulLife
depr[yr] = min(netBookValue * rate, netBookValue)
The convention clients skip and later dispute
Depreciation Convention โ ProRate Beginning Period, ProRate Actual Date, or Mid Period โ determines how much depreciation an asset acquired mid-month attracts. It must match the client's accounting policy, and it must be confirmed with the financial controller, not the FP&A team. It's also the innocent explanation to rule out first when depreciation appears to lag expectations โ before assuming a rule failed to run, check whether the convention is the reason.
Present value in, front-loaded expense out
At commencement, a Right-of-Use asset and a Lease Liability are both recognized at the present value of future lease payments. Thereafter the ROU asset amortizes straight-line while the liability unwinds โ each payment splitting into interest (charged on the opening balance) and principal.
// Present value of the lease at commencement
PV = ฮฃ (annualRent / (1 + discountRate)^t) for t = 1..termYears
ROU_initial = PV
LeaseLiability_initial = PV
// Each year thereafter
interest = liabilityBalance * discountRate
principal = annualRent - interest
liabilityBalance -= principal
rouAmortization = ROU_initial / termYears // straight-line
rouBalance -= rouAmortization
Before vs after, for a store paying $120K/yr flat rent on a 5-year lease:
| Year 1 | Year 5 | |
|---|---|---|
| Before โ flat rent expense | $120K | $120K |
| After โ depreciation | $100K | $100K |
| After โ interest | $28K | $4K |
| After โ total | $128K | $104K |
- Total expense is front-loaded โ interest is charged on a shrinking liability
- EBITDA improves โ rent sat above EBITDA; depreciation and interest sit below it
- Gearing worsens โ assets and liabilities both grow
- Cash flow reclassifies โ the principal portion moves from operating to financing
- Exemptions: short-term leases (12 months or less) and low-value assets
The discount rate question nobody owns
What discount rate, and who supplies it? The incremental borrowing rate is a treasury input that changes over time โ the demo uses a fixed 6.5% for every store, which is exactly the simplification a production rollout cannot make. Confirm the source and refresh cadence for this number before go-live.
US GAAP is different lease accounting, not the same thing with a different name
ASC 842 kept a dual model. Operating leases go on balance sheet but retain a single straight-line P&L expense rather than depreciation plus interest. A US client's lease accounting produces a different expense profile from an IFRS client's โ do not assume "lease accounting" means the same thing across the two standards.
Capitalize vs Expense โ Threshold plus judgement, not threshold alone
Treatment is determined by future economic benefit, reliable measurement, the client's capitalization threshold, and โ for work on existing assets โ whether it extends useful life or capacity (capitalize) or merely maintains (expense). The demo's routing logic requires both conditions: spend at or above the threshold and an extension of life or capacity. Fail either and it expenses.
Dr Fixed Asset (BS) 1,000,000 โ at acquisition, capitalized
Cr Cash / Payables (BS) 1,000,000
Dr Depreciation Expense (IS) 200,000 โ each period thereafter
Cr Accumulated Depreciation (BS, contra) 200,000
The threshold decides routing, not just accounting. Spend below the threshold should never enter Capital at all โ build that into the process, or planners will create $400 assets that clutter the register and add reconciliation noise for no analytical value.
What each production rule does
| Stage | Rules |
|---|---|
| Add | Add Asset, Add Intangibles, Add LeasedAsset โ each with a Dynamic variant taking member names from a runtime prompt |
| Calculate | Calculate Tangible Asset, Calculate Intangible Asset, Calculate Leased Assets, Calculate All Leased Assets |
| Change | Improve Asset (splits the asset, creating a separate improvement value), Impair Intangible, Transfer Asset, Transfer Intangibles |
| End of life | Retire Asset, Retire Intangible (sold or written off, with accounting consequences), Remove Named Asset / Remove Leased Asset |
| Reconcile | Reconcile Asset / Reconcile Leased Asset โ reconciles a new asset request against an existing asset; values push to the assigned asset |
| Integration | Push Data To Financials โ runs the Financial Statement Integration data map; run Sync FS Account Mapping after any Financials account change |
Reconcile Asset is the request-to-actual bridge: planners request assets during the cycle, and reconciliation pushes the request's values onto the actual asset. Clients who miss this step end up with duplicate assets โ requested and actual, both depreciating.
Improve Asset splits the asset rather than adding cost to the existing line โ it creates a separate value for the improvement. Explain this before someone questions why one asset became two lines in the register.
Two obsolete rules to avoid: Calculate All Existing Intangible Assets and Calculate All Existing Tangible Assets are marked obsolete โ use Calculate Intangible Asset and Calculate Tangible Asset. A scheduled job still calling the obsolete rules is a finding in any application review.
Named assets or class level? Named assets track each item individually โ necessary for transfers, improvements, and reconciliation, expensive in data volume. This demo models named-asset behaviour for store leases (each of the 36 physical stores is a distinct asset) but class-level behaviour for the depreciation calculator (cost and life are user inputs, not tied to a specific fixture). Ask a real client whether they manage individual assets or a capex pot before choosing.
Key files
| File | Role |
|---|---|
epm-nlq-src/assets/epm-workforce-capex-data.js | ASSET_CLASSES, genDepreciationSchedule(), getStoreLease(), genLeaseSchedule() |
epm-nlq-src/pages/capital.html | Three-tab UI: lease calculator, depreciation calculator, capitalize-vs-expense router โ each with its own Chart.js canvas |
epm-nlq-src/assets/epm-data.js | Shared ENTITIES and ENTITY_SEEDS โ the lease tab derives every store's rent directly from its revenue seed |
functions/api/nlq-query.js | Shared NLQ endpoint; Capital uses useCase: "capital", the only schema of the nine that has to pick a UI tab (lease/depr/route) in addition to resolving an entity or asset class |
Why the lease term is derived, not loaded
getStoreLease() assigns a 10-year term to Flagship-named or high-revenue stores and 7 years otherwise, and prices annual rent at roughly 11% of the store's annual revenue seed โ both defensible retail occupancy-cost assumptions. This means every one of the 36 physical stores has a complete, internally consistent lease without a second data load, which is what makes "pick any store" a genuinely live demo rather than three hardcoded examples.
NLQ prompt design โ routing across three tabs
Capital's output schema is {"intent":"capital_query","tab":"<lease|depr|route>","entity":"<store|null>","assetClass":"<name|null>","confidence":<0-1>}. The tab field is unique to this use case among the nine โ every other NLQ integration fills in filters on a single screen, but Capital's query first has to decide which of three screens the user means, then fill in that screen's one relevant field. The few-shot examples lean on vocabulary cues: "lease"/"IFRS-16"/"ROU" โ lease; "depreciation"/"useful life"/"net book value" โ depr; "capitalize"/"expense"/"threshold" โ route. The frontend calls the existing switchTab() function with the resolved tab, so query-driven navigation and click-driven navigation share the exact same code path.
Tech stack โ Every tool in this build
| Layer | Tool | Why |
|---|---|---|
| Data layer | epm-workforce-capex-data.js (vanilla JS) | Deterministic financial math โ the LLM only resolves tab/entity/asset class |
| LLM | DeepSeek V3 | Shared NLQ endpoint; only schema with a UI-tab field (lease/depr/route) among the nine use cases |
| Charts | Chart.js 4.x | Shared with Analytics UC4; stacked bar for the lease before/after, line for NBV decay |
| Lease math | Custom PV / amortization functions | Textbook IFRS-16 formulas, no external finance library needed at this scale |
| Edge hosting | Cloudflare Pages | Static file, no server compute needed for this use case |
| Build | Eleventy v3.1.5 | Copies epm-nlq-src/pages/ and epm-nlq-src/assets/ to _site/ verbatim |
Known attack surfaces
| Threat | Mitigation in this build |
|---|---|
| Wrong FS account mapping | Documented as a controller-owned decision in this kit; production must re-run Sync FS Account Mapping after any Financials account change or Capital data silently stops arriving |
| Threshold bypass | Routing logic in the demo requires both conditions (threshold met AND life/capacity extended) โ a single-condition check would misroute maintenance spend as capex |
| Duplicate assets | Reconcile Asset documented explicitly as the fix for the requested-vs-actual double-count failure mode |
| Stale scheduled jobs | Obsolete rule names called out so an inherited application's job schedule can be audited against them |
Guardrails โ What prevents bad capital plans
- Two-condition routing: capitalization requires threshold AND life/capacity extension โ spend that fails either test defaults to expense, the safer failure mode
- Convention surfaced, not hidden: the depreciation convention is displayed next to every schedule so it can't silently default to the wrong policy
- PV computed once, consistently: ROU asset and lease liability are always set equal at commencement in
genLeaseSchedule()โ they cannot drift apart at initial recognition, matching the accounting requirement exactly - Depreciation floor at zero:
genDepreciationSchedule()andgenLeaseSchedule()both clamp balances at zero โ no method can produce a negative net book value or lease liability
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.
9.1 · The identity chain, end to end
- 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
- 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
- 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
- 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
- Roles: Service Administrator, Power User, User, Viewer — assigned to groups, never to individuals
- Data level: Planning access permissions on Entity, plus asset-class scoping where the client segregates real estate from fleet
- Group → role mapping lives in the platform, not in this application
- 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 provider | Protocol | Notes |
|---|---|---|
| Microsoft Entra ID (formerly Azure AD) | SAML 2.0 or OIDC | The 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. |
| Okta | SAML 2.0 or OIDC | Same pattern; Okta groups drive EPM roles through SCIM provisioning. |
| OCI IAM (identity domains) | Native | Already 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 FS | SAML 2.0 | Still 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: EPBCS Capital User; per-property lease detail is typically restricted to real estate and finance.
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 propagation | Pattern B — service account + filtering | |
|---|---|---|
| How | The user’s token is exchanged (OAuth 2.0 on-behalf-of) for one scoped to the EPM API; calls are made as the user | A single read-only integration account calls the API; the application filters the results |
| Enforcement point | Oracle EPM Cloud | This application |
| Audit trail shows | The real end user | The service account — you must log the real user separately |
| Failure mode | Token plumbing is more complex; per-user rate limits apply | A filtering bug is a data breach, and the entitlement copy drifts from reality |
| Verdict | Prefer this wherever the API supports user-token authentication | Acceptable 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 Entity, plus asset-class scoping where the client segregates real estate from fleet.
- 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 Capital & Leases
| Concern | Answer for this use case |
|---|---|
| Native entitlement required | EPBCS Capital User; per-property lease detail is typically restricted to real estate and finance |
| Data-level control | Planning access permissions on Entity, plus asset-class scoping where the client segregates real estate from fleet |
| Use-case-specific sensitivity | Lease terms are contractual and frequently NDA-covered — rent, term and break clauses for a named store are commercially sensitive to the landlord relationship. Restrict per-store lease detail; portfolio aggregates are usually fine for a wider audience. |
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
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 Capital & Leases 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.
10.1 · Production reference architecture
- 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
- 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
- Data stays inside the OCI boundary
- Natural fit when EPM is already in OCI
- Use the hyperscaler the org already governs
- Enterprise DPA, no training on prompts
- For regulated or sovereign data
- Highest control, highest run cost
- Oracle EPBCS Capital module — asset master, lease schedules, and depreciation from the module’s own calc; IFRS-16 ROU/liability values are retrieved, not re-derived
- 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
- 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
- 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.
| Option | Choose when | Data posture |
|---|---|---|
| Oracle OCI Generative AI (Cohere Command, Llama) | EPM Cloud already lives in OCI; you want one cloud boundary and one contract | Prompts stay in the OCI tenancy; no training on customer data; dedicated AI clusters available for isolation |
| Azure OpenAI Service | Microsoft-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 audited | VPC/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 signed | ZDR 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 inference | Full 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
| Control | Implementation |
|---|---|
| Identity & access | Covered 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 account | One 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 & config | No secrets in code or build artifacts; environment-specific config injected at deploy; .dev.vars-style files never leave a developer machine. |
| Network | Private 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 guardrails | Layer 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 guardrails | Layer 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 minimisation | Prompts 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. |
| Encryption | In 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.
| Requirement | How it is satisfied |
|---|---|
| Complete, immutable audit trail | Append-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 management | Prompt 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 duties | Developers cannot deploy to production; the service-account owner is not a developer; production secrets are held by platform operations. |
| Access recertification | Quarterly review of who can use the tool and of the service account’s EPM roles, evidenced and signed. |
| Testing evidence | A 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 management | An 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 & lineage | Every 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. |
| Explainability | The 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
| Environment | EPM target | Data | Gate to leave |
|---|---|---|---|
| Dev | EPM Test pod (developer slice) | Synthetic or masked | Unit tests on the data layer; lint; eval suite ≥ threshold against the Test LLM deployment |
| Test / UAT | EPM Test pod (full refresh) | Masked copy of production | Business UAT sign-off on the golden queries; security scan; performance run (p95 latency, fallback rate) |
| Prod | EPM Production pod | Live | Change 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 Capital & Leases
| Concern | Production answer |
|---|---|
| System of record | Oracle EPBCS Capital module — asset master, lease schedules, and depreciation from the module’s own calc; IFRS-16 ROU/liability values are retrieved, not re-derived |
| Read/write posture | Read-only. Capitalize-vs-expense routing is advisory; the accounting decision is recorded in EPBCS/ERP. |
| Use-case-specific control | Lease terms and rents are contractual and often NDA-covered — restrict per-store lease detail to real-estate and finance roles. |
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