Academy of Profit Center
Hall 5 of 9 · Workshop of Precision
Academy of Profit Center — Your Journey
Workshop of Precision
Hall 5 of 9 · Academy of Profit Center · EPCM Study Tour

The precision instrument

Allocation rules handle 80% of what EPCM needs to do. Custom Calculation rules handle the other 20% — the aggregations, conditional logic, and multi-step staging that no Allocation rule can express. This hall teaches you the syntax, the patterns, the performance traps, and the one operator — NONEMPTYTUPLE — that separates fast Custom Calc rules from catastrophically slow ones.

Conceptual Technical Interactive Lab Exam: 1Z0-1082-25
📐
Conceptual

When Allocation rules aren't enough

An Allocation rule has one job: take a pool total, apply a driver ratio, write PCM_Allocated_In and PCM_Allocated_Out. The moment your logic needs anything more — aggregation, conditional branching, multi-step staging — you need a Custom Calculation rule.

In NovaPrism's model, seq:10 (IT Cost Pool Formation) cannot be an Allocation rule. It needs to read 120+ GL account members tagged under the IT cost group in the Account dimension alt hierarchy and sum them into a single stageable memberIT_Pool_Staged — so that seq:40's Allocation rule has a clean, single-source pool to read from. Allocation rules don't aggregate. Custom Calc rules do.

Custom Calculation rules in EPCM use a subset of Oracle Essbase calculation script language. If you have written Essbase calc scripts before, most of the syntax is familiar. If you haven't, the key things to understand are: member names are case-sensitive, the FIX statement limits which cells the script touches (critical for performance), and the NONEMPTYTUPLE operator is how you avoid calculating against empty intersections.

🔧
The division of labour: Use an Allocation rule when you have a pool total and a driver ratio and want EPCM to manage the PCM_Balance members automatically. Use a Custom Calc rule when you need to build the pool total first, apply conditional logic, or write to specific PCM_Balance members yourself with full control over the arithmetic. In NovaPrism's model, 15 of 18 rules are Allocation or Offset — only 3 are Custom Calc, all in seq:10. That ratio is typical.
🗂️
Conceptual

Allocation rule vs Custom Calc — the decision matrix

The wrong choice doesn't break the model — it just makes it harder to maintain and diagnose. Use this matrix before writing any rule.

RequirementAllocation RuleCustom Calc Rule
Distribute a pool to cost objects using a driver ratioBest fitPossible but over-engineered
Aggregate multiple GL members into a single pool totalCannot doBest fit
Write PCM_Allocated_In and PCM_Allocated_Out automaticallyAutomaticManual — you write every member explicitly
Conditional logic (IF/THEN — different treatment for different entities)Cannot doBest fit
Stage an intermediate value for a downstream rule to readCannot doBest fit
Apply a fixed percentage split (not driver-based)Possible with manual driver membersSimpler
Offset / zero-out a cost centre after allocationCannot doPossible — but use dedicated Offset rule type
Read a value written by a prior rule in a Serial setYesYes
Handle sparse data efficiently (skip empty cells)AutomaticRequires NONEMPTYTUPLE — manual
⚠️
The most dangerous Custom Calc mistake: Writing a Custom Calc rule without a FIX statement or NONEMPTYTUPLE operator in a large ASO cube. EPCM will attempt to calculate every possible cell intersection — including millions of empty ones. A rule that runs in 8 seconds with proper scoping can take 45 minutes without it. NovaPrism's cube has 11 dimensions × thousands of members each — the combinatorial explosion is enormous. Every Custom Calc rule you write must be scoped.
🏢
Conceptual

NovaPrism's three Custom Calc rules

All three Custom Calc rules live in seq:10 (IT Cost Pool Formation, Serial). They run in order: aggregate first, validate second, stage ratios third.

