AI_INTEGRATION — reporting-service
Sibling: APPLICATION_LOGIC · DOMAIN_MODEL · platform anchors: docs/08-ai-architecture.md, docs/07 §11
reporting-service is not an AI inference owner. All model calls go through ai-orchestrator-service over its AIClient port. AI capabilities are scoped, opt-in per tenant, and recorded with full AIProvenance (08 §6).
1. Capabilities exposed
| Capability | Goal | Pattern | Surface |
|---|---|---|---|
report.draft_query | Convert a natural-language ask ("show me last week's no-shows by channel for Property A") into a candidate (templateKey, filters) | NL → tool-augmented LLM with grounded template catalog | BFF endpoint POST /api/v1/reports/ai/draft (proxied) |
report.anomaly_callout | Annotate an in-progress run with short, source-cited insights ("Occupancy dropped 18% vs 4-week median, driven by Room Type STD") | Numeric facts + LLM summarization | Inline post-render step on PDF |
report.summary_blurb | Produce a 2-3 sentence executive summary at the top of the PDF for manager_dashboard category | Numeric facts → LLM summarizer | Inline render step |
report.translate_template_labels | Translate template labels into tenant locales when a template is published in only one locale | LLM translation with glossary | Admin tool, async job |
report.template_diff_summary | Plain-language summary of changes when publishing a new template version | Diff → LLM | Admin tool, sync |
All five capabilities flow through ai-orchestrator-service with the matching capability key (e.g. reporting.draft_query.v1). The orchestrator owns model routing, fallback, prompt cache, and per-tenant budget caps.
2. Capability invariants (all calls)
- Tenant-scoped. Every call carries
tenantId,userId,correlationId. Orchestrator enforces tenant model-allowlist and data-residency. - No raw guest PII as prompt context. Guest names, phone numbers, document IDs, etc., never enter prompts.
AnalyticsClientexposes pre-aggregated, k-anonymized results to the AI helpers; the helpers see counts and dimension values, not row-level PII. - Citations required. AI outputs are stored with
AIProvenance(model, version, promptHash, latencyMs, costUsdMicros, confidence) and rendered with a "Generated by AI · review before sharing" badge in PDFs. - HITL for write actions. "Apply this draft" creates a draft
Reportin staterequires_human_review = true. A human mustPOST /reports/{id}/confirmbefore the report can be run on a schedule. Ad-hoc one-off runs of an AI-drafted report are allowed only by users withreports.author. - Budget guardrails. Per-tenant monthly cap configured via
tenant-service. Orchestrator returns429 MELMASTOON.AI.BUDGET_EXHAUSTEDand we degrade gracefully (skip the AI step, render the report unannotated). - Off-switch. Tenant admin can disable any capability under Settings → AI. When disabled, calls short-circuit and we never invoke the orchestrator.
3. AIClient port (recap)
export interface AIClient {
invoke<TInput, TOutput>(req: {
capability: string; // e.g. 'reporting.draft_query.v1'
tenantId: TenantId;
userId: UserId;
correlationId: string;
input: TInput;
options?: {
maxLatencyMs?: number;
maxCostUsdMicros?: number;
requireGroundedCitations?: boolean;
};
}): Promise<{
output: TOutput;
provenance: AIProvenance;
}>;
}
Reporting binds this to OrchestratorAIClient in production; tests use InMemoryAIClient returning fixtures.
4. Capability flows
4.1 report.draft_query
User → BFF: "Last week's no-shows by channel for Property Kabul-1"
→ reporting-service.DraftQueryUseCase.execute
→ templateCatalog.list(tenantId)
→ AIClient.invoke('reporting.draft_query.v1', {
prompt, templates: [{ key, columns, filters }, …], propertyAccessIds
})
← { templateKey, filters, ambiguities, confidence }
→ returns Draft to BFF; BFF renders chooser with confidence + "Open" / "Run now" / "Edit"
If confidence < 0.7 we always present an editable form rather than auto-running.
4.2 report.anomaly_callout
Triggered after RendererPort produces the numeric block but before final PDF emit. Uses summary statistics only:
const facts = pickFacts(payload, template.spec); // counts, sums, deltas vs baselines
const ai = await aiClient.invoke<typeof facts, AnomalyCallouts>({
capability: 'reporting.anomaly_callout.v1',
tenantId, userId: requestedBy.id, correlationId,
input: { facts, locale: run.locale },
options: { maxLatencyMs: 4000, maxCostUsdMicros: 25_000, requireGroundedCitations: true },
});
if (ai) {
payload.callouts = ai.output.callouts; // Each callout cites a fact id rendered on the page
run.attachAIProvenance(ai.provenance);
}
If the call exceeds latency budget or returns no high-confidence callouts, render proceeds without annotations.
4.3 report.summary_blurb
Same shape as 4.2 but executed for manager_dashboard category. Output is bounded to ≤ 500 characters and language-checked against the tenant's allowed locales.
4.4 report.translate_template_labels
Async job. For every locale in tenant.locales minus template.localeVariants, call the orchestrator with the source labels + glossary. Result is stored as a candidate TemplateVersion with requires_human_review = true; an admin reviews and publishes.
4.5 report.template_diff_summary
Synchronous call during PublishTemplateVersionUseCase. Generates a plain-language changesetSummary if the user did not provide one. Prompt receives the structural diff only (column added/removed/renamed, filter changed, layout block reordered) — never tenant data.
5. Provenance & audit
Every AI-affected report run stores a non-null report_runs.ai_provenance JSONB. The completed event payload includes aiProvenance so downstream services (audit-service, analytics-service) can correlate AI cost and acceptance. Acceptance is measured by:
- AI-drafted templates that are subsequently published.
- AI callouts whose surrounding numeric facts are kept (vs the operator manually deleting the AI block before delivery).
These metrics feed the ai_orchestrator.acceptance_rate projection.
6. Privacy & residency
- AI calls inherit
tenant.dataResidency. If a tenant's residency isKSAand the only model satisfying the capability isEU-hosted, the orchestrator returnsMELMASTOON.AI.RESIDENCY_VIOLATIONand we render without the AI step. - Prompts and outputs are stored hashed (SHA-256) in
audit-service; raw text is retained for 30 days in the orchestrator's secure prompt log only whentenant.aiSettings.retainPrompts === true. - Callouts must reference fact ids physically present in the rendered artifact; the renderer rejects any callout whose
factRefdoes not resolve.
7. Failure & degradation matrix
| Failure | Effect on render | Effect on UX |
|---|---|---|
Orchestrator timeout (> maxLatencyMs) | Skip callouts; emit warning event report.ai_skipped.v1 | PDF emits without AI block; status = completed |
| Orchestrator 5xx | Skip; same as timeout | |
| Budget exhausted | Skip + emit warning + notify tenant.admin once per day | |
| Residency violation | Skip; structured warn log | |
| Low-confidence draft | Surface as editable form | User confirms or edits before run |
8. Cross-references
- Capability registry: 08 §3
- AIProvenance shape: 08 §6, DOMAIN_MODEL §1
- Tenant AI settings:
services/tenant-service/DOMAIN_MODEL.md - Acceptance metric definition:
services/analytics-service/DOMAIN_MODEL.md→ai_orchestrator.acceptance_rate