رویداد پیش رو
-{event.eventTitleFa}
-{formatDateOnly(event.date)}
-{event.relevanceReason}
-diff --git a/DESIGN.md b/DESIGN.md index 832174c..a57a1e9 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -132,6 +132,7 @@ The approved Persian-first baseline remains authoritative where the shipped arti | Version | Date | Decision | | ------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0.13.0 | 2026-08-22 | Replaced equal upcoming-event cards with an RTL shared-baseline priority histogram and ranked evidence cards. Three positive integer shares descend from physical right and total ۱۰۰٪; visible copy distinguishes planning priority from growth probability. Historical occasion evidence takes precedence over onboarding-profile signals, with explicit source and promote/prepare/caution labels. | | 0.12.0 | 2026-08-22 | Replaced the Transactions table with a period-scoped operational-health dashboard: four status KPIs, an absolute-count distribution chart, a 100% composition trend, keyboard inspection, and visible semantic data-table alternatives. Status colors are fixed to blue Paid/awaiting verification, emerald Verified/final, rose Failed, and amber Reversed. | | 0.11.1 | 2026-08-21 | Hardened narrow-screen shell, calendar, and chart behavior: the navbar uses the compact authentication-screen brand mark while the opened mobile drawer carries the full typographic wordmark and the only terminal selector, exact-period fields cannot overlap, payment charts keep their readable width with horizontal scrolling, and the growth teaser gains an action-blue emphasis with a restrained reduced-motion-safe pulse. | | 0.11.0 | 2026-08-21 | Added the feature-gated AI growth analyst: a cache-first 90-day forecast view with count/amount modes, P10–P90 uncertainty band, keyboard and tabular alternatives, evidence-linked Persian campaign guidance, preserved-content refresh states, and restrained semantic opportunity/risk styling. | @@ -311,6 +312,14 @@ The Transactions page contains no rows or drill-down links. Its primary chart us The fee page gives the circular share chart a large physical-right column while three numeric KPI cards stack in the adjacent column. Peer progress bars move from rose through amber and sky to emerald as the direction-adjusted percentile improves. Retry guidance uses amber, sky, or emerald surfaces and changes its supportive sentence at tested recovery/sample thresholds. +### Event Planning Priority + +The growth page ranks up to three upcoming occasions in one shared-baseline histogram. Rank 1 is the physical-right column in RTL; all available columns remain side by side on phones because their relative heights are the comparison. Column height maps literally to an integer planning-priority share, and the visible percentages total ۱۰۰٪. When only one or two candidates exist, a visible note names that limited set and the chart divides attention only among those events. A nearby clarification says these values allocate planning attention and are not growth probabilities. + +The histogram is the section's single visual signature: rank 1 uses the primary data color, while ranks 2 and 3 use progressively quieter mixtures of the same token and the nested surface. It uses no gradient, floating shadow, or decorative motion. Height changes may transition for 240ms and become immediate under reduced motion. Rank, title, percentage, total, and an ordered screen-reader description make the chart understandable without color or pointer interaction. + +Evidence cards stack beneath the compact chart on phones and form up to three physical-right-to-left columns from the medium breakpoint. Each card shows the event range, reason, explicit stance, and one source badge: «بر پایه تجربه شما» or «بر پایه شناخت کسبوکار». Historical cards show the previous occasion range, observation count, paid-session uplift, and amount uplift. Profile-grounded cards show onboarding-derived category, niche, or channel signals; if none are available, they say that only general profile context was used. Typed history-unavailable codes are translated into natural Persian. A visible warning identifies deterministic `PARTIAL` copy. Success, action, or warning accents reinforce the written promote, prepare, or caution stance and never replace it. + ## Do's and Don'ts ### Do: diff --git a/docs/GROWTH-ANALYST-UX-SPEC.md b/docs/GROWTH-ANALYST-UX-SPEC.md index 2bbc367..babfb52 100644 --- a/docs/GROWTH-ANALYST-UX-SPEC.md +++ b/docs/GROWTH-ANALYST-UX-SPEC.md @@ -12,8 +12,14 @@ - Header states the 90-day horizon starts after the latest complete dataset date. A daily `بهروزرسانی تحلیل` action shows `nextRefreshAt`, loading, and typed rate-limit feedback. - Render a responsive, keyboard-accessible forecast chart with count/amount mode, hover/focus points, P10–P90 band, axes/units, chart summary, and a non-visual data alternative. - Show historical wins, up to five upcoming opportunities, up to three gentle low-period warnings, and a peer card when permitted. +- Rank up to three upcoming events as a shared-baseline priority histogram. Rank 1 is the physical-right column in RTL. Each column's literal height equals its integer planning-priority percentage; the displayed percentages are positive, descend by rank, and total exactly `100`. When only one or two relevant candidates exist, say so explicitly and divide the total only among the available candidates. +- State visibly that event priority is a relative planning allocation, not a probability of growth or guaranteed demand. Repeat rank and percentage in text so height and color are never the only carriers of meaning. +- Pair the compact histogram with one evidence card per displayed event. On desktop, up to three cards form RTL columns aligned by rank; on mobile, the compact histogram keeps its shared baseline and evidence cards stack for readable analysis. +- Prefer evidence from the merchant's matching prior occasion window. Show the prior start/end dates, observation count, paid-session uplift, amount uplift, and an explicit promote, prepare, or caution stance. Material decline must produce a visible caution recommendation. +- When matching historical evidence is unavailable, ground the explanation in onboarding profile signals. Show the matched category, niche, or sales-channel signals and, when provided, the reason history was unavailable. +- Label every event explanation as either `بر پایه تجربه شما` or `بر پایه شناخت کسبوکار`. The label must agree with both deterministic evidence and the recommendation's `insightBasis`. - Each brief contains server-rendered timing/range/evidence plus validated AI headline, interpretation, offer/message suggestion, one preparation step, confidence, and limitation. -- `PARTIAL` keeps deterministic charts/cards and explains that the written AI interpretation was unavailable. `LOW` keeps the forecast visible with an explicit limited-history or high-variance warning. `INSUFFICIENT_DATA` is reserved for scopes with no historical observations. `FAILED` offers safe retry without hiding an older successful report. +- `PARTIAL` keeps deterministic charts/cards and visibly explains that the written AI interpretation was unavailable. `INSUFFICIENT_EVENT_CANDIDATES` additionally states the exact number of available events instead of implying that three were ranked. `LOW` keeps the forecast visible with an explicit limited-history or high-variance warning. `INSUFFICIENT_DATA` is reserved for scopes with no historical observations. `FAILED` offers safe retry without hiding an older successful report. - Motion is restrained, supports reduced motion, and does not block reading. All interactive targets are at least 44 px. ## Copy rules @@ -21,11 +27,25 @@ - Use natural professional Persian, Persian `ی`/`ک`, appropriate نیمفاصله, Persian digits, and Rial. - Keep tone constructive: describe opportunity, preparation, and uncertainty; do not shame the merchant or claim guaranteed outcomes. - Every recommendation visibly cites its deterministic evidence card. AI is labeled advisory. +- Event reasons explain why the occasion is relevant to this merchant and must not turn planning priority into a forecast claim. - No chat, notifications, campaign execution, user memory, provider/model names, token usage, or internal failure details appear in merchant UI. +## Event priority contract + +- `occasionKey` is the stable identity used to join an upcoming occurrence to prior occurrences; `eventVariant` is one of `PRIMARY`, `CAMPAIGN_WINDOW`, or `OPERATIONAL_WINDOW` and distinguishes materially different variants without changing that identity. +- `HISTORICAL` is preferred whenever matching prior-window evidence is eligible. `BUSINESS_PROFILE` is used only when that evidence is unavailable. +- `historicalEvidence` contains deterministic dates, sample size, basis-point uplifts, and material-win/material-decline flags. AI copy may interpret these values but may not invent or alter them. +- `profileEvidence.matchedSignals` contains onboarding-derived evidence and may be empty. Empty signals produce truthful general-profile copy rather than invented alignment chips. `historyUnavailableReason` is an optional typed code and is always translated to natural Persian; raw backend codes never appear. +- Deterministic events form a discriminated evidence union: `HISTORICAL` requires `historicalEvidence`, while `BUSINESS_PROFILE` requires `profileEvidence`. Recommendation basis must match the deterministic event basis; mismatched recommendation copy is discarded rather than overriding evidence authority. +- `priorityPercent` is an integer from `1` to `98`. A valid AI recommendation collection contains exactly three items, is sorted descending, and totals exactly `100`; reports with fewer candidates omit AI copy and use the explicit limited-candidate `PARTIAL` state. +- Upcoming-event filtering compares inclusive calendar dates in `Asia/Tehran`, so an event remains visible through its entire `endDate` in the merchant calendar. +- `stance` is one of `PROMOTE`, `PREPARE`, or `CAUTION`; color always appears with its Persian text label. + ## Acceptance - Works at 360, 768, 1024, and wide desktop widths in light/dark RTL layouts. +- The event chart keeps three readable columns at 360 px, places rank 1 physically right, and exposes an equivalent ordered text alternative to assistive technology. +- Event evidence and reasons are visible without hover or pointer interaction, and histogram height transitions are removed under `prefers-reduced-motion`. - All job, cache, refresh, partial, insufficient, unauthorized, and recoverable error states are accessible and tested. - Generated OpenAPI types are used; no handwritten duplicate growth DTOs or browser secrets exist. - Cached navigation performs no generation request. Manual retry remains available while the current scope has no persisted AI-written summary; after one exists, refresh is limited once per merchant per Tehran day. diff --git a/src/lib/api/schema.d.ts b/src/lib/api/schema.d.ts index 822e5f4..e4d0e52 100644 --- a/src/lib/api/schema.d.ts +++ b/src/lib/api/schema.d.ts @@ -907,19 +907,73 @@ export interface components { expectedAmountRial: components["schemas"]["GrowthRange"]; historicalEvidenceId: string; }; - GrowthUpcomingEvent: { + GrowthHistoricalEventEvidence: { + /** Format: date-time */ + priorStartDate: string; + /** Format: date-time */ + priorEndDate: string; + /** @description Number of eligible comparison windows used to establish the baseline. */ + observationCount: number; + /** Format: int64 */ + paidSessionUpliftBps: number; + /** Format: int64 */ + amountUpliftBps: number; + materialWin: boolean; + materialDecline: boolean; + }; + GrowthProfileEventEvidence: { + matchedSignals: string[]; + historyUnavailableReason?: components["schemas"]["GrowthHistoryUnavailableReason"]; + }; + /** @enum {string} */ + GrowthHistoryUnavailableReason: "INSUFFICIENT_BASELINE_OBSERVATIONS" | "INSUFFICIENT_BASELINE_SESSIONS" | "INSUFFICIENT_HISTORY" | "INCOMPLETE_EVENT_WINDOW" | "INSUFFICIENT_COHORT" | "TERMINAL_SCOPE_UNSUPPORTED"; + GrowthUpcomingEventCommon: { evidenceId: string; eventId: string; + occasionKey: string; + /** @enum {string} */ + eventVariant: "PRIMARY" | "CAMPAIGN_WINDOW" | "OPERATIONAL_WINDOW"; eventTitleFa: string; /** Format: date-time */ date: string; + /** Format: date-time */ + endDate: string; eventType: string; officialHoliday: boolean; merchantCategories: string[]; behavioralChannels: string[]; }; + GrowthUpcomingHistoricalEvent: components["schemas"]["GrowthUpcomingEventCommon"] & { + /** @enum {string} */ + insightBasis: "HISTORICAL"; + historicalEvidence: components["schemas"]["GrowthHistoricalEventEvidence"]; + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + insightBasis: "HISTORICAL"; + }; + GrowthUpcomingProfileEvent: components["schemas"]["GrowthUpcomingEventCommon"] & { + /** @enum {string} */ + insightBasis: "BUSINESS_PROFILE"; + profileEvidence: components["schemas"]["GrowthProfileEventEvidence"]; + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + insightBasis: "BUSINESS_PROFILE"; + }; + GrowthUpcomingEvent: components["schemas"]["GrowthUpcomingHistoricalEvent"] | components["schemas"]["GrowthUpcomingProfileEvent"]; + /** @description Exactly three recommendations are returned in descending priority order. Their positive integer priorityPercent values sum to 100. */ GrowthEventRecommendation: { evidenceId: string; + priorityPercent: number; + /** @enum {string} */ + stance: "PROMOTE" | "PREPARE" | "CAUTION"; + /** @enum {string} */ + insightBasis: "HISTORICAL" | "BUSINESS_PROFILE"; relevanceReason: string; }; GrowthRisk: { @@ -977,6 +1031,7 @@ export interface components { category: string; niches: string[]; salesChannels: string[]; + businessDescriptions?: string[]; }; }; GrowthReport: { @@ -991,6 +1046,7 @@ export interface components { horizonDays: 90; deterministicEvidence?: components["schemas"]["GrowthEvidence"]; aiCopy?: { + /** @description Ranked in descending priorityPercent order; the three percentages sum to exactly 100. */ eventRecommendations: components["schemas"]["GrowthEventRecommendation"][]; }; failureCode?: string; diff --git a/src/lib/components/GrowthEventImpactChart.svelte b/src/lib/components/GrowthEventImpactChart.svelte index c78d6d3..1e9861f 100644 --- a/src/lib/components/GrowthEventImpactChart.svelte +++ b/src/lib/components/GrowthEventImpactChart.svelte @@ -1,53 +1,314 @@ {#if visibleEvents.length} -
رویداد پیش رو
-{formatDateOnly(event.date)}
-{event.relevanceReason}
-نسخهٔ موقت تحلیل
++ {visibleEvents.length < 3 + ? 'رویدادهای مرتبط کافی برای رتبهبندی کامل پیدا نشد؛ موارد موجود بهصورت موقت نمایش داده میشوند.' + : 'متن تفسیری هوشمند این نوبت آماده نشد؛ اولویتهای زیر از شواهد موجود ساخته شدهاند.'} +
++ این درصد، سهم اولویت برای برنامهریزی است؛ نه احتمال رشد فروش. جمع + {visibleEvents.length === 3 ? 'سه اولویت' : `اولویتهای ${formatValue(visibleEvents.length)} رویداد`} + ۱۰۰٪ است. +
+{event.eventTitleFa}
++ جمع اولویتها: {formatValue(priorityTotal)}٪ +
++ رتبه {formatValue(event.rank)} · {formatValue(event.priorityPercent)}٪ +
++ زمان رویداد: {dateRange(event.date, event.endDate)} +
+{stanceLabels[event.stance]}
+{event.relevanceReason}
+ + {#if event.insightBasis === 'HISTORICAL' && event.historicalEvidence} +دوره مشابه قبلی
++ {dateRange(event.historicalEvidence.priorStartDate, event.historicalEvidence.priorEndDate)} · + {formatValue(event.historicalEvidence.observationCount)} بازه مقایسهشده +
+نشانههای مرتبط در پروفایل شما
+ {#if event.profileEvidence.matchedSignals.length} ++ این پیشنهاد با شناخت کلی ثبتشده از کسبوکار شما تنظیم شده است؛ نشانهٔ جزئیتری برای نمایش + وجود ندارد. +
+ {/if} + {#if event.profileEvidence.historyUnavailableReason} ++ دلیل استفاده از پروفایل: {historyUnavailableCopy( + event.profileEvidence.historyUnavailableReason, + )} +
+ {/if} +با بهروزرسانی تقویم، این بخش دوباره بررسی میشود.
- سه مناسبت مرتبط با فعالیت شما، بر پایه تقویم بررسیشده و اولویتبندی هوشمند، نمایش داده میشوند. + سه مناسبت نزدیک، بر پایه تجربه دورههای مشابه یا نشانههای ثبتشده در پروفایل کسبوکار شما + رتبهبندی شدهاند.