Seq:10 · Rule 1
GL Account Aggregation
Reads 120+ GL account members → writes IT_Pool_Staged
Uses a FIX on the IT cost group in the Account alt hierarchy to read all IT-tagged GL accounts and sum them into IT_Pool_Staged at the ES Entity × PCM_Input intersection. This creates the clean $52M pool source that seq:40 reads.
Why not Allocation? Allocation rules read a single source member. This rule must aggregate hundreds of source members into one target. Only Custom Calc can do this.
Seq:10 · Rule 2
Pool Validation
Reads IT_Pool_Staged → writes a validation flag member
Confirms IT_Pool_Staged equals the expected $52M from NP.costPools.IT.amount. If the value differs by more than the rounding tolerance, writes a non-zero flag to a control member that Rule Balancing (Hall 7) monitors. This is NovaPrism's data quality gate before any allocation runs.
Must be Serial Rule 2 in seq:10. It reads what Rule 1 just wrote. If seq:10 were Parallel, this rule could read a zero pool total.
Seq:10 · Rule 3
Driver Ratio Staging
Computes ticket-count ratios → writes three driver staging members
Calculates DS (1,240/4,540 = 27.3%), IS (2,890/4,540 = 63.6%), CL (410/4,540 = 9.0%) and writes each ratio to a dedicated driver staging member. Seq:40's Allocation rule reads these pre-computed ratios rather than recomputing them inline — cleaner logic, easier audit.
Also Serial Rule 3: reads the total from Rule 1. Could not exist in a Parallel set with Rules 1 or 2.
⚙️
Technical

Custom Calc syntax — the fundamentals

EPCM Custom Calc rules use Essbase calc script language with EPCM-specific member references. The four constructs you must know: FIX, NONEMPTYTUPLE, member cross-dimension references, and PCM_Balance member writes.

Pattern 1 — Basic FIX scope + member write
/* Aggregate IT GL accounts into IT_Pool_Staged */
/* Scope: ES entity only, PCM_Input balance, current period/scenario/version */

FIX("ES", "PCM_Input")
  "IT_Pool_Staged" = @SUMRANGE("Account", @DESCENDANTS("IT_Cost_Group"));
ENDFIX
📋
What FIX does: The FIX statement tells EPCM to run the enclosed calculation only for the cell intersections where all the named members appear. In the example above: only for the ES entity, only at PCM_Input. Every other Entity × PCM_Balance combination is untouched. Without FIX, the calculation runs against every cell in the cube slice — including millions of empty intersections.
Pattern 2 — NONEMPTYTUPLE to skip empty cells
/* Stage driver ratios — only where source data exists */

FIX("IT_Pool_Staged", "PCM_Input")
  NONEMPTYTUPLE("Entity")
    "IT_Driver_Ratio" =
      "IT_Tickets_Division" / "IT_Tickets_Total";
  ENDNONEMPTYTUPLE
ENDFIX
NONEMPTYTUPLE: Iterates only over the dimension combinations where at least one specified member contains actual data. In NovaPrism's case, the Entity dimension has only 4 members with data (DS, IS, CL, ES) out of potentially many more combinations. NONEMPTYTUPLE skips all the empty Entity×Period×Scenario intersections automatically. Without it, the script loops through every possible combination — most of which are empty — and the runtime multiplies accordingly.
Pattern 3 — Conditional logic (IF/ELSEIF/ENDIF)
/* Apply different treatment to IS vs other divisions */

FIX("PCM_Input")
  NONEMPTYTUPLE("Entity")
    IF (@ISMBR("IS"))
      "Overhead_Surcharge" = "Direct_Cost" * 0.08;
    ELSEIF (@ISMBR("DS"))
      "Overhead_Surcharge" = "Direct_Cost" * 0.05;
    ELSE
      "Overhead_Surcharge" = "Direct_Cost" * 0.03;
    ENDIF
  ENDNONEMPTYTUPLE
