import { apiFetch, apiPath, apiUrl, authHeaders, type ApiSettings } from "@govoplan/core-webui"; export type ReportingDefinitionKind = "dataset" | "semantic_model" | "report" | "quality_plan"; export type ReportingQueryMode = "summary" | "detail" | "pivot"; export type ReportingQuery = { mode: ReportingQueryMode; dimensions: string[]; measures: string[]; filters: Array>; sort: Array<{ key: string; direction: "asc" | "desc" }>; pivot?: { rows: string[]; columns: string[]; measures: string[]; include_totals: boolean; } | null; offset: number; limit: number; }; export type ReportingDefinition = { tenant_id: string; definition_kind: ReportingDefinitionKind; definition_id: string; definition_key: string; revision: number; name: string; description?: string | null; status: "draft" | "active" | "retired"; visibility: "tenant" | "restricted"; content_hash: string; parent_kind?: ReportingDefinitionKind | null; parent_id?: string | null; parent_revision?: number | null; recorded_at: string; change_reason: string; payload: Record; }; export type SemanticModelPayload = { dataset_id: string; dataset_revision: number; dimensions: Array<{ key: string; field: string; label: string }>; measures: Array<{ key: string; label: string; aggregation: string }>; default_dimensions: string[]; default_measures: string[]; }; export type ReportPayload = { semantic_model_id: string; semantic_model_revision: number; parameters: Array<{ key: string; label: string; type: string; required: boolean; default?: unknown; allowed_values: unknown[]; }>; default_query: ReportingQuery; visualization: { kind: string; category_dimension?: string | null; series_dimension?: string | null; measures: string[]; tabular_fallback: boolean; }; institutional_references: Array>; }; export type ReportExecution = { execution_id: string; report_id: string; report_revision: number; semantic_model_id: string; semantic_model_revision: number; dataset_id: string; dataset_revision: number; status: "running" | "succeeded" | "failed"; parameters: Record; query: ReportingQuery; definition_hashes: Record; source_fingerprints: Array>; output_hash?: string | null; executor_version?: string | null; schema: Array<{ name: string; type: string }>; rows: Array>; total_rows: number; truncated: boolean; diagnostics: Array<{ severity?: string; code?: string; message?: string }>; provenance: Record; started_at: string; finished_at?: string | null; visualization?: { kind: string; requested_kind?: string; category?: string | null; series?: string | null; measures?: string[]; options?: Record; fallback_reason?: string | null; }; delivery_authorization?: Record; }; export type ReportingDrillContext = { token: string; drill_context_id: string; execution_id: string; dimension_path: Array<{ dimension: string; label: string; value: unknown }>; expires_at: string; }; export type ReportingDrillResult = Omit & { rows: Array>; schema: Array<{ name: string; type: string }>; total_rows: number; truncated: boolean; source_fingerprints: Array>; policy_provenance: Record; }; export type ReportingSchedule = { schedule_id: string; report_id: string; report_revision: number; name: string; revision: number; trigger_kind: "scheduled" | "interval"; trigger_config: Record; parameters: Record; query: ReportingQuery; publication_target: Record; enabled: boolean; next_run_at?: string | null; last_run_at?: string | null; last_execution_id?: string | null; }; export type ReportingPublicationTarget = { capability: string; label: string; available: boolean; reason?: string | null; formats: string[]; target_label: string; target_required: boolean; required_options: string[]; }; export type ReportingPublication = { publication_id: string; execution_id: string; target_capability: string; target_ref?: string | null; format: string; status: string; evidence: Record; error?: string | null; completed_at?: string | null; }; export type ReportingSavedView = { view_id: string; report_id: string; report_revision: number; name: string; revision: number; state: { query?: ReportingQuery }; shared: boolean; updated_at: string; }; export type ProviderReportParameter = { key: string; label: string; type: string; required: boolean; description?: string | null; options_from_provider: boolean; }; export type ProviderReportField = { path: string; label: string; type: string; group: string; nullable: boolean; sensitive: boolean; }; export type ProviderReportDescriptor = { contract_version: string; provider_id: string; report_id: string; revision: string; title: string; summary: string; parameters: ProviderReportParameter[]; result_schema: ProviderReportField[]; privacy_transforms: Array<{ id: string; label: string; required: boolean }>; purpose_required: boolean; audience_scope_required: boolean; retention_class: string; export_formats: Array<"json" | "csv">; reidentification_risk: "low" | "moderate" | "high"; presentation: { kind?: string }; available: boolean; unavailable_reason?: string | null; governance: { retention_days?: number | null; export_formats: string[]; required_privacy_transforms: string[]; provenance: Record; }; }; export type ProviderReportExecution = { execution_id: string; provider_id: string; report_id: string; report_revision: string; contract_version: string; purpose: string; audience_scope: Record; parameters: Record; result_schema: ProviderReportField[]; result: Record; source_revisions: Array>; effective_scope: Record; privacy_transforms: string[]; provenance: Record; governance_provenance: Record; retention_class: string; retention_days?: number | null; expires_at?: string | null; output_hash: string; generated_at: string; actor_id?: string | null; }; export function listDefinitions( settings: ApiSettings, options: { kinds?: ReportingDefinitionKind[]; status?: string[]; query?: string; limit?: number }, signal?: AbortSignal ): Promise<{ definitions: ReportingDefinition[]; total: number }> { return apiFetch(settings, apiPath("/api/v1/reporting/definitions", { definition_kind: options.kinds, status: options.status, query: options.query, limit: options.limit ?? 200 }), { signal }); } export function listProviderReports( settings: ApiSettings, signal?: AbortSignal ): Promise<{ reports: ProviderReportDescriptor[]; diagnostics: Array> }> { return apiFetch(settings, "/api/v1/reporting/provider-reports", { signal }); } export function listProviderParameterOptions( settings: ApiSettings, report: ProviderReportDescriptor, parameterKey: string, query = "", signal?: AbortSignal ): Promise<{ options: Array<{ value: string; label: string; description?: string | null }> }> { return apiFetch(settings, apiPath( `/api/v1/reporting/provider-reports/${encodeURIComponent(report.provider_id)}/${encodeURIComponent(report.report_id)}/parameters/${encodeURIComponent(parameterKey)}/options`, { query, limit: 200 } ), { signal }); } export function runProviderReport( settings: ApiSettings, report: ProviderReportDescriptor, parameters: Record, purpose: string, audienceScope: Record ): Promise { return apiFetch( settings, `/api/v1/reporting/provider-reports/${encodeURIComponent(report.provider_id)}/${encodeURIComponent(report.report_id)}/executions`, { method: "POST", body: JSON.stringify({ parameters, purpose, audience_scope: audienceScope, idempotency_key: crypto.randomUUID() }) } ); } export async function downloadProviderExecution( settings: ApiSettings, execution: ProviderReportExecution, format: "json" | "csv", purpose: string, audienceScope: Record ): Promise { const response = await fetch( apiUrl(settings, `/api/v1/reporting/provider-executions/${encodeURIComponent(execution.execution_id)}/exports`), { method: "POST", headers: { ...authHeaders(settings), "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ format, purpose, audience_scope: audienceScope }) } ); if (!response.ok) throw new Error(await response.text()); const blob = await response.blob(); const disposition = response.headers.get("Content-Disposition") ?? ""; const filename = disposition.match(/filename="?([^";]+)"?/)?.[1] ?? `provider-report.${format}`; const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = filename; link.click(); URL.revokeObjectURL(link.href); } export function getDefinition( settings: ApiSettings, kind: ReportingDefinitionKind, id: string, revision?: number, signal?: AbortSignal ): Promise { return apiFetch(settings, apiPath( `/api/v1/reporting/definitions/${encodeURIComponent(kind)}/${encodeURIComponent(id)}`, { revision } ), { signal }); } export function runReport( settings: ApiSettings, report: ReportingDefinition, query: ReportingQuery, parameters: Record ): Promise { return apiFetch(settings, `/api/v1/reporting/reports/${encodeURIComponent(report.definition_id)}/executions`, { method: "POST", body: JSON.stringify({ report_revision: report.revision, parameters, query, idempotency_key: crypto.randomUUID() }) }); } export function listExecutions( settings: ApiSettings, reportId: string, signal?: AbortSignal ): Promise<{ executions: ReportExecution[] }> { return apiFetch(settings, `/api/v1/reporting/reports/${encodeURIComponent(reportId)}/executions?limit=30`, { signal }); } export function createDrillContext( settings: ApiSettings, executionId: string, aggregateRow: Record, limit = 200 ): Promise { return apiFetch(settings, `/api/v1/reporting/executions/${encodeURIComponent(executionId)}/drill-contexts`, { method: "POST", body: JSON.stringify({ aggregate_row: aggregateRow, limit }) }); } export function resolveDrillContext( settings: ApiSettings, token: string, signal?: AbortSignal ): Promise { return apiFetch(settings, `/api/v1/reporting/drill-contexts/${encodeURIComponent(token)}`, { signal }); } export function listSavedViews( settings: ApiSettings, reportId: string, signal?: AbortSignal ): Promise<{ views: ReportingSavedView[] }> { return apiFetch(settings, `/api/v1/reporting/reports/${encodeURIComponent(reportId)}/saved-views`, { signal }); } export function saveView( settings: ApiSettings, report: ReportingDefinition, name: string, query: ReportingQuery ): Promise { const viewId = crypto.randomUUID(); return apiFetch(settings, `/api/v1/reporting/reports/${encodeURIComponent(report.definition_id)}/saved-views/${viewId}`, { method: "PUT", body: JSON.stringify({ view_id: viewId, report_revision: report.revision, name, state: { query }, shared: false, access: {}, expected_revision: null }) }); } export function createIntervalSchedule( settings: ApiSettings, report: ReportingDefinition, name: string, seconds: number, query: ReportingQuery, parameters: Record ): Promise> { const scheduleId = crypto.randomUUID(); return apiFetch(settings, `/api/v1/reporting/schedules/${scheduleId}`, { method: "PUT", body: JSON.stringify({ schedule_id: scheduleId, report_id: report.definition_id, report_revision: report.revision, name, trigger_kind: "interval", trigger_config: { seconds }, parameters, query, publication_target: {}, enabled: true, next_run_at: null, expected_revision: null }) }); } export function listSchedules( settings: ApiSettings, reportId: string, signal?: AbortSignal ): Promise<{ schedules: ReportingSchedule[] }> { return apiFetch(settings, apiPath("/api/v1/reporting/schedules", { report_id: reportId }), { signal }); } export function updateSchedule( settings: ApiSettings, schedule: ReportingSchedule, changes: Partial> ): Promise { return apiFetch(settings, `/api/v1/reporting/schedules/${encodeURIComponent(schedule.schedule_id)}`, { method: "PUT", body: JSON.stringify({ schedule_id: schedule.schedule_id, report_id: schedule.report_id, report_revision: schedule.report_revision, name: changes.name ?? schedule.name, trigger_kind: schedule.trigger_kind, trigger_config: schedule.trigger_config, parameters: schedule.parameters, query: schedule.query, publication_target: schedule.publication_target, enabled: changes.enabled ?? schedule.enabled, next_run_at: schedule.next_run_at ?? null, expected_revision: schedule.revision }) }); } export function listPublicationTargets( settings: ApiSettings, signal?: AbortSignal ): Promise<{ targets: ReportingPublicationTarget[] }> { return apiFetch(settings, "/api/v1/reporting/publication-targets", { signal }); } export function listPublications( settings: ApiSettings, executionId: string, signal?: AbortSignal ): Promise<{ publications: ReportingPublication[] }> { return apiFetch(settings, apiPath("/api/v1/reporting/publications", { execution_id: executionId }), { signal }); } export function publishExecution( settings: ApiSettings, executionId: string, request: { target_capability: string; target_ref?: string | null; format: string; options: Record; } ): Promise { return apiFetch(settings, `/api/v1/reporting/executions/${encodeURIComponent(executionId)}/publications`, { method: "POST", body: JSON.stringify({ ...request, idempotency_key: crypto.randomUUID() }) }); } export async function downloadExecution( settings: ApiSettings, executionId: string, format: "csv" | "json" ): Promise { const response = await fetch(apiUrl(settings, apiPath( `/api/v1/reporting/executions/${encodeURIComponent(executionId)}/export`, { format } )), { headers: authHeaders(settings), credentials: "include" }); if (!response.ok) throw new Error(await response.text() || "The report export failed."); const blob = await response.blob(); const disposition = response.headers.get("content-disposition") ?? ""; const filename = disposition.match(/filename="?([^";]+)"?/i)?.[1] ?? `report.${format}`; const href = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = href; link.download = filename; link.click(); URL.revokeObjectURL(href); } export function reportPayload(definition: ReportingDefinition): ReportPayload { return definition.payload as unknown as ReportPayload; } export function semanticPayload(definition: ReportingDefinition): SemanticModelPayload { return definition.payload as unknown as SemanticModelPayload; }