Data Bear

Fabric Apps – Finance Forecast Writeback

Discussing issues

We built a Fabric App that lets finance edit the forecast, and the hard part was not the UI

Most write-ups about Microsoft Fabric Apps stop at the same place: here is a data app, here is a chart, look how it resembles a web application instead of a report. That is the easy 80%. The interesting question is what happens when you ask a Fabric App to do the one thing a Power BI report structurally cannot: let a user change a number and have that change survive as governed, audited data.

fabric apps review page

We built exactly that. Our Finance Writeback demo is a Fabric App that reproduces a Power BI financial dashboard and then adds forecast writeback on top: a finance user opens the Plan page, increases a marketing forecast by £60,000, watches the affected totals recalculate on screen, then saves it as a draft, submits it for review, and commits it. Every one of those actions lands in an append-only audit log. No semantic model refresh, no export to Excel, no email thread. The number changes in the report because the user changed it in the app.

This post is the build log for the parts that actually bit us, because those are the parts you cannot get from the documentation.

Why writeback is the real test, not “does it look like an app”

A store or finance manager rarely needs a prettier dashboard. They need to answer a question and then act on it. The reporting half of  the Fabric Apps demo (the Financial Overview) is genuinely just table stakes: Power BI already renders an income statement with actuals, budget, prior year and variance perfectly well, and if that is all you need, a report is cheaper to build and easier to maintain. We say that plainly in our own scope: pixel-replicating read-only report pages is disproportionately expensive relative to the demo value.

Fabric Apps charts

The differentiator is the write path. When a forecast adjustment can be entered, recalculated, saved, submitted, committed and audited inside the same surface the user is already looking at, the app stops being a report and becomes a small piece of finance software. That is the boundary where a Fabric App earns its place, and it is the boundary this post lives on.

Fabric Apps Dashboard

The sign convention that makes a correct demo look broken

Here is the first thing nobody warns you about, and it cost us a genuine bug twice before we pinned it.

In the finance dataset, expenses are stored as negative values. October’s Digital Marketing budget for the UK entity is not `80,892`, it is `-80,892`. That is correct and normal at the data layer. The problem is what “increase spend by 60,000” means once you are in negative space: increasing spend makes the stored number *more* negative. `-80,892` becomes `-140,892`, and the delta the audit log records is `-60,000`.

If you render those raw signed values to a finance user, the demo looks broken. They asked to spend more and the number went down. So the app has to store signed and display friendly: the screen shows “Forecast 140,892” and “Spend +60,000” while the database holds `-140,892` and `-60,000`. Get this backwards in even one place and either the maths is wrong or the screen is confusing, and the two failure modes look identical from the outside.

Fabric apps charts

The fix that actually held was to stop treating sign as a formatting afterthought. We centralised the whole convention, the magnitude flip, the stored-from-display conversion, the delta maths and the formatters, into a single module and reused it everywhere. The lesson generalises well beyond finance: when a domain has a storage convention that inverts the user’s mental model, that conversion is not display polish, it is a load-bearing part of the data contract, and it belongs in exactly one place.

A related trap on the same theme: there were two amount columns in the model, a raw local-currency `Amount` and a converted `AmountUSD`. Computing on the wrong one was the single root cause of our original KPI miss, and it tried to come back a second time when we built the writeback recalculation. If you take one portable thing from this post, let it be: find out which column is authoritative before you write a single measure, and write it down where the next person will see it.

Fabric Apps Writeback
The edit updates the affected line and its full-year total on screen, before anything is saved.

The constraint that quietly reshapes the whole architecture: embedded auth

This is the one we most wish someone had told us at the start, and it is now the first thing we classify on any new Fabric App.

The writeback runs against Rayfin SQL under Fabric’s embedded authentication. That auth is tied to the signed-in session inside the deployed, running app. A command-line or headless process cannot complete it. Not “it is awkward from the CLI”, it genuinely cannot obtain the credential.

That single fact reshapes everything downstream. It means you cannot seed your working forecast from a script. You cannot verify the persisted rows from a terminal. You cannot drive the writes from any automated Node process at all. Every operation that touches the writeback tables, seeding the baseline, saving a draft, reading back what was stored, has to happen as a click inside the app, by a signed-in human.

We spent real time discovering this the hard way before we understood it was structural rather than a bug in our setup. The takeaway we now carry: identify the operations that are structurally un-automatable, embedded-auth writes, in-app-only steps, at intake, and plan them as expected human-in-the-app steps from day one. If you assume you can script your test data and you cannot, you find out at the worst possible moment, mid-build, with a demo date approaching.

