feat: add governed cross-module reporting
This commit is contained in:
@@ -119,6 +119,74 @@ export type ReportingSavedView = {
|
||||
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<string, unknown>;
|
||||
};
|
||||
};
|
||||
|
||||
export type ProviderReportExecution = {
|
||||
execution_id: string;
|
||||
provider_id: string;
|
||||
report_id: string;
|
||||
report_revision: string;
|
||||
contract_version: string;
|
||||
purpose: string;
|
||||
audience_scope: Record<string, unknown>;
|
||||
parameters: Record<string, unknown>;
|
||||
result_schema: ProviderReportField[];
|
||||
result: Record<string, unknown>;
|
||||
source_revisions: Array<Record<string, unknown>>;
|
||||
effective_scope: Record<string, unknown>;
|
||||
privacy_transforms: string[];
|
||||
provenance: Record<string, unknown>;
|
||||
governance_provenance: Record<string, unknown>;
|
||||
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 },
|
||||
@@ -132,6 +200,75 @@ export function listDefinitions(
|
||||
}), { signal });
|
||||
}
|
||||
|
||||
export function listProviderReports(
|
||||
settings: ApiSettings,
|
||||
signal?: AbortSignal
|
||||
): Promise<{ reports: ProviderReportDescriptor[]; diagnostics: Array<Record<string, string>> }> {
|
||||
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<string, unknown>,
|
||||
purpose: string,
|
||||
audienceScope: Record<string, unknown>
|
||||
): Promise<ProviderReportExecution> {
|
||||
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<string, unknown>
|
||||
): Promise<void> {
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user