Canonical source: docs/claude/reports-api-contract.md. This page is generated by docs/scripts/sync-handbook.mjs. Edit the source file in the repo; changes appear here on the next build.
Business Reports API — contract for the Analytics tab
Backend built + deployed 2026-07-04. The Analytics-tab UI (built by another agent) calls the callable below. Do NOT rebuild the aggregation — call this endpoint.
Callable: generate_business_report
Firebase HTTPS callable (region us-central1). Auth: the signed-in staff user.
Permission: caller must be clinic staff with view_clinic (or all).
Request
{
clinic_id: string, // required
query?: string, // natural language, e.g. "revenue by service last 30 days"
spec?: { // OR pass a structured spec directly (skips NL parse)
dataset: "sessions"|"revenue"|"orders"|"clients",
group_by?: string, // see options below
window_days?: number, // default 30
staff_name?: string, // sessions only
filters?: {field,op,value}[], // clients only (query_clients catalog)
}
}
Pass query (NL, parsed server-side by Haiku) OR spec (structured). If both,
spec wins. If neither dataset resolves, defaults to sessions.
Response
{
ok: true,
report: {
title: string,
summary: string, // one-line headline
currency?: string, // revenue only, e.g. "USD"
rows: Row[],
},
spec: {...} // the resolved spec (useful to show filters/echo NL parse)
}
Row shapes (per dataset — render as table/chart)
- sessions:
{ label, completed, cancelled, noshow, upcoming, completionRate }(completionRate is 0–100 or null) - revenue:
{ label, amount }(amount in dollars;report.currencyset) - orders:
{ label, count, amount }(amount in dollars) - clients:
{ label, count }
group_by options by dataset
- sessions →
staff | service | location | status - revenue →
membership | service | product | month - orders →
status - clients →
status | membershipType
Frontend example
import { httpsCallable } from 'firebase/functions';
const fn = httpsCallable(functions, 'generate_business_report');
const res: any = await fn({ clinic_id, query: 'completed vs cancelled sessions by coach, last 7 days' });
const { report, spec } = res.data; // report.rows -> chart/table
Shared engine (same code Atlas uses)
functions_reports.py::run_report(clinic_id, dataset, group_by, window_days, staff_name, filters)
is the single aggregation core. The Atlas generate_report tool AND this callable
both call it — so web copilot, Slack, and the Analytics tab stay consistent.
Adding a dataset/metric to run_report extends all three at once.
Notes for the Analytics UI agent
- Suggest a NL input box ("Ask for a report…") that posts
query, PLUS quick-pick chips that post a structuredspec(faster, no LLM). - Echo
specback to the user so they can see/adjust the parsed dataset/window. - Errors: the callable throws
HttpsError(unauthenticated / permission-denied / invalid-argument) — surface the.message. - Empty results come back as
report.rows: []with a summary explaining why.