Skip to main content
Synced from the repo — do not edit here

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.currency set)
  • 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 structured spec (faster, no LLM).
  • Echo spec back 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.