package insight import ( "encoding/json" "errors" "time" ) type Module string type Section string const PageInsightVersion = "page_insight_v8" const ( ModuleOverview Module = "overview" ModulePayments Module = "payments" ModuleFees Module = "fees" ModulePeers Module = "peers" ModuleTransactions Module = "transactions" ModuleGrowth Module = "growth" ) const ( SectionFull Section = "full" SectionPast Section = "past" SectionLatestObserved Section = "latest-observed" SectionFuture7Days Section = "future-7-days" SectionFuture30Days Section = "future-30-days" SectionFuture90Days Section = "future-90-days" SectionFindings Section = "findings" SectionOptions Section = "options" ) var ErrNotFound = errors.New("ai insight: not found") var ErrUnknownTerminal = errors.New("ai insight: unknown terminal") var ErrRefreshLimited = errors.New("ai insight: refresh limited") var ErrRefreshCooldown = errors.New("ai insight: refresh cooldown") var ErrIdempotencyConflict = errors.New("ai insight: idempotency conflict") var ErrOnboardingRequired = errors.New("ai insight: onboarding required") type RefreshCooldownError struct{ NextRefreshAt time.Time } func (e *RefreshCooldownError) Error() string { return "ai insight: refresh cooldown" } func (e *RefreshCooldownError) Unwrap() error { return ErrRefreshCooldown } func ParseModule(value string) (Module, bool) { module := Module(value) switch module { case ModuleOverview, ModulePayments, ModuleFees, ModulePeers, ModuleTransactions, ModuleGrowth: return module, true default: return "", false } } func ParseSection(module Module, value string) (Section, bool) { section := Section(value) switch section { case SectionPast, SectionLatestObserved, SectionFuture7Days, SectionFuture30Days, SectionFindings, SectionOptions: return section, true case SectionFuture90Days: return section, module == ModuleGrowth default: return "", false } } type Confidence string const ( ConfidenceHigh Confidence = "HIGH" ConfidenceMedium Confidence = "MEDIUM" ConfidenceLow Confidence = "LOW" ConfidenceInsufficientData Confidence = "INSUFFICIENT_DATA" ) type Narrative struct { Headline string `json:"headline"` Body string `json:"body"` EvidenceID []string `json:"evidenceIds"` } type Finding struct { EvidenceID string `json:"evidenceId"` Title string `json:"title"` Summary string `json:"summary"` ScoreBPS int `json:"scoreBps"` Confidence Confidence `json:"confidence"` } type Option struct { Title string `json:"title"` Description string `json:"description"` EvidenceID []string `json:"evidenceIds"` } type CalendarContext struct { EventKey string `json:"eventKey"` TitleFA string `json:"titleFa"` StartDate string `json:"startDate"` EndDate string `json:"endDate"` Tone string `json:"tone"` CommercialPosture string `json:"commercialPosture"` OfficialHoliday bool `json:"officialHoliday"` EvidenceSupported bool `json:"evidenceSupported"` OverlapConfounded bool `json:"overlapConfounded"` } type Copy struct { Past Narrative `json:"past"` LatestObserved Narrative `json:"latestObserved"` Future7Days Narrative `json:"future7Days"` Future30Days Narrative `json:"future30Days"` Future90Days *Narrative `json:"future90Days,omitempty"` Findings []Finding `json:"findings"` Options []Option `json:"options"` } func MergeSection(base, generated Copy, section Section) (Copy, error) { switch section { case SectionFull: return generated, nil case SectionPast: base.Past = generated.Past case SectionLatestObserved: base.LatestObserved = generated.LatestObserved case SectionFuture7Days: base.Future7Days = generated.Future7Days case SectionFuture30Days: base.Future30Days = generated.Future30Days case SectionFuture90Days: if generated.Future90Days == nil { return Copy{}, errors.New("ai insight: future 90 day section unavailable") } base.Future90Days = generated.Future90Days case SectionFindings: base.Findings = generated.Findings case SectionOptions: base.Options = generated.Options default: return Copy{}, errors.New("ai insight: invalid section") } return base, nil } type Scope struct { TerminalID *string `json:"terminalId"` PeriodID *string `json:"periodId,omitempty"` } type PeriodScope struct { PeriodID string PeriodType string StartAt time.Time EndAt time.Time JalaliLabelFA string } type RefreshState struct { State string `json:"state"` JobID string `json:"jobId,omitempty"` EventsURL string `json:"eventsUrl,omitempty"` NextRefreshAt *time.Time `json:"nextRefreshAt,omitempty"` } type Snapshot struct { InsightID string `json:"insightId"` Module Module `json:"module"` Revision int `json:"revision"` Status string `json:"status"` Scope Scope `json:"scope"` AsOfDate string `json:"asOfDate"` DataThroughDate string `json:"dataThroughDate"` ForecastStart string `json:"forecastStartDate"` Freshness Freshness `json:"freshness"` DataLagDays int `json:"dataLagDays"` Confidence Confidence `json:"confidence"` Copy Copy `json:"copy"` Evidence json.RawMessage `json:"evidence"` CalendarContext []CalendarContext `json:"calendarContext"` RefreshState RefreshState `json:"refreshState"` SectionRefresh map[Section]RefreshState `json:"sectionRefreshState"` GeneratedAt time.Time `json:"generatedAt"` } type Job struct { JobID string `json:"jobId"` SnapshotID string `json:"snapshotId"` Module Module `json:"module"` Section Section `json:"sectionId"` State string `json:"state"` EventsURL string `json:"eventsUrl"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } type Event struct { ID int64 `json:"eventId"` Type string `json:"type"` Payload json.RawMessage `json:"payload"` OccurredAt time.Time `json:"occurredAt"` } type CreateRequest struct { MerchantID int64 TerminalID string Module Module Section Section TriggerReason string ManualRefresh bool IdempotencyKey string RequestHash string Fingerprint string Time TimeContext Confidence Confidence CatalogVersion string Evidence json.RawMessage Period PeriodScope } type ProviderUsage struct { InputTokens int64 OutputTokens int64 ProviderRequests int64 LatencyMS int64 ProviderRequestID string EstimatedCost string } type Completion struct { Analysis Analysis AICopy *Copy Status string FailureCode string Model string Usage ProviderUsage }