diff --git a/docs/GROWTH-ANALYST-UX-SPEC.md b/docs/GROWTH-ANALYST-UX-SPEC.md index 9a80adb..2bbc367 100644 --- a/docs/GROWTH-ANALYST-UX-SPEC.md +++ b/docs/GROWTH-ANALYST-UX-SPEC.md @@ -13,7 +13,7 @@ - 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. - 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. `INSUFFICIENT_DATA` explains the minimum evidence without presenting a forecast. `FAILED` offers safe retry without hiding an older successful report. +- `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. - Motion is restrained, supports reduced motion, and does not block reading. All interactive targets are at least 44 px. ## Copy rules @@ -28,4 +28,4 @@ - Works at 360, 768, 1024, and wide desktop widths in light/dark RTL layouts. - 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 refresh is explicit and limited once per merchant per Tehran day. +- 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 04502de..9ae13cc 100644 --- a/src/lib/api/schema.d.ts +++ b/src/lib/api/schema.d.ts @@ -337,7 +337,7 @@ export interface components { schemas: { CreateGrowthReportRequest: { terminalId?: string; - /** @description When omitted, the request reuses the current matching report. */ + /** @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: { @@ -365,7 +365,7 @@ export interface components { /** @enum {integer} */ horizonDays: 90; /** @enum {string} */ - confidence: 'HIGH' | 'MEDIUM' | 'INSUFFICIENT_DATA'; + confidence: 'HIGH' | 'MEDIUM' | 'LOW' | 'INSUFFICIENT_DATA'; suppressionReason?: string; points: components['schemas']['GrowthForecastPoint'][]; backtest: { @@ -498,7 +498,10 @@ export interface components { failureCode?: string; cached: boolean; stale: boolean; - /** Format: date-time */ + /** + * 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; diff --git a/src/lib/components/GrowthAIWorkingIllustration.svelte b/src/lib/components/GrowthAIWorkingIllustration.svelte new file mode 100644 index 0000000..4b8ec67 --- /dev/null +++ b/src/lib/components/GrowthAIWorkingIllustration.svelte @@ -0,0 +1,223 @@ + + + + + diff --git a/src/lib/components/GrowthAIWorkingIllustration.test.ts b/src/lib/components/GrowthAIWorkingIllustration.test.ts new file mode 100644 index 0000000..125c090 --- /dev/null +++ b/src/lib/components/GrowthAIWorkingIllustration.test.ts @@ -0,0 +1,32 @@ +import { render, screen } from '@testing-library/svelte'; +import { describe, expect, it } from 'vitest'; + +import GrowthAIWorkingIllustration from './GrowthAIWorkingIllustration.svelte'; + +describe('GrowthAIWorkingIllustration', () => { + it('announces that the AI is assembling the initial growth analysis', () => { + const { container } = render(GrowthAIWorkingIllustration, { status: 'ANALYZING' }); + + expect( + screen.getByRole('status', { name: 'هوش مصنوعی در حال ساخت تحلیل رشد است' }).getAttribute('aria-busy'), + ).toBe('true'); + expect(screen.getByText('در حال بررسی روندهای کسبوکار شما')).not.toBeNull(); + expect(screen.getByText(/دادهها را کنار هم میگذارد/)).not.toBeNull(); + expect(container.querySelector('svg')?.getAttribute('aria-hidden')).toBe('true'); + }); + + it('describes the writing phase when the model is generating the summary', () => { + render(GrowthAIWorkingIllustration, { status: 'GENERATING' }); + + expect(screen.getByText('در حال نوشتن جمعبندی تحلیلی')).not.toBeNull(); + }); + + it('announces refresh work while covering an existing report', () => { + render(GrowthAIWorkingIllustration, { status: 'QUEUED', overlay: true }); + + expect( + screen.getByRole('status', { name: 'هوش مصنوعی در حال بهروزرسانی تحلیل رشد است' }), + ).not.toBeNull(); + expect(screen.getByText('نسخه تازه تحلیل در حال آمادهسازی است')).not.toBeNull(); + }); +}); diff --git a/src/lib/components/GrowthForecastChart.svelte b/src/lib/components/GrowthForecastChart.svelte index 87fa3fd..89a4e97 100644 --- a/src/lib/components/GrowthForecastChart.svelte +++ b/src/lib/components/GrowthForecastChart.svelte @@ -214,6 +214,15 @@
شروع پیشبینی: {dateLabel(startDate)}. این برآورد قطعی نیست و بر پایه الگوی دادههای کامل واردشده است.
+ {#if confidence === 'LOW'} ++ این پیشبینی با سابقه محدود یا الگوی پرنوسان ساخته شده است؛ بازه احتمالی را مبنای تصمیم بگیرید، نه + عدد میانی را بهعنوان نتیجه قطعی. +
+ {/if}- این بخش فقط زمانی پیشبینی نشان میدهد که سابقه کافی برای مقایسه روزهای مشابه و ارزیابی دقت مدل - وجود داشته باشد. + پس از ثبت نخستین داده کامل، مدل پیشبینی را با درجه اطمینان متناسب نمایش میدهد.