ENDFIX
⚠️
PCM_Balance writes are your responsibility: In an Allocation rule, EPCM automatically writes PCM_Allocated_In to the target and PCM_Allocated_Out to the source. In a Custom Calc rule, you write every PCM_Balance member explicitly. If you write to a target member but forget to write the corresponding PCM_Allocated_Out to the cost centre, the model will be unbalanced — ES will show a residual balance that Rule Balancing flags as an error. This is the most common Custom Calc mistake in production implementations.
Pattern 4 — Writing PCM_Balance members explicitly (the right way)
/* Correct: write both sides of the allocation */

FIX("Actual", "Working")
  /* Write the allocated-in amount to receiving divisions */
  FIX("PCM_Allocated_In")
    NONEMPTYTUPLE("Entity")
      "IT_Overhead"->"DS" = "IT_Pool_Staged"->"ES" * 0.273;
      "IT_Overhead"->"IS" = "IT_Pool_Staged"->"ES" * 0.636;
      "IT_Overhead"->"CL" = "IT_Pool_Staged"->"ES" * 0.091;
    ENDNONEMPTYTUPLE
  ENDFIX
  /* Write the allocated-out amount to ES (the source) */
  FIX("PCM_Allocated_Out", "ES")
    "IT_Pool_Staged" = "IT_Pool_Staged"->"PCM_Input";
  ENDFIX
ENDFIX

Cross-dimension member references

The -> operator pins a member from a specific dimension, regardless of the current FIX context. In the example above, "IT_Pool_Staged"->"ES" reads the IT_Pool_Staged value specifically for the ES entity — even inside a FIX that iterates across all entities. This is how you reference a source member while writing to a target member in the same script block.

Syntax elementPurposeNovaPrism example
FIX(…) ENDFIXScope the calculation to specific member intersections — essential for performanceFIX("ES", "PCM_Input") — only runs against Enterprise Support at PCM_Input
NONEMPTYTUPLE(dim)Skip empty cells in the named dimension — prevents brute-force iterationNONEMPTYTUPLE("Entity") — only processes DS, IS, CL, ES where data exists
member->dimensionCross-dimension member reference — pin a member from a specific dimension"IT_Pool_Staged"->"ES" — read IT_Pool_Staged specifically at the ES entity
@SUMRANGE(dim, set)Sum all members in a set within a dimension@SUMRANGE("Account", @DESCENDANTS("IT_Cost_Group"))
@DESCENDANTS(member)Return all descendants of a parent member@DESCENDANTS("IT_Cost_Group") — all 120+ IT GL accounts
@ISMBR(member)Test whether the current calculation context includes a specific member@ISMBR("IS") — true when calculating the IS entity row
IF/ELSEIF/ELSE/ENDIFConditional logic — apply different arithmetic for different membersDifferent overhead surcharge rates per division
Performance

The performance cost of unscoped Custom Calc

The same calculation. The same result. Wildly different runtimes. Select a rule variant below to see the impact of scoping on NovaPrism's IT pool aggregation rule.

NovaPrism seq:10 Rule 1 — Runtime comparison Select a variant to inspect
Context: NovaPrism's PCM_CLC cube — 11 dimensions, ~8,400 entity members across all hierarchies, 1,200 Account members, 4 Scenarios × 4 Versions × 5 Periods = 80 POV combinations. The IT pool aggregation rule must run in every M01 calculation.
⚠ Without scoping (Variant A)
Cells evaluated~24 million
Empty cells processed~23.95 million
Estimated runtime38–46 minutes
Correct result?Yes — eventually
The calculation is mathematically correct — it just evaluates 24 million cells to do what 4 cells require. In production, this blocks every other model calculation during the window.
✓ With FIX + NONEMPTYTUPLE (Variant C)
Cells evaluated4
Empty cells processed0
Estimated runtime<2 seconds
Correct result?Yes — fast
FIX restricts to the ES entity + PCM_Input balance. NONEMPTYTUPLE then skips the handful of empty period/version combinations. Only the 4 cells that actually hold data are touched.
The practitioner rule: Every Custom Calc rule you write in EPCM starts with FIX. If FIX alone doesn't scope it tightly enough, add NONEMPTYTUPLE on the dimension with the most sparse data (usually Entity or Customer). Test runtime in sandbox before promoting to production — a rule that runs 40 minutes in sandbox will run 40 minutes in production during the monthly close window, blocking everything downstream.
🎯
Performance

