From 1d8d4edf922452b61991bd08165f027c76c8285f Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Fri, 21 Aug 2026 00:29:25 +0330 Subject: [PATCH] docs: define AI growth report API contract --- docs/openapi.yaml | 213 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index f9693c2..570bf98 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -578,6 +578,66 @@ paths: '504': $ref: '#/components/responses/ExportTimeout' + /analytics/growth-reports: + post: + tags: [AI growth analyst] + operationId: createGrowthReport + summary: Create or reuse a growth report + parameters: + - $ref: '#/components/parameters/IdempotencyKeyHeader' + - $ref: '#/components/parameters/CsrfTokenHeader' + - $ref: '#/components/parameters/OriginHeader' + - $ref: '#/components/parameters/RequestIdHeader' + requestBody: + required: true + content: + application/json: + schema: {$ref: '#/components/schemas/CreateGrowthReportRequest'} + responses: + '200': {description: Matching durable report reused., content: {application/json: {schema: {$ref: '#/components/schemas/GrowthReport'}}}} + '202': {description: Report accepted for generation., content: {application/json: {schema: {$ref: '#/components/schemas/GrowthReport'}}}} + '401': {$ref: '#/components/responses/Unauthenticated'} + '403': {$ref: '#/components/responses/Forbidden'} + '409': {$ref: '#/components/responses/Conflict'} + '422': {$ref: '#/components/responses/ValidationFailed'} + '429': {$ref: '#/components/responses/RateLimited'} + '503': {$ref: '#/components/responses/AnalyticsUnavailable'} + /analytics/growth-reports/latest: + get: + tags: [AI growth analyst] + operationId: getLatestGrowthReport + parameters: + - {name: terminalId, in: query, required: false, schema: {type: string}} + - $ref: '#/components/parameters/RequestIdHeader' + responses: + '200': {description: Latest successful report for the authorized scope., content: {application/json: {schema: {$ref: '#/components/schemas/GrowthReport'}}}} + '401': {$ref: '#/components/responses/Unauthenticated'} + '404': {$ref: '#/components/responses/ResourceNotFound'} + '503': {$ref: '#/components/responses/AnalyticsUnavailable'} + /analytics/growth-reports/{reportId}: + get: + tags: [AI growth analyst] + operationId: getGrowthReport + parameters: + - {name: reportId, in: path, required: true, schema: {type: string, pattern: '^growth_[a-f0-9]{24}$'}} + - $ref: '#/components/parameters/RequestIdHeader' + responses: + '200': {description: Authorized report snapshot., content: {application/json: {schema: {$ref: '#/components/schemas/GrowthReport'}}}} + '401': {$ref: '#/components/responses/Unauthenticated'} + '404': {$ref: '#/components/responses/ResourceNotFound'} + '503': {$ref: '#/components/responses/AnalyticsUnavailable'} + /analytics/growth-reports/{reportId}/events: + get: + tags: [AI growth analyst] + operationId: streamGrowthReportEvents + parameters: + - {name: reportId, in: path, required: true, schema: {type: string}} + - {name: Last-Event-ID, in: header, required: false, schema: {type: string}} + responses: + '200': {description: Stable-id report snapshots and heartbeats., content: {text/event-stream: {schema: {type: string}}}} + '401': {$ref: '#/components/responses/Unauthenticated'} + '404': {$ref: '#/components/responses/ResourceNotFound'} + components: securitySchemes: merchantSession: @@ -943,6 +1003,159 @@ components: $ref: '#/components/schemas/Problem' schemas: + CreateGrowthReportRequest: + type: object + additionalProperties: false + properties: + terminalId: {type: string, minLength: 1, maxLength: 128} + refresh: {type: boolean, default: false} + GrowthRange: + type: object + additionalProperties: false + required: [p10, p50, p90] + properties: + p10: {type: integer, format: int64, minimum: 0} + p50: {type: integer, format: int64, minimum: 0} + p90: {type: integer, format: int64, minimum: 0} + GrowthForecastPoint: + type: object + additionalProperties: false + required: [date, baselineSessions, baselineAmountRial, paidSessions, paidAmountRial] + properties: + date: {type: string, format: date-time} + baselineSessions: {type: integer, format: int64, minimum: 0} + baselineAmountRial: {type: integer, format: int64, minimum: 0} + paidSessions: {$ref: '#/components/schemas/GrowthRange'} + paidAmountRial: {$ref: '#/components/schemas/GrowthRange'} + eventIds: {type: array, items: {type: string}} + GrowthForecast: + type: object + additionalProperties: false + required: [startDate, horizonDays, confidence, points, backtest] + properties: + startDate: {type: string, format: date-time} + horizonDays: {type: integer, enum: [90]} + confidence: {type: string, enum: [HIGH, MEDIUM, INSUFFICIENT_DATA]} + suppressionReason: {type: string} + points: {type: array, maxItems: 90, items: {$ref: '#/components/schemas/GrowthForecastPoint'}} + backtest: + type: object + additionalProperties: false + required: [origins, sessionMapeBps, amountMapeBps, intervalCoverageBps] + properties: + origins: {type: integer, minimum: 0} + sessionMapeBps: {type: integer, format: int64, minimum: 0} + amountMapeBps: {type: integer, format: int64, minimum: 0} + intervalCoverageBps: {type: integer, format: int64, minimum: 0, maximum: 10000} + GrowthHistoricalWin: + type: object + additionalProperties: true + required: [evidenceId, eventId, eventTitleFa, startDate, endDate, materialWin] + properties: + evidenceId: {type: string} + eventId: {type: string} + eventTitleFa: {type: string} + startDate: {type: string, format: date-time} + endDate: {type: string, format: date-time} + eligible: {type: boolean} + suppressionReason: {type: string} + observationCount: {type: integer} + baselineSessionCount: {type: integer, format: int64} + baselinePaidSessions: {type: integer, format: int64} + baselineAmountRial: {type: integer, format: int64} + actualSessionCount: {type: integer, format: int64} + actualPaidSessions: {type: integer, format: int64} + actualAmountRial: {type: integer, format: int64} + sessionUpliftBps: {type: integer, format: int64} + paidSessionUpliftBps: {type: integer, format: int64} + amountUpliftBps: {type: integer, format: int64} + materialWin: {type: boolean} + materialDecline: {type: boolean} + GrowthOpportunity: + type: object + additionalProperties: false + required: [evidenceId, eventId, eventTitleFa, date, similarityBps, expectedSessions, expectedAmountRial, historicalEvidenceId] + properties: + evidenceId: {type: string} + eventId: {type: string} + eventTitleFa: {type: string} + date: {type: string, format: date-time} + similarityBps: {type: integer, format: int64, minimum: 6000, maximum: 10000} + expectedSessions: {$ref: '#/components/schemas/GrowthRange'} + expectedAmountRial: {$ref: '#/components/schemas/GrowthRange'} + historicalEvidenceId: {type: string} + GrowthRisk: + type: object + additionalProperties: false + required: [evidenceId, date, sessionDeclineBps, amountDeclineBps, expectedSessions, expectedAmountRial] + properties: + evidenceId: {type: string} + date: {type: string, format: date-time} + endDate: {type: string, format: date-time} + sessionDeclineBps: {type: integer, format: int64, minimum: 2000} + amountDeclineBps: {type: integer, format: int64, minimum: 2000} + expectedSessions: {$ref: '#/components/schemas/GrowthRange'} + expectedAmountRial: {$ref: '#/components/schemas/GrowthRange'} + GrowthPeer: + type: object + additionalProperties: false + required: [state, eligiblePeerCount, combinedPeerSessionCount, medianPaidSessions, percentileBps] + properties: + state: {type: string, enum: [READY, SUPPRESSED]} + reason: {type: string} + eligiblePeerCount: {type: integer, minimum: 0} + combinedPeerSessionCount: {type: integer, format: int64, minimum: 0} + medianPaidSessions: {type: integer, format: int64, minimum: 0} + percentileBps: {type: integer, format: int64, minimum: 0, maximum: 10000} + GrowthCampaignBrief: + type: object + additionalProperties: false + required: [headline, interpretation, offerSuggestion, messageSuggestion, preparationAction, limitation, evidenceIds] + properties: + headline: {type: string} + interpretation: {type: string} + offerSuggestion: {type: string} + messageSuggestion: {type: string} + preparationAction: {type: string} + limitation: {type: string} + evidenceIds: {type: array, minItems: 1, items: {type: string}} + GrowthEvidence: + type: object + additionalProperties: false + required: [forecast, historicalWins, opportunities, risks, fallbackBriefs] + properties: + forecast: {$ref: '#/components/schemas/GrowthForecast'} + historicalWins: {type: array, items: {$ref: '#/components/schemas/GrowthHistoricalWin'}} + opportunities: {type: array, maxItems: 5, items: {$ref: '#/components/schemas/GrowthOpportunity'}} + risks: {type: array, maxItems: 3, items: {$ref: '#/components/schemas/GrowthRisk'}} + peer: {$ref: '#/components/schemas/GrowthPeer'} + fallbackBriefs: {type: array, items: {$ref: '#/components/schemas/GrowthCampaignBrief'}} + GrowthReport: + type: object + additionalProperties: false + required: [reportId, status, scope, horizonDays, deterministicEvidence, cached, stale, nextRefreshAt, createdAt, updatedAt, eventsUrl] + properties: + reportId: {type: string} + status: {type: string, enum: [QUEUED, ANALYZING, GENERATING, READY, PARTIAL, FAILED]} + scope: + type: object + additionalProperties: false + properties: {terminalId: {type: [string, 'null']}} + horizonDays: {type: integer, enum: [90]} + forecastStartDate: {type: string, format: date-time} + deterministicEvidence: {$ref: '#/components/schemas/GrowthEvidence'} + aiCopy: + type: object + additionalProperties: false + required: [briefs] + properties: {briefs: {type: array, maxItems: 5, items: {$ref: '#/components/schemas/GrowthCampaignBrief'}}} + failureCode: {type: string} + cached: {type: boolean} + stale: {type: boolean} + nextRefreshAt: {type: string, format: date-time} + createdAt: {type: string, format: date-time} + updatedAt: {type: string, format: date-time} + eventsUrl: {type: string, format: uri-reference} LoginRequest: type: object additionalProperties: false