Writes and reads are not symmetric, and treating them the same is a bug

Once you accept the app is doing real writes, retry policy stops being boilerplate.

Reads are safe to retry. If a read fails on a transient cold-mount error, firing it again just gets you the same rows. We gave reads a single one-shot retry for exactly that transient 5xx case, plus a manual retry button, and moved on.

Writes are a different animal. A forecast “save” that silently retries can create the row twice. There is no safe transparent retry for a create, because you cannot tell from the outside whether the first attempt failed before or after it wrote. So writes get *no* transparent retry. When a write fails, the app surfaces an explicit Retry control and hands the decision to the user, who can check whether the row is already there.

This is a small design decision that only shows up once you are writing rather than reading, and it is invisible in any demo that never persists anything. It is also the kind of thing a generic “how to build a data app” article will never mention, because that article never had to worry about a duplicate committed forecast.

The state machine, and why “committed” is the only status that counts

The lifecycle is Draft to Submitted to Committed, and each transition does specific work:

A **Draft** is editable by its owner and is deliberately *excluded* from the adjusted forecast total. **Submitted** locks the row to its owner and marks it pending review. **Committed** locks it permanently and is the only status whose delta actually merges into the adjusted forecast. Draft, Submitted and Rejected are all filtered out of the total. This matters because it means a half-finished “what if” a user is still typing never contaminates the headline number. Only a deliberate commit moves the forecast.

Every transition appends exactly one audit row: old value, new value, signed delta, timestamp, user email. And the original baseline is never overwritten. The adjusted forecast is always baseline plus committed deltas, computed, never a destructive edit to the source figures.

fabric app writeback demo
Submit and commit are one-way locks, and only a commit moves the forecast total.

The append-only audit trap: a clean demo is a data-layer job, not a UI feature

Here is the lesson that surprised us most, and it is a genuinely portable one.

The audit page is append-only by design. No delete control in the UI, no “undo”, no casual reset button, because a finance audit trail that a user can quietly edit is not an audit trail. That is obviously the right call for governance.

But it collides head-on with a demo requirement: you need to rehearse the full Draft-to-Save-to-Submit-to-Commit story repeatedly, from a clean slate, in front of different audiences. And you have just deliberately built a system with no way to get back to a clean slate from inside the app.

The naive instinct is to add a reset feature. That instinct is wrong, and recognising why is the lesson. If you add an in-app reset, you have compromised the append-only guarantee you just spent effort building; the feature that makes the demo convenient is the exact feature that makes the audit trail untrustworthy. The correct answer is that a repeatable-clean-state requirement, when combined with an append-only data model, forces an out-of-band reset at the data layer. Not a UI feature. In our case that is a direct, scoped, preview-gated SQL delete against the Fabric endpoint, run deliberately by a human between rehearsals, never something the app itself can do.

The generalisable version: when you design any append-only or no-delete model, ask at that moment how clean state will be re-established, and commit to answering it at the data layer. If you defer that question, you will eventually be tempted to answer it with a UI feature that quietly undermines the whole point.

audit page fabric app
Clean state for the next demo happens at the data layer, out of band, by a human.

When you should not build this at all

A Fabric App is not automatically the right answer because it is newer. If your users need flexible, exploratory analysis and never need to write anything back, a Power BI report is cheaper to build, easier to maintain, and better suited to weekly visual iteration. If your team has no appetite to own custom code, an app is a liability, not an asset. The writeback demo earns its place precisely because writeback is something the report genuinely cannot do. If your requirement does not include that write path, use the report.

What this actually proves

The reporting layer of a Fabric App is the part everyone shows you and the part that matters least, because Power BI already does it. The writeback layer, edit, recalculate, save, submit, commit, audit, with the baseline never overwritten and only committed changes moving the number, is where a Fabric App becomes something a report is not.

And the work that makes it trustworthy is almost entirely underneath the surface: getting the sign convention right so a correct demo does not look broken, planning around embedded auth that no script can cross, treating writes and reads as the different risks they are, and resolving the append-only-versus-clean-state tension at the data layer instead of papering over it with a button. That is the part worth writing down, because it is the part you cannot look up.

To learn more about Microsoft Fabric and how we can help with Fabric Apps, see our Microsoft Fabric page.