Two legitimate automation paths. Different trade-offs. Most production EPCM implementations use both — knowing when to use which separates solid implementations from fragile ones.
EPM Automate is a CLI tool you install on a machine outside Oracle Cloud — Windows, Linux, or a CI/CD agent. Java 17 required. You script it in Bash (Linux) or PowerShell (Windows). Authentication is via plain-text credentials or an encrypted password file (encrypt command). Runs on a schedule via Task Scheduler, cron, OIC, or any external orchestrator.
The command epmautomate login serviceAdmin password url is the mandatory first step of every script. One session per machine. Scripts run synchronously by default.
# runs on any Linux server or CI agent epmautomate login $USER $PWD_FILE $EPM_URL epmautomate calculateModel "NovaPrism_EPCM_PROD" "M01" epmautomate copyDataByPointOfView "PCM_CLC" "PCM_REP" \ "Actual#Working#Q1#FY2025" epmautomate logout
Groovy business rules live inside EPCM's Calculation Manager. No external tool, no server, no credentials file. You write the script in a browser, save it, and assign it to a menu item, a form action, or a Task Manager job. Runs as the authenticated user — no separate auth setup.
The Groovy EPM API gives you access to cube data, member hierarchies, forms, and REST connections through a Java-based object model. operation.application is your entry point to everything in the application.
/*RTPS: {Period} {Year}*/ def period = rtps.Period.value def year = rtps.Year.value // Call EPCM REST API — no external tool needed def conn = operation.application.getConnection("Local") def body = json([ jobType: "CalculateModel", jobName: "M01", parameters: [Period: period, Year: year] ]) def resp = conn.post( '/HyperionPlanning/rest/v3/applications/NovaPrism_EPCM_PROD/jobs' ).header("Content-Type","application/json").body(body).asString() if (resp.status != 200) throwCalcException("M01 failed: " + resp.body)
EPM Automate returns exit code 0 on success, non-zero on failure. You catch this in Bash/PowerShell with $? or $LASTEXITCODE. Error detail goes to the EPM Automate log file and the EPM Cloud job console. You handle retry logic, alerting, and escalation in your external script.
Problem: the error is outside Oracle Cloud. If your monitoring system is down, the failure is silent. Log parsing is required to distinguish "job submitted but failed" from "could not connect."
epmautomate calculateModel "NovaPrism_EPCM_PROD" "M01" if [ $? -ne 0 ]; then echo "M01 FAILED — alerting Finance Controller" mail -s "EPCM Close FAILED" fc@novaprism.com epmautomate logout exit 1 fi
Groovy gives you full Java exception handling. Wrap any operation in try/catch, inspect the response body, throw a CalcException with a meaningful message that surfaces directly in the EPM Cloud job console and Task Manager alert system. No external monitoring required.
You can also write validation results back to a control member in the cube — Rule Balancing can then monitor that member and surface the error in the Finance Controller's dashboard before they even check email.
try { def resp = conn.post(calcUrl).body(body).asString() def job = parseJson(resp.body) // poll until complete while (job.status == "PROCESSING") { sleep(5000) job = parseJson(conn.get(statusUrl + job.jobId).asString().body) } if (job.status != "SUCCESS") { // write flag to cube for Rule Balancing visibility operation.grid.dataCellIterator("CALC_STATUS_FLAG").each { it.data = -1 // non-zero = Rule Balancing will flag it } throwCalcException("M01 failed: " + job.errorMessage) } } catch (Exception e) { throwCalcException("Groovy error: " + e.message) }
calculateModel runs the entire model — all 5 rule sets, all 18 rules, against the full POV. You cannot scope it to "only the entities where data changed this period." Every EPM Automate run is a full model run. For NovaPrism's M01, that's fine — $134M needs to be fully reallocated anyway. But for intra-month corrections or what-if scenarios, running the full model for a 3-cell change is expensive.
Groovy can read exactly which cells the user just modified in a form, dynamically generate a FIX statement scoped only to those entities and periods, and run a targeted Custom Calc against those intersections only. This is Groovy's original killer feature — and why Oracle built it.
For NovaPrism's M04 (Actual/WhatIf), a single headcount change in IS should trigger only the Finance and HR pool re-allocations, not IT (ticket-count driver is unchanged). Groovy can encode that logic. EPM Automate cannot.
// read which entities were modified in the current form session def changedEntities = operation.grid.dataCellIterator() .findAll { it.changed } .collect { it.getMemberName("Entity") } .unique() if (changedEntities.isEmpty()) return // generate targeted calc script — only affected entities def fixClause = changedEntities.collect { "\"${it}\"" }.join(",") def script = """ FIX(${fixClause}, "PCM_Input", "Actual", "Working", "Q1") /* recalc Finance+HR pools only */ "Finance_Pool_Staged" = ...; "HR_Pool_Staged" = ...; ENDFIX """ operation.application.getEssbaseServerConnection().calculate(script)
EPM Automate has 40+ commands purpose-built for bulk operations: addUsers, importMetadata, exportSnapshot, cloneEnvironment, assignRole. These are administrative operations that live outside the calculation layer. Running them from Groovy is technically possible but pointless — you'd call epmAutomate() from inside Groovy just to replicate what the CLI does natively.
For NovaPrism's quarterly dimension refresh (adding 5 new engagement codes to the Customer dimension), EPM Automate is the right tool: upload CSV → importMetadata → refreshCube. No Groovy needed.
Groovy's epmAutomate() method lets you call EPM Automate commands inline from a business rule. This is the hybrid approach — Groovy as the orchestrator, EPM Automate as the executor. It's useful for sequencing: Groovy validates data, then calls epmAutomate("exportSnapshot", [name:"pre-close-backup"]) before kicking off the calculation. But for pure admin tasks, this adds Groovy complexity for no gain.
// Groovy validates, then calls EPM Automate inline def poolTotal = operation.grid.getDataCell( "IT_Pool_Staged", "ES", "PCM_Input" ).data if (Math.abs(poolTotal - 52.0) > 0.1) { throwCalcException("IT pool validation failed: ${poolTotal}") } // snapshot before calculation — epmautomate called inline epmAutomate("exportSnapshot", [name: "pre-M01-Q1-FY25"]) // now trigger the model via REST // ... (REST call as shown in Round 1)
EPM Automate runs completely outside the EPCM Pipeline framework. It doesn't need Pipeline job type support — it calls the environment directly. calculateModel works. Full stop. No workaround needed.
This is why most EPCM production close pipelines are EPM Automate shell scripts rather than native Pipeline jobs: the Pipeline framework cannot call calculateModel directly (it only supports Calculation Manager business rules), so EPM Automate is the practical choice for scheduled close automation.
EPCM Pipelines can only call Calculation Manager business rules — not native EPCM rule sets directly. This is the documented limitation. The standard workaround: create a Groovy business rule in Calculation Manager that calls the EPCM REST API to trigger calculateModel. The Pipeline calls the Groovy rule; the Groovy rule calls the model. One extra hop, but it keeps everything inside the Pipeline orchestration framework.
When the Finance Controller wants a one-click Pipeline on the EPCM home page that runs load → calculate → copy → aggregate, the Groovy wrapper is the only way to include the calculation step natively.
/*RTPS: {ModelName} {Scenario} {Year} {Period}*/ // This rule is called by the Pipeline job step // It in turn calls the EPCM REST API to run the model def app = "NovaPrism_EPCM_PROD" def model = rtps.ModelName.value // runtime prompt = "M01" def conn = operation.application.getConnection("Local") def url = "/HyperionPlanning/rest/v3/applications/${app}/jobs" def resp = conn.post(url) .header("Content-Type", "application/json") .body(json([jobType:"CalculateModel", jobName:model, parameters:[awaitCompletion:true]])) .asString() if (resp.status != 200) throwCalcException(resp.body)
EPM Automate scripts are plain Bash or PowerShell files — they live in Git, they get code-reviewed, they run in GitHub Actions or Azure DevOps pipelines, they have change history. This is standard DevOps. For organisations with mature release management, EPM Automate scripts integrate with zero friction.
cloneEnvironment and importSnapshot enable promote-from-test-to-prod workflows that are scriptable, repeatable, and auditable without touching a browser.
Groovy rules live inside the EPM application. They can be exported as part of a Migration snapshot and imported into test/prod. But there's no native Git integration — you'd export the Calculation Manager rule to a text file, commit that, and import on the target. More friction than committing a .sh file.
That said, Groovy rules are promoted through the same LCM migration path as all other EPCM artifacts — so for teams using EPM Automate's importSnapshot for their release process, Groovy rules come along automatically.
EPM Automate runs on a schedule or when manually invoked. It has no awareness of what users are doing in the EPCM UI. If NovaPrism's Finance Controller submits new headcount drivers in a Smart View form, EPM Automate cannot detect that event and trigger a recalculation. You schedule the calculation for midnight. The Controller's change sits unprocessed until then.
A Groovy rule assigned to a form's "After Save" event fires the moment a user submits data. For NovaPrism, a Groovy rule on the headcount driver form can immediately validate the submission, update staging members, and trigger a targeted recalculation — all before the Finance Controller closes the tab. No schedule. No midnight batch window. No stale data.
This is the user-experience argument for Groovy: the model responds in seconds to data changes, rather than the user waiting until the next scheduled run.
The debate is a false binary. Every serious EPCM implementation uses all three approaches, layered:
LAYER 1 — EPM AUTOMATE (external scheduler, nightly/monthly close) → calculateModel, copyDataByPointOfView, exportSnapshot → Runs on schedule. DevOps-managed. Git-controlled. No browser needed. LAYER 2 — GROOVY WRAPPERS (Pipeline-callable business rules) → Groovy rule in Calc Manager wraps REST API calls to calculateModel → Makes model calculation available as a Pipeline job step → Adds pre-calculation validation (pool total check, driver sanity) LAYER 3 — GROOVY EVENT HANDLERS (user-driven, form-level) → After-save on driver forms: validate + targeted recalc → Dynamic FIX statements scoped to changed entities only → Write results back to control members for Rule Balancing visibility NOVAPRISM PRODUCTION ARCHITECTURE: Night of Day 1: EPM Automate → calculateModel M01 (full alloc) Day 2-4: Groovy form handler → targeted recalc on driver updates Day 4 CFO review: Pipeline (Groovy wrapper) → recalc + copy + aggregate Day 5 lock: EPM Automate → exportSnapshot (pre-lock backup)
The same 10-step close pipeline, written three ways. Each has different trade-offs for scheduling, error handling, and in-product integration.
#!/bin/bash # NovaPrism M01 Monthly Close — EPM Automate # Runs nightly on Day 1 of close via cron / Azure DevOps set -e # exit on any error PERIOD="Q1"; YEAR="FY2025" # 1. Authenticate epmautomate login "$EPM_USER" "$EPM_PWD_FILE" "$EPM_URL" # 2. Validate pool totals via export before calc epmautomate exportData "PreCalc_Validation_Export" epmautomate downloadFile "PreCalc_Validation_Export.zip" "./validation/" # ... external script validates CSV contents ... # 3. Snapshot before calculation epmautomate exportSnapshot "NovaPrism_PreClose_${PERIOD}_${YEAR}" # 4. Calculate M01 — all 5 rule sets, 18 rules epmautomate calculateModel "NovaPrism_EPCM_PROD" "M01" if [ $? -ne 0 ]; then mail -s "EPCM M01 FAILED" fc@novaprism.com; exit 1 fi # 5. Copy PCM_CLC → PCM_REP (publish results) epmautomate copyDataByPointOfView "PCM_CLC" "PCM_REP" \ "Actual#Working#${PERIOD}#${YEAR}" # 6. Aggregate PCM_REP for Smart View query performance epmautomate executeAggregationProcess "PCM_REP" # 7. Export calc statistics before auto-purge epmautomate exportData "CalcStats_${PERIOD}_${YEAR}" epmautomate downloadFile "CalcStats_${PERIOD}_${YEAR}.zip" "./archive/" # 8. Verify pool balances (Rule Balancing export) epmautomate exportData "RuleBalancing_Export_${PERIOD}" # 9. Clean up inbox epmautomate deleteFile "CalcStats_${PERIOD}_${YEAR}.zip" # 10. Disconnect epmautomate logout echo "M01 close complete — ${PERIOD} ${YEAR}"
✅ Git-versionable · ✅ DevOps-schedulable · ✅ Zero Oracle install footprint needed in pipeline · ✅ calculateModel works natively · ❌ No in-app visibility without monitoring · ❌ Validation is external · ❌ Cannot scope to changed cells only
/*RTPS: {Period} {Year}*/ // NovaPrism M01 Monthly Close — Groovy implementation // Assigned to Task Manager job; callable from Pipeline via job step def period = rtps.Period.value def year = rtps.Year.value def app = "NovaPrism_EPCM_PROD" def conn = operation.application.getConnection("Local") def base = "/HyperionPlanning/rest/v3/applications/${app}" // STEP 1: Pre-calc validation — read pool total from cube def itPool = operation.grid.getDataCell( "IT_Pool_Staged","ES","PCM_Input","Actual",year,period ).data if (Math.abs(itPool - 52.0) > 0.5) throwCalcException("IT pool validation failed. Expected ~52M, got ${itPool}M") // STEP 2: Snapshot before calculation (calling EPM Automate inline) epmAutomate("exportSnapshot", [name: "NovaPrism_PreClose_${period}_${year}"]) // STEP 3: Run M01 via REST API — await completion def jobResp = conn.post("${base}/jobs") .header("Content-Type","application/json") .body(json([jobType:"CalculateModel",jobName:"M01", parameters:[Period:period,Year:year,awaitCompletion:true]])) .asString() def job = parseJson(jobResp.body) if (job.status != "SUCCESS") throwCalcException("M01 failed: " + job.errorMessage) // STEP 4: Copy PCM_CLC → PCM_REP via REST def pov = "Actual#Working#${period}#${year}" conn.post("${base}/jobs") .body(json([jobType:"CopyData",parameters:[ sourceCube:"PCM_CLC",targetCube:"PCM_REP",POV:pov,awaitCompletion:true ]])).asString() // STEP 5: Aggregation for query performance conn.post("${base}/jobs") .body(json([jobType:"AggregateData",jobName:"PCM_REP",awaitCompletion:true])) .asString() // STEP 6: Write completion flag to cube for Rule Balancing visibility operation.grid.dataCellIterator("CLOSE_STATUS_FLAG").each { it.data = 1 } println "M01 close complete — ${period} ${year}"
✅ Pipeline-callable · ✅ In-product validation before calc · ✅ Cube write-back for status visibility · ✅ Runtime prompts for Period/Year · ❌ REST API polling adds complexity · ❌ No native Git (export/import via LCM) · ❌ Harder for non-Groovy team members to maintain
/*RTPS: {Period} {Year}*/ // HYBRID ARCHITECTURE — best of both // Groovy: validation, dynamic logic, event handling, status write-back // EPM Automate (inline): bulk ops, snapshots, aggregation def period = rtps.Period.value def year = rtps.Year.value // ── GROOVY: Pre-calc validation ────────────────────────────────── ["IT_Pool_Staged":52.0, "Facilities_Pool_Staged":38.0, "Finance_Pool_Staged":28.0, "HR_Pool_Staged":16.0].each { pool, expected -> def actual = operation.grid.getDataCell(pool,"ES","PCM_Input").data if (Math.abs(actual - expected) > 0.5) throwCalcException("Pool validation failed: ${pool} = ${actual}, expected ${expected}") } // ── EPM AUTOMATE INLINE: snapshot before calc ───────────────────── epmAutomate("exportSnapshot", [name: "PreClose_${period}_${year}"]) // ── EPM AUTOMATE INLINE: run the model ─────────────────────────── // calculateModel is idiomatic here — no REST API needed epmAutomate("calculateModel", [appName: "NovaPrism_EPCM_PROD", modelName: "M01"]) // ── EPM AUTOMATE INLINE: publish + aggregate ────────────────────── epmAutomate("copyDataByPointOfView", [ sourceCube: "PCM_CLC", targetCube: "PCM_REP", POV: "Actual#Working#${period}#${year}" ]) epmAutomate("executeAggregationProcess", [cubeName: "PCM_REP"]) // ── GROOVY: Write completion status to cube ─────────────────────── operation.grid.dataCellIterator("CLOSE_STATUS_FLAG").each { it.data = 1 } operation.grid.dataCellIterator("CLOSE_TIMESTAMP").each { it.data = System.currentTimeMillis() / 1000L } println "Hybrid close complete — ${period} ${year}"
✅ In-product validation · ✅ calculateModel via epmautomate() — no REST polling · ✅ Pipeline-callable · ✅ Cube write-back for status · ✅ No external server needed · ⚠️ Requires Groovy + EPM Automate knowledge on the team · ⚠️ epmAutomate() call is synchronous — blocks until complete
Not a preference question. A requirements question. Each scenario has a right answer.
| Scenario | Recommended | Reason |
|---|---|---|
| Scheduled nightly close (Day 1 of month-end) | EPM AUTOMATE | Cron-schedulable, DevOps-managed, calculateModel is native. No Groovy needed. |
| Trigger model calculation from a Pipeline job step | GROOVY | Pipeline only supports Calculation Manager rules. Groovy wrapper calling REST API is the documented workaround. |
| Finance Controller submits driver data — recalculate immediately | GROOVY | Form "After Save" event handler. EPM Automate has no event awareness. |
| Recalculate only the entities whose headcount changed | GROOVY | Dynamic FIX statement from dataCellIterator().findAll{it.changed}. EPM Automate runs the full model only. |
| Add 5 new Customer dimension members before close | EPM AUTOMATE | importMetadata + refreshCube. Pure admin — Groovy overhead is unnecessary. |
| Bulk provision 20 new analyst User accounts | EPM AUTOMATE | addUsers + assignRole from CSV. The only practical approach for bulk provisioning. |
| Pre-calculation validation (pool totals, driver sanity) | GROOVY | Reads live cube data. Can abort the pipeline with a meaningful error before wasting a full model run. |
| Environment clone (test → production promotion) | EPM AUTOMATE | cloneEnvironment. Purely administrative, no Groovy equivalent. |
| Snapshot before any major change | EITHER | EPM Automate: exportSnapshot in a Bash script. Groovy hybrid: epmAutomate("exportSnapshot",...) inline. Both work. |
| Write computation results back to cube cells | GROOVY | operation.grid.dataCellIterator(). EPM Automate cannot write individual cell values. |
| Full close pipeline (load + calc + publish + aggregate) | HYBRID | Groovy handles validation and status write-back. EPM Automate (inline or external) handles the bulk steps. |
| Export calc statistics before auto-purge | EPM AUTOMATE | exportData + downloadFile. Groovy has no file download capability. |
| Validate data in a form before the user can submit | GROOVY | Groovy "Before Save" event. EPM Automate is blind to form interactions. |
| Run model calculation every 15 minutes during close window | EPM AUTOMATE | External cron schedule. No need for Groovy unless you want in-product status visibility. |
EPM Automate commands + Groovy API equivalents — search or filter by category.
The 1Z0-1082-25 exam tests automation knowledge through scenario questions. These are the traps.
calculateModel. The Pipeline calls the Groovy rule; the Groovy rule calls the model.copyPov is the Planning command. EPCM uses copyDataByPointOfView. Easy to confuse when candidates study both products. The Groovy equivalent calls jobType: "CopyData" via REST.refreshCube is required after metadata changes (importing new dimension members, structure changes). Data loads do not require a cube refresh. Running it unnecessarily adds 2–5 minutes to the close pipeline for no benefit.getConnection("Local")) or external APIs (Google, Slack, approval systems). The connection(url, user, password) method enables any HTTP call. This is Groovy's most powerful feature beyond calc scripting.epmAutomate() is synchronous — the script waits for the EPM Automate command to complete before continuing. This is why the hybrid approach can safely call epmAutomate("calculateModel",...) and then immediately read the results — the model is guaranteed complete when the next line executes.epmAutomate() method in Groovy invokes EPM Automate commands without requiring a local EPM Automate installation. This is described in Oracle documentation as "Running Commands without Installing EPM Automate." The Groovy runtime handles the execution inside Oracle Cloud.