Reports MFE · epic #2102

Shipping the Reports App

Every unit of work needed to ship, in dependency order, and where each one stands. Eight of the nine tiers are built — Tier 6 is not, and neither is the backend that the one recommended next screen needs. What dominates what remains is the half of the product that lets the people owed money see what they are owed.

The bar counts the nine tiers plus the five new screens. The section tables below also carry the payout-correctness fixes and the open bugs, so their pill totals are larger — the two are counting different things.

Done — built, merged, closed In QA — built and merged, awaiting sign-off Not started — no code exists

8/9 Tiers built
1/5 New screens built
6 Fixed, awaiting QA
5 Bugs still open
16 PRs merged

The tiers

Ordered by dependency, not by cost — that is how the scope plan defines them. The backend tiers (0, 5, 7, 8) had to land before the app tiers that read them.

  1. ✓

    Prerequisite — thirteen routed billing endpoints were unusable

    Done

    Blocked Tiers 1–4 outright. #2109 · PR #2182

  2. 0

    Tier 0 — the billing-reporting data layer

    Done

    Written from scratch; unlike Growth, this app had no shared-services layer to port. PR #2108

  3. 5

    Tier 5 — backend: tiered commissions

    Done

    Own tables, period-grained. Load-bearing for Tier 1's largest page. #2111 · PR #2183

  4. 7

    Tier 7 — backend: commission earners beyond practitioners

    Done

    Staff could be assigned to a rule but never paid. Also closed a leak where every practitioner read every colleague's commissions. PR #2180

  5. 8

    Tier 8 — backend: the stakeholder payout model

    Done

    A real facility P&L: collected revenue, refunds, entered costs, commissions, carry-forward, and a settlement gate. PRs #2221 · #2226 · #2227

  6. 1

    Tier 1 — commissions pages

    Done

    Seven pages re-pointed to domains/billing. #2112 · PR #2185

  7. 2

    Tier 2 — revenue sharing

    In QA

    Five pages, plus absorbing the nested-stakeholder model. #2113 · PR #2191

  8. 3

    Tier 3 — premium reports

    In QA

    Re-pointed and typed, plus an authorization fix — every tenant member could previously read the practice's whole revenue aggregate. #2114 · PR #2187

  9. 4

    Tier 4 — exports and the audit trail

    In QA

    Six of seven export types produced no file; the audit trail had no data source. Both fixed. #2115 · PR #2202

  10. 6

    Tier 6 — backend: saved reports and templates

    Not started

    No models, no routes, no screens. Confirmed in scope. Only ExportReportsPage's saved-report affordances depend on it, so nothing else is blocked by it. #2116

The five screens with no legacy equivalent

The scope plan's own heading: every legacy screen exists; five new ones do not. These are separate scope from the tier ports — the ports re-pointed existing pages, and only one of these came with them. This is the largest remaining gap.

ScreenTierStatusWhy it matters
Facility cost entry 8 Done Tier 8's payout cannot run without a cost figure, and nothing captured one. PR #2226
My Earnings
self-scoped earner view
1 Not started Its backend is built and unreachable. view_own_commissions is seeded on by default for practitioners and staff, the Pundit scope narrows to their own rows, and policy_scope runs — with no route to any of it. Scoped in PR #2269.
My Statement
self-scoped stakeholder view
2 Not started Decision 10 invites external stakeholders — investors, partners — as users to read their own statement rather than being served out of band.
Revenue waterfall 3 Not started The Tier 8 P&L made visible — the report that shows an owner why their number is what it is.
Earnings statement · payout run · stakeholder statement · distribution run 3 Not started The four grounded Tier 3 reports. The earnings statement is the reporting form of My Earnings — the plan says build them against one scope, not two.

Payout correctness — done after Tier 8

Tier 8 shipped the payout model; these five closed the cases where the figure it produced was wrong. Every one was money in the facility's favour, and none was visible on screen.

What was wrongShipped
Commissions were deducted from the month they were approved, not the month the work was billed — permanently one month late, and the first month bore none. Plus a commission_basis marker and a drift report.PR #2240
Periods were bucketed in UTC, not the facility's timezone — an Indian clinic's month closed 5:30 am late.PR #2240
Paying a commission settlement never marked its commissions paid, so the payout gate never opened for any facility that settles in batches. Their stakeholders could not be paid at all.PR #2247
Tiered commissions were never deducted from the pool — a facility on that path distributed a figure whose cost was still owed.PR #2262
A tiered run that only measured facility revenue consumed every invoice item, so the run that actually pays practitioners found nothing and paid zero — silently, reporting success.PR #2270

Fixed, awaiting QA

Open only because QA owns the close decision. Checklists and the QA label are on each issue — #2261's were added after review caught that this board listed it here without them; the known-limitations brief for testers is on #2102, and it does not yet name #2261 or #2271.

Open bugs

None of these is fixed. Ordered by whether money is currently wrong. Four of the five were found by review rather than by testing.

What I would do next

The shape of the gap

The app can compute what everyone is owed and cannot yet show it to them. Every screen that exists is an administrator surface — which is precisely what the scope plan identifies as the legacy app's problem.

  1. #2273 — any tenant member can read every invoice and line item in the tenant. The policy's own comment says it gates on view_billing; the code checks only tenant_member?. Production exposure today, unrelated to this epic, and the smallest fix on this list.
  2. #2272 — the commission period filter raises on every request that uses it, and the rescue turns it into a generic 422. Blocks My Earnings, and settling the period basis is the same change.
  3. #2271 — the only place money is still wrong, and it is wrong in months that have already closed. Nothing else on this list is owed to someone who was underpaid. It starts with a query, not a fix: until somebody knows which facility-months were starved, there is no way to size the repair or to tell whether it is worth doing at all.
  4. My Earnings — still the shortest path from work already done to value delivered, but not the pure frontend job an earlier version of this board described. The Tier 7 scope is built, enforced and tested; the read payload is not. Review of PR #2269 found four backend gaps: no server-side self-scope for a tenant-wide reader, no signal distinguishing "no earnings this period" from "not linked to an earner record", settlement fields absent from the index view, and no line-item description on it either. All three decisions are now settled (2026-09-05): the invoice line description, shown only on encounter-attributed rows; a server-side self-scope so the page is self-scoped for everyone including admins; and the invoice-date period basis, so a month here means the month the payout pays. The remaining blocker is #2272.
  5. Revenue waterfall, then My Statement — the two remaining views for the people the money belongs to.
  6. #2245 and #2244 — operator dead ends. Not money, but each is a trap a tester or an ops person walks into and cannot walk out of.
  7. Tier 6 last. Nothing depends on it.
Two things this board cannot tell you

Whether the numbers match the agreements. The tests prove every figure is derived consistently and deducted once. Only someone who knows what a facility's contract says can confirm the figure is the right one.

Whether the backend suite is green. bin/ci's domains ratchet is red on the base branch — 178 non-passing against a ceiling of 20 — and has been for a while. It is not this work, and nothing on GitHub enforces it, because backend CI only runs on PRs into master. It needs a decision: fix the suites, or move the baseline honestly.