Reporting · 2022

Dynamic Cube Views + XFBR-driven formatting for stakeholder reporting

OneStreamCube ViewsXFBR

Quick Facts

  • Industry: Global loyalty / membership platform
  • Role: Senior Consultant — Reporting workstream
  • Timeline: ~5 months across two reporting cycles
  • Team: Solo build, with two reporting analysts validating each iteration against live stakeholder packs
  • Impact: Dimensional changes that previously rippled through dozens of rows in dozens of reports now propagate automatically. Per-report XFBR ad-hoc rules consolidated into a shared library. Formatting variance across the pack eliminated.

Overview

The cubes change every quarter; the reports shouldn't have to. A global loyalty and membership platform was running their stakeholder reporting on OneStream Cube Views, and dimensional structures moved constantly — new entities, new accounts, new loyalty programmes region by region. Each shift forced the reporting team to walk every report and patch row references, extenders, and formatting by hand. We rebuilt the reporting layer so reports read the dimension state at runtime, consolidated the ad-hoc XFBR rules into a shared library, and replaced fragile per-report formatting hacks with a single generic parameter solution driven by row-name suffixes. The reports stopped needing edits when the dimensions moved.

The Problem

The reporting pack was correct, but static in a world that wasn't. Three pain points compounded each other.

  • Every dimensional change rippled through the pack. When a new entity landed in the consolidation tree, the analyst opened each affected Cube View and added the row by hand, then chased the formatting (indent, bold rollups, italics for eliminations, bracketed negatives for credit accounts) until it matched. A typical new-programme launch touched 30+ reports and burned the better part of a sprint.
  • XFBR rules were ad-hoc and inconsistent. Dashboard XFBR rules had grown up per report, written by whichever analyst was building that view. Two reports asked the same conceptual question ("what's the active scenario for this user?") through three different paths, with three different fallbacks. The bugs were the worst kind: the report rendered, the numbers looked plausible, but the scenario header was a sprint behind. We chased four of those over the previous cycle.
  • Row-name suffixes were doing real work, invisibly. The team had discovered organically that you could stuff metadata into the row-name string — _B for bold, _I for indent-one, _PCT for percentage. It worked, kind of, until two reports used _B differently, or until an analyst renamed a member and accidentally changed its formatting. The convention was load-bearing and undocumented; nobody trusted it, and everybody used it.

The brief: make the reports adapt to the dimensions automatically, consolidate the rule sprawl, and formalise the row-name-suffix trick. No rewrite of the underlying cubes — the reporting layer had to do the work.

Process

Phase 1 — Template Cube Views that read the dimension state live

The first move was to stop hard-coding member lists. A standard Cube View lists its rows explicitly: row 1 is EntityA, row 2 is EntityB, and so on. When EntityC arrives, the report doesn't notice unless an analyst notices first.

The fix was to rebuild the high-traffic reports as Template Cube Views: views whose row and column definitions are expressions that resolve against the live dimension state at render time, not snapshots taken at design time. The row spec for an entity report became "all descendants of TotalEntities whose IsActive property is true," evaluated as it exists when the user clicks Refresh. A new entity in the right place with the right properties appears on the next render, no human touch.

The same pattern applied to account rows (descendants of OperatingIncome where account type ≠ NoInput) and to programme rollups (a custom UD expression that walked the programme tree by region). Where a report needed to break out a sub-tree differently, the template syntax let us compose multiple dimension expressions inside one Cube View rather than maintain two parallel reports.

For each report we converted, we ran the legacy version and the template version side-by-side against three historical periods and byte-compared the output. About a dozen edge cases surfaced — mostly accounts excluded in the legacy report for reasons that had stopped being true two years earlier — and we worked them through with the reporting analysts before flipping the switch.

Phase 2 — Consolidate the XFBR and extender layer

With the row definitions stabilised, we turned to the rule sprawl. The plan was to identify every distinct XFBR rule and Cube View extender in production, group them by what they were actually doing (not by name), and refactor the duplicates into a shared library.

The audit surfaced about forty rules. After grouping, they collapsed into nine shared functions: scenario, period, and entity-context resolution; currency formatting; conditional row visibility; signage flipping for credit accounts; header-text composition; drill-back URL construction; and a generic "this user's default view" resolver. Each was rewritten once with a clear contract (named parameters in, typed value out, documented fallback) and dropped into a shared Business Rule every report could call.

The Cube View extenders followed the same pattern. The team had been using extenders to inject conditional shading, suppress empty rows, and stamp reports with run-time metadata — same conceptual work, slightly different shapes across reports. We pulled the common logic into a single library, parameterised the per-report differences, and replaced inline extender code with thin calls. The next time we needed to change how the scenario header rendered — which used to be a forty-report find-and-replace — we changed it in one place.

