← Back to case studies
Mid-Size Accounting Firm Single source of truth for EHR

Firm Profitability Ops Dashboard

The Nightmare

Partners couldn't see effective hourly rate (EHR) by client — Xero revenue and Karbon time lived in separate systems, with manual Excel bridging the gap every month-end.

The System

I built an internal ops dashboard joining Xero invoices and Karbon time entries via client mapping, with profitability, budget variance, team utilisation, and client-group rollups in one view.

What this is: an internal profitability dashboard for a mid-size accounting firm that joins Xero invoiced revenue to Karbon logged time, so partners can see effective hourly rate (EHR) per client in one place instead of rebuilding it in Excel every month-end.

Stack: Karbon + Xero APIs, PostgreSQL, Express, React Replaces: a manual month-end export-and-VLOOKUP cycle

What was broken?

A mid-size firm was flying blind on client profitability:

  • Xero held invoiced revenue by contact name
  • Karbon held logged hours and job costs under different client names
  • Budgets lived in a spreadsheet with no reliable link to Karbon work items
  • No shared view of effective hourly rate (EHR), utilisation, or budget variance

Month-end meant exporting, VLOOKUP-ing, and hoping the Excel model didn’t break.

What I built

A practice-management analytics dashboard — the same class of tool firms pay SaaS vendors for, tailored to how this firm actually works:

ModuleWhat it shows
ProfitabilityRevenue vs hours per client, EHR, onshore/offshore split, client-group rollups
EHR RateChart + table vs a target rate slider
Team UtilisationActual hours vs contracted capacity per team member
Budget AnalysisUploaded budget vs Karbon actual cost, grouped by client or job
Client MappingXero ↔ Karbon name matching with auto-match and manual override
Client drill-downInvoices, jobs, time entries, and monthly trend per entity

How it’s architected

  • Production system: Karbon + Xero APIs → PostgreSQL → Express API → React dashboard
  • Portfolio demo: Static React app with synthetic JSON fixtures — same UI, no backend, no real client data

The demo uses fictional names (Acme Tax Partners, Harbor Retail, etc.) and a visible Demo data badge.

Try the demo

Use the FY25/26 date preset on the Profitability page to see the full dataset. Switch to Grouped view to see the Summit Family client group rollup.

Interactive demo

Synthetic data only — explore the dashboard without a backend or real client records.

Common questions

How do you calculate effective hourly rate (EHR) by client?
By joining invoiced revenue to logged time. Xero holds invoiced revenue by contact name and Karbon holds logged hours and job costs under different client names, so the two are matched through a client mapping layer with auto-matching and manual override. Revenue divided by hours then gives EHR per client, with onshore/offshore split and client-group rollups.
Why doesn't Xero show client profitability on its own?
Xero knows what was invoiced but not what the work cost in time. The hours sit in the practice management system — Karbon in this case — under client names that do not match Xero's contact names. Without a mapping layer between them, profitability is a manual month-end export and VLOOKUP exercise.
Can you compare budgets against actual job costs?
Yes. Budgets that previously lived in a standalone spreadsheet with no reliable link to Karbon work items are uploaded and compared against Karbon actual cost, grouped by client or by job.
Does the public demo use real client data?
No. The demo is a static React app running on synthetic JSON fixtures with fictional names such as Acme Tax Partners and Harbor Retail, and it carries a visible Demo data badge. The production system runs on the Karbon and Xero APIs into PostgreSQL.

Ready for similar results?

Book a Workflow Audit