Choosing the right scope strategy

FIX and NONEMPTYTUPLE are not interchangeable — they solve different parts of the scoping problem. Use this guide to decide which you need.

Scoping toolWhat it restrictsWhen to useNovaPrism usage
FIXRestricts calculation to specific named members across one or more dimensionsWhen you know exactly which members the rule should touch — the source entity, the PCM_Balance member, the ScenarioFIX("ES", "PCM_Input", "Actual", "Working") — locks the pool aggregation to one entity + one balance member + one POV
NONEMPTYTUPLEWithin the FIX scope, skips dimension combinations where data is absentWhen the FIX scope still spans many combinations and most are empty — especially Customer (60 engagements) or PeriodNONEMPTYTUPLE("Entity") in the driver ratio rule — ensures only DS, IS, CL are processed, not every possible entity combination
Both togetherFIX locks to known members; NONEMPTYTUPLE then skips sparse combinations within that locked spaceComplex rules that touch multiple entities and periods — the production standard for NovaPrismAll three seq:10 Custom Calc rules use both. The combination reduces 24M potential cell evaluations to under 10.
🔬
Lab Scenario

Write NovaPrism's seq:10 Custom Calc rules

Hall 4 designed the rule sets. This workshop writes the actual scripts. Seq:10 is Serial with 3 Custom Calc rules. You are writing all three before the Q1 close window opens.

Mission Brief — Seq:10 Custom Calc Scripts
Situation: NovaPrism's Oracle implementation partner has configured the application, dimensions, and models. Seq:10 (IT Cost Pool Formation) is the only ruleset that requires Custom Calc rules — the other four rulesets use Allocation and Offset rules handled by the platform. You must write the three Custom Calc scripts for seq:10 before the Finance Controller can run M01 for the first time. If seq:10 doesn't stage IT_Pool_Staged correctly, seq:40 allocates $0 of IT cost to every division. Silently.
Write Rule 1 — GL Account Aggregation. Use FIX to restrict to ES entity + PCM_Input balance. Use @SUMRANGE and @DESCENDANTS to aggregate all members under the "IT_Cost_Group" Account hierarchy parent into IT_Pool_Staged. State why FIX must name both "ES" and "PCM_Input" — not just one of them.
Write Rule 2 — Pool Validation. After Rule 1 writes IT_Pool_Staged, Rule 2 must confirm the value equals $52M (from NP.costPools.IT.amount). Write the conditional: if the value differs by more than $0.10, write a non-zero flag to IT_Pool_Validation_Flag. Explain why this rule must be Serial Rule 2 and cannot run in a Parallel set with Rule 1.
Write Rule 3 — Driver Ratio Staging. Using the ticket counts from NP.drivers.itTickets (DS: 1,240; IS: 2,890; CL: 410; total: 4,540), write three ratios to driver staging members. Use NONEMPTYTUPLE on the Entity dimension. Explain why pre-computing the ratios here — rather than computing them inline inside seq:40's Allocation rule — is a better design.
Diagnose the silent failure scenario. The Finance Controller runs M01 and notices DS receives $0 IT allocation, IS receives $0, CL receives $0. The Facilities and HR/Finance allocations ran correctly. Seq:40 shows "completed successfully" with no errors. Walk through the diagnosis: which rule failed, what went wrong in the script, and what the correct fix is.
Write the performance brief. The sandbox run of Rule 1 without FIX took 43 minutes on NovaPrism's 11-dimension cube. With FIX("ES","PCM_Input") it takes 4 seconds. Explain in non-technical language why removing FIX causes this difference, why the result is mathematically identical either way, and what the monthly close governance implication is (hint: the M01 close window is 2 hours).
0 of 5 tasks completed
🎓
Dean's hint on task 4 (silent failure diagnosis): The most likely cause is that seq:10 Rule 1 ran but wrote to the wrong PCM_Balance member — e.g., wrote to PCM_Output instead of PCM_Input. Seq:40's Allocation rule reads IT_Pool_Staged at PCM_Input. If Rule 1 wrote to the wrong balance member, the value at PCM_Input is zero, seq:40 computes 0 × ratio = 0 for every division, and reports success because no calculation error occurred — zero multiplied by anything is still zero. The fix: correct the FIX target in Rule 1 to explicitly name "PCM_Input".
📝
Exam Alignment · 1Z0-1082-25

