From 2bea50a2910bd9c04d46b9334fd8017c28a2939f Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Fri, 21 Aug 2026 18:51:12 +0800 Subject: [PATCH] feat: add independent AI insights to analytics pages --- src/lib/api/client.ts | 67 + src/lib/api/schema.d.ts | 3837 +++++++++++--------- src/lib/components/AIInsightPanel.svelte | 199 + src/lib/components/AnalyticsPage.svelte | 3 + src/lib/components/GrowthPage.svelte | 3 + src/lib/components/TransactionsPage.svelte | 6 + 6 files changed, 2315 insertions(+), 1800 deletions(-) create mode 100644 src/lib/components/AIInsightPanel.svelte diff --git a/src/lib/api/client.ts b/src/lib/api/client.ts index 76b982e..beaca6f 100644 --- a/src/lib/api/client.ts +++ b/src/lib/api/client.ts @@ -15,6 +15,59 @@ export type GrowthReport = components['schemas']['GrowthReport']; export type CreateGrowthReportRequest = operations['createGrowthReport']['requestBody']['content']['application/json']; +export type AIInsightModule = 'overview' | 'payments' | 'fees' | 'peers' | 'transactions' | 'growth'; +export type AIInsightNarrative = { headline: string; body: string; evidenceIds: string[] }; +export type AIInsight = { + insightId: string; + module: AIInsightModule; + revision: number; + status: 'READY' | 'PARTIAL'; + scope: { terminalId?: string | null }; + asOfDate: string; + dataThroughDate: string; + forecastStartDate: string; + freshness: 'FRESH' | 'LAGGING' | 'STALE'; + dataLagDays: number; + confidence: 'HIGH' | 'MEDIUM' | 'LOW' | 'INSUFFICIENT_DATA'; + copy: { + past: AIInsightNarrative; + latestObserved: AIInsightNarrative; + future7Days: AIInsightNarrative; + future30Days: AIInsightNarrative; + future90Days?: AIInsightNarrative; + findings: Array<{ + evidenceId: string; + title: string; + summary: string; + scoreBps: number; + confidence: string; + }>; + options: Array<{ title: string; description: string; evidenceIds: string[] }>; + }; + evidence: Record; + calendarContext: Array<{ + eventKey: string; + titleFa: string; + startDate: string; + endDate: string; + tone: string; + commercialPosture: string; + officialHoliday: boolean; + evidenceSupported: boolean; + overlapConfounded: boolean; + }>; + refreshState: { state: string; jobId?: string; eventsUrl?: string; nextRefreshAt?: string }; + generatedAt: string; +}; +export type AIInsightJob = { + jobId: string; + snapshotId: string; + state: string; + eventsUrl: string; + createdAt: string; + updatedAt: string; +}; + const apiBase = (import.meta.env.PUBLIC_API_BASE_URL || '/api/v1').replace(/\/$/, ''); export class ApiError extends Error { @@ -139,6 +192,20 @@ export const api = { growthReport(reportId: string) { return request(`/analytics/growth-reports/${encodeURIComponent(reportId)}`); }, + latestAIInsight(module: AIInsightModule, terminalId?: string) { + const query = terminalId ? `?terminalId=${encodeURIComponent(terminalId)}` : ''; + return request(`/analytics/ai-insights/${module}${query}`); + }, + refreshAIInsight(module: AIInsightModule, csrfToken: string, terminalId?: string) { + return request(`/analytics/ai-insights/${module}/refreshes`, { + method: 'POST', + headers: { 'X-CSRF-Token': csrfToken, 'Idempotency-Key': crypto.randomUUID() }, + body: JSON.stringify(terminalId ? { terminalId } : {}), + }); + }, + aiInsightJob(jobId: string) { + return request(`/analytics/ai-insight-refreshes/${encodeURIComponent(jobId)}`); + }, exportUrl( periodId: string, view: DetailView, diff --git a/src/lib/api/schema.d.ts b/src/lib/api/schema.d.ts index 69d871b..3ed0b10 100644 --- a/src/lib/api/schema.d.ts +++ b/src/lib/api/schema.d.ts @@ -4,1870 +4,2107 @@ */ export interface paths { - '/auth/sessions': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/auth/sessions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Authenticate a demo merchant user + * @description Creates a revocable server-side session. Expected credential failures are + * externally indistinguishable. The response sets the host-only + * `__Host-zpa_session` cookie; the raw token is never present in JSON. + */ + post: operations["createAuthSession"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - /** - * Authenticate a demo merchant user - * @description Creates a revocable server-side session. Expected credential failures are - * externally indistinguishable. The response sets the host-only - * `__Host-zpa_session` cookie; the raw token is never present in JSON. - */ - post: operations['createAuthSession']; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/auth/session': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/auth/session": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get the current session summary */ + get: operations["getAuthSession"]; + put?: never; + post?: never; + /** + * Revoke the current session + * @description Revokes server-side state before expiring the browser cookie. + */ + delete: operations["deleteAuthSession"]; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - /** Get the current session summary */ - get: operations['getAuthSession']; - put?: never; - post?: never; - /** - * Revoke the current session - * @description Revokes server-side state before expiring the browser cookie. - */ - delete: operations['deleteAuthSession']; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/analytics/periods': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/analytics/periods": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List approved reporting periods + * @description Returns every complete allowlisted day, Iranian week, Jalali month, and + * Persian season inside the imported dataset's local-date coverage, with + * server-calculated UTC and Tehran-local half-open boundaries. Periods are + * grouped by type and ordered newest-first inside each group. Clients start + * runs with a returned `periodId` and never submit arbitrary timestamps. + */ + get: operations["listAnalyticsPeriods"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - /** - * List approved reporting periods - * @description Returns every complete allowlisted day, Iranian week, Jalali month, and - * Persian season inside the imported dataset's local-date coverage, with - * server-calculated UTC and Tehran-local half-open boundaries. Periods are - * grouped by type and ordered newest-first inside each group. Clients start - * runs with a returned `periodId` and never submit arbitrary timestamps. - */ - get: operations['listAnalyticsPeriods']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/analytics/terminals': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/analytics/terminals": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List terminals owned by the authenticated merchant + * @description Returns display-safe terminal choices scoped exclusively from the authenticated server session. + */ + get: operations["listMerchantTerminals"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - /** - * List terminals owned by the authenticated merchant - * @description Returns display-safe terminal choices scoped exclusively from the authenticated server session. - */ - get: operations['listMerchantTerminals']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/analytics/runs': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/analytics/runs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create or return the current analytics run + * @description Idempotently resolves the current merchant/dataset/metric version + * and starts only missing sections. Equivalent concurrent starts deduplicate. + */ + post: operations["createAnalyticsRun"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - /** - * Create or return the current analytics run - * @description Idempotently resolves the current merchant/dataset/metric version - * and starts only missing sections. Equivalent concurrent starts deduplicate. - */ - post: operations['createAnalyticsRun']; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/analytics/runs/{runId}': { - parameters: { - query?: never; - header?: never; - path: { - /** @description Opaque analytics run identifier; possession does not authorize access. */ - runId: components['parameters']['RunIdPath']; - }; - cookie?: never; + "/analytics/runs/{runId}": { + parameters: { + query?: never; + header?: never; + path: { + /** @description Opaque analytics run identifier; possession does not authorize access. */ + runId: components["parameters"]["RunIdPath"]; + }; + cookie?: never; + }; + /** Get the current run snapshot */ + get: operations["getAnalyticsRun"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - /** Get the current run snapshot */ - get: operations['getAnalyticsRun']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/analytics/runs/{runId}/events': { - parameters: { - query?: never; - header?: never; - path: { - /** @description Opaque analytics run identifier; possession does not authorize access. */ - runId: components['parameters']['RunIdPath']; - }; - cookie?: never; + "/analytics/runs/{runId}/events": { + parameters: { + query?: never; + header?: never; + path: { + /** @description Opaque analytics run identifier; possession does not authorize access. */ + runId: components["parameters"]["RunIdPath"]; + }; + cookie?: never; + }; + /** + * Stream partial analytics results + * @description Authenticated SSE stream. Events are retained for one hour. A retained + * `Last-Event-ID` replays only later events. Missing, invalid, unauthorized, + * or expired ids reset safely by emitting `run.snapshot` with + * `recovery=RESET_TO_SNAPSHOT`, then continuing from new events. + * + * Event types are `run.snapshot`, `section.started`, `section.ready`, + * `section.failed`, `section.suppressed`, `run.completed`, and `heartbeat`. + * Each event has an opaque `id` and one-line JSON `data` matching one of the + * schemas named by `AnalyticsSseEvent`. + */ + get: operations["streamAnalyticsRunEvents"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - /** - * Stream partial analytics results - * @description Authenticated SSE stream. Events are retained for one hour. A retained - * `Last-Event-ID` replays only later events. Missing, invalid, unauthorized, - * or expired ids reset safely by emitting `run.snapshot` with - * `recovery=RESET_TO_SNAPSHOT`, then continuing from new events. - * - * Event types are `run.snapshot`, `section.started`, `section.ready`, - * `section.failed`, `section.suppressed`, `run.completed`, and `heartbeat`. - * Each event has an opaque `id` and one-line JSON `data` matching one of the - * schemas named by `AnalyticsSseEvent`. - */ - get: operations['streamAnalyticsRunEvents']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/analytics/runs/{runId}/sections/{sectionId}/retry-attempts': { - parameters: { - query?: never; - header?: never; - path: { - /** @description Opaque analytics run identifier; possession does not authorize access. */ - runId: components['parameters']['RunIdPath']; - sectionId: components['parameters']['SectionIdPath']; - }; - cookie?: never; + "/analytics/runs/{runId}/sections/{sectionId}/retry-attempts": { + parameters: { + query?: never; + header?: never; + path: { + /** @description Opaque analytics run identifier; possession does not authorize access. */ + runId: components["parameters"]["RunIdPath"]; + sectionId: components["parameters"]["SectionIdPath"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Retry one exhausted failed section + * @description Does not clear or restart ready sibling sections. + */ + post: operations["createAnalyticsSectionRetryAttempt"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - /** - * Retry one exhausted failed section - * @description Does not clear or restart ready sibling sections. - */ - post: operations['createAnalyticsSectionRetryAttempt']; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/analytics/session-details': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/analytics/session-details": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List authorized merchant-owned session evidence + * @description Cursor-paginated detail rows for one approved period and view. Stable + * ordering is `createdAt DESC, sessionId DESC`. The server binds cursors to + * merchant, filters, and ordering. Cross-tenant and absent ids are not + * distinguishable. + */ + get: operations["listAnalyticsSessionDetails"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - /** - * List authorized merchant-owned session evidence - * @description Cursor-paginated detail rows for one approved period and view. Stable - * ordering is `createdAt DESC, sessionId DESC`. The server binds cursors to - * merchant, filters, and ordering. Cross-tenant and absent ids are not - * distinguishable. - */ - get: operations['listAnalyticsSessionDetails']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/analytics/sessions/{sessionId}': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/analytics/sessions/{sessionId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get one merchant-owned payment session and attempt history */ + get: operations["getAnalyticsSession"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - /** Get one merchant-owned payment session and attempt history */ - get: operations['getAnalyticsSession']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/analytics/session-details.csv': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/analytics/session-details.csv": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Export the authorized detail view as CSV + * @description Uses the same tenant scope, approved period, filters, and allowlisted + * projection as `listAnalyticsSessionDetails`. Cursor and page limit do not + * apply. Output is UTF-8 with a BOM, a Persian header row whose labels use + * normal spaces, comma separators, CRLF records, and RFC 4180 quoting. + * + * Stable columns are: + * `شناسه نشست پرداخت`, `شناسه پایانه`, `وضعیت پرداخت`, `مبلغ ریال`, + * `کارمزد ریال`, `زمان ایجاد`, `زمان انقضا`, `تعداد تلاش`, + * `وضعیت آخرین تلاش`, `زمان آخرین تلاش`, `زمان تأیید`, `زمان تسویه`, + * `مرحله مشاهده شده شکست`. + * + * CSV status labels are Persian: `Paid` is `پرداخت شده و تأیید نشده`, + * `Verified` is `تأیید شده`, `Failed` is `ناموفق`, `Reversed` is + * `برگشت خورده`, and `InBank` is `در بانک`. Numeric cells remain plain + * calculation-safe numbers and timestamps remain UTC ISO values. + * + * Columns unavailable to the chosen authorized view are empty. Payer tokens, + * credentials, peer data, internal ids, and any field unavailable in the JSON + * detail view are forbidden. Potential spreadsheet formulas in textual cells + * are prefixed with an apostrophe before CSV quoting. + */ + get: operations["exportAnalyticsSessionDetailsCsv"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - /** - * Export the authorized detail view as CSV - * @description Uses the same tenant scope, approved period, filters, and allowlisted - * projection as `listAnalyticsSessionDetails`. Cursor and page limit do not - * apply. Output is UTF-8 with a BOM, a Persian header row whose labels use - * normal spaces, comma separators, CRLF records, and RFC 4180 quoting. - * - * Stable columns are: - * `شناسه نشست پرداخت`, `شناسه پایانه`, `وضعیت پرداخت`, `مبلغ ریال`, - * `کارمزد ریال`, `زمان ایجاد`, `زمان انقضا`, `تعداد تلاش`, - * `وضعیت آخرین تلاش`, `زمان آخرین تلاش`, `زمان تأیید`, `زمان تسویه`, - * `مرحله مشاهده شده شکست`. - * - * CSV status labels are Persian: `Paid` is `پرداخت شده و تأیید نشده`, - * `Verified` is `تأیید شده`, `Failed` is `ناموفق`, `Reversed` is - * `برگشت خورده`, and `InBank` is `در بانک`. Numeric cells remain plain - * calculation-safe numbers and timestamps remain UTC ISO values. - * - * Columns unavailable to the chosen authorized view are empty. Payer tokens, - * credentials, peer data, internal ids, and any field unavailable in the JSON - * detail view are forbidden. Potential spreadsheet formulas in textual cells - * are prefixed with an apostrophe before CSV quoting. - */ - get: operations['exportAnalyticsSessionDetailsCsv']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/analytics/growth-reports': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/analytics/growth-reports": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Create or reuse a growth report */ + post: operations["createGrowthReport"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - /** Create or reuse a growth report */ - post: operations['createGrowthReport']; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/analytics/growth-reports/latest': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/analytics/growth-reports/latest": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getLatestGrowthReport"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get: operations['getLatestGrowthReport']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/analytics/growth-reports/{reportId}': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/analytics/growth-reports/{reportId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getGrowthReport"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get: operations['getGrowthReport']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/analytics/growth-reports/{reportId}/events': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/analytics/growth-reports/{reportId}/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["streamGrowthReportEvents"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analytics/ai-insights/{module}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Return the latest persisted insight without waiting for generation */ + get: operations["getLatestAIInsight"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analytics/ai-insights/{module}/refreshes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Enqueue an independent insight refresh */ + post: operations["refreshAIInsight"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analytics/ai-insight-refreshes/{jobId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getAIInsightRefresh"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analytics/ai-insight-refreshes/{jobId}/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Replay up to one hour of durable job events and continue as SSE */ + get: operations["streamAIInsightRefreshEvents"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get: operations['streamGrowthReportEvents']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; } export type webhooks = Record; export interface components { - schemas: { - CreateGrowthReportRequest: { - terminalId?: string; - /** @description When omitted, the request reuses the current matching report. Manual retries remain available until the requested scope has persisted AI copy. */ - refresh?: boolean; + schemas: { + CreateGrowthReportRequest: { + terminalId?: string; + /** @description When omitted, the request reuses the current matching report. Manual retries remain available until the requested scope has persisted AI copy. */ + refresh?: boolean; + }; + GrowthRange: { + /** Format: int64 */ + p10: number; + /** Format: int64 */ + p50: number; + /** Format: int64 */ + p90: number; + }; + GrowthForecastPoint: { + /** Format: date-time */ + date: string; + /** Format: int64 */ + baselineSessions: number; + /** Format: int64 */ + baselineAmountRial: number; + paidSessions: components["schemas"]["GrowthRange"]; + paidAmountRial: components["schemas"]["GrowthRange"]; + eventIds?: string[]; + }; + GrowthForecast: { + /** Format: date-time */ + startDate: string; + /** @enum {integer} */ + horizonDays: 90; + /** @enum {string} */ + confidence: "HIGH" | "MEDIUM" | "LOW" | "INSUFFICIENT_DATA"; + suppressionReason?: string; + points: components["schemas"]["GrowthForecastPoint"][]; + backtest: { + origins: number; + /** Format: int64 */ + sessionMapeBps: number; + /** Format: int64 */ + amountMapeBps: number; + /** Format: int64 */ + intervalCoverageBps: number; + }; + }; + GrowthHistoricalWin: { + evidenceId: string; + eventId: string; + eventTitleFa: string; + /** Format: date-time */ + startDate: string; + /** Format: date-time */ + endDate: string; + eligible?: boolean; + suppressionReason?: string; + observationCount?: number; + /** Format: int64 */ + baselineSessionCount?: number; + /** Format: int64 */ + baselinePaidSessions?: number; + /** Format: int64 */ + baselineAmountRial?: number; + /** Format: int64 */ + actualSessionCount?: number; + /** Format: int64 */ + actualPaidSessions?: number; + /** Format: int64 */ + actualAmountRial?: number; + /** Format: int64 */ + sessionUpliftBps?: number; + /** Format: int64 */ + paidSessionUpliftBps?: number; + /** Format: int64 */ + amountUpliftBps?: number; + materialWin: boolean; + materialDecline?: boolean; + }; + GrowthOpportunity: { + evidenceId: string; + eventId: string; + eventTitleFa: string; + /** Format: date-time */ + date: string; + /** Format: int64 */ + similarityBps: number; + expectedSessions: components["schemas"]["GrowthRange"]; + expectedAmountRial: components["schemas"]["GrowthRange"]; + historicalEvidenceId: string; + }; + GrowthRisk: { + evidenceId: string; + /** Format: date-time */ + date: string; + /** Format: date-time */ + endDate?: string; + /** + * Format: int64 + * @description At least one decline dimension is 2000 BPS or greater. + */ + sessionDeclineBps: number; + /** + * Format: int64 + * @description At least one decline dimension is 2000 BPS or greater. + */ + amountDeclineBps: number; + expectedSessions: components["schemas"]["GrowthRange"]; + expectedAmountRial: components["schemas"]["GrowthRange"]; + }; + GrowthPeer: { + /** @enum {string} */ + state: "READY" | "SUPPRESSED"; + reason?: string; + eligiblePeerCount: number; + /** Format: int64 */ + combinedPeerSessionCount: number; + /** Format: int64 */ + medianPaidSessions: number; + /** Format: int64 */ + percentileBps: number; + }; + GrowthCampaignBrief: { + headline: string; + interpretation: string; + offerSuggestion: string; + messageSuggestion: string; + preparationAction: string; + limitation: string; + evidenceIds: string[]; + }; + GrowthFallbackBrief: { + headlineFa: string; + guidanceFa: string; + offerFa: string; + messageFa: string; + preparationFa: string; + limitationFa: string; + evidenceIds: string[]; + }; + GrowthEvidence: { + forecast: components["schemas"]["GrowthForecast"]; + historicalWins: components["schemas"]["GrowthHistoricalWin"][]; + opportunities: components["schemas"]["GrowthOpportunity"][]; + risks: components["schemas"]["GrowthRisk"][]; + peer?: components["schemas"]["GrowthPeer"]; + fallbackBriefs: components["schemas"]["GrowthFallbackBrief"][]; + }; + GrowthReport: { + reportId: string; + revision: number; + /** @enum {string} */ + status: "QUEUED" | "ANALYZING" | "GENERATING" | "READY" | "PARTIAL" | "FAILED"; + scope: { + terminalId?: string | null; + }; + /** @enum {integer} */ + horizonDays: 90; + /** Format: date-time */ + forecastStartDate?: string; + deterministicEvidence?: components["schemas"]["GrowthEvidence"]; + aiCopy?: { + briefs: components["schemas"]["GrowthCampaignBrief"][]; + }; + failureCode?: string; + cached: boolean; + stale: boolean; + /** + * Format: date-time + * @description Current time while no AI summary exists for the scope; otherwise the next refresh boundary for that terminal scope in the Tehran day. The all-terminals selection is a separate scope. + */ + nextRefreshAt: string; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + updatedAt: string; + /** Format: uri-reference */ + eventsUrl: string; + }; + /** @enum {string} */ + AIInsightModule: "overview" | "payments" | "fees" | "peers" | "transactions" | "growth"; + AIInsightNarrative: { + headline: string; + body: string; + evidenceIds: string[]; + }; + AIInsightFinding: { + evidenceId: string; + title: string; + summary: string; + scoreBps: number; + /** @enum {string} */ + confidence: "HIGH" | "MEDIUM" | "LOW" | "INSUFFICIENT_DATA"; + }; + AIInsightOption: { + title: string; + description: string; + evidenceIds: string[]; + }; + AIInsightCopy: { + past: components["schemas"]["AIInsightNarrative"]; + latestObserved: components["schemas"]["AIInsightNarrative"]; + future7Days: components["schemas"]["AIInsightNarrative"]; + future30Days: components["schemas"]["AIInsightNarrative"]; + future90Days?: components["schemas"]["AIInsightNarrative"]; + findings: components["schemas"]["AIInsightFinding"][]; + options: components["schemas"]["AIInsightOption"][]; + }; + AIInsight: { + insightId: string; + module: components["schemas"]["AIInsightModule"]; + revision: number; + /** @enum {string} */ + status: "READY" | "PARTIAL"; + scope: { + terminalId?: string | null; + }; + /** Format: date */ + asOfDate: string; + /** Format: date */ + dataThroughDate: string; + /** Format: date */ + forecastStartDate: string; + /** @enum {string} */ + freshness: "FRESH" | "LAGGING" | "STALE"; + dataLagDays: number; + /** @enum {string} */ + confidence: "HIGH" | "MEDIUM" | "LOW" | "INSUFFICIENT_DATA"; + copy: components["schemas"]["AIInsightCopy"]; + evidence: { + [key: string]: unknown; + }; + calendarContext: { + [key: string]: unknown; + }[]; + refreshState: { + [key: string]: unknown; + }; + /** Format: date-time */ + generatedAt: string; + }; + AIInsightRefreshRequest: { + terminalId?: string; + }; + AIInsightJob: { + jobId: string; + snapshotId: string; + /** @enum {string} */ + state: "QUEUED" | "RUNNING" | "COMPLETED" | "FAILED"; + /** Format: uri-reference */ + eventsUrl: string; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + updatedAt: string; + }; + LoginRequest: { + /** @example merchant-0001 */ + username: string; + password: string; + }; + AuthSession: { + user: components["schemas"]["MerchantUserSummary"]; + merchant: components["schemas"]["MerchantSummary"]; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + idleExpiresAt: string; + /** Format: date-time */ + absoluteExpiresAt: string; + /** @description Session-bound CSRF token. It is not an authentication token and must not be logged. */ + csrfToken: string; + }; + MerchantUserSummary: { + userId: string; + /** @example merchant-0001 */ + username: string; + }; + MerchantSummary: { + /** @description Opaque API identifier, never accepted as authorization input. */ + merchantId: string; + displayName: string; + profileIsSynthetic: boolean; + }; + /** @enum {string} */ + PeriodType: "DAY" | "WEEK" | "JALALI_MONTH" | "PERSIAN_SEASON"; + /** @example jm-1405-05 */ + PeriodId: string; + ReportingPeriod: { + periodId: components["schemas"]["PeriodId"]; + periodType: components["schemas"]["PeriodType"]; + /** + * Format: date-time + * @description Inclusive UTC boundary. + * @example 2026-07-23T20:30:00Z + */ + startAt: string; + /** + * Format: date-time + * @description Exclusive UTC boundary. + * @example 2026-08-22T20:30:00Z + */ + endAt: string; + /** + * Format: date-time + * @description Inclusive Asia/Tehran civil boundary with explicit offset. + * @example 2026-07-24T00:00:00+03:30 + */ + localStartAt: string; + /** + * Format: date-time + * @description Exclusive Asia/Tehran civil boundary with explicit offset. + * @example 2026-08-23T00:00:00+03:30 + */ + localEndAt: string; + /** @constant */ + timezone: "Asia/Tehran"; + /** @example مرداد ۱۴۰۵ */ + jalaliLabelFa: string; + isComplete: boolean; + isDefault: boolean; + }; + PeriodList: { + defaultPeriodId: components["schemas"]["PeriodId"]; + /** @description Dataset-bounded complete periods, grouped by type and newest-first within each group. */ + periods: components["schemas"]["ReportingPeriod"][]; + }; + CreateAnalyticsRunRequest: { + periodId: components["schemas"]["PeriodId"]; + terminalId?: string; + }; + Terminal: { + terminalId: string; + displayName: string; + /** Format: int64 */ + sessionCount: number; + isDefault: boolean; + }; + TerminalList: { + terminals: components["schemas"]["Terminal"][]; + }; + AnalyticsScope: { + terminalId: string | null; + }; + /** @example run_01K2M7YYYYYYYYYYYYYYYYYYYY */ + RunId: string; + /** @enum {string} */ + SectionId: "overview" | "payment_performance" | "fees" | "peer_comparison"; + /** @enum {string} */ + AnalyticsRunState: "IN_PROGRESS" | "COMPLETE" | "COMPLETE_WITH_FAILURES"; + /** @enum {string} */ + SectionState: "QUEUED" | "CALCULATING" | "READY" | "FAILED" | "SUPPRESSED"; + AnalyticsRun: { + runId: components["schemas"]["RunId"]; + state: components["schemas"]["AnalyticsRunState"]; + period: components["schemas"]["ReportingPeriod"]; + /** @description Opaque identifier for the immutable completed-import snapshot. */ + datasetSnapshotId: string; + /** @constant */ + metricDefinitionVersion: "0.4.0"; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + updatedAt: string; + /** Format: uri-reference */ + eventsUrl: string; + scope: components["schemas"]["AnalyticsScope"]; + sections: { + overview: components["schemas"]["OverviewSection"]; + paymentPerformance: components["schemas"]["PaymentPerformanceSection"]; + fees: components["schemas"]["FeesSection"]; + peerComparison: components["schemas"]["PeerComparisonSection"]; + }; + }; + QueuedSection: { + /** @constant */ + state: "QUEUED"; + /** Format: date-time */ + queuedAt?: string | null; + }; + CalculatingSection: { + /** @constant */ + state: "CALCULATING"; + /** Format: date-time */ + startedAt: string; + }; + FailedSection: { + /** @constant */ + state: "FAILED"; + /** Format: date-time */ + failedAt: string; + /** @enum {string} */ + errorCode: "CALCULATION_FAILED" | "CALCULATION_TIMEOUT" | "SOURCE_UNAVAILABLE" | "UNSUPPORTED_METRIC_VERSION"; + canRetry: boolean; + /** Format: uri-reference */ + retryAttemptsUrl?: string | null; + }; + ReadySectionMetadata: { + /** Format: date-time */ + calculatedAt: string; + datasetSnapshotId: string; + /** @constant */ + metricDefinitionVersion: "0.4.0"; + }; + IntegerMetric: { + metricId: string; + value: number; + /** @enum {string} */ + unit: "SESSIONS" | "RIAL"; + }; + DecimalMetric: { + metricId: string; + /** @description Null only when the denominator is zero or source is unavailable. */ + value: string | null; + /** @enum {string} */ + unit: "PERCENT" | "RATIO"; + numerator: number; + denominator: number; + }; + TimingDistribution: { + /** @enum {string} */ + metricId: "init_time_ms" | "verify_time_ms"; + p50Ms: number | null; + p95Ms: number | null; + sampleSize: number; + eligibleSampleSize: number; + coveragePercent: string | null; + }; + PreviousPeriodComparison: { + /** @enum {string} */ + state: "AVAILABLE" | "UNAVAILABLE"; + baselinePeriod: components["schemas"]["ReportingPeriod"]; + absoluteChange?: string | null; + relativeChangePercent?: string | null; + /** @enum {string|null} */ + unavailableReason?: "EMPTY_BASELINE" | "ZERO_BASELINE" | "SOURCE_UNAVAILABLE" | null; + isActionableInsightEligible?: boolean; + }; + OverviewData: { + /** + * @example { + * "metricId": "bank_paid_sessions", + * "value": 420, + * "unit": "SESSIONS" + * } + */ + bankPaidSessions: components["schemas"]["IntegerMetric"]; + /** + * @example { + * "metricId": "bank_paid_amount_rial", + * "value": 680000000, + * "unit": "RIAL" + * } + */ + bankPaidAmountRial: components["schemas"]["IntegerMetric"]; + paymentSuccessRate: components["schemas"]["DecimalMetric"]; + verificationCompletionRate: components["schemas"]["DecimalMetric"]; + comparisons: { + [key: string]: components["schemas"]["PreviousPeriodComparison"]; + }; + paymentHistory: components["schemas"]["PaymentHistoryPoint"][]; + }; + PaymentHistoryPoint: { + /** Format: date */ + localDate: string; + /** @description Tehran-local hour bucket, present only for DAY periods. */ + localHour?: number; + /** Format: int64 */ + totalSessions: number; + /** Format: int64 */ + bankPaidSessions: number; + /** Format: int64 */ + bankPaidAmountRial: number; + }; + PaymentPerformanceData: { + bankPaidSessions: components["schemas"]["IntegerMetric"]; + verifiedSessions: components["schemas"]["IntegerMetric"]; + unverifiedPaidSessions: components["schemas"]["IntegerMetric"]; + failedSessions: components["schemas"]["IntegerMetric"]; + reversedSessions: components["schemas"]["IntegerMetric"]; + noAttemptSessions: components["schemas"]["IntegerMetric"]; + reachedBankSessions: components["schemas"]["IntegerMetric"]; + retriedSessions: components["schemas"]["IntegerMetric"]; + retryRecoveryRate: components["schemas"]["DecimalMetric"]; + initTiming: components["schemas"]["TimingDistribution"]; + verifyTiming: components["schemas"]["TimingDistribution"]; + }; + FeesData: { + paidFeeAmountRial: components["schemas"]["IntegerMetric"]; + bankPaidAmountRial: components["schemas"]["IntegerMetric"]; + paidFeeRate: components["schemas"]["DecimalMetric"]; + comparison: components["schemas"]["PreviousPeriodComparison"]; + }; + PeerMetric: { + /** @enum {string} */ + metricId: "payment_success_rate" | "verification_completion_rate" | "retry_recovery_rate" | "init_time_ms_p50" | "init_time_ms_p95" | "verify_time_ms_p50" | "verify_time_ms_p95" | "paid_fee_rate"; + cohortMedian: string; + /** @description Higher always means better; lower-is-better metrics are direction-adjusted. */ + merchantPerformancePercentile: string; + /** @enum {string} */ + direction: "HIGHER_IS_BETTER" | "LOWER_IS_BETTER"; + }; + PeerComparisonData: { + eligiblePeerCount: number; + combinedPeerSessionCount: number; + metrics: components["schemas"]["PeerMetric"][]; + }; + OverviewReadySection: { + /** @constant */ + sectionId: "overview"; + /** @constant */ + state: "READY"; + metadata: components["schemas"]["ReadySectionMetadata"]; + data: components["schemas"]["OverviewData"]; + }; + PaymentPerformanceReadySection: { + /** @constant */ + sectionId: "payment_performance"; + /** @constant */ + state: "READY"; + metadata: components["schemas"]["ReadySectionMetadata"]; + data: components["schemas"]["PaymentPerformanceData"]; + }; + FeesReadySection: { + /** @constant */ + sectionId: "fees"; + /** @constant */ + state: "READY"; + metadata: components["schemas"]["ReadySectionMetadata"]; + data: components["schemas"]["FeesData"]; + }; + PeerComparisonReadySection: { + /** @constant */ + sectionId: "peer_comparison"; + /** @constant */ + state: "READY"; + metadata: components["schemas"]["ReadySectionMetadata"]; + data: components["schemas"]["PeerComparisonData"]; + }; + PeerComparisonSuppressedSection: { + /** @constant */ + sectionId: "peer_comparison"; + /** @constant */ + state: "SUPPRESSED"; + metadata: components["schemas"]["ReadySectionMetadata"]; + /** + * @description `TERMINAL_SCOPE_UNSUPPORTED` is returned for terminal-scoped runs because + * the approved peer cohort is merchant-grain and must not be presented as + * if it benchmarked one terminal. + * @enum {string} + */ + reason: "INSUFFICIENT_COHORT" | "TERMINAL_SCOPE_UNSUPPORTED"; + eligiblePeerCount: number; + combinedPeerSessionCount: number; + /** @constant */ + requiredPeerCount: 10; + /** @constant */ + requiredPeerSessionCount: 1000; + }; + OverviewSection: components["schemas"]["QueuedSection"] | components["schemas"]["CalculatingSection"] | components["schemas"]["FailedSection"] | components["schemas"]["OverviewReadySection"]; + PaymentPerformanceSection: components["schemas"]["QueuedSection"] | components["schemas"]["CalculatingSection"] | components["schemas"]["FailedSection"] | components["schemas"]["PaymentPerformanceReadySection"]; + FeesSection: components["schemas"]["QueuedSection"] | components["schemas"]["CalculatingSection"] | components["schemas"]["FailedSection"] | components["schemas"]["FeesReadySection"]; + PeerComparisonSection: components["schemas"]["QueuedSection"] | components["schemas"]["CalculatingSection"] | components["schemas"]["FailedSection"] | components["schemas"]["PeerComparisonReadySection"] | components["schemas"]["PeerComparisonSuppressedSection"]; + /** @enum {string} */ + DetailView: "ALL" | "UNVERIFIED_PAID" | "FAILED" | "RETRIED"; + /** @enum {string} */ + FailureStage: "NO_ATTEMPT" | "REACHED_BANK" | "OTHER_OBSERVED"; + /** @enum {string} */ + SessionStatus: "Paid" | "Verified" | "Failed" | "Reversed"; + /** @enum {string} */ + AttemptStatus: "InBank" | "Paid" | "Verified" | "Failed" | "Reversed"; + AttemptSummary: { + sequence: number; + status: components["schemas"]["AttemptStatus"]; + /** Format: date-time */ + createdAt?: string | null; + initTimeMs?: number | null; + verifyTimeMs?: number | null; + }; + SessionDetail: { + /** @description Opaque merchant-local API id, not the internal database key. */ + sessionId: string; + /** @description Existing opaque source terminal reference shown as `شناسه پایانه`; tenant-scoped display/trace data only. */ + terminalId: string; + sessionStatus: components["schemas"]["SessionStatus"]; + amountRial: number; + feeAmountRial: number; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + expiresAt?: string | null; + /** Format: date-time */ + verifiedAt?: string | null; + /** Format: date-time */ + settledAt?: string | null; + attemptCount: number; + failureStage?: components["schemas"]["FailureStage"] | null; + /** @description Bounded ordered attempt summary; payer tokens and response codes are absent. */ + attempts: components["schemas"]["AttemptSummary"][]; + }; + PageInfo: { + limit: number; + hasNextPage: boolean; + nextCursor: string | null; + }; + SessionDetailPage: { + period: components["schemas"]["ReportingPeriod"]; + view: components["schemas"]["DetailView"]; + appliedFilters: { + terminalId?: string | null; + failureStage?: components["schemas"]["FailureStage"] | null; + status?: components["schemas"]["SessionStatus"] | null; + /** Format: int64 */ + minAmountRial?: number | null; + /** Format: int64 */ + maxAmountRial?: number | null; + }; + items: components["schemas"]["SessionDetail"][]; + pageInfo: components["schemas"]["PageInfo"]; + }; + EventId: string; + SseEventBase: { + eventId: components["schemas"]["EventId"]; + runId: components["schemas"]["RunId"]; + /** Format: date-time */ + occurredAt: string; + }; + RunSnapshotEvent: components["schemas"]["SseEventBase"] & { + /** @enum {string} */ + recovery: "INITIAL" | "REPLAYED" | "RESET_TO_SNAPSHOT"; + run: components["schemas"]["AnalyticsRun"]; + }; + SectionStartedEvent: components["schemas"]["SseEventBase"] & { + sectionId: components["schemas"]["SectionId"]; + /** Format: date-time */ + startedAt: string; + }; + SectionReadyEvent: components["schemas"]["SseEventBase"] & { + sectionId: components["schemas"]["SectionId"]; + section: components["schemas"]["OverviewReadySection"] | components["schemas"]["PaymentPerformanceReadySection"] | components["schemas"]["FeesReadySection"] | components["schemas"]["PeerComparisonReadySection"]; + }; + SectionFailedEvent: components["schemas"]["SseEventBase"] & { + sectionId: components["schemas"]["SectionId"]; + section: components["schemas"]["FailedSection"]; + }; + SectionSuppressedEvent: components["schemas"]["SseEventBase"] & { + /** @constant */ + sectionId: "peer_comparison"; + section: components["schemas"]["PeerComparisonSuppressedSection"]; + }; + RunCompletedEvent: components["schemas"]["SseEventBase"] & { + run: components["schemas"]["AnalyticsRun"]; + }; + HeartbeatEvent: components["schemas"]["SseEventBase"] & { + /** Format: date-time */ + serverTime: string; + }; + /** @description JSON payload carried in the SSE `data` field; event name selects the schema. */ + AnalyticsSseEvent: components["schemas"]["RunSnapshotEvent"] | components["schemas"]["SectionStartedEvent"] | components["schemas"]["SectionReadyEvent"] | components["schemas"]["SectionFailedEvent"] | components["schemas"]["SectionSuppressedEvent"] | components["schemas"]["RunCompletedEvent"] | components["schemas"]["HeartbeatEvent"]; + FieldError: { + /** @description Allowlisted public field name; never a database path. */ + field: string; + reason: string; + }; + /** @description RFC 9457 Problem Details with stable application extensions. */ + Problem: { + /** + * Format: uri + * @description Stable problem type URI under `https://zarinpal.amiirkhl.ir/docs/errors/{error-slug}`. + */ + type: string; + title: string; + /** @description Must equal the actual HTTP response status. */ + status: number; + /** @description Safe occurrence-specific guidance; clients must not parse it. */ + detail: string; + /** Format: uri-reference */ + instance: string; + /** @description Stable application error code used by clients. */ + code: string; + requestId: string; + errors?: components["schemas"]["FieldError"][]; + /** + * Format: uri-reference + * @description Optional safe recovery link for run-related problems. + */ + snapshotUrl?: string; + /** + * Format: date-time + * @description Present for REFRESH_LIMITED responses. + */ + nextRefreshAt?: string; + }; }; - GrowthRange: { - /** Format: int64 */ - p10: number; - /** Format: int64 */ - p50: number; - /** Format: int64 */ - p90: number; + responses: { + /** @description Request syntax, cursor, event id, or parameter combination is invalid. */ + BadRequest: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description The current session is missing, malformed, expired, or revoked. */ + Unauthenticated: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + /** + * @example { + * "type": "https://zarinpal.amiirkhl.ir/docs/errors/unauthenticated", + * "title": "Authentication required", + * "status": 401, + * "detail": "Sign in to continue.", + * "instance": "/api/v1/analytics/runs", + * "code": "UNAUTHENTICATED", + * "requestId": "req_01K2M8A0000000000000000000" + * } + */ + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description The authenticated request is not permitted or failed CSRF/origin checks. */ + Forbidden: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Resource is absent or is outside the authenticated merchant boundary. */ + ResourceNotFound: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + /** + * @example { + * "type": "https://zarinpal.amiirkhl.ir/docs/errors/resource-not-found", + * "title": "Resource not found", + * "status": 404, + * "detail": "The requested resource is unavailable.", + * "instance": "/api/v1/analytics/runs/run_unknown", + * "code": "RESOURCE_NOT_FOUND", + * "requestId": "req_01K2M8A0000000000000000000" + * } + */ + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Current resource state does not permit the requested operation. */ + Conflict: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Request exceeds the 16 KiB JSON body limit. */ + RequestTooLarge: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Export exceeds 100,000 rows or 50 MiB estimated uncompressed output. */ + ExportTooLarge: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + /** + * @example { + * "type": "https://zarinpal.amiirkhl.ir/docs/errors/export-too-large", + * "title": "Export is too large", + * "status": 413, + * "detail": "Narrow the approved filters and try again.", + * "instance": "/api/v1/analytics/session-details.csv", + * "code": "EXPORT_TOO_LARGE", + * "requestId": "req_01K2M8A0000000000000000000" + * } + */ + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Request media type is not supported. */ + UnsupportedMediaType: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Request is well-formed but violates the interface contract. */ + ValidationFailed: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + /** + * @example { + * "type": "https://zarinpal.amiirkhl.ir/docs/errors/validation-error", + * "title": "Request validation failed", + * "status": 422, + * "detail": "One or more request values are invalid.", + * "instance": "/api/v1/analytics/runs", + * "code": "VALIDATION_ERROR", + * "requestId": "req_01K2M8A0000000000000000000", + * "errors": [ + * { + * "field": "periodId", + * "reason": "UNKNOWN_PERIOD" + * } + * ] + * } + */ + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Authenticated-user or merchant rate limit reached before work began. */ + RateLimited: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "Retry-After": components["headers"]["RetryAfter"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description SSE concurrency limit reached for the user or merchant. */ + ConnectionLimited: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "Retry-After": components["headers"]["RetryAfter"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Unexpected server failure; implementation details are not exposed. */ + InternalError: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Required authoritative dependency is unavailable; protected requests fail closed. */ + DependencyUnavailable: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "Retry-After": components["headers"]["RetryAfter"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Analytics dependency is temporarily unavailable. */ + AnalyticsUnavailable: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "Retry-After": components["headers"]["RetryAfter"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Export did not complete within the 60-second deadline before streaming began. */ + ExportTimeout: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "Retry-After": components["headers"]["RetryAfter"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; }; - GrowthForecastPoint: { - /** Format: date-time */ - date: string; - /** Format: int64 */ - baselineSessions: number; - /** Format: int64 */ - baselineAmountRial: number; - paidSessions: components['schemas']['GrowthRange']; - paidAmountRial: components['schemas']['GrowthRange']; - eventIds?: string[]; - }; - GrowthForecast: { - /** Format: date-time */ - startDate: string; - /** @enum {integer} */ - horizonDays: 90; - /** @enum {string} */ - confidence: 'HIGH' | 'MEDIUM' | 'LOW' | 'INSUFFICIENT_DATA'; - suppressionReason?: string; - points: components['schemas']['GrowthForecastPoint'][]; - backtest: { - origins: number; - /** Format: int64 */ - sessionMapeBps: number; - /** Format: int64 */ - amountMapeBps: number; - /** Format: int64 */ - intervalCoverageBps: number; - }; - }; - GrowthHistoricalWin: { - evidenceId: string; - eventId: string; - eventTitleFa: string; - /** Format: date-time */ - startDate: string; - /** Format: date-time */ - endDate: string; - eligible?: boolean; - suppressionReason?: string; - observationCount?: number; - /** Format: int64 */ - baselineSessionCount?: number; - /** Format: int64 */ - baselinePaidSessions?: number; - /** Format: int64 */ - baselineAmountRial?: number; - /** Format: int64 */ - actualSessionCount?: number; - /** Format: int64 */ - actualPaidSessions?: number; - /** Format: int64 */ - actualAmountRial?: number; - /** Format: int64 */ - sessionUpliftBps?: number; - /** Format: int64 */ - paidSessionUpliftBps?: number; - /** Format: int64 */ - amountUpliftBps?: number; - materialWin: boolean; - materialDecline?: boolean; - }; - GrowthOpportunity: { - evidenceId: string; - eventId: string; - eventTitleFa: string; - /** Format: date-time */ - date: string; - /** Format: int64 */ - similarityBps: number; - expectedSessions: components['schemas']['GrowthRange']; - expectedAmountRial: components['schemas']['GrowthRange']; - historicalEvidenceId: string; - }; - GrowthRisk: { - evidenceId: string; - /** Format: date-time */ - date: string; - /** Format: date-time */ - endDate?: string; - /** - * Format: int64 - * @description At least one decline dimension is 2000 BPS or greater. - */ - sessionDeclineBps: number; - /** - * Format: int64 - * @description At least one decline dimension is 2000 BPS or greater. - */ - amountDeclineBps: number; - expectedSessions: components['schemas']['GrowthRange']; - expectedAmountRial: components['schemas']['GrowthRange']; - }; - GrowthPeer: { - /** @enum {string} */ - state: 'READY' | 'SUPPRESSED'; - reason?: string; - eligiblePeerCount: number; - /** Format: int64 */ - combinedPeerSessionCount: number; - /** Format: int64 */ - medianPaidSessions: number; - /** Format: int64 */ - percentileBps: number; - }; - GrowthCampaignBrief: { - headline: string; - interpretation: string; - offerSuggestion: string; - messageSuggestion: string; - preparationAction: string; - limitation: string; - evidenceIds: string[]; - }; - GrowthFallbackBrief: { - headlineFa: string; - guidanceFa: string; - offerFa: string; - messageFa: string; - preparationFa: string; - limitationFa: string; - evidenceIds: string[]; - }; - GrowthEvidence: { - forecast: components['schemas']['GrowthForecast']; - historicalWins: components['schemas']['GrowthHistoricalWin'][]; - opportunities: components['schemas']['GrowthOpportunity'][]; - risks: components['schemas']['GrowthRisk'][]; - peer?: components['schemas']['GrowthPeer']; - fallbackBriefs: components['schemas']['GrowthFallbackBrief'][]; - }; - GrowthReport: { - reportId: string; - revision: number; - /** @enum {string} */ - status: 'QUEUED' | 'ANALYZING' | 'GENERATING' | 'READY' | 'PARTIAL' | 'FAILED'; - scope: { - terminalId?: string | null; - }; - /** @enum {integer} */ - horizonDays: 90; - /** Format: date-time */ - forecastStartDate?: string; - deterministicEvidence?: components['schemas']['GrowthEvidence']; - aiCopy?: { - briefs: components['schemas']['GrowthCampaignBrief'][]; - }; - failureCode?: string; - cached: boolean; - stale: boolean; - /** - * Format: date-time - * @description Current time while no AI summary exists for the scope; otherwise the next refresh boundary for that terminal scope in the Tehran day. The all-terminals selection is a separate scope. - */ - nextRefreshAt: string; - /** Format: date-time */ - createdAt: string; - /** Format: date-time */ - updatedAt: string; - /** Format: uri-reference */ - eventsUrl: string; - }; - LoginRequest: { - /** @example merchant-0001 */ - username: string; - password: string; - }; - AuthSession: { - user: components['schemas']['MerchantUserSummary']; - merchant: components['schemas']['MerchantSummary']; - /** Format: date-time */ - createdAt: string; - /** Format: date-time */ - idleExpiresAt: string; - /** Format: date-time */ - absoluteExpiresAt: string; - /** @description Session-bound CSRF token. It is not an authentication token and must not be logged. */ - csrfToken: string; - }; - MerchantUserSummary: { - userId: string; - /** @example merchant-0001 */ - username: string; - }; - MerchantSummary: { - /** @description Opaque API identifier, never accepted as authorization input. */ - merchantId: string; - displayName: string; - profileIsSynthetic: boolean; - }; - /** @enum {string} */ - PeriodType: 'DAY' | 'WEEK' | 'JALALI_MONTH' | 'PERSIAN_SEASON'; - /** @example jm-1405-05 */ - PeriodId: string; - ReportingPeriod: { - periodId: components['schemas']['PeriodId']; - periodType: components['schemas']['PeriodType']; - /** - * Format: date-time - * @description Inclusive UTC boundary. - * @example 2026-07-23T20:30:00Z - */ - startAt: string; - /** - * Format: date-time - * @description Exclusive UTC boundary. - * @example 2026-08-22T20:30:00Z - */ - endAt: string; - /** - * Format: date-time - * @description Inclusive Asia/Tehran civil boundary with explicit offset. - * @example 2026-07-24T00:00:00+03:30 - */ - localStartAt: string; - /** - * Format: date-time - * @description Exclusive Asia/Tehran civil boundary with explicit offset. - * @example 2026-08-23T00:00:00+03:30 - */ - localEndAt: string; - /** @constant */ - timezone: 'Asia/Tehran'; - /** @example مرداد ۱۴۰۵ */ - jalaliLabelFa: string; - isComplete: boolean; - isDefault: boolean; - }; - PeriodList: { - defaultPeriodId: components['schemas']['PeriodId']; - /** @description Dataset-bounded complete periods, grouped by type and newest-first within each group. */ - periods: components['schemas']['ReportingPeriod'][]; - }; - CreateAnalyticsRunRequest: { - periodId: components['schemas']['PeriodId']; - terminalId?: string; - }; - Terminal: { - terminalId: string; - displayName: string; - /** Format: int64 */ - sessionCount: number; - isDefault: boolean; - }; - TerminalList: { - terminals: components['schemas']['Terminal'][]; - }; - AnalyticsScope: { - terminalId: string | null; - }; - /** @example run_01K2M7YYYYYYYYYYYYYYYYYYYY */ - RunId: string; - /** @enum {string} */ - SectionId: 'overview' | 'payment_performance' | 'fees' | 'peer_comparison'; - /** @enum {string} */ - AnalyticsRunState: 'IN_PROGRESS' | 'COMPLETE' | 'COMPLETE_WITH_FAILURES'; - /** @enum {string} */ - SectionState: 'QUEUED' | 'CALCULATING' | 'READY' | 'FAILED' | 'SUPPRESSED'; - AnalyticsRun: { - runId: components['schemas']['RunId']; - state: components['schemas']['AnalyticsRunState']; - period: components['schemas']['ReportingPeriod']; - /** @description Opaque identifier for the immutable completed-import snapshot. */ - datasetSnapshotId: string; - /** @constant */ - metricDefinitionVersion: '0.4.0'; - /** Format: date-time */ - createdAt: string; - /** Format: date-time */ - updatedAt: string; - /** Format: uri-reference */ - eventsUrl: string; - scope: components['schemas']['AnalyticsScope']; - sections: { - overview: components['schemas']['OverviewSection']; - paymentPerformance: components['schemas']['PaymentPerformanceSection']; - fees: components['schemas']['FeesSection']; - peerComparison: components['schemas']['PeerComparisonSection']; - }; - }; - QueuedSection: { - /** @constant */ - state: 'QUEUED'; - /** Format: date-time */ - queuedAt?: string | null; - }; - CalculatingSection: { - /** @constant */ - state: 'CALCULATING'; - /** Format: date-time */ - startedAt: string; - }; - FailedSection: { - /** @constant */ - state: 'FAILED'; - /** Format: date-time */ - failedAt: string; - /** @enum {string} */ - errorCode: - | 'CALCULATION_FAILED' - | 'CALCULATION_TIMEOUT' - | 'SOURCE_UNAVAILABLE' - | 'UNSUPPORTED_METRIC_VERSION'; - canRetry: boolean; - /** Format: uri-reference */ - retryAttemptsUrl?: string | null; - }; - ReadySectionMetadata: { - /** Format: date-time */ - calculatedAt: string; - datasetSnapshotId: string; - /** @constant */ - metricDefinitionVersion: '0.4.0'; - }; - IntegerMetric: { - metricId: string; - value: number; - /** @enum {string} */ - unit: 'SESSIONS' | 'RIAL'; - }; - DecimalMetric: { - metricId: string; - /** @description Null only when the denominator is zero or source is unavailable. */ - value: string | null; - /** @enum {string} */ - unit: 'PERCENT' | 'RATIO'; - numerator: number; - denominator: number; - }; - TimingDistribution: { - /** @enum {string} */ - metricId: 'init_time_ms' | 'verify_time_ms'; - p50Ms: number | null; - p95Ms: number | null; - sampleSize: number; - eligibleSampleSize: number; - coveragePercent: string | null; - }; - PreviousPeriodComparison: { - /** @enum {string} */ - state: 'AVAILABLE' | 'UNAVAILABLE'; - baselinePeriod: components['schemas']['ReportingPeriod']; - absoluteChange?: string | null; - relativeChangePercent?: string | null; - /** @enum {string|null} */ - unavailableReason?: 'EMPTY_BASELINE' | 'ZERO_BASELINE' | 'SOURCE_UNAVAILABLE' | null; - isActionableInsightEligible?: boolean; - }; - OverviewData: { - /** - * @example { - * "metricId": "bank_paid_sessions", - * "value": 420, - * "unit": "SESSIONS" - * } - */ - bankPaidSessions: components['schemas']['IntegerMetric']; - /** - * @example { - * "metricId": "bank_paid_amount_rial", - * "value": 680000000, - * "unit": "RIAL" - * } - */ - bankPaidAmountRial: components['schemas']['IntegerMetric']; - paymentSuccessRate: components['schemas']['DecimalMetric']; - verificationCompletionRate: components['schemas']['DecimalMetric']; - comparisons: { - [key: string]: components['schemas']['PreviousPeriodComparison']; - }; - paymentHistory: components['schemas']['PaymentHistoryPoint'][]; - }; - PaymentHistoryPoint: { - /** Format: date */ - localDate: string; - /** @description Tehran-local hour bucket, present only for DAY periods. */ - localHour?: number; - /** Format: int64 */ - totalSessions: number; - /** Format: int64 */ - bankPaidSessions: number; - /** Format: int64 */ - bankPaidAmountRial: number; - }; - PaymentPerformanceData: { - bankPaidSessions: components['schemas']['IntegerMetric']; - verifiedSessions: components['schemas']['IntegerMetric']; - unverifiedPaidSessions: components['schemas']['IntegerMetric']; - failedSessions: components['schemas']['IntegerMetric']; - reversedSessions: components['schemas']['IntegerMetric']; - noAttemptSessions: components['schemas']['IntegerMetric']; - reachedBankSessions: components['schemas']['IntegerMetric']; - retriedSessions: components['schemas']['IntegerMetric']; - retryRecoveryRate: components['schemas']['DecimalMetric']; - initTiming: components['schemas']['TimingDistribution']; - verifyTiming: components['schemas']['TimingDistribution']; - }; - FeesData: { - paidFeeAmountRial: components['schemas']['IntegerMetric']; - bankPaidAmountRial: components['schemas']['IntegerMetric']; - paidFeeRate: components['schemas']['DecimalMetric']; - comparison: components['schemas']['PreviousPeriodComparison']; - }; - PeerMetric: { - /** @enum {string} */ - metricId: - | 'payment_success_rate' - | 'verification_completion_rate' - | 'retry_recovery_rate' - | 'init_time_ms_p50' - | 'init_time_ms_p95' - | 'verify_time_ms_p50' - | 'verify_time_ms_p95' - | 'paid_fee_rate'; - cohortMedian: string; - /** @description Higher always means better; lower-is-better metrics are direction-adjusted. */ - merchantPerformancePercentile: string; - /** @enum {string} */ - direction: 'HIGHER_IS_BETTER' | 'LOWER_IS_BETTER'; - }; - PeerComparisonData: { - eligiblePeerCount: number; - combinedPeerSessionCount: number; - metrics: components['schemas']['PeerMetric'][]; - }; - OverviewReadySection: { - /** @constant */ - sectionId: 'overview'; - /** @constant */ - state: 'READY'; - metadata: components['schemas']['ReadySectionMetadata']; - data: components['schemas']['OverviewData']; - }; - PaymentPerformanceReadySection: { - /** @constant */ - sectionId: 'payment_performance'; - /** @constant */ - state: 'READY'; - metadata: components['schemas']['ReadySectionMetadata']; - data: components['schemas']['PaymentPerformanceData']; - }; - FeesReadySection: { - /** @constant */ - sectionId: 'fees'; - /** @constant */ - state: 'READY'; - metadata: components['schemas']['ReadySectionMetadata']; - data: components['schemas']['FeesData']; - }; - PeerComparisonReadySection: { - /** @constant */ - sectionId: 'peer_comparison'; - /** @constant */ - state: 'READY'; - metadata: components['schemas']['ReadySectionMetadata']; - data: components['schemas']['PeerComparisonData']; - }; - PeerComparisonSuppressedSection: { - /** @constant */ - sectionId: 'peer_comparison'; - /** @constant */ - state: 'SUPPRESSED'; - metadata: components['schemas']['ReadySectionMetadata']; - /** - * @description `TERMINAL_SCOPE_UNSUPPORTED` is returned for terminal-scoped runs because - * the approved peer cohort is merchant-grain and must not be presented as - * if it benchmarked one terminal. - * @enum {string} - */ - reason: 'INSUFFICIENT_COHORT' | 'TERMINAL_SCOPE_UNSUPPORTED'; - eligiblePeerCount: number; - combinedPeerSessionCount: number; - /** @constant */ - requiredPeerCount: 10; - /** @constant */ - requiredPeerSessionCount: 1000; - }; - OverviewSection: - | components['schemas']['QueuedSection'] - | components['schemas']['CalculatingSection'] - | components['schemas']['FailedSection'] - | components['schemas']['OverviewReadySection']; - PaymentPerformanceSection: - | components['schemas']['QueuedSection'] - | components['schemas']['CalculatingSection'] - | components['schemas']['FailedSection'] - | components['schemas']['PaymentPerformanceReadySection']; - FeesSection: - | components['schemas']['QueuedSection'] - | components['schemas']['CalculatingSection'] - | components['schemas']['FailedSection'] - | components['schemas']['FeesReadySection']; - PeerComparisonSection: - | components['schemas']['QueuedSection'] - | components['schemas']['CalculatingSection'] - | components['schemas']['FailedSection'] - | components['schemas']['PeerComparisonReadySection'] - | components['schemas']['PeerComparisonSuppressedSection']; - /** @enum {string} */ - DetailView: 'ALL' | 'UNVERIFIED_PAID' | 'FAILED' | 'RETRIED'; - /** @enum {string} */ - FailureStage: 'NO_ATTEMPT' | 'REACHED_BANK' | 'OTHER_OBSERVED'; - /** @enum {string} */ - SessionStatus: 'Paid' | 'Verified' | 'Failed' | 'Reversed'; - /** @enum {string} */ - AttemptStatus: 'InBank' | 'Paid' | 'Verified' | 'Failed' | 'Reversed'; - AttemptSummary: { - sequence: number; - status: components['schemas']['AttemptStatus']; - /** Format: date-time */ - createdAt?: string | null; - initTimeMs?: number | null; - verifyTimeMs?: number | null; - }; - SessionDetail: { - /** @description Opaque merchant-local API id, not the internal database key. */ - sessionId: string; - /** @description Existing opaque source terminal reference shown as `شناسه پایانه`; tenant-scoped display/trace data only. */ - terminalId: string; - sessionStatus: components['schemas']['SessionStatus']; - amountRial: number; - feeAmountRial: number; - /** Format: date-time */ - createdAt: string; - /** Format: date-time */ - expiresAt?: string | null; - /** Format: date-time */ - verifiedAt?: string | null; - /** Format: date-time */ - settledAt?: string | null; - attemptCount: number; - failureStage?: components['schemas']['FailureStage'] | null; - /** @description Bounded ordered attempt summary; payer tokens and response codes are absent. */ - attempts: components['schemas']['AttemptSummary'][]; - }; - PageInfo: { - limit: number; - hasNextPage: boolean; - nextCursor: string | null; - }; - SessionDetailPage: { - period: components['schemas']['ReportingPeriod']; - view: components['schemas']['DetailView']; - appliedFilters: { - terminalId?: string | null; - failureStage?: components['schemas']['FailureStage'] | null; - status?: components['schemas']['SessionStatus'] | null; - /** Format: int64 */ - minAmountRial?: number | null; - /** Format: int64 */ - maxAmountRial?: number | null; - }; - items: components['schemas']['SessionDetail'][]; - pageInfo: components['schemas']['PageInfo']; - }; - EventId: string; - SseEventBase: { - eventId: components['schemas']['EventId']; - runId: components['schemas']['RunId']; - /** Format: date-time */ - occurredAt: string; - }; - RunSnapshotEvent: components['schemas']['SseEventBase'] & { - /** @enum {string} */ - recovery: 'INITIAL' | 'REPLAYED' | 'RESET_TO_SNAPSHOT'; - run: components['schemas']['AnalyticsRun']; - }; - SectionStartedEvent: components['schemas']['SseEventBase'] & { - sectionId: components['schemas']['SectionId']; - /** Format: date-time */ - startedAt: string; - }; - SectionReadyEvent: components['schemas']['SseEventBase'] & { - sectionId: components['schemas']['SectionId']; - section: - | components['schemas']['OverviewReadySection'] - | components['schemas']['PaymentPerformanceReadySection'] - | components['schemas']['FeesReadySection'] - | components['schemas']['PeerComparisonReadySection']; - }; - SectionFailedEvent: components['schemas']['SseEventBase'] & { - sectionId: components['schemas']['SectionId']; - section: components['schemas']['FailedSection']; - }; - SectionSuppressedEvent: components['schemas']['SseEventBase'] & { - /** @constant */ - sectionId: 'peer_comparison'; - section: components['schemas']['PeerComparisonSuppressedSection']; - }; - RunCompletedEvent: components['schemas']['SseEventBase'] & { - run: components['schemas']['AnalyticsRun']; - }; - HeartbeatEvent: components['schemas']['SseEventBase'] & { - /** Format: date-time */ - serverTime: string; - }; - /** @description JSON payload carried in the SSE `data` field; event name selects the schema. */ - AnalyticsSseEvent: - | components['schemas']['RunSnapshotEvent'] - | components['schemas']['SectionStartedEvent'] - | components['schemas']['SectionReadyEvent'] - | components['schemas']['SectionFailedEvent'] - | components['schemas']['SectionSuppressedEvent'] - | components['schemas']['RunCompletedEvent'] - | components['schemas']['HeartbeatEvent']; - FieldError: { - /** @description Allowlisted public field name; never a database path. */ - field: string; - reason: string; - }; - /** @description RFC 9457 Problem Details with stable application extensions. */ - Problem: { - /** - * Format: uri - * @description Stable problem type URI under `https://zarinpal.amiirkhl.ir/docs/errors/{error-slug}`. - */ - type: string; - title: string; - /** @description Must equal the actual HTTP response status. */ - status: number; - /** @description Safe occurrence-specific guidance; clients must not parse it. */ - detail: string; - /** Format: uri-reference */ - instance: string; - /** @description Stable application error code used by clients. */ - code: string; - requestId: string; - errors?: components['schemas']['FieldError'][]; - /** - * Format: uri-reference - * @description Optional safe recovery link for run-related problems. - */ - snapshotUrl?: string; - /** - * Format: date-time - * @description Present for REFRESH_LIMITED responses. - */ - nextRefreshAt?: string; - }; - }; - responses: { - /** @description Request syntax, cursor, event id, or parameter combination is invalid. */ - BadRequest: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - 'application/problem+json': components['schemas']['Problem']; - }; - }; - /** @description The current session is missing, malformed, expired, or revoked. */ - Unauthenticated: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { + parameters: { + /** @description Opaque analytics run identifier; possession does not authorize access. */ + RunIdPath: components["schemas"]["RunId"]; + SectionIdPath: components["schemas"]["SectionId"]; /** - * @example { - * "type": "https://zarinpal.amiirkhl.ir/docs/errors/unauthenticated", - * "title": "Authentication required", - * "status": 401, - * "detail": "Sign in to continue.", - * "instance": "/api/v1/analytics/runs", - * "code": "UNAUTHENTICATED", - * "requestId": "req_01K2M8A0000000000000000000" - * } + * @description Client-generated intent key reused unchanged for retries of one intent. + * @example 95f6ec2d-26e8-4983-afd4-8a6d4434726b */ - 'application/problem+json': components['schemas']['Problem']; - }; + IdempotencyKeyHeader: string; + /** @description CSRF token for the current authenticated session; never log this value. */ + CsrfTokenHeader: string; + /** @description Strict same-origin evidence validated with Fetch Metadata where available. */ + OriginHeader: string; + /** @description Optional safe correlation id. The server may replace invalid values. */ + RequestIdHeader: string; + /** @description Stable id returned by `listAnalyticsPeriods`. */ + PeriodIdQuery: components["schemas"]["PeriodId"]; + DetailViewQuery: components["schemas"]["DetailView"]; + /** @description Valid only when `view=FAILED`. */ + FailureStageQuery: components["schemas"]["FailureStage"]; + /** @description Approved opaque source terminal reference resolved only inside the authenticated merchant; never an authorization input. */ + TerminalIdQuery: string; + SessionStatusQuery: components["schemas"]["SessionStatus"]; + MinAmountRialQuery: number; + MaxAmountRialQuery: number; + /** @description Opaque integrity-protected cursor bound to tenant, view, filters, and ordering. */ + CursorQuery: string; + /** @description Page size; defaults to 25 and cannot exceed 100. */ + LimitQuery: number; }; - /** @description The authenticated request is not permitted or failed CSRF/origin checks. */ - Forbidden: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - 'application/problem+json': components['schemas']['Problem']; - }; + requestBodies: never; + headers: { + /** @description Server correlation id for this request. */ + RequestId: string; + /** @description Prevents storage of authenticated merchant data. */ + PrivateNoStore: "private, no-store"; + /** @description Seconds until retry is permitted. */ + RetryAfter: number; }; - /** @description Resource is absent or is outside the authenticated merchant boundary. */ - ResourceNotFound: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - /** - * @example { - * "type": "https://zarinpal.amiirkhl.ir/docs/errors/resource-not-found", - * "title": "Resource not found", - * "status": 404, - * "detail": "The requested resource is unavailable.", - * "instance": "/api/v1/analytics/runs/run_unknown", - * "code": "RESOURCE_NOT_FOUND", - * "requestId": "req_01K2M8A0000000000000000000" - * } - */ - 'application/problem+json': components['schemas']['Problem']; - }; - }; - /** @description Current resource state does not permit the requested operation. */ - Conflict: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - 'application/problem+json': components['schemas']['Problem']; - }; - }; - /** @description Request exceeds the 16 KiB JSON body limit. */ - RequestTooLarge: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - 'application/problem+json': components['schemas']['Problem']; - }; - }; - /** @description Export exceeds 100,000 rows or 50 MiB estimated uncompressed output. */ - ExportTooLarge: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - /** - * @example { - * "type": "https://zarinpal.amiirkhl.ir/docs/errors/export-too-large", - * "title": "Export is too large", - * "status": 413, - * "detail": "Narrow the approved filters and try again.", - * "instance": "/api/v1/analytics/session-details.csv", - * "code": "EXPORT_TOO_LARGE", - * "requestId": "req_01K2M8A0000000000000000000" - * } - */ - 'application/problem+json': components['schemas']['Problem']; - }; - }; - /** @description Request media type is not supported. */ - UnsupportedMediaType: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - 'application/problem+json': components['schemas']['Problem']; - }; - }; - /** @description Request is well-formed but violates the interface contract. */ - ValidationFailed: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - /** - * @example { - * "type": "https://zarinpal.amiirkhl.ir/docs/errors/validation-error", - * "title": "Request validation failed", - * "status": 422, - * "detail": "One or more request values are invalid.", - * "instance": "/api/v1/analytics/runs", - * "code": "VALIDATION_ERROR", - * "requestId": "req_01K2M8A0000000000000000000", - * "errors": [ - * { - * "field": "periodId", - * "reason": "UNKNOWN_PERIOD" - * } - * ] - * } - */ - 'application/problem+json': components['schemas']['Problem']; - }; - }; - /** @description Authenticated-user or merchant rate limit reached before work began. */ - RateLimited: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'Retry-After': components['headers']['RetryAfter']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - 'application/problem+json': components['schemas']['Problem']; - }; - }; - /** @description SSE concurrency limit reached for the user or merchant. */ - ConnectionLimited: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'Retry-After': components['headers']['RetryAfter']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - 'application/problem+json': components['schemas']['Problem']; - }; - }; - /** @description Unexpected server failure; implementation details are not exposed. */ - InternalError: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - 'application/problem+json': components['schemas']['Problem']; - }; - }; - /** @description Required authoritative dependency is unavailable; protected requests fail closed. */ - DependencyUnavailable: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'Retry-After': components['headers']['RetryAfter']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - 'application/problem+json': components['schemas']['Problem']; - }; - }; - /** @description Analytics dependency is temporarily unavailable. */ - AnalyticsUnavailable: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'Retry-After': components['headers']['RetryAfter']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - 'application/problem+json': components['schemas']['Problem']; - }; - }; - /** @description Export did not complete within the 60-second deadline before streaming began. */ - ExportTimeout: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'Retry-After': components['headers']['RetryAfter']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - 'application/problem+json': components['schemas']['Problem']; - }; - }; - }; - parameters: { - /** @description Opaque analytics run identifier; possession does not authorize access. */ - RunIdPath: components['schemas']['RunId']; - SectionIdPath: components['schemas']['SectionId']; - /** - * @description Client-generated intent key reused unchanged for retries of one intent. - * @example 95f6ec2d-26e8-4983-afd4-8a6d4434726b - */ - IdempotencyKeyHeader: string; - /** @description CSRF token for the current authenticated session; never log this value. */ - CsrfTokenHeader: string; - /** @description Strict same-origin evidence validated with Fetch Metadata where available. */ - OriginHeader: string; - /** @description Optional safe correlation id. The server may replace invalid values. */ - RequestIdHeader: string; - /** @description Stable id returned by `listAnalyticsPeriods`. */ - PeriodIdQuery: components['schemas']['PeriodId']; - DetailViewQuery: components['schemas']['DetailView']; - /** @description Valid only when `view=FAILED`. */ - FailureStageQuery: components['schemas']['FailureStage']; - /** @description Approved opaque source terminal reference resolved only inside the authenticated merchant; never an authorization input. */ - TerminalIdQuery: string; - SessionStatusQuery: components['schemas']['SessionStatus']; - MinAmountRialQuery: number; - MaxAmountRialQuery: number; - /** @description Opaque integrity-protected cursor bound to tenant, view, filters, and ordering. */ - CursorQuery: string; - /** @description Page size; defaults to 25 and cannot exceed 100. */ - LimitQuery: number; - }; - requestBodies: never; - headers: { - /** @description Server correlation id for this request. */ - RequestId: string; - /** @description Prevents storage of authenticated merchant data. */ - PrivateNoStore: 'private, no-store'; - /** @description Seconds until retry is permitted. */ - RetryAfter: number; - }; - pathItems: never; + pathItems: never; } export type $defs = Record; export interface operations { - createAuthSession: { - parameters: { - query?: never; - header: { - /** @description Strict same-origin evidence validated with Fetch Metadata where available. */ - Origin: components['parameters']['OriginHeader']; - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['LoginRequest']; - }; - }; - responses: { - /** @description Authenticated session created. */ - 201: { - headers: { - /** - * @description Secure opaque session cookie; value is intentionally omitted from examples. - * @example __Host-zpa_session=; Path=/; Secure; HttpOnly; SameSite=Lax - */ - 'Set-Cookie'?: string; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; + createAuthSession: { + parameters: { + query?: never; + header: { + /** @description Strict same-origin evidence validated with Fetch Metadata where available. */ + Origin: components["parameters"]["OriginHeader"]; + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path?: never; + cookie?: never; }; - content: { - 'application/json': components['schemas']['AuthSession']; + requestBody: { + content: { + "application/json": components["schemas"]["LoginRequest"]; + }; + }; + responses: { + /** @description Authenticated session created. */ + 201: { + headers: { + /** + * @description Secure opaque session cookie; value is intentionally omitted from examples. + * @example __Host-zpa_session=; Path=/; Secure; HttpOnly; SameSite=Lax + */ + "Set-Cookie"?: string; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AuthSession"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthenticated"]; + 413: components["responses"]["RequestTooLarge"]; + 415: components["responses"]["UnsupportedMediaType"]; + 422: components["responses"]["ValidationFailed"]; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; + 503: components["responses"]["DependencyUnavailable"]; }; - }; - 400: components['responses']['BadRequest']; - 401: components['responses']['Unauthenticated']; - 413: components['responses']['RequestTooLarge']; - 415: components['responses']['UnsupportedMediaType']; - 422: components['responses']['ValidationFailed']; - 429: components['responses']['RateLimited']; - 500: components['responses']['InternalError']; - 503: components['responses']['DependencyUnavailable']; }; - }; - getAuthSession: { - parameters: { - query?: never; - header?: { - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path?: never; - cookie?: never; + getAuthSession: { + parameters: { + query?: never; + header?: { + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Current authenticated merchant-user session. */ + 200: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AuthSession"]; + }; + }; + 401: components["responses"]["Unauthenticated"]; + 500: components["responses"]["InternalError"]; + 503: components["responses"]["DependencyUnavailable"]; + }; }; - requestBody?: never; - responses: { - /** @description Current authenticated merchant-user session. */ - 200: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; + deleteAuthSession: { + parameters: { + query?: never; + header: { + /** @description CSRF token for the current authenticated session; never log this value. */ + "X-CSRF-Token": components["parameters"]["CsrfTokenHeader"]; + /** @description Strict same-origin evidence validated with Fetch Metadata where available. */ + Origin: components["parameters"]["OriginHeader"]; + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path?: never; + cookie?: never; }; - content: { - 'application/json': components['schemas']['AuthSession']; + requestBody?: never; + responses: { + /** @description Current authenticated session revoked. */ + 204: { + headers: { + /** + * @description Expired session cookie. + * @example __Host-zpa_session=; Path=/; Max-Age=0; Secure; HttpOnly; SameSite=Lax + */ + "Set-Cookie"?: string; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content?: never; + }; + 401: components["responses"]["Unauthenticated"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; + 503: components["responses"]["DependencyUnavailable"]; }; - }; - 401: components['responses']['Unauthenticated']; - 500: components['responses']['InternalError']; - 503: components['responses']['DependencyUnavailable']; }; - }; - deleteAuthSession: { - parameters: { - query?: never; - header: { - /** @description CSRF token for the current authenticated session; never log this value. */ - 'X-CSRF-Token': components['parameters']['CsrfTokenHeader']; - /** @description Strict same-origin evidence validated with Fetch Metadata where available. */ - Origin: components['parameters']['OriginHeader']; - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path?: never; - cookie?: never; + listAnalyticsPeriods: { + parameters: { + query?: { + /** @description Optional allowlisted period type. */ + periodType?: components["schemas"]["PeriodType"]; + }; + header?: { + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Approved period choices and default selection. */ + 200: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PeriodList"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthenticated"]; + 422: components["responses"]["ValidationFailed"]; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; + 503: components["responses"]["DependencyUnavailable"]; + }; }; - requestBody?: never; - responses: { - /** @description Current authenticated session revoked. */ - 204: { - headers: { - /** - * @description Expired session cookie. - * @example __Host-zpa_session=; Path=/; Max-Age=0; Secure; HttpOnly; SameSite=Lax - */ - 'Set-Cookie'?: string; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; + listMerchantTerminals: { + parameters: { + query?: never; + header?: { + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Merchant-owned terminal choices ordered by session volume. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TerminalList"]; + }; + }; + 401: components["responses"]["Unauthenticated"]; + 503: components["responses"]["AnalyticsUnavailable"]; }; - content?: never; - }; - 401: components['responses']['Unauthenticated']; - 403: components['responses']['Forbidden']; - 429: components['responses']['RateLimited']; - 500: components['responses']['InternalError']; - 503: components['responses']['DependencyUnavailable']; }; - }; - listAnalyticsPeriods: { - parameters: { - query?: { - /** @description Optional allowlisted period type. */ - periodType?: components['schemas']['PeriodType']; - }; - header?: { - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path?: never; - cookie?: never; + createAnalyticsRun: { + parameters: { + query?: never; + header: { + /** + * @description Client-generated intent key reused unchanged for retries of one intent. + * @example 95f6ec2d-26e8-4983-afd4-8a6d4434726b + */ + "Idempotency-Key": components["parameters"]["IdempotencyKeyHeader"]; + /** @description CSRF token for the current authenticated session; never log this value. */ + "X-CSRF-Token": components["parameters"]["CsrfTokenHeader"]; + /** @description Strict same-origin evidence validated with Fetch Metadata where available. */ + Origin: components["parameters"]["OriginHeader"]; + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateAnalyticsRunRequest"]; + }; + }; + responses: { + /** @description Existing current run returned with no new work required. */ + 200: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + /** @description Whether this response replayed a prior intent result. */ + "Idempotency-Replayed"?: boolean; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AnalyticsRun"]; + }; + }; + /** @description Current run accepted and one or more missing sections are queued/calculating. */ + 202: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + /** @description Whether this response replayed a prior intent result. */ + "Idempotency-Replayed"?: boolean; + /** @description Relative URL of the run snapshot. */ + Location?: string; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AnalyticsRun"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthenticated"]; + 403: components["responses"]["Forbidden"]; + 413: components["responses"]["RequestTooLarge"]; + 415: components["responses"]["UnsupportedMediaType"]; + 422: components["responses"]["ValidationFailed"]; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; + 503: components["responses"]["AnalyticsUnavailable"]; + }; }; - requestBody?: never; - responses: { - /** @description Approved period choices and default selection. */ - 200: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; + getAnalyticsRun: { + parameters: { + query?: never; + header?: { + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path: { + /** @description Opaque analytics run identifier; possession does not authorize access. */ + runId: components["parameters"]["RunIdPath"]; + }; + cookie?: never; }; - content: { - 'application/json': components['schemas']['PeriodList']; + requestBody?: never; + responses: { + /** @description Current state of every run section. */ + 200: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AnalyticsRun"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthenticated"]; + 404: components["responses"]["ResourceNotFound"]; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; + 503: components["responses"]["AnalyticsUnavailable"]; }; - }; - 400: components['responses']['BadRequest']; - 401: components['responses']['Unauthenticated']; - 422: components['responses']['ValidationFailed']; - 429: components['responses']['RateLimited']; - 500: components['responses']['InternalError']; - 503: components['responses']['DependencyUnavailable']; }; - }; - listMerchantTerminals: { - parameters: { - query?: never; - header?: { - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path?: never; - cookie?: never; + streamAnalyticsRunEvents: { + parameters: { + query?: never; + header?: { + /** @description Opaque last processed event id; must not contain NUL, LF, or CR. */ + "Last-Event-ID"?: string; + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path: { + /** @description Opaque analytics run identifier; possession does not authorize access. */ + runId: components["parameters"]["RunIdPath"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description UTF-8 event stream. A named heartbeat is emitted every 15 seconds. */ + 200: { + headers: { + /** @description Prevents transformation or storage of the live event stream. */ + "Cache-Control"?: "no-cache, no-transform"; + /** @description Deployment hint disabling Nginx response buffering. */ + "X-Accel-Buffering"?: "no"; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "text/event-stream": string; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthenticated"]; + 404: components["responses"]["ResourceNotFound"]; + 429: components["responses"]["ConnectionLimited"]; + 500: components["responses"]["InternalError"]; + 503: components["responses"]["AnalyticsUnavailable"]; + }; }; - requestBody?: never; - responses: { - /** @description Merchant-owned terminal choices ordered by session volume. */ - 200: { - headers: { - [name: string]: unknown; + createAnalyticsSectionRetryAttempt: { + parameters: { + query?: never; + header: { + /** + * @description Client-generated intent key reused unchanged for retries of one intent. + * @example 95f6ec2d-26e8-4983-afd4-8a6d4434726b + */ + "Idempotency-Key": components["parameters"]["IdempotencyKeyHeader"]; + /** @description CSRF token for the current authenticated session; never log this value. */ + "X-CSRF-Token": components["parameters"]["CsrfTokenHeader"]; + /** @description Strict same-origin evidence validated with Fetch Metadata where available. */ + Origin: components["parameters"]["OriginHeader"]; + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path: { + /** @description Opaque analytics run identifier; possession does not authorize access. */ + runId: components["parameters"]["RunIdPath"]; + sectionId: components["parameters"]["SectionIdPath"]; + }; + cookie?: never; }; - content: { - 'application/json': components['schemas']['TerminalList']; + requestBody?: never; + responses: { + /** @description Retry accepted or equivalent current retry returned. */ + 202: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + /** @description Relative run snapshot URL. */ + Location?: string; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AnalyticsRun"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthenticated"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["ResourceNotFound"]; + 409: components["responses"]["Conflict"]; + 422: components["responses"]["ValidationFailed"]; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; + 503: components["responses"]["AnalyticsUnavailable"]; }; - }; - 401: components['responses']['Unauthenticated']; - 503: components['responses']['AnalyticsUnavailable']; }; - }; - createAnalyticsRun: { - parameters: { - query?: never; - header: { - /** - * @description Client-generated intent key reused unchanged for retries of one intent. - * @example 95f6ec2d-26e8-4983-afd4-8a6d4434726b - */ - 'Idempotency-Key': components['parameters']['IdempotencyKeyHeader']; - /** @description CSRF token for the current authenticated session; never log this value. */ - 'X-CSRF-Token': components['parameters']['CsrfTokenHeader']; - /** @description Strict same-origin evidence validated with Fetch Metadata where available. */ - Origin: components['parameters']['OriginHeader']; - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path?: never; - cookie?: never; + listAnalyticsSessionDetails: { + parameters: { + query: { + /** @description Stable id returned by `listAnalyticsPeriods`. */ + periodId: components["parameters"]["PeriodIdQuery"]; + view: components["parameters"]["DetailViewQuery"]; + /** @description Valid only when `view=FAILED`. */ + failureStage?: components["parameters"]["FailureStageQuery"]; + /** @description Approved opaque source terminal reference resolved only inside the authenticated merchant; never an authorization input. */ + terminalId?: components["parameters"]["TerminalIdQuery"]; + status?: components["parameters"]["SessionStatusQuery"]; + minAmountRial?: components["parameters"]["MinAmountRialQuery"]; + maxAmountRial?: components["parameters"]["MaxAmountRialQuery"]; + /** @description Opaque integrity-protected cursor bound to tenant, view, filters, and ordering. */ + cursor?: components["parameters"]["CursorQuery"]; + /** @description Page size; defaults to 25 and cannot exceed 100. */ + limit?: components["parameters"]["LimitQuery"]; + }; + header?: { + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Authorized detail page. */ + 200: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionDetailPage"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthenticated"]; + 404: components["responses"]["ResourceNotFound"]; + 422: components["responses"]["ValidationFailed"]; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; + 503: components["responses"]["AnalyticsUnavailable"]; + }; }; - requestBody: { - content: { - 'application/json': components['schemas']['CreateAnalyticsRunRequest']; - }; + getAnalyticsSession: { + parameters: { + query?: never; + header?: { + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path: { + sessionId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Authorized merchant-owned session. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionDetail"]; + }; + }; + 401: components["responses"]["Unauthenticated"]; + 404: components["responses"]["ResourceNotFound"]; + 503: components["responses"]["AnalyticsUnavailable"]; + }; }; - responses: { - /** @description Existing current run returned with no new work required. */ - 200: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - /** @description Whether this response replayed a prior intent result. */ - 'Idempotency-Replayed'?: boolean; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; + exportAnalyticsSessionDetailsCsv: { + parameters: { + query: { + /** @description Stable id returned by `listAnalyticsPeriods`. */ + periodId: components["parameters"]["PeriodIdQuery"]; + view: components["parameters"]["DetailViewQuery"]; + /** @description Valid only when `view=FAILED`. */ + failureStage?: components["parameters"]["FailureStageQuery"]; + /** @description Approved opaque source terminal reference resolved only inside the authenticated merchant; never an authorization input. */ + terminalId?: components["parameters"]["TerminalIdQuery"]; + status?: components["parameters"]["SessionStatusQuery"]; + minAmountRial?: components["parameters"]["MinAmountRialQuery"]; + maxAmountRial?: components["parameters"]["MaxAmountRialQuery"]; + }; + header?: { + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path?: never; + cookie?: never; }; - content: { - 'application/json': components['schemas']['AnalyticsRun']; + requestBody?: never; + responses: { + /** @description Complete authorized CSV export. */ + 200: { + headers: { + "Cache-Control": components["headers"]["PrivateNoStore"]; + /** + * @description Attachment with ASCII fallback first and UTF-8 `filename*` second. + * Filename contains only server-generated view, period id, and time. + * @example attachment; filename="unverified_paid-jm-1405-05-20260820T093000Z.csv"; filename*=UTF-8''unverified_paid-jm-1405-05-20260820T093000Z.csv + */ + "Content-Disposition"?: string; + /** @description Prevents content sniffing. */ + "X-Content-Type-Options"?: "nosniff"; + "X-Request-ID": components["headers"]["RequestId"]; + [name: string]: unknown; + }; + content: { + /** + * @example "شناسه نشست پرداخت","شناسه پایانه","وضعیت پرداخت","مبلغ ریال","کارمزد ریال","زمان ایجاد","زمان انقضا","تعداد تلاش","وضعیت آخرین تلاش","زمان آخرین تلاش","زمان تأیید","زمان تسویه","مرحله مشاهده شده شکست" + * ses_01K2M8,term_0042,پرداخت شده و تأیید نشده,1500000,2000,2026-06-01T08:00:00Z,2026-06-01T08:15:00Z,1,پرداخت شده و تأیید نشده,2026-06-01T08:02:00Z,,, + */ + "text/csv; charset=utf-8": string; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthenticated"]; + 404: components["responses"]["ResourceNotFound"]; + 413: components["responses"]["ExportTooLarge"]; + 422: components["responses"]["ValidationFailed"]; + 429: components["responses"]["RateLimited"]; + 500: components["responses"]["InternalError"]; + 503: components["responses"]["AnalyticsUnavailable"]; + 504: components["responses"]["ExportTimeout"]; }; - }; - /** @description Current run accepted and one or more missing sections are queued/calculating. */ - 202: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - /** @description Whether this response replayed a prior intent result. */ - 'Idempotency-Replayed'?: boolean; - /** @description Relative URL of the run snapshot. */ - Location?: string; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['AnalyticsRun']; - }; - }; - 400: components['responses']['BadRequest']; - 401: components['responses']['Unauthenticated']; - 403: components['responses']['Forbidden']; - 413: components['responses']['RequestTooLarge']; - 415: components['responses']['UnsupportedMediaType']; - 422: components['responses']['ValidationFailed']; - 429: components['responses']['RateLimited']; - 500: components['responses']['InternalError']; - 503: components['responses']['AnalyticsUnavailable']; }; - }; - getAnalyticsRun: { - parameters: { - query?: never; - header?: { - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path: { - /** @description Opaque analytics run identifier; possession does not authorize access. */ - runId: components['parameters']['RunIdPath']; - }; - cookie?: never; + createGrowthReport: { + parameters: { + query?: never; + header: { + /** + * @description Client-generated intent key reused unchanged for retries of one intent. + * @example 95f6ec2d-26e8-4983-afd4-8a6d4434726b + */ + "Idempotency-Key": components["parameters"]["IdempotencyKeyHeader"]; + /** @description CSRF token for the current authenticated session; never log this value. */ + "X-CSRF-Token": components["parameters"]["CsrfTokenHeader"]; + /** @description Strict same-origin evidence validated with Fetch Metadata where available. */ + Origin: components["parameters"]["OriginHeader"]; + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateGrowthReportRequest"]; + }; + }; + responses: { + /** @description Matching durable report reused. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GrowthReport"]; + }; + }; + /** @description Report accepted for generation. */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GrowthReport"]; + }; + }; + 401: components["responses"]["Unauthenticated"]; + 403: components["responses"]["Forbidden"]; + 409: components["responses"]["Conflict"]; + 422: components["responses"]["ValidationFailed"]; + 429: components["responses"]["RateLimited"]; + 503: components["responses"]["AnalyticsUnavailable"]; + }; }; - requestBody?: never; - responses: { - /** @description Current state of every run section. */ - 200: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; + getLatestGrowthReport: { + parameters: { + query?: { + terminalId?: string; + }; + header?: { + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path?: never; + cookie?: never; }; - content: { - 'application/json': components['schemas']['AnalyticsRun']; + requestBody?: never; + responses: { + /** @description Latest successful report for the authorized scope. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GrowthReport"]; + }; + }; + 401: components["responses"]["Unauthenticated"]; + 404: components["responses"]["ResourceNotFound"]; + 503: components["responses"]["AnalyticsUnavailable"]; }; - }; - 400: components['responses']['BadRequest']; - 401: components['responses']['Unauthenticated']; - 404: components['responses']['ResourceNotFound']; - 429: components['responses']['RateLimited']; - 500: components['responses']['InternalError']; - 503: components['responses']['AnalyticsUnavailable']; }; - }; - streamAnalyticsRunEvents: { - parameters: { - query?: never; - header?: { - /** @description Opaque last processed event id; must not contain NUL, LF, or CR. */ - 'Last-Event-ID'?: string; - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path: { - /** @description Opaque analytics run identifier; possession does not authorize access. */ - runId: components['parameters']['RunIdPath']; - }; - cookie?: never; + getGrowthReport: { + parameters: { + query?: never; + header?: { + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path: { + reportId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Authorized report snapshot. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GrowthReport"]; + }; + }; + 401: components["responses"]["Unauthenticated"]; + 404: components["responses"]["ResourceNotFound"]; + 503: components["responses"]["AnalyticsUnavailable"]; + }; }; - requestBody?: never; - responses: { - /** @description UTF-8 event stream. A named heartbeat is emitted every 15 seconds. */ - 200: { - headers: { - /** @description Prevents transformation or storage of the live event stream. */ - 'Cache-Control'?: 'no-cache, no-transform'; - /** @description Deployment hint disabling Nginx response buffering. */ - 'X-Accel-Buffering'?: 'no'; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; + streamGrowthReportEvents: { + parameters: { + query?: never; + header?: { + "Last-Event-ID"?: string; + }; + path: { + reportId: string; + }; + cookie?: never; }; - content: { - 'text/event-stream': string; + requestBody?: never; + responses: { + /** @description Stable-id report snapshots and heartbeats. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/event-stream": string; + }; + }; + 401: components["responses"]["Unauthenticated"]; + 404: components["responses"]["ResourceNotFound"]; }; - }; - 400: components['responses']['BadRequest']; - 401: components['responses']['Unauthenticated']; - 404: components['responses']['ResourceNotFound']; - 429: components['responses']['ConnectionLimited']; - 500: components['responses']['InternalError']; - 503: components['responses']['AnalyticsUnavailable']; }; - }; - createAnalyticsSectionRetryAttempt: { - parameters: { - query?: never; - header: { - /** - * @description Client-generated intent key reused unchanged for retries of one intent. - * @example 95f6ec2d-26e8-4983-afd4-8a6d4434726b - */ - 'Idempotency-Key': components['parameters']['IdempotencyKeyHeader']; - /** @description CSRF token for the current authenticated session; never log this value. */ - 'X-CSRF-Token': components['parameters']['CsrfTokenHeader']; - /** @description Strict same-origin evidence validated with Fetch Metadata where available. */ - Origin: components['parameters']['OriginHeader']; - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path: { - /** @description Opaque analytics run identifier; possession does not authorize access. */ - runId: components['parameters']['RunIdPath']; - sectionId: components['parameters']['SectionIdPath']; - }; - cookie?: never; + getLatestAIInsight: { + parameters: { + query?: { + terminalId?: string; + }; + header?: { + /** @description Optional safe correlation id. The server may replace invalid values. */ + "X-Request-ID"?: components["parameters"]["RequestIdHeader"]; + }; + path: { + module: components["schemas"]["AIInsightModule"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Latest ready or deterministic-partial snapshot. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AIInsight"]; + }; + }; + 401: components["responses"]["Unauthenticated"]; + 404: components["responses"]["ResourceNotFound"]; + 422: components["responses"]["ValidationFailed"]; + 503: components["responses"]["AnalyticsUnavailable"]; + }; }; - requestBody?: never; - responses: { - /** @description Retry accepted or equivalent current retry returned. */ - 202: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - /** @description Relative run snapshot URL. */ - Location?: string; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; + refreshAIInsight: { + parameters: { + query?: never; + header: { + /** + * @description Client-generated intent key reused unchanged for retries of one intent. + * @example 95f6ec2d-26e8-4983-afd4-8a6d4434726b + */ + "Idempotency-Key": components["parameters"]["IdempotencyKeyHeader"]; + /** @description CSRF token for the current authenticated session; never log this value. */ + "X-CSRF-Token": components["parameters"]["CsrfTokenHeader"]; + /** @description Strict same-origin evidence validated with Fetch Metadata where available. */ + Origin: components["parameters"]["OriginHeader"]; + }; + path: { + module: components["schemas"]["AIInsightModule"]; + }; + cookie?: never; }; - content: { - 'application/json': components['schemas']['AnalyticsRun']; + requestBody: { + content: { + "application/json": components["schemas"]["AIInsightRefreshRequest"]; + }; + }; + responses: { + /** @description Idempotent job reused. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AIInsightJob"]; + }; + }; + /** @description Refresh accepted. */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AIInsightJob"]; + }; + }; + 401: components["responses"]["Unauthenticated"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["ResourceNotFound"]; + 409: components["responses"]["Conflict"]; + 422: components["responses"]["ValidationFailed"]; + 429: components["responses"]["RateLimited"]; + 503: components["responses"]["AnalyticsUnavailable"]; }; - }; - 400: components['responses']['BadRequest']; - 401: components['responses']['Unauthenticated']; - 403: components['responses']['Forbidden']; - 404: components['responses']['ResourceNotFound']; - 409: components['responses']['Conflict']; - 422: components['responses']['ValidationFailed']; - 429: components['responses']['RateLimited']; - 500: components['responses']['InternalError']; - 503: components['responses']['AnalyticsUnavailable']; }; - }; - listAnalyticsSessionDetails: { - parameters: { - query: { - /** @description Stable id returned by `listAnalyticsPeriods`. */ - periodId: components['parameters']['PeriodIdQuery']; - view: components['parameters']['DetailViewQuery']; - /** @description Valid only when `view=FAILED`. */ - failureStage?: components['parameters']['FailureStageQuery']; - /** @description Approved opaque source terminal reference resolved only inside the authenticated merchant; never an authorization input. */ - terminalId?: components['parameters']['TerminalIdQuery']; - status?: components['parameters']['SessionStatusQuery']; - minAmountRial?: components['parameters']['MinAmountRialQuery']; - maxAmountRial?: components['parameters']['MaxAmountRialQuery']; - /** @description Opaque integrity-protected cursor bound to tenant, view, filters, and ordering. */ - cursor?: components['parameters']['CursorQuery']; - /** @description Page size; defaults to 25 and cannot exceed 100. */ - limit?: components['parameters']['LimitQuery']; - }; - header?: { - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path?: never; - cookie?: never; + getAIInsightRefresh: { + parameters: { + query?: never; + header?: never; + path: { + jobId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Tenant-scoped refresh job. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AIInsightJob"]; + }; + }; + 401: components["responses"]["Unauthenticated"]; + 404: components["responses"]["ResourceNotFound"]; + }; }; - requestBody?: never; - responses: { - /** @description Authorized detail page. */ - 200: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; + streamAIInsightRefreshEvents: { + parameters: { + query?: never; + header?: { + "Last-Event-ID"?: number; + }; + path: { + jobId: string; + }; + cookie?: never; }; - content: { - 'application/json': components['schemas']['SessionDetailPage']; + requestBody?: never; + responses: { + /** @description Durable job events and heartbeats. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/event-stream": string; + }; + }; + 401: components["responses"]["Unauthenticated"]; + 404: components["responses"]["ResourceNotFound"]; }; - }; - 400: components['responses']['BadRequest']; - 401: components['responses']['Unauthenticated']; - 404: components['responses']['ResourceNotFound']; - 422: components['responses']['ValidationFailed']; - 429: components['responses']['RateLimited']; - 500: components['responses']['InternalError']; - 503: components['responses']['AnalyticsUnavailable']; }; - }; - getAnalyticsSession: { - parameters: { - query?: never; - header?: { - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path: { - sessionId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Authorized merchant-owned session. */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['SessionDetail']; - }; - }; - 401: components['responses']['Unauthenticated']; - 404: components['responses']['ResourceNotFound']; - 503: components['responses']['AnalyticsUnavailable']; - }; - }; - exportAnalyticsSessionDetailsCsv: { - parameters: { - query: { - /** @description Stable id returned by `listAnalyticsPeriods`. */ - periodId: components['parameters']['PeriodIdQuery']; - view: components['parameters']['DetailViewQuery']; - /** @description Valid only when `view=FAILED`. */ - failureStage?: components['parameters']['FailureStageQuery']; - /** @description Approved opaque source terminal reference resolved only inside the authenticated merchant; never an authorization input. */ - terminalId?: components['parameters']['TerminalIdQuery']; - status?: components['parameters']['SessionStatusQuery']; - minAmountRial?: components['parameters']['MinAmountRialQuery']; - maxAmountRial?: components['parameters']['MaxAmountRialQuery']; - }; - header?: { - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Complete authorized CSV export. */ - 200: { - headers: { - 'Cache-Control': components['headers']['PrivateNoStore']; - /** - * @description Attachment with ASCII fallback first and UTF-8 `filename*` second. - * Filename contains only server-generated view, period id, and time. - * @example attachment; filename="unverified_paid-jm-1405-05-20260820T093000Z.csv"; filename*=UTF-8''unverified_paid-jm-1405-05-20260820T093000Z.csv - */ - 'Content-Disposition'?: string; - /** @description Prevents content sniffing. */ - 'X-Content-Type-Options'?: 'nosniff'; - 'X-Request-ID': components['headers']['RequestId']; - [name: string]: unknown; - }; - content: { - /** - * @example "شناسه نشست پرداخت","شناسه پایانه","وضعیت پرداخت","مبلغ ریال","کارمزد ریال","زمان ایجاد","زمان انقضا","تعداد تلاش","وضعیت آخرین تلاش","زمان آخرین تلاش","زمان تأیید","زمان تسویه","مرحله مشاهده شده شکست" - * ses_01K2M8,term_0042,پرداخت شده و تأیید نشده,1500000,2000,2026-06-01T08:00:00Z,2026-06-01T08:15:00Z,1,پرداخت شده و تأیید نشده,2026-06-01T08:02:00Z,,, - */ - 'text/csv; charset=utf-8': string; - }; - }; - 400: components['responses']['BadRequest']; - 401: components['responses']['Unauthenticated']; - 404: components['responses']['ResourceNotFound']; - 413: components['responses']['ExportTooLarge']; - 422: components['responses']['ValidationFailed']; - 429: components['responses']['RateLimited']; - 500: components['responses']['InternalError']; - 503: components['responses']['AnalyticsUnavailable']; - 504: components['responses']['ExportTimeout']; - }; - }; - createGrowthReport: { - parameters: { - query?: never; - header: { - /** - * @description Client-generated intent key reused unchanged for retries of one intent. - * @example 95f6ec2d-26e8-4983-afd4-8a6d4434726b - */ - 'Idempotency-Key': components['parameters']['IdempotencyKeyHeader']; - /** @description CSRF token for the current authenticated session; never log this value. */ - 'X-CSRF-Token': components['parameters']['CsrfTokenHeader']; - /** @description Strict same-origin evidence validated with Fetch Metadata where available. */ - Origin: components['parameters']['OriginHeader']; - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['CreateGrowthReportRequest']; - }; - }; - responses: { - /** @description Matching durable report reused. */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['GrowthReport']; - }; - }; - /** @description Report accepted for generation. */ - 202: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['GrowthReport']; - }; - }; - 401: components['responses']['Unauthenticated']; - 403: components['responses']['Forbidden']; - 409: components['responses']['Conflict']; - 422: components['responses']['ValidationFailed']; - 429: components['responses']['RateLimited']; - 503: components['responses']['AnalyticsUnavailable']; - }; - }; - getLatestGrowthReport: { - parameters: { - query?: { - terminalId?: string; - }; - header?: { - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Latest successful report for the authorized scope. */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['GrowthReport']; - }; - }; - 401: components['responses']['Unauthenticated']; - 404: components['responses']['ResourceNotFound']; - 503: components['responses']['AnalyticsUnavailable']; - }; - }; - getGrowthReport: { - parameters: { - query?: never; - header?: { - /** @description Optional safe correlation id. The server may replace invalid values. */ - 'X-Request-ID'?: components['parameters']['RequestIdHeader']; - }; - path: { - reportId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Authorized report snapshot. */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['GrowthReport']; - }; - }; - 401: components['responses']['Unauthenticated']; - 404: components['responses']['ResourceNotFound']; - 503: components['responses']['AnalyticsUnavailable']; - }; - }; - streamGrowthReportEvents: { - parameters: { - query?: never; - header?: { - 'Last-Event-ID'?: string; - }; - path: { - reportId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Stable-id report snapshots and heartbeats. */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'text/event-stream': string; - }; - }; - 401: components['responses']['Unauthenticated']; - 404: components['responses']['ResourceNotFound']; - }; - }; } diff --git a/src/lib/components/AIInsightPanel.svelte b/src/lib/components/AIInsightPanel.svelte new file mode 100644 index 0000000..9d1c756 --- /dev/null +++ b/src/lib/components/AIInsightPanel.svelte @@ -0,0 +1,199 @@ + + +
+ {#if loading} + + {:else if !insight} + + +
+

تحلیل هوشمند

+

{error}

+
+ +
+
+ {:else} + + +
+
+ تحلیل هوشمند این صفحه +

+ مبنای تحلیل تا {formatDate(insight.dataThroughDate)} است؛ تاریخ واقعی تحلیل {formatDate( + insight.asOfDate, + )} و شروع چشم‌انداز {formatDate(insight.forecastStartDate)} است. +

+
+
+ {insight.freshness === 'FRESH' + ? 'داده تازه' + : insight.freshness === 'LAGGING' + ? `${insight.dataLagDays} روز فاصله` + : `داده قدیمی؛ ${insight.dataLagDays} روز فاصله`} + {confidenceLabel} + {#if insight.status === 'PARTIAL'}نسخه قطعی و بدون متن مولد{/if} +
+
+
+ +
+ {#each [insight.copy.past, insight.copy.latestObserved, insight.copy.future7Days, insight.copy.future30Days] as section} +
+

{section.headline}

+

{section.body}

+
+ {/each} +
+ {#if insight.copy.future90Days} +
+

{insight.copy.future90Days.headline}

+

{insight.copy.future90Days.body}

+
+ {/if} + {#if insight.calendarContext.length} +
+

+ رویدادهای مرتبط +

+
+ {#each insight.calendarContext as event}{event.titleFa} · {formatDate(event.startDate)}{/each} +
+
+ {/if} +
+ شواهد و گزینه‌های بررسی +
+ {#each insight.copy.findings as finding}
+

{finding.title}

+

{finding.summary}

+ {finding.evidenceId} +
{/each}{#each insight.copy.options as option}
+

{option.title}

+

{option.description}

+
{/each} +
+
+
+

+ {error || + (refreshing + ? 'نسخه تازه در پس‌زمینه ساخته می‌شود؛ تحلیل فعلی قابل استفاده است.' + : 'این تحلیل پیشنهادی است و تصمیم خودکار ایجاد نمی‌کند.')} +

+ +
+
+
+ {/if} +
diff --git a/src/lib/components/AnalyticsPage.svelte b/src/lib/components/AnalyticsPage.svelte index 532c276..82b836e 100644 --- a/src/lib/components/AnalyticsPage.svelte +++ b/src/lib/components/AnalyticsPage.svelte @@ -36,6 +36,7 @@ import FeeShareDonut from './FeeShareDonut.svelte'; import PageHeader from './PageHeader.svelte'; import PaymentHistoryChart from './PaymentHistoryChart.svelte'; + import AIInsightPanel from './AIInsightPanel.svelte'; let { kind }: { kind: 'overview' | 'payments' | 'fees' | 'peers' } = $props(); const dashboard = useDashboardContext(); @@ -120,6 +121,8 @@ + + {#if loading}
{#each Array(4) as _}{/each} diff --git a/src/lib/components/GrowthPage.svelte b/src/lib/components/GrowthPage.svelte index c3af831..60f87e5 100644 --- a/src/lib/components/GrowthPage.svelte +++ b/src/lib/components/GrowthPage.svelte @@ -16,6 +16,7 @@ import GrowthForecastChart from './GrowthForecastChart.svelte'; import GrowthInsights from './GrowthInsights.svelte'; import PageHeader from './PageHeader.svelte'; + import AIInsightPanel from './AIInsightPanel.svelte'; const dashboard = useDashboardContext(); const terminalId = $derived(page.url.searchParams.get('terminal') ?? 'all'); @@ -196,6 +197,8 @@ controls={false} /> + + {#if !growthFeatureEnabled} terminal.terminalId)); @@ -98,6 +99,11 @@ icon={ArrowUpDownIcon} /> + + {#if loading}