Phase 3 — Formalise the row-name-suffix convention

The row-name-suffix trick was the most interesting part of the project. The team had stumbled onto a genuinely useful pattern; the only thing wrong with it was that it wasn't a standard. Our job was to make the standard explicit.

We defined a generic parameter solution: a single XFBR rule that parsed any row name for a structured suffix grammar and emitted the corresponding formatting directives. The grammar was small and composable. A suffix like _B_I2_PCT meant "bold, indent two, format as percentage." The parser was strict — unknown tokens raised at render time rather than failing silently — and the token vocabulary was documented in a single page the reporting team owned.

The grammar covered everything the ad-hoc suffixes had been doing (_B bold, _I[n] indent depth, _PCT percentage, _BR bracketed negatives, _HR horizontal rule above, _TOT total-row styling) plus a few we added once the team realised they could ask for behaviour that had previously required a custom Cube View. The renderer applied the directives consistently across every Cube View that pulled in the shared XFBR layer.

Dynamic calculations handled the reports that didn't fit a standard rollup — comparative views with cross-period logic, programme-level attribution allocating shared cost lines, KPI cards needing guarded divisions across sparse intersections. The calc logic was written once and called by name from the row expressions. The dynamic calcs read from the same live dimension state the templates did, so a new entity or account flowed through the calc chain without the calc being touched.

Solution

Four components, layered:

1. Template Cube Views (dimension-aware)

Reports defined in terms of dimension expressions, not member lists. Row and column specs resolve against the live dimension state at render time, so dimensional changes propagate on the next refresh. Side-by-side regression diff against the legacy reports validated correctness during migration.

2. Shared XFBR rule library

Nine consolidated rules covering scenario, period, and entity-context resolution, currency formatting, conditional row visibility, signage flipping, header-text composition, drill-back URLs, and default-view resolution. Each has a named parameter list, a typed return, and a documented fallback. Every report calls into the library; nothing is duplicated locally.

3. Cube View extenders library

Pre-render, post-render, and cell-level extender logic pulled into a single library and parameterised. Per-report differences expressed as configuration, not forked code. Changes to common behaviour land across the pack from one place.

4. Generic row-name-suffix parameter solution

A single XFBR rule that parses structured suffixes (_B, _I[n], _PCT, _BR, _HR, _TOT, and composable combinations) and emits the corresponding formatting directives. Strict parser — unknown tokens raise at render time. Documented vocabulary owned by the reporting team. Formatting behaviour across every Cube View now lives behind this one rule.

Results

| Metric | Before | After | Change | |---|---|---|---| | Rows hand-edited per dimensional addition | dozens across the pack | 0 | full automation | | XFBR rules in production | ~40 ad-hoc, per-report | 9 shared library functions | ~4× consolidation | | Formatting variance across the pack | visible, complained about in reviews | none | eliminated | | Reporting maintenance per quarter | significant (multi-day) | minimal (token / config) | step change | | Scenario-header-stale bugs / cycle | ~1 | 0 | error class eliminated |

Soft outcomes:

  • Dimensional changes stopped blocking the reporting cycle. A new loyalty programme used to mean a week of report patching before the next pack could go out. After the rebuild, the platform team adds the dimension, the reports pick it up on the next refresh, and the analysts spend their time on commentary instead of row-by-row edits.
  • The pack started looking like one pack. Formatting consistency had been the recurring stakeholder complaint — the same row type styled three different ways across three reports. After the suffix grammar landed, the pack visibly cohered, and the comment thread shifted from "fix the styling" to "what does this number mean."
  • The reporting team got a vocabulary. "Make this row bold, indent two, bracketed negatives" stopped being a layout discussion and started being a _B_I2_BR. The shared language quietly accelerated every subsequent build.

Learnings

What worked. Treating the dimension state as a live input rather than a design-time snapshot was the unlock. Once the reports read from the dimension at render time, the whole class of "the dimensions moved and the reports didn't" bugs disappeared. The same instinct — pull behaviour out of the report and into a shared layer the report queries — drove the XFBR consolidation and the suffix grammar. Three problems, one mental model.

What I'd do differently. Formalise the row-name-suffix grammar earlier, in parallel with the Template Cube View migration rather than after it. The ad-hoc suffixes were doing enough work in the legacy reports that auditing them during the migration would have been cheaper than auditing them again as a separate phase. The lesson: when you find a load-bearing convention that nobody documented, the right time to formalise it is the first time you touch the code that uses it.

Skill developed. Reading a reporting pack as a set of conventions, not as a set of files. The team had built up a real vocabulary in the row names, the rule shapes, the extender patterns — it just wasn't written down. Finding it, naming it, and giving it back as a shared standard turned out to be more valuable than any individual technical piece. That instinct — assume there is already a system, find it, then formalise it — has shaped how I scope reporting engagements since.