What Workshop of Precision covers on the exam

Hall 5 maps into the fourth and fifth syllabus topics: Managing Rule Sets and Rules (continued), and Calculating Models. Custom Calc rules appear in 3–5 questions across the 50-question exam — always as part of a "when to use which rule type" or "what does this script actually do" question.

📝 Syllabus coverage — Custom Calculation rules
What the exam tests on Custom Calc:

1. When to use Custom Calc vs Allocation rule — the exam will describe a scenario and ask which rule type is appropriate. Know that Custom Calc is required for aggregation, conditional logic, and staging; Allocation is required for driver-based distribution.

2. FIX statement purpose and syntax — know that FIX restricts the calculation to named members and is mandatory for performance. Know the ENDFIX closing syntax.

3. NONEMPTYTUPLE purpose — know that it skips empty cell combinations within the FIX scope, and that it takes a dimension name as its argument.

4. PCM_Balance responsibility — know that in a Custom Calc rule (unlike an Allocation rule), the implementer must write both PCM_Allocated_In (target) and PCM_Allocated_Out (source) explicitly. Forgetting the PCM_Allocated_Out write creates an unbalanced model.
⚠️ The four most-tested traps in this section
Trap 1: "FIX and NONEMPTYTUPLE are interchangeable."
FALSE. FIX restricts to named members. NONEMPTYTUPLE skips empty combinations within the FIX scope. They solve different problems and are often used together.

Trap 2: "A Custom Calc rule automatically manages PCM_Allocated_In and PCM_Allocated_Out."
FALSE. Only Allocation rules do this automatically. In Custom Calc, you write every PCM_Balance member explicitly. Missing the Allocated_Out write is the most common production error.

Trap 3: "Removing FIX from a Custom Calc rule makes it calculate more accurately."
FALSE. The result is identical — removing FIX only makes it slower, sometimes catastrophically. FIX is a performance scope, not a filter that excludes correct data.

Trap 4: "Custom Calc rules can only read from members in the same dimension."
FALSE. The -> cross-dimension member reference syntax allows a Custom Calc rule to read from any dimension intersection explicitly, regardless of the current FIX context.
0/5
Hall 5 Exam Score
ConceptExam answerImplementation reality
When to use Custom CalcAggregation, conditional logic, staging, multi-step arithmetic — anything an Allocation rule cannot expressMost real models use Custom Calc sparingly — 2–4 rules per model is typical. Seq:10 is the classic use case.
FIX purposeRestricts calculation to specific named member intersections — required for performance in multi-dimensional ASO cubesEvery Custom Calc rule in production must have FIX. An unscoped rule in a large cube is a close-window incident waiting to happen.
NONEMPTYTUPLE purposeSkips empty cell combinations within the FIX scope — iterates only over dimension members where data existsAdd NONEMPTYTUPLE when FIX alone still spans too many combinations, especially on sparse dimensions like Customer (60 engagements).
PCM_Balance in Custom CalcMust be written explicitly by the implementer — both Allocated_In (target) and Allocated_Out (source)Create a personal checklist: every Custom Calc that moves money must have both sides. Rule Balancing will catch the miss, but only after you've run the full model.
Cross-dimension referencemember->dimension syntax — reads a specific member from a named dimension regardless of current contextUsed constantly in staging rules where you read ES source values while iterating over DS/IS/CL targets.