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.
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 member — IT_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.
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.
| Requirement | Allocation Rule | Custom Calc Rule |
|---|---|---|
| Distribute a pool to cost objects using a driver ratio | Best fit | Possible but over-engineered |
| Aggregate multiple GL members into a single pool total | Cannot do | Best fit |
| Write PCM_Allocated_In and PCM_Allocated_Out automatically | Automatic | Manual — you write every member explicitly |
| Conditional logic (IF/THEN — different treatment for different entities) | Cannot do | Best fit |
| Stage an intermediate value for a downstream rule to read | Cannot do | Best fit |
| Apply a fixed percentage split (not driver-based) | Possible with manual driver members | Simpler |
| Offset / zero-out a cost centre after allocation | Cannot do | Possible — but use dedicated Offset rule type |
| Read a value written by a prior rule in a Serial set | Yes | Yes |
| Handle sparse data efficiently (skip empty cells) | Automatic | Requires NONEMPTYTUPLE — manual |
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.
IT_Pool_Staged at the ES Entity × PCM_Input intersection. This creates the clean $52M pool source that seq:40 reads.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.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.
/* 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
/* 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
/* 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
/* 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 element | Purpose | NovaPrism example |
|---|---|---|
FIX(…) ENDFIX | Scope the calculation to specific member intersections — essential for performance | FIX("ES", "PCM_Input") — only runs against Enterprise Support at PCM_Input |
NONEMPTYTUPLE(dim) | Skip empty cells in the named dimension — prevents brute-force iteration | NONEMPTYTUPLE("Entity") — only processes DS, IS, CL, ES where data exists |
member->dimension | Cross-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/ENDIF | Conditional logic — apply different arithmetic for different members | Different overhead surcharge rates per division |
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.
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 tool | What it restricts | When to use | NovaPrism usage |
|---|---|---|---|
FIX | Restricts calculation to specific named members across one or more dimensions | When you know exactly which members the rule should touch — the source entity, the PCM_Balance member, the Scenario | FIX("ES", "PCM_Input", "Actual", "Working") — locks the pool aggregation to one entity + one balance member + one POV |
NONEMPTYTUPLE | Within the FIX scope, skips dimension combinations where data is absent | When the FIX scope still spans many combinations and most are empty — especially Customer (60 engagements) or Period | NONEMPTYTUPLE("Entity") in the driver ratio rule — ensures only DS, IS, CL are processed, not every possible entity combination |
| Both together | FIX locks to known members; NONEMPTYTUPLE then skips sparse combinations within that locked space | Complex rules that touch multiple entities and periods — the production standard for NovaPrism | All three seq:10 Custom Calc rules use both. The combination reduces 24M potential cell evaluations to under 10. |
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.
IT_Pool_Staged correctly, seq:40 allocates $0 of IT cost to every division. Silently.
IT_Pool_Staged. State why FIX must name both "ES" and "PCM_Input" — not just one of them.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.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".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.
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.
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.
| Concept | Exam answer | Implementation reality |
|---|---|---|
| When to use Custom Calc | Aggregation, conditional logic, staging, multi-step arithmetic — anything an Allocation rule cannot express | Most real models use Custom Calc sparingly — 2–4 rules per model is typical. Seq:10 is the classic use case. |
| FIX purpose | Restricts calculation to specific named member intersections — required for performance in multi-dimensional ASO cubes | Every Custom Calc rule in production must have FIX. An unscoped rule in a large cube is a close-window incident waiting to happen. |
| NONEMPTYTUPLE purpose | Skips empty cell combinations within the FIX scope — iterates only over dimension members where data exists | Add NONEMPTYTUPLE when FIX alone still spans too many combinations, especially on sparse dimensions like Customer (60 engagements). |
| PCM_Balance in Custom Calc | Must 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 reference | member->dimension syntax — reads a specific member from a named dimension regardless of current context | Used constantly in staging rules where you read ES source values while iterating over DS/IS/CL targets. |