feat: add persisted page insight platform

This commit is contained in:
2026-08-21 18:51:00 +08:00
parent 8507b5d017
commit 17e3f1a7d0
18 changed files with 1587 additions and 3 deletions

View File

@@ -638,6 +638,67 @@ paths:
'401': {$ref: '#/components/responses/Unauthenticated'}
'404': {$ref: '#/components/responses/ResourceNotFound'}
/analytics/ai-insights/{module}:
get:
tags: [AI page insights]
operationId: getLatestAIInsight
summary: Return the latest persisted insight without waiting for generation
parameters:
- {name: module, in: path, required: true, schema: {$ref: '#/components/schemas/AIInsightModule'}}
- {name: terminalId, in: query, required: false, schema: {type: string, maxLength: 128}}
- $ref: '#/components/parameters/RequestIdHeader'
responses:
'200': {description: Latest ready or deterministic-partial snapshot., content: {application/json: {schema: {$ref: '#/components/schemas/AIInsight'}}}}
'401': {$ref: '#/components/responses/Unauthenticated'}
'404': {$ref: '#/components/responses/ResourceNotFound'}
'422': {$ref: '#/components/responses/ValidationFailed'}
'503': {$ref: '#/components/responses/AnalyticsUnavailable'}
/analytics/ai-insights/{module}/refreshes:
post:
tags: [AI page insights]
operationId: refreshAIInsight
summary: Enqueue an independent insight refresh
parameters:
- {name: module, in: path, required: true, schema: {$ref: '#/components/schemas/AIInsightModule'}}
- $ref: '#/components/parameters/IdempotencyKeyHeader'
- $ref: '#/components/parameters/CsrfTokenHeader'
- $ref: '#/components/parameters/OriginHeader'
requestBody:
required: true
content: {application/json: {schema: {$ref: '#/components/schemas/AIInsightRefreshRequest'}}}
responses:
'200': {description: Idempotent job reused., content: {application/json: {schema: {$ref: '#/components/schemas/AIInsightJob'}}}}
'202': {description: Refresh accepted., content: {application/json: {schema: {$ref: '#/components/schemas/AIInsightJob'}}}}
'401': {$ref: '#/components/responses/Unauthenticated'}
'403': {$ref: '#/components/responses/Forbidden'}
'404': {$ref: '#/components/responses/ResourceNotFound'}
'409': {$ref: '#/components/responses/Conflict'}
'422': {$ref: '#/components/responses/ValidationFailed'}
'429': {$ref: '#/components/responses/RateLimited'}
'503': {$ref: '#/components/responses/AnalyticsUnavailable'}
/analytics/ai-insight-refreshes/{jobId}:
get:
tags: [AI page insights]
operationId: getAIInsightRefresh
parameters:
- {name: jobId, in: path, required: true, schema: {type: string, pattern: '^insightjob_[a-f0-9]{24}$'}}
responses:
'200': {description: Tenant-scoped refresh job., content: {application/json: {schema: {$ref: '#/components/schemas/AIInsightJob'}}}}
'401': {$ref: '#/components/responses/Unauthenticated'}
'404': {$ref: '#/components/responses/ResourceNotFound'}
/analytics/ai-insight-refreshes/{jobId}/events:
get:
tags: [AI page insights]
operationId: streamAIInsightRefreshEvents
summary: Replay up to one hour of durable job events and continue as SSE
parameters:
- {name: jobId, in: path, required: true, schema: {type: string, pattern: '^insightjob_[a-f0-9]{24}$'}}
- {name: Last-Event-ID, in: header, required: false, schema: {type: integer, format: int64}}
responses:
'200': {description: Durable job events and heartbeats., content: {text/event-stream: {schema: {type: string}}}}
'401': {$ref: '#/components/responses/Unauthenticated'}
'404': {$ref: '#/components/responses/ResourceNotFound'}
components:
securitySchemes:
merchantSession:
@@ -1174,6 +1235,83 @@ components:
createdAt: {type: string, format: date-time}
updatedAt: {type: string, format: date-time}
eventsUrl: {type: string, format: uri-reference}
AIInsightModule:
type: string
enum: [overview, payments, fees, peers, transactions, growth]
AIInsightNarrative:
type: object
additionalProperties: false
required: [headline, body, evidenceIds]
properties:
headline: {type: string}
body: {type: string}
evidenceIds: {type: array, items: {type: string}}
AIInsightFinding:
type: object
additionalProperties: false
required: [evidenceId, title, summary, scoreBps, confidence]
properties:
evidenceId: {type: string}
title: {type: string}
summary: {type: string}
scoreBps: {type: integer, minimum: 0, maximum: 10000}
confidence: {type: string, enum: [HIGH, MEDIUM, LOW, INSUFFICIENT_DATA]}
AIInsightOption:
type: object
additionalProperties: false
required: [title, description, evidenceIds]
properties:
title: {type: string}
description: {type: string}
evidenceIds: {type: array, items: {type: string}}
AIInsightCopy:
type: object
additionalProperties: false
required: [past, latestObserved, future7Days, future30Days, findings, options]
properties:
past: {$ref: '#/components/schemas/AIInsightNarrative'}
latestObserved: {$ref: '#/components/schemas/AIInsightNarrative'}
future7Days: {$ref: '#/components/schemas/AIInsightNarrative'}
future30Days: {$ref: '#/components/schemas/AIInsightNarrative'}
future90Days: {$ref: '#/components/schemas/AIInsightNarrative'}
findings: {type: array, maxItems: 3, items: {$ref: '#/components/schemas/AIInsightFinding'}}
options: {type: array, items: {$ref: '#/components/schemas/AIInsightOption'}}
AIInsight:
type: object
additionalProperties: false
required: [insightId, module, revision, status, scope, asOfDate, dataThroughDate, forecastStartDate, freshness, dataLagDays, confidence, copy, evidence, calendarContext, refreshState, generatedAt]
properties:
insightId: {type: string}
module: {$ref: '#/components/schemas/AIInsightModule'}
revision: {type: integer, minimum: 1}
status: {type: string, enum: [READY, PARTIAL]}
scope: {type: object, properties: {terminalId: {type: [string, 'null']}}}
asOfDate: {type: string, format: date}
dataThroughDate: {type: string, format: date}
forecastStartDate: {type: string, format: date}
freshness: {type: string, enum: [FRESH, LAGGING, STALE]}
dataLagDays: {type: integer, minimum: 0}
confidence: {type: string, enum: [HIGH, MEDIUM, LOW, INSUFFICIENT_DATA]}
copy: {$ref: '#/components/schemas/AIInsightCopy'}
evidence: {type: object, additionalProperties: true}
calendarContext: {type: array, items: {type: object, additionalProperties: true}}
refreshState: {type: object, additionalProperties: true}
generatedAt: {type: string, format: date-time}
AIInsightRefreshRequest:
type: object
additionalProperties: false
properties: {terminalId: {type: string, maxLength: 128}}
AIInsightJob:
type: object
additionalProperties: false
required: [jobId, snapshotId, state, eventsUrl, createdAt, updatedAt]
properties:
jobId: {type: string}
snapshotId: {type: string}
state: {type: string, enum: [QUEUED, RUNNING, COMPLETED, FAILED]}
eventsUrl: {type: string, format: uri-reference}
createdAt: {type: string, format: date-time}
updatedAt: {type: string, format: date-time}
LoginRequest:
type: object
additionalProperties: false