Skip to main content

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

CapabilityGoalPatternSurface
report.draft_queryConvert 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 catalogBFF endpoint POST /api/v1/reports/ai/draft (proxied)
report.anomaly_calloutAnnotate an in-progress run with short, source-cited insights ("Occupancy dropped 18% vs 4-week median, driven by Room Type STD")Numeric facts + LLM summarizationInline post-render step on PDF
report.summary_blurbProduce a 2-3 sentence executive summary at the top of the PDF for manager_dashboard categoryNumeric facts → LLM summarizerInline render step
report.translate_template_labelsTranslate template labels into tenant locales when a template is published in only one localeLLM translation with glossaryAdmin tool, async job
report.template_diff_summaryPlain-language summary of changes when publishing a new template versionDiff → LLMAdmin 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)

  1. Tenant-scoped. Every call carries tenantId, userId, correlationId. Orchestrator enforces tenant model-allowlist and data-residency.
  2. No raw guest PII as prompt context. Guest names, phone numbers, document IDs, etc., never enter prompts. AnalyticsClient exposes pre-aggregated, k-anonymized results to the AI helpers; the helpers see counts and dimension values, not row-level PII.
  3. 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.
  4. HITL for write actions. "Apply this draft" creates a draft Report in state requires_human_review = true. A human must POST /reports/{id}/confirm before the report can be run on a schedule. Ad-hoc one-off runs of an AI-drafted report are allowed only by users with reports.author.
  5. Budget guardrails. Per-tenant monthly cap configured via tenant-service. Orchestrator returns 429 MELMASTOON.AI.BUDGET_EXHAUSTED and we degrade gracefully (skip the AI step, render the report unannotated).
  6. 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 is KSA and the only model satisfying the capability is EU-hosted, the orchestrator returns MELMASTOON.AI.RESIDENCY_VIOLATION and 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 when tenant.aiSettings.retainPrompts === true.
  • Callouts must reference fact ids physically present in the rendered artifact; the renderer rejects any callout whose factRef does not resolve.

7. Failure & degradation matrix

FailureEffect on renderEffect on UX
Orchestrator timeout (> maxLatencyMs)Skip callouts; emit warning event report.ai_skipped.v1PDF emits without AI block; status = completed
Orchestrator 5xxSkip; same as timeout
Budget exhaustedSkip + emit warning + notify tenant.admin once per day
Residency violationSkip; structured warn log
Low-confidence draftSurface as editable formUser 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.mdai_orchestrator.acceptance_rate