diff --git a/ai_evals/adapters/frontend/core/global/globalEvalRunner.ts b/ai_evals/adapters/frontend/core/global/globalEvalRunner.ts index 063b2235a7..a6d78da36b 100644 --- a/ai_evals/adapters/frontend/core/global/globalEvalRunner.ts +++ b/ai_evals/adapters/frontend/core/global/globalEvalRunner.ts @@ -32,6 +32,10 @@ const MUTATING_GLOBAL_TOOLS = new Set([ ]); const DISABLE_ACTIVE_EDITOR_CONTEXT_ENV = "WMILL_AI_EVAL_DISABLE_ACTIVE_EDITOR_CONTEXT"; +// A/B gate for the search_app read tool: set to "1" to run the baseline arm +// (toolset without search_app) so its token cost can be compared against the arm +// that offers it. +const DISABLE_SEARCH_APP_ENV = "WMILL_AI_EVAL_DISABLE_SEARCH_APP"; const LIVE_EDITOR_ITEM_KINDS = { script: "script", @@ -193,25 +197,28 @@ function clearLiveEditorDrafts( } function getGlobalEvalTools(): ProductionTool<{}>[] { - return (globalTools as ProductionTool<{}>[]).map((tool) => { - if (!MUTATING_GLOBAL_TOOLS.has(tool.def.function.name)) { - return tool; - } + const disableSearchApp = process.env[DISABLE_SEARCH_APP_ENV] === "1"; + return (globalTools as ProductionTool<{}>[]) + .filter((tool) => !(disableSearchApp && tool.def.function.name === "search_app")) + .map((tool) => { + if (!MUTATING_GLOBAL_TOOLS.has(tool.def.function.name)) { + return tool; + } - return { - ...tool, - requiresConfirmation: false, - validateBeforeConfirmation: undefined, - fn: async () => - JSON.stringify( - { - success: false, - error: - "This mutating workspace tool is disabled during ai_evals global mode.", - }, - null, - 2, - ), - }; - }); + return { + ...tool, + requiresConfirmation: false, + validateBeforeConfirmation: undefined, + fn: async () => + JSON.stringify( + { + success: false, + error: + "This mutating workspace tool is disabled during ai_evals global mode.", + }, + null, + 2, + ), + }; + }); } diff --git a/ai_evals/adapters/frontend/mockBackend.ts b/ai_evals/adapters/frontend/mockBackend.ts index 74fa812883..b78351512d 100644 --- a/ai_evals/adapters/frontend/mockBackend.ts +++ b/ai_evals/adapters/frontend/mockBackend.ts @@ -1,5 +1,12 @@ import { randomUUID } from 'node:crypto' -import type { CompletedJob, Flow, Job, Script } from '../../../frontend/src/lib/gen' +import type { + AppWithLastVersion, + CompletedJob, + Flow, + Job, + ListableApp, + Script +} from '../../../frontend/src/lib/gen' import type { DataTableTables, DataTableTableSchema, @@ -33,6 +40,18 @@ export interface BenchmarkWorkspaceFlow { value: Flow['value'] } +export interface BenchmarkWorkspaceApp { + path: string + summary: string + value: { + files: Record + runnables: Record + data?: unknown + policy?: unknown + custom_path?: unknown + } +} + export interface BenchmarkWorkspaceJob { /** Stable id so a case prompt can reference a specific run (e.g. for get_job_logs). */ id?: string @@ -47,6 +66,7 @@ export interface BenchmarkWorkspaceJob { export interface BenchmarkWorkspaceRunnables { scripts?: BenchmarkWorkspaceScript[] flows?: BenchmarkWorkspaceFlow[] + apps?: BenchmarkWorkspaceApp[] datatables?: BenchmarkDatatableSeed[] jobs?: BenchmarkWorkspaceJob[] } @@ -161,6 +181,22 @@ export function getBenchmarkFlowByPath(workspace: string, path: string): Flow | return flow ? buildBenchmarkFlow(flow) : null } +export function listBenchmarkApps(workspace: string): ListableApp[] | null { + const runnables = benchmarkWorkspaceRunnables.get(workspace) + if (!runnables) { + return null + } + return (runnables.apps ?? []).map(buildBenchmarkListableApp) +} + +export function getBenchmarkAppByPath(workspace: string, path: string): AppWithLastVersion | null { + const app = benchmarkWorkspaceRunnables + .get(workspace) + ?.apps?.find((entry) => entry.path === path) + + return app ? buildBenchmarkApp(app) : null +} + export function createBenchmarkCompletedJob(input: { workspace: string jobKind: CompletedJob['job_kind'] @@ -604,3 +640,35 @@ function buildBenchmarkFlow(flow: BenchmarkWorkspaceFlow): Flow { extra_perms: {} } as Flow } + +function buildBenchmarkListableApp(app: BenchmarkWorkspaceApp): ListableApp { + return { + id: 0, + workspace_id: 'benchmark', + path: app.path, + summary: app.summary, + version: 1, + extra_perms: {}, + edited_at: BENCHMARK_TIMESTAMP, + execution_mode: 'viewer', + raw_app: true + } +} + +function buildBenchmarkApp(app: BenchmarkWorkspaceApp): AppWithLastVersion { + return { + id: 0, + workspace_id: 'benchmark', + path: app.path, + summary: app.summary, + versions: [1], + created_by: 'benchmark', + created_at: BENCHMARK_TIMESTAMP, + value: app.value, + policy: (app.value.policy ?? {}) as AppWithLastVersion['policy'], + execution_mode: 'viewer', + extra_perms: {}, + custom_path: app.value.custom_path as string | undefined, + raw_app: true + } +} diff --git a/ai_evals/adapters/frontend/vitestAdapter.test.ts b/ai_evals/adapters/frontend/vitestAdapter.test.ts index 657f9aca6b..621ecaefcd 100644 --- a/ai_evals/adapters/frontend/vitestAdapter.test.ts +++ b/ai_evals/adapters/frontend/vitestAdapter.test.ts @@ -33,6 +33,7 @@ vi.mock('$lib/components/vscode', () => ({})) vi.mock('$lib/gen', async () => { const actual = await vi.importActual('$lib/gen') const { + getBenchmarkAppByPath, getBenchmarkCompletedJob, getBenchmarkCompletedJobResultMaybe, getBenchmarkDatatableSchema, @@ -42,6 +43,7 @@ vi.mock('$lib/gen', async () => { getBenchmarkScriptByHash, getBenchmarkScriptByPath, hasBenchmarkWorkspace, + listBenchmarkApps, listBenchmarkDatatables, listBenchmarkDrafts, listBenchmarkFlows, @@ -299,12 +301,20 @@ vi.mock('$lib/gen', async () => { }), AppService: wrapService(actual.AppService, { existsApp: async (data: { workspace: string; path: string }) => - hasBenchmarkWorkspace(data.workspace) ? false : actual.AppService.existsApp(data), + hasBenchmarkWorkspace(data.workspace) + ? Boolean(getBenchmarkAppByPath(data.workspace, data.path)) + : actual.AppService.existsApp(data), listApps: async (data: { workspace: string }) => - hasBenchmarkWorkspace(data.workspace) ? [] : actual.AppService.listApps(data), + hasBenchmarkWorkspace(data.workspace) + ? (listBenchmarkApps(data.workspace) ?? []) + : actual.AppService.listApps(data), getAppByPath: async (data: { workspace: string; path: string }) => { if (hasBenchmarkWorkspace(data.workspace)) { - throw new Error(`App "${data.path}" not found in benchmark workspace`) + const app = getBenchmarkAppByPath(data.workspace, data.path) + if (!app) { + throw new Error(`App "${data.path}" not found in benchmark workspace`) + } + return app } return actual.AppService.getAppByPath(data) } diff --git a/ai_evals/cases/global.yaml b/ai_evals/cases/global.yaml index 766515519b..319d13b272 100644 --- a/ai_evals/cases/global.yaml +++ b/ai_evals/cases/global.yaml @@ -943,3 +943,173 @@ - deploy_workspace_item - delete_workspace_item skipJudge: true + +# --- Raw app on a large project (context-usage benchmark) --- +# These cases run against the deliberately large `analytics_dashboard` raw-app +# fixture (~20 frontend files incl. a 5k-line data module, plus backend runnables). +# They exist to measure how much context the global chat consumes when working in a +# big raw app: test29 is a read-heavy debugging hunt, test30 is a small edit baseline. +# tokenUsage is recorded per run, so the same cases re-run after a read-tool change +# (the read_app_file cap + offset/limit paging) quantify the optimization. skipJudge: +# the judge only sees the drafts artifact and cannot run the app, so we validate +# deterministically. + +- id: global-test29-raw-app-debug-large + prompt: |- + The analytics dashboard app at `f/evals/global/analytics_dashboard` has a bug: + the Revenue Summary tile shows a total that is lower than the per-order line + totals and the per-region breakdown. Track down what is computing revenue + incorrectly and fix it. Keep the change as an AI draft only; do not deploy or + save it. + initial: ai_evals/fixtures/frontend/global/initial/analytics_dashboard + runtime: + maxTurns: 20 + validate: + draftCountExactly: 1 + requiredDrafts: + - type: app + path: f/evals/global/analytics_dashboard + valueIncludes: + - "return order.unitPrice * order.quantity" + toolExpect: + requiredToolsAnyOf: + # Inspecting the app's files is satisfied by either reading them directly + # or grepping for the revenue calculation. + - [read_app_file, search_app] + forbiddenToolsUsed: + - deploy_workspace_item + - delete_workspace_item + skipJudge: true + judgeChecklist: + - inspects the dashboard app's files to locate the revenue calculation + - fixes the per-order revenue so it multiplies unit price by quantity + - leaves the result as an AI draft and does not deploy or save it + +- id: global-test30-raw-app-small-edit-large + prompt: |- + In the dashboard app at `f/evals/global/analytics_dashboard`, change the main + page heading from "Operations Console" to "Revenue Overview". Leave everything + else unchanged. Keep it as an AI draft only; do not deploy or save it. + initial: ai_evals/fixtures/frontend/global/initial/analytics_dashboard + runtime: + maxTurns: 10 + validate: + draftCountExactly: 1 + requiredDrafts: + - type: app + path: f/evals/global/analytics_dashboard + valueIncludes: + - "Revenue Overview" + toolExpect: + requiredToolsAnyOf: + # Inspecting the app's files is satisfied by reading them directly or + # grepping for the target with search_app. + - [read_app_file, search_app] + forbiddenToolsUsed: + - deploy_workspace_item + - delete_workspace_item + skipJudge: true + judgeChecklist: + - renames the main page heading to Revenue Overview + - does not change other dashboard behavior + - leaves the result as an AI draft only + +- id: global-test31-raw-app-debug-inspect-data + prompt: |- + The raw app dashboard at `f/evals/global/analytics_dashboard` is reporting + revenue totals that look too low. Inspect the app's files — both the sample + order data module and the revenue calculation — to work out whether the bug is + in the data or in the calculation, then fix the actual cause. Keep the change as + an AI draft only; do not deploy or save it. + initial: ai_evals/fixtures/frontend/global/initial/analytics_dashboard + runtime: + maxTurns: 22 + validate: + draftCountExactly: 1 + requiredDrafts: + - type: app + path: f/evals/global/analytics_dashboard + valueIncludes: + - "return order.unitPrice * order.quantity" + toolExpect: + requiredToolsAnyOf: + # Inspecting the app's files is satisfied by reading them directly or + # grepping for the target with search_app. + - [read_app_file, search_app] + forbiddenToolsUsed: + - deploy_workspace_item + - delete_workspace_item + skipJudge: true + judgeChecklist: + - inspects both the sample order data module and the revenue aggregation logic + - identifies the per-order revenue bug and fixes it to multiply unit price by quantity + - leaves the result as an AI draft only + +- id: global-test32-raw-app-cross-file-consistency + prompt: |- + The raw app dashboard at `f/evals/global/analytics_dashboard` shows revenue + totals that disagree between the Revenue Summary tile, the orders table, and the + regional breakdown. Investigate how each of those computes revenue, work out + which calculation is wrong, and fix it. Keep the change as an AI draft only; do + not deploy or save it. + # Cross-file investigation: forces the model through several overlapping files + # (the summary's aggregation helper, the orders table, the regional breakdown) — + # a realistic multi-file read load that exercises the read_app_file cap. + initial: ai_evals/fixtures/frontend/global/initial/analytics_dashboard + runtime: + maxTurns: 24 + validate: + draftCountExactly: 1 + requiredDrafts: + - type: app + path: f/evals/global/analytics_dashboard + valueIncludes: + - "return order.unitPrice * order.quantity" + toolExpect: + requiredToolsAnyOf: + # Inspecting the app's files is satisfied by reading them directly or + # grepping for the target with search_app. + - [read_app_file, search_app] + forbiddenToolsUsed: + - deploy_workspace_item + - delete_workspace_item + skipJudge: true + judgeChecklist: + - inspects the revenue calculation behind the summary tile, the orders table, and the regional breakdown + - identifies that the per-order revenue helper omits quantity and fixes it to multiply unit price by quantity + - leaves the result as an AI draft only + +- id: global-test33-raw-app-rename-across-files + prompt: |- + In the dashboard app at `f/evals/global/analytics_dashboard`, rename the + `formatCurrency` helper to `formatMoney` everywhere it is defined, imported, and + called. Leave the separate `formatCurrencyPrecise` helper exactly as it is. Keep + the change as an AI draft only; do not deploy or save it. + # Find-all-usages rename: formatCurrency is defined once and called in 6 places + # spread across 4 component files (and imported in 4). Locating every usage is the + # exact task search_app is meant to make cheap — one grep returns all file:line + # rows instead of reading each component whole. valueExcludes "formatCurrency(" + # asserts the definition and all call sites were renamed while tolerating the + # preserved formatCurrencyPrecise (which is never followed by "("). + initial: ai_evals/fixtures/frontend/global/initial/analytics_dashboard + runtime: + maxTurns: 22 + validate: + draftCountExactly: 1 + requiredDrafts: + - type: app + path: f/evals/global/analytics_dashboard + valueIncludes: + - "export function formatMoney" + - "formatMoney(" + valueExcludes: + - "formatCurrency(" + toolExpect: + forbiddenToolsUsed: + - deploy_workspace_item + - delete_workspace_item + skipJudge: true + judgeChecklist: + - renames the formatCurrency definition, imports, and all call sites to formatMoney + - leaves the unrelated formatCurrencyPrecise helper unchanged + - leaves the result as an AI draft only diff --git a/ai_evals/core/types.ts b/ai_evals/core/types.ts index 6825fef0ba..52667fa321 100644 --- a/ai_evals/core/types.ts +++ b/ai_evals/core/types.ts @@ -168,6 +168,13 @@ export interface ToolCallArgumentRule { export interface ToolValidationSpec { requiredToolsUsed?: string[]; + /** + * Each inner array is an alternatives group: the check passes when at least + * one tool in the group was used. Use when several tools satisfy the same + * intent so a model that picks any valid path passes — e.g. inspecting an + * app's files via either `read_app_file` or `search_app`. + */ + requiredToolsAnyOf?: string[][]; forbiddenToolsUsed?: string[]; toolCallArgs?: ToolCallArgumentRule[]; } diff --git a/ai_evals/core/validators.test.ts b/ai_evals/core/validators.test.ts index 7f0e841366..5183a20226 100644 --- a/ai_evals/core/validators.test.ts +++ b/ai_evals/core/validators.test.ts @@ -245,6 +245,49 @@ describe("validateToolExpectations", () => { 'accepted substrings: insert into, update; values: "DROP TABLE orders"', }); }); + + it("passes requiredToolsAnyOf when any alternative in the group is used", () => { + const checks = validateToolExpectations({ + run: { + success: true, + actual: {}, + assistantMessageCount: 1, + toolCallCount: 1, + toolsUsed: ["search_app", "patch_app_file"], + skillsInvoked: [], + }, + toolExpect: { + requiredToolsAnyOf: [["read_app_file", "search_app"]], + }, + }); + + expect(checks).toContainEqual({ + name: "uses one of read_app_file, search_app", + passed: true, + }); + }); + + it("fails requiredToolsAnyOf when no alternative in the group is used", () => { + const checks = validateToolExpectations({ + run: { + success: true, + actual: {}, + assistantMessageCount: 1, + toolCallCount: 1, + toolsUsed: ["patch_app_file"], + skillsInvoked: [], + }, + toolExpect: { + requiredToolsAnyOf: [["read_app_file", "search_app"]], + }, + }); + + expect(checks).toContainEqual({ + name: "uses one of read_app_file, search_app", + passed: false, + details: "tools used: patch_app_file", + }); + }); }); describe("validateGlobalState", () => { diff --git a/ai_evals/core/validators.ts b/ai_evals/core/validators.ts index e7a7641c00..23a6709f9b 100644 --- a/ai_evals/core/validators.ts +++ b/ai_evals/core/validators.ts @@ -169,6 +169,16 @@ export function validateToolExpectations(input: { ); } + for (const group of expect.requiredToolsAnyOf ?? []) { + checks.push( + check( + `uses one of ${group.join(", ")}`, + group.some((toolName) => input.run.toolsUsed.includes(toolName)), + `tools used: ${input.run.toolsUsed.join(", ") || "none"}` + ) + ); + } + for (const toolName of expect.forbiddenToolsUsed ?? []) { checks.push( check( diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/computeSummary/main.ts b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/computeSummary/main.ts new file mode 100644 index 0000000000..b3c67cfdc7 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/computeSummary/main.ts @@ -0,0 +1,68 @@ +type OrderStatus = 'paid' | 'shipped' | 'delivered' | 'pending' | 'refunded' | 'cancelled' + +interface Order { + id: string + region: string + quantity: number + unitPrice: number + status: OrderStatus + placedAt: string +} + +// Server-side revenue rollup. Mirrors the client aggregation but is computed +// from the authoritative mocked order book so it can be used to cross-check +// the dashboard and to back the export. +const orders: Order[] = [ + { id: 'ORD-10001', region: 'North America', quantity: 3, unitPrice: 1195, status: 'delivered', placedAt: '2024-05-02' }, + { id: 'ORD-10002', region: 'EMEA', quantity: 5, unitPrice: 880, status: 'shipped', placedAt: '2024-05-03' }, + { id: 'ORD-10003', region: 'APAC', quantity: 2, unitPrice: 640, status: 'paid', placedAt: '2024-05-05' }, + { id: 'ORD-10004', region: 'LATAM', quantity: 7, unitPrice: 315, status: 'delivered', placedAt: '2024-05-07' }, + { id: 'ORD-10005', region: 'North America', quantity: 4, unitPrice: 150, status: 'refunded', placedAt: '2024-05-09' }, + { id: 'ORD-10006', region: 'EMEA', quantity: 6, unitPrice: 220, status: 'shipped', placedAt: '2024-05-12' }, + { id: 'ORD-10007', region: 'APAC', quantity: 1, unitPrice: 980, status: 'pending', placedAt: '2024-05-15' }, + { id: 'ORD-10008', region: 'North America', quantity: 8, unitPrice: 1100, status: 'delivered', placedAt: '2024-05-18' }, + { id: 'ORD-10009', region: 'EMEA', quantity: 2, unitPrice: 860, status: 'cancelled', placedAt: '2024-05-22' }, + { id: 'ORD-10010', region: 'LATAM', quantity: 9, unitPrice: 290, status: 'paid', placedAt: '2024-05-26' } +] + +const REVENUE_STATUSES: OrderStatus[] = ['paid', 'shipped', 'delivered'] + +export async function main({ + from, + to, + region +}: { + from: string + to: string + region: string +}): Promise<{ + totalRevenue: number + netRevenue: number + totalOrders: number + averageOrderValue: number + unitsSold: number + refundedRevenue: number + currency: string +}> { + let scoped = orders.filter((order) => order.placedAt >= from && order.placedAt <= to) + if (region && region !== 'all') { + scoped = scoped.filter((order) => order.region === region) + } + + const booked = scoped.filter((order) => REVENUE_STATUSES.includes(order.status)) + const totalRevenue = booked.reduce((acc, order) => acc + order.unitPrice * order.quantity, 0) + const unitsSold = booked.reduce((acc, order) => acc + order.quantity, 0) + const refundedRevenue = scoped + .filter((order) => order.status === 'refunded') + .reduce((acc, order) => acc + order.unitPrice * order.quantity, 0) + + return { + totalRevenue, + netRevenue: totalRevenue - refundedRevenue, + totalOrders: booked.length, + averageOrderValue: booked.length === 0 ? 0 : Math.round(totalRevenue / booked.length), + unitsSold, + refundedRevenue, + currency: 'USD' + } +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/computeSummary/meta.json b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/computeSummary/meta.json new file mode 100644 index 0000000000..ab2e5537f8 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/computeSummary/meta.json @@ -0,0 +1,4 @@ +{ + "name": "Compute Summary", + "language": "bun" +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/exportReport/main.ts b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/exportReport/main.ts new file mode 100644 index 0000000000..ec10c1d951 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/exportReport/main.ts @@ -0,0 +1,51 @@ +// Builds a downloadable report for the current dashboard view. Returns a data +// URL the browser can open directly so the export works without object storage. +export async function main({ + from, + to, + region, + format +}: { + from: string + to: string + region: string + format: 'csv' | 'json' +}): Promise<{ url: string; rows: number; filename: string }> { + const summary = { + from, + to, + region: region || 'all', + generatedAt: new Date().toISOString(), + rows: [ + { region: 'North America', revenue: 211_400, orders: 168 }, + { region: 'EMEA', revenue: 142_900, orders: 121 }, + { region: 'APAC', revenue: 86_500, orders: 78 }, + { region: 'LATAM', revenue: 41_500, orders: 45 } + ] + } + + const scoped = + region && region !== 'all' + ? summary.rows.filter((row) => row.region === region) + : summary.rows + + let body: string + let mime: string + if (format === 'csv') { + const header = 'region,revenue,orders' + const lines = scoped.map((row) => `${row.region},${row.revenue},${row.orders}`) + body = [header, ...lines].join('\n') + mime = 'text/csv' + } else { + body = JSON.stringify({ ...summary, rows: scoped }, null, 2) + mime = 'application/json' + } + + const encoded = Buffer.from(body, 'utf-8').toString('base64') + const filename = `revenue-report-${from}_${to}.${format}` + return { + url: `data:${mime};base64,${encoded}`, + rows: scoped.length, + filename + } +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/exportReport/meta.json b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/exportReport/meta.json new file mode 100644 index 0000000000..8f198d716f --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/exportReport/meta.json @@ -0,0 +1,4 @@ +{ + "name": "Export Report", + "language": "bun" +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/loadMetrics/main.ts b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/loadMetrics/main.ts new file mode 100644 index 0000000000..4ada29ad22 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/loadMetrics/main.ts @@ -0,0 +1,40 @@ +interface MetricCardData { + id: string + label: string + value: number + unit: 'currency' | 'count' | 'percent' + delta: number + hint: string +} + +// Returns the headline metric cards for the selected range and region. Values +// are mocked but internally consistent (revenue / orders ≈ avg order value). +const baseByRegion: Record = { + all: { revenue: 482_300, orders: 412, units: 1840, refunds: 11_900 }, + 'North America': { revenue: 211_400, orders: 168, units: 770, refunds: 4_200 }, + EMEA: { revenue: 142_900, orders: 121, units: 560, refunds: 3_500 }, + APAC: { revenue: 86_500, orders: 78, units: 340, refunds: 2_600 }, + LATAM: { revenue: 41_500, orders: 45, units: 170, refunds: 1_600 } +} + +export async function main({ + from, + to, + region +}: { + from: string + to: string + region: string +}): Promise<{ cards: MetricCardData[]; generatedAt: string }> { + const base = baseByRegion[region] ?? baseByRegion.all + const aov = base.orders === 0 ? 0 : Math.round(base.revenue / base.orders) + const cards: MetricCardData[] = [ + { id: 'revenue', label: 'Total Revenue', value: base.revenue, unit: 'currency', delta: 0.082, hint: `Booked revenue ${from} – ${to}` }, + { id: 'orders', label: 'Orders', value: base.orders, unit: 'count', delta: 0.041, hint: 'Revenue-bearing orders in range' }, + { id: 'aov', label: 'Avg Order Value', value: aov, unit: 'currency', delta: -0.013, hint: 'Total revenue / order count' }, + { id: 'units', label: 'Units Sold', value: base.units, unit: 'count', delta: 0.067, hint: 'Total units in range' }, + { id: 'refunds', label: 'Refunded', value: base.refunds, unit: 'currency', delta: -0.021, hint: 'Revenue lost to refunds' }, + { id: 'conversion', label: 'Conversion', value: 0.187, unit: 'percent', delta: 0.009, hint: 'Sessions that became orders' } + ] + return { cards, generatedAt: new Date().toISOString() } +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/loadMetrics/meta.json b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/loadMetrics/meta.json new file mode 100644 index 0000000000..1fbc3337c9 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/loadMetrics/meta.json @@ -0,0 +1,4 @@ +{ + "name": "Load Metrics", + "language": "bun" +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/loadOrders/main.ts b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/loadOrders/main.ts new file mode 100644 index 0000000000..27a5396f2e --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/loadOrders/main.ts @@ -0,0 +1,54 @@ +type OrderStatus = 'paid' | 'shipped' | 'delivered' | 'pending' | 'refunded' | 'cancelled' + +interface Order { + id: string + placedAt: string + customer: string + product: string + sku: string + region: string + channel: string + rep: string + quantity: number + unitPrice: number + status: OrderStatus +} + +// Mocked order book. In a real deployment this would query the orders table; +// here it returns a representative slice so the table renders in preview. +const orders: Order[] = [ + { id: 'ORD-10001', placedAt: '2024-05-02T09:14:00Z', customer: 'Contoso Ltd', product: 'Aurora Analytics Suite', sku: 'ANL-100', region: 'North America', channel: 'direct', rep: 'Dana Wills', quantity: 3, unitPrice: 1195, status: 'delivered' }, + { id: 'ORD-10002', placedAt: '2024-05-03T11:42:00Z', customer: 'Fabrikam Inc', product: 'Borealis CRM', sku: 'CRM-210', region: 'EMEA', channel: 'partner', rep: 'Lena Fischer', quantity: 5, unitPrice: 880, status: 'shipped' }, + { id: 'ORD-10003', placedAt: '2024-05-05T15:03:00Z', customer: 'Tailspin Toys', product: 'Cascade Data Pipeline', sku: 'PIPE-330', region: 'APAC', channel: 'self-serve', rep: 'Sora Tanaka', quantity: 2, unitPrice: 640, status: 'paid' }, + { id: 'ORD-10004', placedAt: '2024-05-07T08:21:00Z', customer: 'Proseware Inc', product: 'Delta Insights', sku: 'INS-440', region: 'LATAM', channel: 'marketplace', rep: 'Diego Marin', quantity: 7, unitPrice: 315, status: 'delivered' }, + { id: 'ORD-10005', placedAt: '2024-05-09T13:58:00Z', customer: 'Litware Inc', product: 'Echo Monitoring', sku: 'MON-550', region: 'North America', channel: 'direct', rep: 'Owen Pratt', quantity: 4, unitPrice: 150, status: 'refunded' }, + { id: 'ORD-10006', placedAt: '2024-05-12T10:30:00Z', customer: 'Fourth Coffee', product: 'Helix Identity', sku: 'IDN-880', region: 'EMEA', channel: 'partner', rep: 'Aisha Khan', quantity: 6, unitPrice: 220, status: 'shipped' }, + { id: 'ORD-10007', placedAt: '2024-05-15T17:11:00Z', customer: 'Coho Vineyard', product: 'Kelvin Forecasting', sku: 'FCT-202', region: 'APAC', channel: 'direct', rep: 'Priya Nair', quantity: 1, unitPrice: 980, status: 'pending' }, + { id: 'ORD-10008', placedAt: '2024-05-18T12:05:00Z', customer: 'Alpine Ski House', product: 'Nimbus Compute', sku: 'CMP-505', region: 'North America', channel: 'self-serve', rep: 'Hugo Bernard', quantity: 8, unitPrice: 1100, status: 'delivered' }, + { id: 'ORD-10009', placedAt: '2024-05-22T14:47:00Z', customer: 'Trey Research', product: 'Onyx Security', sku: 'SEC-606', region: 'EMEA', channel: 'direct', rep: 'Sven Olsen', quantity: 2, unitPrice: 860, status: 'cancelled' }, + { id: 'ORD-10010', placedAt: '2024-05-26T16:39:00Z', customer: 'Blue Yonder Airlines', product: 'Polaris Reporting', sku: 'RPT-707', region: 'LATAM', channel: 'partner', rep: 'Mateo Russo', quantity: 9, unitPrice: 290, status: 'paid' } +] + +export async function main({ + from, + to, + region, + status +}: { + from: string + to: string + region: string + status: string +}): Promise<{ orders: Order[]; total: number }> { + let filtered = orders.filter((order) => { + const day = order.placedAt.slice(0, 10) + return day >= from && day <= to + }) + if (region && region !== 'all') { + filtered = filtered.filter((order) => order.region === region) + } + if (status && status !== 'all') { + filtered = filtered.filter((order) => order.status === status) + } + return { orders: filtered, total: filtered.length } +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/loadOrders/meta.json b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/loadOrders/meta.json new file mode 100644 index 0000000000..098e077560 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/backend/loadOrders/meta.json @@ -0,0 +1,4 @@ +{ + "name": "Load Orders", + "language": "bun" +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/DateRangePicker.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/DateRangePicker.tsx new file mode 100644 index 0000000000..f0a76a09f3 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/DateRangePicker.tsx @@ -0,0 +1,45 @@ +import React from 'react' +import type { DateRange } from '../lib/api' +import { rangeForPreset } from '../lib/api' +import { formatDateShort } from '../lib/format' + +interface DateRangePickerProps { + preset: string + range: DateRange + onPresetChange: (preset: string, range: DateRange) => void +} + +const PRESETS: { id: string; label: string }[] = [ + { id: '7d', label: 'Last 7 days' }, + { id: '14d', label: 'Last 14 days' }, + { id: '30d', label: 'Last 30 days' }, + { id: 'qtd', label: 'Quarter to date' } +] + +export const DateRangePicker: React.FC = ({ + preset, + range, + onPresetChange +}) => { + return ( +
+ + + {formatDateShort(range.from)} – {formatDateShort(range.to)} + +
+ ) +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/EmptyState.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/EmptyState.tsx new file mode 100644 index 0000000000..f4dc840c60 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/EmptyState.tsx @@ -0,0 +1,28 @@ +import React from 'react' + +interface EmptyStateProps { + title: string + description?: string + icon?: string + action?: React.ReactNode +} + +export const EmptyState: React.FC = ({ + title, + description, + icon = '📊', + action +}) => { + return ( +
+
+ {icon} +
+

{title}

+ {description ? ( +

{description}

+ ) : null} + {action ?
{action}
: null} +
+ ) +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/ExportButton.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/ExportButton.tsx new file mode 100644 index 0000000000..6fc9b5adde --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/ExportButton.tsx @@ -0,0 +1,51 @@ +import React, { useState } from 'react' +import { requestExport } from '../lib/api' +import type { DateRange } from '../lib/api' + +interface ExportButtonProps { + range: DateRange + region: string +} + +export const ExportButton: React.FC = ({ range, region }) => { + const [busy, setBusy] = useState(false) + const [error, setError] = useState(null) + + const handleExport = async (format: 'csv' | 'json') => { + setBusy(true) + setError(null) + try { + const result = await requestExport(range, region, format) + const anchor = document.createElement('a') + anchor.href = result.url + anchor.download = `revenue-report.${format}` + anchor.click() + } catch (err) { + setError(err instanceof Error ? err.message : 'Export failed') + } finally { + setBusy(false) + } + } + + return ( +
+ + + {error ? {error} : null} +
+ ) +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/FilterBar.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/FilterBar.tsx new file mode 100644 index 0000000000..38de9d6a9b --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/FilterBar.tsx @@ -0,0 +1,59 @@ +import React from 'react' +import type { DateRange } from '../lib/api' +import type { OrderStatus } from '../data/seedData' +import { REGIONS, ORDER_STATUSES, STATUS_LABELS } from '../data/seedData' +import { DateRangePicker } from './DateRangePicker' +import { ExportButton } from './ExportButton' + +interface FilterBarProps { + region: string + status: string + preset: string + range: DateRange + onRegionChange: (region: string) => void + onStatusChange: (status: string) => void + onPresetChange: (preset: string, range: DateRange) => void +} + +export const FilterBar: React.FC = ({ + region, + status, + preset, + range, + onRegionChange, + onStatusChange, + onPresetChange +}) => { + return ( +
+
+ + + +
+ +
+ ) +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/MetricCard.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/MetricCard.tsx new file mode 100644 index 0000000000..23dcd90123 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/MetricCard.tsx @@ -0,0 +1,40 @@ +import React from 'react' +import type { MetricCardData } from '../data/seedData' +import { formatCurrency, formatNumber, formatPercent, formatSignedPercent } from '../lib/format' + +interface MetricCardProps { + metric: MetricCardData + loading?: boolean +} + +function renderValue(metric: MetricCardData): string { + switch (metric.unit) { + case 'currency': + return formatCurrency(metric.value) + case 'percent': + return formatPercent(metric.value) + case 'count': + default: + return formatNumber(metric.value) + } +} + +export const MetricCard: React.FC = ({ metric, loading }) => { + const positive = metric.delta >= 0 + return ( +
+
+ {metric.label} + + {formatSignedPercent(metric.delta)} + +
+
+ {loading ? : renderValue(metric)} +
+

{metric.hint}

+
+ ) +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/MetricGrid.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/MetricGrid.tsx new file mode 100644 index 0000000000..c42a8aa733 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/MetricGrid.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import type { MetricCardData } from '../data/seedData' +import { MetricCard } from './MetricCard' + +interface MetricGridProps { + metrics: MetricCardData[] + loading?: boolean +} + +export const MetricGrid: React.FC = ({ metrics, loading }) => { + return ( +
+ {metrics.map((metric) => ( + + ))} +
+ ) +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/OrdersTable.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/OrdersTable.tsx new file mode 100644 index 0000000000..fa0411c371 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/OrdersTable.tsx @@ -0,0 +1,117 @@ +import React, { useMemo, useState } from 'react' +import type { Order } from '../data/seedData' +import { StatusBadge } from './StatusBadge' +import { EmptyState } from './EmptyState' +import { formatCurrencyPrecise, formatDate, formatNumber, truncate } from '../lib/format' + +interface OrdersTableProps { + orders: Order[] + loading?: boolean +} + +type SortKey = 'placedAt' | 'customer' | 'lineTotal' | 'quantity' +type SortDir = 'asc' | 'desc' + +// The per-row line total a customer was charged: unit price times quantity. +function lineTotal(order: Order): number { + return order.quantity * order.unitPrice +} + +export const OrdersTable: React.FC = ({ orders, loading }) => { + const [sortKey, setSortKey] = useState('placedAt') + const [sortDir, setSortDir] = useState('desc') + + const sorted = useMemo(() => { + const copy = [...orders] + copy.sort((a, b) => { + let comparison = 0 + switch (sortKey) { + case 'customer': + comparison = a.customer.localeCompare(b.customer) + break + case 'lineTotal': + comparison = lineTotal(a) - lineTotal(b) + break + case 'quantity': + comparison = a.quantity - b.quantity + break + case 'placedAt': + default: + comparison = a.placedAt.localeCompare(b.placedAt) + break + } + return sortDir === 'asc' ? comparison : -comparison + }) + return copy + }, [orders, sortKey, sortDir]) + + const toggleSort = (key: SortKey) => { + if (key === sortKey) { + setSortDir((dir) => (dir === 'asc' ? 'desc' : 'asc')) + } else { + setSortKey(key) + setSortDir('desc') + } + } + + if (!loading && orders.length === 0) { + return ( + + ) + } + + const arrow = (key: SortKey) => (key === sortKey ? (sortDir === 'asc' ? '▲' : '▼') : '') + + return ( +
+ + + + + + + + + + + + + + + {sorted.map((order) => ( + + + + + + + + + + + ))} + +
toggleSort('placedAt')}> + Date {arrow('placedAt')} + toggleSort('customer')}> + Customer {arrow('customer')} + ProductRegion toggleSort('quantity')}> + Qty {arrow('quantity')} + Unit Price toggleSort('lineTotal')}> + Line Total {arrow('lineTotal')} + Status
{formatDate(order.placedAt)} + {truncate(order.customer, 24)} + {order.product}{order.region}{formatNumber(order.quantity)} + {formatCurrencyPrecise(order.unitPrice)} + + {formatCurrencyPrecise(lineTotal(order))} + + +
+
+ ) +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/RegionTable.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/RegionTable.tsx new file mode 100644 index 0000000000..41e47430b9 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/RegionTable.tsx @@ -0,0 +1,52 @@ +import React, { useMemo } from 'react' +import type { Order } from '../data/seedData' +import { breakdownByRegion } from '../lib/aggregations' +import { formatCurrency, formatNumber, formatPercent } from '../lib/format' +import { EmptyState } from './EmptyState' + +interface RegionTableProps { + orders: Order[] +} + +export const RegionTable: React.FC = ({ orders }) => { + const rows = useMemo(() => breakdownByRegion(orders), [orders]) + const total = useMemo(() => rows.reduce((acc, row) => acc + row.revenue, 0), [rows]) + + if (rows.length === 0) { + return ( + + ) + } + + return ( +
+

Revenue by Region

+ + + + + + + + + + + {rows.map((row) => ( + + + + + + + ))} + +
RegionOrdersRevenueShare
{row.region}{formatNumber(row.orders)}{formatCurrency(row.revenue)} + {formatPercent(total === 0 ? 0 : row.revenue / total)} +
+
+ ) +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/RevenueChart.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/RevenueChart.tsx new file mode 100644 index 0000000000..f16fe779fe --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/RevenueChart.tsx @@ -0,0 +1,49 @@ +import React, { useMemo } from 'react' +import type { Order } from '../data/seedData' +import { dailyRevenue } from '../lib/aggregations' +import { formatCompact, formatDateShort } from '../lib/format' +import { EmptyState } from './EmptyState' + +interface RevenueChartProps { + orders: Order[] +} + +// Lightweight inline bar chart for daily revenue. Avoids a charting dependency +// by sizing flexed columns relative to the busiest day in the window. +export const RevenueChart: React.FC = ({ orders }) => { + const points = useMemo(() => dailyRevenue(orders), [orders]) + const max = useMemo(() => points.reduce((acc, point) => Math.max(acc, point.revenue), 0), [points]) + + if (points.length === 0) { + return ( + + ) + } + + return ( +
+

Daily Revenue

+
+ {points.map((point) => { + const heightPct = max === 0 ? 0 : Math.round((point.revenue / max) * 100) + return ( +
+
+ + {formatDateShort(point.date)} + +
+ ) + })} +
+
+ ) +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/Sidebar.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/Sidebar.tsx new file mode 100644 index 0000000000..5e92f4719b --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/Sidebar.tsx @@ -0,0 +1,50 @@ +import React from 'react' + +export type DashboardView = 'overview' | 'orders' | 'regions' | 'products' + +interface SidebarProps { + active: DashboardView + onSelect: (view: DashboardView) => void +} + +const NAV_ITEMS: { id: DashboardView; label: string; icon: string }[] = [ + { id: 'overview', label: 'Overview', icon: '📈' }, + { id: 'orders', label: 'Orders', icon: '🧾' }, + { id: 'regions', label: 'Regions', icon: '🌍' }, + { id: 'products', label: 'Products', icon: '📦' } +] + +export const Sidebar: React.FC = ({ active, onSelect }) => { + return ( + + ) +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/StatusBadge.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/StatusBadge.tsx new file mode 100644 index 0000000000..114d2a7073 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/StatusBadge.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import type { OrderStatus } from '../data/seedData' +import { STATUS_LABELS } from '../data/seedData' + +interface StatusBadgeProps { + status: OrderStatus +} + +const STATUS_STYLES: Record = { + paid: 'bg-blue-100 text-blue-700', + shipped: 'bg-indigo-100 text-indigo-700', + delivered: 'bg-emerald-100 text-emerald-700', + pending: 'bg-amber-100 text-amber-700', + refunded: 'bg-rose-100 text-rose-700', + cancelled: 'bg-gray-200 text-gray-600' +} + +export const StatusBadge: React.FC = ({ status }) => { + return ( + + {STATUS_LABELS[status]} + + ) +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/SummaryPanel.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/SummaryPanel.tsx new file mode 100644 index 0000000000..0ec6a345bc --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/SummaryPanel.tsx @@ -0,0 +1,51 @@ +import React, { useMemo } from 'react' +import type { Order } from '../data/seedData' +import { summarizeRevenue } from '../lib/aggregations' +import { formatCurrency, formatCurrencyPrecise, formatNumber } from '../lib/format' + +interface SummaryPanelProps { + orders: Order[] + loading?: boolean +} + +// Headline revenue panel. It re-aggregates the orders client-side via +// summarizeRevenue so the totals stay in sync with whatever filter the user +// has applied, without waiting for another backend round trip. +export const SummaryPanel: React.FC = ({ orders, loading }) => { + const summary = useMemo(() => summarizeRevenue(orders), [orders]) + + const tiles = [ + { label: 'Total Revenue', value: formatCurrency(summary.totalRevenue), emphasis: true }, + { label: 'Net Revenue', value: formatCurrency(summary.netRevenue) }, + { label: 'Orders', value: formatNumber(summary.totalOrders) }, + { label: 'Avg Order Value', value: formatCurrencyPrecise(summary.averageOrderValue) }, + { label: 'Units Sold', value: formatNumber(summary.unitsSold) }, + { label: 'Refunded', value: formatCurrency(summary.refundedRevenue) } + ] + + return ( +
+
+

Revenue Summary

+ {loading ? Refreshing… : null} +
+
+ {tiles.map((tile) => ( +
+
+ {tile.label} +
+
+ {tile.value} +
+
+ ))} +
+
+ ) +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/TopProducts.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/TopProducts.tsx new file mode 100644 index 0000000000..9876e9701d --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/components/TopProducts.tsx @@ -0,0 +1,55 @@ +import React, { useMemo } from 'react' +import type { Order } from '../data/seedData' +import { topProducts } from '../lib/aggregations' +import { formatCurrency } from '../lib/format' +import { EmptyState } from './EmptyState' + +interface TopProductsProps { + orders: Order[] + limit?: number +} + +export const TopProducts: React.FC = ({ orders, limit = 5 }) => { + const products = useMemo(() => topProducts(orders, limit), [orders, limit]) + const max = useMemo( + () => products.reduce((acc, item) => Math.max(acc, item.revenue), 0), + [products] + ) + + if (products.length === 0) { + return ( + + ) + } + + return ( +
+

Top Products

+
    + {products.map((item, index) => { + const widthPct = max === 0 ? 0 : Math.round((item.revenue / max) * 100) + return ( +
  • +
    + + {index + 1}. {item.product} + + {formatCurrency(item.revenue)} +
    +
    +
    +
    +
  • + ) + })} +
+
+ ) +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/data/seedData.ts b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/data/seedData.ts new file mode 100644 index 0000000000..742a83be3d --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/data/seedData.ts @@ -0,0 +1,5051 @@ +// Synthetic seed data for the analytics dashboard. +// +// This module is the app's offline data source: the backend runnables filter +// and aggregate these rows, and a few components fall back to them when a +// backend call is unavailable in preview. The array is intentionally large and +// varied so the dashboard renders realistic totals, regional splits, and time +// series. It is plain committed source — do not regenerate it at load time. + +export type OrderStatus = + | 'paid' + | 'shipped' + | 'delivered' + | 'pending' + | 'refunded' + | 'cancelled' + +export interface Order { + id: string + placedAt: string + customer: string + product: string + sku: string + region: string + channel: string + rep: string + quantity: number + unitPrice: number + status: OrderStatus +} + +export interface MetricCardData { + id: string + label: string + value: number + unit: 'currency' | 'count' | 'percent' + delta: number + hint: string +} + +export const PRODUCT_CATALOG: { name: string; sku: string; listPrice: number }[] = [ + { name: "Aurora Analytics Suite", sku: "ANL-100", listPrice: 1200 }, + { name: "Borealis CRM", sku: "CRM-210", listPrice: 890 }, + { name: "Cascade Data Pipeline", sku: "PIPE-330", listPrice: 640 }, + { name: "Delta Insights", sku: "INS-440", listPrice: 320 }, + { name: "Echo Monitoring", sku: "MON-550", listPrice: 150 }, + { name: "Fjord Storage", sku: "STO-660", listPrice: 75 }, + { name: "Glacier Backup", sku: "BAK-770", listPrice: 45 }, + { name: "Helix Identity", sku: "IDN-880", listPrice: 220 }, + { name: "Ion Messaging", sku: "MSG-990", listPrice: 60 }, + { name: "Juniper Workflow", sku: "WFL-101", listPrice: 410 }, + { name: "Kelvin Forecasting", sku: "FCT-202", listPrice: 980 }, + { name: "Lumen Dashboards", sku: "DSH-303", listPrice: 520 }, + { name: "Meridian ETL", sku: "ETL-404", listPrice: 730 }, + { name: "Nimbus Compute", sku: "CMP-505", listPrice: 1100 }, + { name: "Onyx Security", sku: "SEC-606", listPrice: 860 }, + { name: "Polaris Reporting", sku: "RPT-707", listPrice: 290 }, +] + +export const REGIONS: string[] = [ + "North America", + "EMEA", + "APAC", + "LATAM", +] + +export const ORDER_STATUSES: OrderStatus[] = [ + "paid", + "shipped", + "delivered", + "pending", + "refunded", + "cancelled", +] + +export const STATUS_LABELS: Record = { + paid: 'Paid', + shipped: 'Shipped', + delivered: 'Delivered', + pending: 'Pending', + refunded: 'Refunded', + cancelled: 'Cancelled' +} + +export const seedOrders: Order[] = [ + { + id: "ORD-10070", + placedAt: "2024-05-01T02:15:00Z", + customer: "Wingtip Toys", + product: "Fjord Storage", + sku: "STO-660", + region: "EMEA", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 4, + unitPrice: 71, + status: "pending" + }, + { + id: "ORD-10245", + placedAt: "2024-05-01T03:12:00Z", + customer: "Adventure Works", + product: "Echo Monitoring", + sku: "MON-550", + region: "APAC", + channel: "partner", + rep: "Priya Nair", + quantity: 8, + unitPrice: 161, + status: "shipped" + }, + { + id: "ORD-10368", + placedAt: "2024-05-01T06:05:00Z", + customer: "Blue Yonder Airlines", + product: "Polaris Reporting", + sku: "RPT-707", + region: "EMEA", + channel: "direct", + rep: "Mateo Russo", + quantity: 8, + unitPrice: 295, + status: "paid" + }, + { + id: "ORD-10235", + placedAt: "2024-05-01T07:51:00Z", + customer: "Contoso Ltd", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "North America", + channel: "partner", + rep: "Priya Nair", + quantity: 7, + unitPrice: 997, + status: "shipped" + }, + { + id: "ORD-10142", + placedAt: "2024-05-01T08:59:00Z", + customer: "Lucerne Publishing", + product: "Nimbus Compute", + sku: "CMP-505", + region: "LATAM", + channel: "marketplace", + rep: "Priya Nair", + quantity: 6, + unitPrice: 1094, + status: "refunded" + }, + { + id: "ORD-10300", + placedAt: "2024-05-01T12:08:00Z", + customer: "Wide World Importers", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "EMEA", + channel: "self-serve", + rep: "Priya Nair", + quantity: 7, + unitPrice: 516, + status: "shipped" + }, + { + id: "ORD-10229", + placedAt: "2024-05-01T12:26:00Z", + customer: "Lucerne Publishing", + product: "Echo Monitoring", + sku: "MON-550", + region: "EMEA", + channel: "marketplace", + rep: "Sven Olsen", + quantity: 4, + unitPrice: 147, + status: "refunded" + }, + { + id: "ORD-10091", + placedAt: "2024-05-01T14:29:00Z", + customer: "Wingtip Toys", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "LATAM", + channel: "direct", + rep: "Diego Marin", + quantity: 2, + unitPrice: 984, + status: "delivered" + }, + { + id: "ORD-10329", + placedAt: "2024-05-01T19:07:00Z", + customer: "City Power & Light", + product: "Ion Messaging", + sku: "MSG-990", + region: "EMEA", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 5, + unitPrice: 59, + status: "delivered" + }, + { + id: "ORD-10200", + placedAt: "2024-05-01T20:25:00Z", + customer: "Fourth Coffee", + product: "Helix Identity", + sku: "IDN-880", + region: "APAC", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 1, + unitPrice: 223, + status: "delivered" + }, + { + id: "ORD-10177", + placedAt: "2024-05-01T21:13:00Z", + customer: "Wingtip Toys", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "APAC", + channel: "partner", + rep: "Lena Fischer", + quantity: 2, + unitPrice: 1188, + status: "paid" + }, + { + id: "ORD-10101", + placedAt: "2024-05-01T21:21:00Z", + customer: "Fabrikam Inc", + product: "Echo Monitoring", + sku: "MON-550", + region: "APAC", + channel: "marketplace", + rep: "Dana Wills", + quantity: 1, + unitPrice: 155, + status: "shipped" + }, + { + id: "ORD-10148", + placedAt: "2024-05-01T21:24:00Z", + customer: "Coho Vineyard", + product: "Delta Insights", + sku: "INS-440", + region: "EMEA", + channel: "partner", + rep: "Sven Olsen", + quantity: 7, + unitPrice: 313, + status: "refunded" + }, + { + id: "ORD-10358", + placedAt: "2024-05-02T00:56:00Z", + customer: "Blue Yonder Airlines", + product: "Fjord Storage", + sku: "STO-660", + region: "EMEA", + channel: "marketplace", + rep: "Hugo Bernard", + quantity: 3, + unitPrice: 63, + status: "cancelled" + }, + { + id: "ORD-10059", + placedAt: "2024-05-02T06:34:00Z", + customer: "Coho Vineyard", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "North America", + channel: "partner", + rep: "Owen Pratt", + quantity: 7, + unitPrice: 990, + status: "refunded" + }, + { + id: "ORD-10279", + placedAt: "2024-05-02T13:07:00Z", + customer: "Wide World Importers", + product: "Glacier Backup", + sku: "BAK-770", + region: "North America", + channel: "direct", + rep: "Priya Nair", + quantity: 7, + unitPrice: 47, + status: "delivered" + }, + { + id: "ORD-10215", + placedAt: "2024-05-02T14:05:00Z", + customer: "Adventure Works", + product: "Glacier Backup", + sku: "BAK-770", + region: "APAC", + channel: "direct", + rep: "Sora Tanaka", + quantity: 1, + unitPrice: 29, + status: "pending" + }, + { + id: "ORD-10341", + placedAt: "2024-05-02T15:19:00Z", + customer: "Northwind Traders", + product: "Echo Monitoring", + sku: "MON-550", + region: "North America", + channel: "direct", + rep: "Sora Tanaka", + quantity: 5, + unitPrice: 160, + status: "paid" + }, + { + id: "ORD-10039", + placedAt: "2024-05-02T15:24:00Z", + customer: "Alpine Ski House", + product: "Glacier Backup", + sku: "BAK-770", + region: "North America", + channel: "partner", + rep: "Diego Marin", + quantity: 4, + unitPrice: 46, + status: "paid" + }, + { + id: "ORD-10380", + placedAt: "2024-05-02T18:33:00Z", + customer: "Margies Travel", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "APAC", + channel: "self-serve", + rep: "Sora Tanaka", + quantity: 5, + unitPrice: 537, + status: "shipped" + }, + { + id: "ORD-10083", + placedAt: "2024-05-02T21:21:00Z", + customer: "Fabrikam Inc", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "APAC", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 3, + unitPrice: 634, + status: "shipped" + }, + { + id: "ORD-10233", + placedAt: "2024-05-02T22:38:00Z", + customer: "Contoso Ltd", + product: "Ion Messaging", + sku: "MSG-990", + region: "LATAM", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 4, + unitPrice: 71, + status: "delivered" + }, + { + id: "ORD-10243", + placedAt: "2024-05-03T03:42:00Z", + customer: "Northwind Traders", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "EMEA", + channel: "self-serve", + rep: "Diego Marin", + quantity: 2, + unitPrice: 634, + status: "paid" + }, + { + id: "ORD-10269", + placedAt: "2024-05-03T03:43:00Z", + customer: "City Power & Light", + product: "Meridian ETL", + sku: "ETL-404", + region: "LATAM", + channel: "direct", + rep: "Mateo Russo", + quantity: 3, + unitPrice: 745, + status: "shipped" + }, + { + id: "ORD-10230", + placedAt: "2024-05-03T07:54:00Z", + customer: "Adventure Works", + product: "Fjord Storage", + sku: "STO-660", + region: "APAC", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 4, + unitPrice: 73, + status: "shipped" + }, + { + id: "ORD-10354", + placedAt: "2024-05-03T10:30:00Z", + customer: "Humongous Insurance", + product: "Borealis CRM", + sku: "CRM-210", + region: "EMEA", + channel: "partner", + rep: "Lena Fischer", + quantity: 8, + unitPrice: 879, + status: "refunded" + }, + { + id: "ORD-10256", + placedAt: "2024-05-03T12:01:00Z", + customer: "Tailspin Toys", + product: "Polaris Reporting", + sku: "RPT-707", + region: "LATAM", + channel: "marketplace", + rep: "Aisha Khan", + quantity: 1, + unitPrice: 298, + status: "delivered" + }, + { + id: "ORD-10146", + placedAt: "2024-05-03T14:03:00Z", + customer: "Fourth Coffee", + product: "Borealis CRM", + sku: "CRM-210", + region: "APAC", + channel: "marketplace", + rep: "Sven Olsen", + quantity: 8, + unitPrice: 895, + status: "cancelled" + }, + { + id: "ORD-10162", + placedAt: "2024-05-03T14:09:00Z", + customer: "Margies Travel", + product: "Borealis CRM", + sku: "CRM-210", + region: "APAC", + channel: "self-serve", + rep: "Sora Tanaka", + quantity: 2, + unitPrice: 887, + status: "shipped" + }, + { + id: "ORD-10313", + placedAt: "2024-05-03T21:27:00Z", + customer: "Fourth Coffee", + product: "Ion Messaging", + sku: "MSG-990", + region: "EMEA", + channel: "marketplace", + rep: "Aisha Khan", + quantity: 1, + unitPrice: 46, + status: "refunded" + }, + { + id: "ORD-10282", + placedAt: "2024-05-03T22:46:00Z", + customer: "Lucerne Publishing", + product: "Juniper Workflow", + sku: "WFL-101", + region: "North America", + channel: "self-serve", + rep: "Diego Marin", + quantity: 7, + unitPrice: 405, + status: "delivered" + }, + { + id: "ORD-10224", + placedAt: "2024-05-03T23:05:00Z", + customer: "Contoso Ltd", + product: "Polaris Reporting", + sku: "RPT-707", + region: "North America", + channel: "self-serve", + rep: "Diego Marin", + quantity: 8, + unitPrice: 308, + status: "shipped" + }, + { + id: "ORD-10372", + placedAt: "2024-05-03T23:35:00Z", + customer: "Lucerne Publishing", + product: "Delta Insights", + sku: "INS-440", + region: "EMEA", + channel: "marketplace", + rep: "Priya Nair", + quantity: 2, + unitPrice: 314, + status: "delivered" + }, + { + id: "ORD-10073", + placedAt: "2024-05-04T02:12:00Z", + customer: "City Power & Light", + product: "Ion Messaging", + sku: "MSG-990", + region: "North America", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 1, + unitPrice: 55, + status: "delivered" + }, + { + id: "ORD-10323", + placedAt: "2024-05-04T06:09:00Z", + customer: "Litware Inc", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "North America", + channel: "direct", + rep: "Priya Nair", + quantity: 1, + unitPrice: 635, + status: "shipped" + }, + { + id: "ORD-10012", + placedAt: "2024-05-04T06:18:00Z", + customer: "Proseware Inc", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "North America", + channel: "marketplace", + rep: "Dana Wills", + quantity: 4, + unitPrice: 526, + status: "shipped" + }, + { + id: "ORD-10124", + placedAt: "2024-05-04T06:28:00Z", + customer: "Litware Inc", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "APAC", + channel: "marketplace", + rep: "Owen Pratt", + quantity: 2, + unitPrice: 503, + status: "refunded" + }, + { + id: "ORD-10338", + placedAt: "2024-05-04T06:32:00Z", + customer: "Northwind Traders", + product: "Borealis CRM", + sku: "CRM-210", + region: "EMEA", + channel: "direct", + rep: "Hugo Bernard", + quantity: 5, + unitPrice: 895, + status: "cancelled" + }, + { + id: "ORD-10194", + placedAt: "2024-05-04T07:15:00Z", + customer: "Northwind Traders", + product: "Borealis CRM", + sku: "CRM-210", + region: "EMEA", + channel: "direct", + rep: "Dana Wills", + quantity: 6, + unitPrice: 881, + status: "delivered" + }, + { + id: "ORD-10006", + placedAt: "2024-05-04T11:55:00Z", + customer: "Litware Inc", + product: "Fjord Storage", + sku: "STO-660", + region: "EMEA", + channel: "partner", + rep: "Sven Olsen", + quantity: 6, + unitPrice: 85, + status: "delivered" + }, + { + id: "ORD-10183", + placedAt: "2024-05-04T14:27:00Z", + customer: "Humongous Insurance", + product: "Glacier Backup", + sku: "BAK-770", + region: "EMEA", + channel: "partner", + rep: "Diego Marin", + quantity: 1, + unitPrice: 62, + status: "paid" + }, + { + id: "ORD-10079", + placedAt: "2024-05-04T19:21:00Z", + customer: "Litware Inc", + product: "Onyx Security", + sku: "SEC-606", + region: "EMEA", + channel: "marketplace", + rep: "Hugo Bernard", + quantity: 4, + unitPrice: 840, + status: "delivered" + }, + { + id: "ORD-10304", + placedAt: "2024-05-04T20:22:00Z", + customer: "Graphic Design Institute", + product: "Polaris Reporting", + sku: "RPT-707", + region: "North America", + channel: "direct", + rep: "Hugo Bernard", + quantity: 7, + unitPrice: 287, + status: "shipped" + }, + { + id: "ORD-10020", + placedAt: "2024-05-04T20:56:00Z", + customer: "School of Fine Art", + product: "Delta Insights", + sku: "INS-440", + region: "APAC", + channel: "direct", + rep: "Priya Nair", + quantity: 3, + unitPrice: 324, + status: "delivered" + }, + { + id: "ORD-10133", + placedAt: "2024-05-05T02:28:00Z", + customer: "Coho Vineyard", + product: "Echo Monitoring", + sku: "MON-550", + region: "EMEA", + channel: "partner", + rep: "Dana Wills", + quantity: 1, + unitPrice: 152, + status: "refunded" + }, + { + id: "ORD-10028", + placedAt: "2024-05-05T02:50:00Z", + customer: "Northwind Traders", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "LATAM", + channel: "marketplace", + rep: "Dana Wills", + quantity: 7, + unitPrice: 513, + status: "delivered" + }, + { + id: "ORD-10097", + placedAt: "2024-05-05T08:20:00Z", + customer: "Lucerne Publishing", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "North America", + channel: "partner", + rep: "Dana Wills", + quantity: 7, + unitPrice: 1218, + status: "delivered" + }, + { + id: "ORD-10117", + placedAt: "2024-05-05T08:35:00Z", + customer: "Northwind Traders", + product: "Echo Monitoring", + sku: "MON-550", + region: "APAC", + channel: "marketplace", + rep: "Diego Marin", + quantity: 5, + unitPrice: 160, + status: "paid" + }, + { + id: "ORD-10109", + placedAt: "2024-05-05T09:18:00Z", + customer: "Fourth Coffee", + product: "Meridian ETL", + sku: "ETL-404", + region: "LATAM", + channel: "marketplace", + rep: "Sora Tanaka", + quantity: 4, + unitPrice: 734, + status: "delivered" + }, + { + id: "ORD-10285", + placedAt: "2024-05-05T11:54:00Z", + customer: "Alpine Ski House", + product: "Meridian ETL", + sku: "ETL-404", + region: "APAC", + channel: "self-serve", + rep: "Lena Fischer", + quantity: 6, + unitPrice: 711, + status: "shipped" + }, + { + id: "ORD-10107", + placedAt: "2024-05-05T12:04:00Z", + customer: "Margies Travel", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "EMEA", + channel: "partner", + rep: "Diego Marin", + quantity: 1, + unitPrice: 986, + status: "cancelled" + }, + { + id: "ORD-10366", + placedAt: "2024-05-05T17:27:00Z", + customer: "Blue Yonder Airlines", + product: "Nimbus Compute", + sku: "CMP-505", + region: "APAC", + channel: "partner", + rep: "Sven Olsen", + quantity: 6, + unitPrice: 1108, + status: "shipped" + }, + { + id: "ORD-10356", + placedAt: "2024-05-05T19:37:00Z", + customer: "Lucerne Publishing", + product: "Delta Insights", + sku: "INS-440", + region: "LATAM", + channel: "marketplace", + rep: "Dana Wills", + quantity: 2, + unitPrice: 328, + status: "refunded" + }, + { + id: "ORD-10052", + placedAt: "2024-05-05T20:26:00Z", + customer: "Adventure Works", + product: "Delta Insights", + sku: "INS-440", + region: "EMEA", + channel: "direct", + rep: "Mateo Russo", + quantity: 2, + unitPrice: 312, + status: "delivered" + }, + { + id: "ORD-10367", + placedAt: "2024-05-05T23:03:00Z", + customer: "Litware Inc", + product: "Onyx Security", + sku: "SEC-606", + region: "North America", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 5, + unitPrice: 879, + status: "pending" + }, + { + id: "ORD-10234", + placedAt: "2024-05-05T23:09:00Z", + customer: "Margies Travel", + product: "Juniper Workflow", + sku: "WFL-101", + region: "EMEA", + channel: "marketplace", + rep: "Owen Pratt", + quantity: 7, + unitPrice: 415, + status: "delivered" + }, + { + id: "ORD-10280", + placedAt: "2024-05-06T03:20:00Z", + customer: "Wide World Importers", + product: "Helix Identity", + sku: "IDN-880", + region: "EMEA", + channel: "partner", + rep: "Priya Nair", + quantity: 6, + unitPrice: 205, + status: "shipped" + }, + { + id: "ORD-10221", + placedAt: "2024-05-06T07:55:00Z", + customer: "Litware Inc", + product: "Meridian ETL", + sku: "ETL-404", + region: "APAC", + channel: "partner", + rep: "Owen Pratt", + quantity: 1, + unitPrice: 718, + status: "refunded" + }, + { + id: "ORD-10332", + placedAt: "2024-05-06T08:38:00Z", + customer: "Lucerne Publishing", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "APAC", + channel: "marketplace", + rep: "Dana Wills", + quantity: 1, + unitPrice: 504, + status: "delivered" + }, + { + id: "ORD-10121", + placedAt: "2024-05-06T11:55:00Z", + customer: "Wide World Importers", + product: "Ion Messaging", + sku: "MSG-990", + region: "North America", + channel: "marketplace", + rep: "Dana Wills", + quantity: 5, + unitPrice: 50, + status: "delivered" + }, + { + id: "ORD-10128", + placedAt: "2024-05-06T13:54:00Z", + customer: "City Power & Light", + product: "Polaris Reporting", + sku: "RPT-707", + region: "North America", + channel: "marketplace", + rep: "Mateo Russo", + quantity: 7, + unitPrice: 299, + status: "delivered" + }, + { + id: "ORD-10238", + placedAt: "2024-05-06T15:53:00Z", + customer: "Wide World Importers", + product: "Nimbus Compute", + sku: "CMP-505", + region: "EMEA", + channel: "partner", + rep: "Sora Tanaka", + quantity: 2, + unitPrice: 1090, + status: "shipped" + }, + { + id: "ORD-10193", + placedAt: "2024-05-06T15:55:00Z", + customer: "Trey Research", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "EMEA", + channel: "marketplace", + rep: "Mateo Russo", + quantity: 6, + unitPrice: 1198, + status: "delivered" + }, + { + id: "ORD-10111", + placedAt: "2024-05-06T16:37:00Z", + customer: "Fourth Coffee", + product: "Onyx Security", + sku: "SEC-606", + region: "North America", + channel: "self-serve", + rep: "Aisha Khan", + quantity: 7, + unitPrice: 868, + status: "delivered" + }, + { + id: "ORD-10220", + placedAt: "2024-05-06T16:57:00Z", + customer: "Margies Travel", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "LATAM", + channel: "self-serve", + rep: "Sora Tanaka", + quantity: 6, + unitPrice: 525, + status: "pending" + }, + { + id: "ORD-10076", + placedAt: "2024-05-06T17:25:00Z", + customer: "Tailspin Toys", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "EMEA", + channel: "partner", + rep: "Mateo Russo", + quantity: 4, + unitPrice: 500, + status: "refunded" + }, + { + id: "ORD-10369", + placedAt: "2024-05-06T17:43:00Z", + customer: "Coho Vineyard", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "LATAM", + channel: "marketplace", + rep: "Lena Fischer", + quantity: 2, + unitPrice: 1190, + status: "shipped" + }, + { + id: "ORD-10378", + placedAt: "2024-05-06T19:28:00Z", + customer: "Fourth Coffee", + product: "Juniper Workflow", + sku: "WFL-101", + region: "North America", + channel: "partner", + rep: "Diego Marin", + quantity: 7, + unitPrice: 397, + status: "delivered" + }, + { + id: "ORD-10283", + placedAt: "2024-05-06T20:02:00Z", + customer: "Margies Travel", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "APAC", + channel: "self-serve", + rep: "Dana Wills", + quantity: 4, + unitPrice: 983, + status: "shipped" + }, + { + id: "ORD-10277", + placedAt: "2024-05-06T20:35:00Z", + customer: "Blue Yonder Airlines", + product: "Echo Monitoring", + sku: "MON-550", + region: "EMEA", + channel: "partner", + rep: "Hugo Bernard", + quantity: 6, + unitPrice: 149, + status: "delivered" + }, + { + id: "ORD-10212", + placedAt: "2024-05-07T02:01:00Z", + customer: "Wide World Importers", + product: "Delta Insights", + sku: "INS-440", + region: "EMEA", + channel: "self-serve", + rep: "Sven Olsen", + quantity: 4, + unitPrice: 337, + status: "pending" + }, + { + id: "ORD-10340", + placedAt: "2024-05-07T03:23:00Z", + customer: "Blue Yonder Airlines", + product: "Delta Insights", + sku: "INS-440", + region: "EMEA", + channel: "marketplace", + rep: "Dana Wills", + quantity: 7, + unitPrice: 332, + status: "paid" + }, + { + id: "ORD-10044", + placedAt: "2024-05-07T04:10:00Z", + customer: "Proseware Inc", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "LATAM", + channel: "direct", + rep: "Hugo Bernard", + quantity: 5, + unitPrice: 526, + status: "delivered" + }, + { + id: "ORD-10293", + placedAt: "2024-05-07T05:52:00Z", + customer: "Fourth Coffee", + product: "Echo Monitoring", + sku: "MON-550", + region: "APAC", + channel: "partner", + rep: "Lena Fischer", + quantity: 8, + unitPrice: 166, + status: "shipped" + }, + { + id: "ORD-10058", + placedAt: "2024-05-07T06:38:00Z", + customer: "Litware Inc", + product: "Juniper Workflow", + sku: "WFL-101", + region: "APAC", + channel: "direct", + rep: "Aisha Khan", + quantity: 2, + unitPrice: 423, + status: "delivered" + }, + { + id: "ORD-10298", + placedAt: "2024-05-07T06:47:00Z", + customer: "Wide World Importers", + product: "Juniper Workflow", + sku: "WFL-101", + region: "APAC", + channel: "partner", + rep: "Owen Pratt", + quantity: 1, + unitPrice: 413, + status: "shipped" + }, + { + id: "ORD-10259", + placedAt: "2024-05-07T11:51:00Z", + customer: "Trey Research", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "North America", + channel: "direct", + rep: "Dana Wills", + quantity: 5, + unitPrice: 627, + status: "cancelled" + }, + { + id: "ORD-10295", + placedAt: "2024-05-07T14:13:00Z", + customer: "Humongous Insurance", + product: "Glacier Backup", + sku: "BAK-770", + region: "LATAM", + channel: "marketplace", + rep: "Dana Wills", + quantity: 8, + unitPrice: 60, + status: "delivered" + }, + { + id: "ORD-10219", + placedAt: "2024-05-07T14:15:00Z", + customer: "Northwind Traders", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "North America", + channel: "partner", + rep: "Diego Marin", + quantity: 4, + unitPrice: 983, + status: "refunded" + }, + { + id: "ORD-10043", + placedAt: "2024-05-07T15:24:00Z", + customer: "Humongous Insurance", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "APAC", + channel: "direct", + rep: "Lena Fischer", + quantity: 1, + unitPrice: 978, + status: "delivered" + }, + { + id: "ORD-10271", + placedAt: "2024-05-07T15:28:00Z", + customer: "Margies Travel", + product: "Onyx Security", + sku: "SEC-606", + region: "APAC", + channel: "self-serve", + rep: "Aisha Khan", + quantity: 7, + unitPrice: 849, + status: "delivered" + }, + { + id: "ORD-10050", + placedAt: "2024-05-07T19:40:00Z", + customer: "Wide World Importers", + product: "Borealis CRM", + sku: "CRM-210", + region: "EMEA", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 5, + unitPrice: 888, + status: "delivered" + }, + { + id: "ORD-10167", + placedAt: "2024-05-07T21:03:00Z", + customer: "Humongous Insurance", + product: "Glacier Backup", + sku: "BAK-770", + region: "APAC", + channel: "partner", + rep: "Aisha Khan", + quantity: 8, + unitPrice: 49, + status: "pending" + }, + { + id: "ORD-10328", + placedAt: "2024-05-08T00:23:00Z", + customer: "Wingtip Toys", + product: "Helix Identity", + sku: "IDN-880", + region: "EMEA", + channel: "direct", + rep: "Mateo Russo", + quantity: 7, + unitPrice: 204, + status: "shipped" + }, + { + id: "ORD-10089", + placedAt: "2024-05-08T01:57:00Z", + customer: "Adventure Works", + product: "Ion Messaging", + sku: "MSG-990", + region: "EMEA", + channel: "direct", + rep: "Lena Fischer", + quantity: 4, + unitPrice: 71, + status: "delivered" + }, + { + id: "ORD-10166", + placedAt: "2024-05-08T02:21:00Z", + customer: "Margies Travel", + product: "Fjord Storage", + sku: "STO-660", + region: "LATAM", + channel: "self-serve", + rep: "Owen Pratt", + quantity: 5, + unitPrice: 86, + status: "cancelled" + }, + { + id: "ORD-10067", + placedAt: "2024-05-08T03:31:00Z", + customer: "Fourth Coffee", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "LATAM", + channel: "self-serve", + rep: "Lena Fischer", + quantity: 2, + unitPrice: 645, + status: "delivered" + }, + { + id: "ORD-10004", + placedAt: "2024-05-08T08:06:00Z", + customer: "Fourth Coffee", + product: "Delta Insights", + sku: "INS-440", + region: "North America", + channel: "partner", + rep: "Diego Marin", + quantity: 8, + unitPrice: 326, + status: "paid" + }, + { + id: "ORD-10174", + placedAt: "2024-05-08T16:11:00Z", + customer: "Adventure Works", + product: "Nimbus Compute", + sku: "CMP-505", + region: "EMEA", + channel: "partner", + rep: "Hugo Bernard", + quantity: 3, + unitPrice: 1113, + status: "refunded" + }, + { + id: "ORD-10204", + placedAt: "2024-05-08T18:09:00Z", + customer: "Contoso Ltd", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "North America", + channel: "self-serve", + rep: "Sven Olsen", + quantity: 1, + unitPrice: 510, + status: "delivered" + }, + { + id: "ORD-10173", + placedAt: "2024-05-08T20:58:00Z", + customer: "School of Fine Art", + product: "Meridian ETL", + sku: "ETL-404", + region: "North America", + channel: "marketplace", + rep: "Owen Pratt", + quantity: 6, + unitPrice: 715, + status: "paid" + }, + { + id: "ORD-10297", + placedAt: "2024-05-08T22:20:00Z", + customer: "Tailspin Toys", + product: "Ion Messaging", + sku: "MSG-990", + region: "APAC", + channel: "marketplace", + rep: "Sora Tanaka", + quantity: 5, + unitPrice: 56, + status: "delivered" + }, + { + id: "ORD-10144", + placedAt: "2024-05-08T22:40:00Z", + customer: "Lucerne Publishing", + product: "Polaris Reporting", + sku: "RPT-707", + region: "EMEA", + channel: "marketplace", + rep: "Aisha Khan", + quantity: 6, + unitPrice: 287, + status: "paid" + }, + { + id: "ORD-10123", + placedAt: "2024-05-09T00:54:00Z", + customer: "Tailspin Toys", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "North America", + channel: "self-serve", + rep: "Owen Pratt", + quantity: 8, + unitPrice: 966, + status: "cancelled" + }, + { + id: "ORD-10038", + placedAt: "2024-05-09T02:54:00Z", + customer: "Fourth Coffee", + product: "Fjord Storage", + sku: "STO-660", + region: "LATAM", + channel: "marketplace", + rep: "Owen Pratt", + quantity: 7, + unitPrice: 92, + status: "paid" + }, + { + id: "ORD-10330", + placedAt: "2024-05-09T04:15:00Z", + customer: "Margies Travel", + product: "Juniper Workflow", + sku: "WFL-101", + region: "LATAM", + channel: "partner", + rep: "Owen Pratt", + quantity: 1, + unitPrice: 417, + status: "paid" + }, + { + id: "ORD-10098", + placedAt: "2024-05-09T05:21:00Z", + customer: "Humongous Insurance", + product: "Borealis CRM", + sku: "CRM-210", + region: "LATAM", + channel: "partner", + rep: "Aisha Khan", + quantity: 6, + unitPrice: 907, + status: "delivered" + }, + { + id: "ORD-10377", + placedAt: "2024-05-09T06:21:00Z", + customer: "City Power & Light", + product: "Ion Messaging", + sku: "MSG-990", + region: "APAC", + channel: "partner", + rep: "Sora Tanaka", + quantity: 7, + unitPrice: 77, + status: "paid" + }, + { + id: "ORD-10239", + placedAt: "2024-05-09T07:32:00Z", + customer: "Tailspin Toys", + product: "Onyx Security", + sku: "SEC-606", + region: "APAC", + channel: "marketplace", + rep: "Diego Marin", + quantity: 1, + unitPrice: 848, + status: "paid" + }, + { + id: "ORD-10337", + placedAt: "2024-05-09T14:16:00Z", + customer: "City Power & Light", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "LATAM", + channel: "direct", + rep: "Mateo Russo", + quantity: 6, + unitPrice: 1191, + status: "shipped" + }, + { + id: "ORD-10251", + placedAt: "2024-05-09T15:22:00Z", + customer: "Lucerne Publishing", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "LATAM", + channel: "marketplace", + rep: "Priya Nair", + quantity: 5, + unitPrice: 976, + status: "shipped" + }, + { + id: "ORD-10305", + placedAt: "2024-05-09T19:38:00Z", + customer: "Margies Travel", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "APAC", + channel: "direct", + rep: "Hugo Bernard", + quantity: 5, + unitPrice: 1191, + status: "shipped" + }, + { + id: "ORD-10240", + placedAt: "2024-05-09T20:34:00Z", + customer: "Contoso Ltd", + product: "Polaris Reporting", + sku: "RPT-707", + region: "North America", + channel: "marketplace", + rep: "Sora Tanaka", + quantity: 4, + unitPrice: 301, + status: "shipped" + }, + { + id: "ORD-10090", + placedAt: "2024-05-09T23:38:00Z", + customer: "Contoso Ltd", + product: "Juniper Workflow", + sku: "WFL-101", + region: "LATAM", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 1, + unitPrice: 424, + status: "pending" + }, + { + id: "ORD-10060", + placedAt: "2024-05-10T01:13:00Z", + customer: "Alpine Ski House", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "APAC", + channel: "direct", + rep: "Dana Wills", + quantity: 8, + unitPrice: 532, + status: "delivered" + }, + { + id: "ORD-10203", + placedAt: "2024-05-10T02:13:00Z", + customer: "Northwind Traders", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "APAC", + channel: "direct", + rep: "Dana Wills", + quantity: 6, + unitPrice: 985, + status: "shipped" + }, + { + id: "ORD-10056", + placedAt: "2024-05-10T02:17:00Z", + customer: "Wingtip Toys", + product: "Helix Identity", + sku: "IDN-880", + region: "APAC", + channel: "self-serve", + rep: "Owen Pratt", + quantity: 3, + unitPrice: 222, + status: "shipped" + }, + { + id: "ORD-10362", + placedAt: "2024-05-10T02:34:00Z", + customer: "Humongous Insurance", + product: "Juniper Workflow", + sku: "WFL-101", + region: "North America", + channel: "direct", + rep: "Hugo Bernard", + quantity: 5, + unitPrice: 413, + status: "shipped" + }, + { + id: "ORD-10241", + placedAt: "2024-05-10T04:29:00Z", + customer: "Margies Travel", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "North America", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 8, + unitPrice: 1189, + status: "pending" + }, + { + id: "ORD-10344", + placedAt: "2024-05-10T09:07:00Z", + customer: "Alpine Ski House", + product: "Helix Identity", + sku: "IDN-880", + region: "LATAM", + channel: "marketplace", + rep: "Aisha Khan", + quantity: 1, + unitPrice: 208, + status: "delivered" + }, + { + id: "ORD-10066", + placedAt: "2024-05-10T09:13:00Z", + customer: "School of Fine Art", + product: "Borealis CRM", + sku: "CRM-210", + region: "EMEA", + channel: "self-serve", + rep: "Owen Pratt", + quantity: 3, + unitPrice: 909, + status: "shipped" + }, + { + id: "ORD-10084", + placedAt: "2024-05-10T10:14:00Z", + customer: "Coho Vineyard", + product: "Delta Insights", + sku: "INS-440", + region: "LATAM", + channel: "direct", + rep: "Mateo Russo", + quantity: 8, + unitPrice: 325, + status: "delivered" + }, + { + id: "ORD-10349", + placedAt: "2024-05-10T10:19:00Z", + customer: "Fourth Coffee", + product: "Meridian ETL", + sku: "ETL-404", + region: "LATAM", + channel: "partner", + rep: "Hugo Bernard", + quantity: 4, + unitPrice: 721, + status: "paid" + }, + { + id: "ORD-10262", + placedAt: "2024-05-10T13:05:00Z", + customer: "Fabrikam Inc", + product: "Fjord Storage", + sku: "STO-660", + region: "North America", + channel: "partner", + rep: "Sora Tanaka", + quantity: 6, + unitPrice: 90, + status: "shipped" + }, + { + id: "ORD-10376", + placedAt: "2024-05-10T14:01:00Z", + customer: "Northwind Traders", + product: "Helix Identity", + sku: "IDN-880", + region: "EMEA", + channel: "self-serve", + rep: "Owen Pratt", + quantity: 5, + unitPrice: 226, + status: "delivered" + }, + { + id: "ORD-10278", + placedAt: "2024-05-10T15:14:00Z", + customer: "Proseware Inc", + product: "Fjord Storage", + sku: "STO-660", + region: "APAC", + channel: "partner", + rep: "Lena Fischer", + quantity: 5, + unitPrice: 76, + status: "delivered" + }, + { + id: "ORD-10211", + placedAt: "2024-05-10T15:38:00Z", + customer: "Adventure Works", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "EMEA", + channel: "partner", + rep: "Sven Olsen", + quantity: 2, + unitPrice: 622, + status: "paid" + }, + { + id: "ORD-10151", + placedAt: "2024-05-10T16:46:00Z", + customer: "Alpine Ski House", + product: "Glacier Backup", + sku: "BAK-770", + region: "LATAM", + channel: "direct", + rep: "Priya Nair", + quantity: 5, + unitPrice: 56, + status: "delivered" + }, + { + id: "ORD-10063", + placedAt: "2024-05-10T19:38:00Z", + customer: "Adventure Works", + product: "Onyx Security", + sku: "SEC-606", + region: "APAC", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 1, + unitPrice: 872, + status: "shipped" + }, + { + id: "ORD-10347", + placedAt: "2024-05-10T21:40:00Z", + customer: "Adventure Works", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "EMEA", + channel: "marketplace", + rep: "Sora Tanaka", + quantity: 8, + unitPrice: 983, + status: "refunded" + }, + { + id: "ORD-10034", + placedAt: "2024-05-10T22:55:00Z", + customer: "City Power & Light", + product: "Borealis CRM", + sku: "CRM-210", + region: "North America", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 1, + unitPrice: 880, + status: "delivered" + }, + { + id: "ORD-10131", + placedAt: "2024-05-11T02:18:00Z", + customer: "Blue Yonder Airlines", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "EMEA", + channel: "direct", + rep: "Diego Marin", + quantity: 2, + unitPrice: 642, + status: "shipped" + }, + { + id: "ORD-10218", + placedAt: "2024-05-11T02:18:00Z", + customer: "Fourth Coffee", + product: "Juniper Workflow", + sku: "WFL-101", + region: "APAC", + channel: "direct", + rep: "Priya Nair", + quantity: 1, + unitPrice: 393, + status: "delivered" + }, + { + id: "ORD-10361", + placedAt: "2024-05-11T02:44:00Z", + customer: "Northwind Traders", + product: "Ion Messaging", + sku: "MSG-990", + region: "APAC", + channel: "self-serve", + rep: "Sora Tanaka", + quantity: 1, + unitPrice: 43, + status: "pending" + }, + { + id: "ORD-10199", + placedAt: "2024-05-11T04:13:00Z", + customer: "Alpine Ski House", + product: "Glacier Backup", + sku: "BAK-770", + region: "EMEA", + channel: "partner", + rep: "Hugo Bernard", + quantity: 1, + unitPrice: 39, + status: "shipped" + }, + { + id: "ORD-10127", + placedAt: "2024-05-11T08:16:00Z", + customer: "Adventure Works", + product: "Onyx Security", + sku: "SEC-606", + region: "LATAM", + channel: "marketplace", + rep: "Mateo Russo", + quantity: 4, + unitPrice: 877, + status: "refunded" + }, + { + id: "ORD-10355", + placedAt: "2024-05-11T10:52:00Z", + customer: "Humongous Insurance", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "North America", + channel: "direct", + rep: "Aisha Khan", + quantity: 1, + unitPrice: 631, + status: "delivered" + }, + { + id: "ORD-10320", + placedAt: "2024-05-11T13:30:00Z", + customer: "Northwind Traders", + product: "Polaris Reporting", + sku: "RPT-707", + region: "North America", + channel: "partner", + rep: "Diego Marin", + quantity: 3, + unitPrice: 286, + status: "delivered" + }, + { + id: "ORD-10273", + placedAt: "2024-05-11T16:17:00Z", + customer: "Fabrikam Inc", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "North America", + channel: "self-serve", + rep: "Priya Nair", + quantity: 5, + unitPrice: 1209, + status: "shipped" + }, + { + id: "ORD-10263", + placedAt: "2024-05-11T17:44:00Z", + customer: "Fabrikam Inc", + product: "Glacier Backup", + sku: "BAK-770", + region: "APAC", + channel: "marketplace", + rep: "Priya Nair", + quantity: 6, + unitPrice: 30, + status: "refunded" + }, + { + id: "ORD-10172", + placedAt: "2024-05-11T18:43:00Z", + customer: "Adventure Works", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "LATAM", + channel: "marketplace", + rep: "Diego Marin", + quantity: 7, + unitPrice: 518, + status: "shipped" + }, + { + id: "ORD-10255", + placedAt: "2024-05-11T21:49:00Z", + customer: "City Power & Light", + product: "Onyx Security", + sku: "SEC-606", + region: "APAC", + channel: "direct", + rep: "Dana Wills", + quantity: 8, + unitPrice: 858, + status: "shipped" + }, + { + id: "ORD-10275", + placedAt: "2024-05-11T22:08:00Z", + customer: "Adventure Works", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "EMEA", + channel: "direct", + rep: "Hugo Bernard", + quantity: 8, + unitPrice: 631, + status: "delivered" + }, + { + id: "ORD-10048", + placedAt: "2024-05-12T08:34:00Z", + customer: "Trey Research", + product: "Polaris Reporting", + sku: "RPT-707", + region: "LATAM", + channel: "partner", + rep: "Sora Tanaka", + quantity: 8, + unitPrice: 276, + status: "paid" + }, + { + id: "ORD-10141", + placedAt: "2024-05-12T12:59:00Z", + customer: "Graphic Design Institute", + product: "Meridian ETL", + sku: "ETL-404", + region: "LATAM", + channel: "direct", + rep: "Owen Pratt", + quantity: 3, + unitPrice: 710, + status: "shipped" + }, + { + id: "ORD-10319", + placedAt: "2024-05-12T14:48:00Z", + customer: "Contoso Ltd", + product: "Onyx Security", + sku: "SEC-606", + region: "North America", + channel: "partner", + rep: "Lena Fischer", + quantity: 4, + unitPrice: 869, + status: "paid" + }, + { + id: "ORD-10197", + placedAt: "2024-05-12T15:48:00Z", + customer: "Proseware Inc", + product: "Echo Monitoring", + sku: "MON-550", + region: "LATAM", + channel: "marketplace", + rep: "Lena Fischer", + quantity: 8, + unitPrice: 157, + status: "delivered" + }, + { + id: "ORD-10281", + placedAt: "2024-05-12T19:15:00Z", + customer: "City Power & Light", + product: "Ion Messaging", + sku: "MSG-990", + region: "North America", + channel: "direct", + rep: "Mateo Russo", + quantity: 3, + unitPrice: 64, + status: "paid" + }, + { + id: "ORD-10205", + placedAt: "2024-05-12T20:32:00Z", + customer: "Fourth Coffee", + product: "Meridian ETL", + sku: "ETL-404", + region: "EMEA", + channel: "direct", + rep: "Mateo Russo", + quantity: 4, + unitPrice: 732, + status: "delivered" + }, + { + id: "ORD-10085", + placedAt: "2024-05-13T00:31:00Z", + customer: "Blue Yonder Airlines", + product: "Echo Monitoring", + sku: "MON-550", + region: "EMEA", + channel: "direct", + rep: "Sora Tanaka", + quantity: 7, + unitPrice: 162, + status: "paid" + }, + { + id: "ORD-10373", + placedAt: "2024-05-13T00:58:00Z", + customer: "City Power & Light", + product: "Echo Monitoring", + sku: "MON-550", + region: "North America", + channel: "direct", + rep: "Owen Pratt", + quantity: 5, + unitPrice: 142, + status: "shipped" + }, + { + id: "ORD-10138", + placedAt: "2024-05-13T02:08:00Z", + customer: "City Power & Light", + product: "Juniper Workflow", + sku: "WFL-101", + region: "North America", + channel: "partner", + rep: "Sven Olsen", + quantity: 8, + unitPrice: 410, + status: "delivered" + }, + { + id: "ORD-10370", + placedAt: "2024-05-13T02:27:00Z", + customer: "City Power & Light", + product: "Borealis CRM", + sku: "CRM-210", + region: "LATAM", + channel: "direct", + rep: "Sora Tanaka", + quantity: 3, + unitPrice: 870, + status: "pending" + }, + { + id: "ORD-10005", + placedAt: "2024-05-13T05:43:00Z", + customer: "Margies Travel", + product: "Echo Monitoring", + sku: "MON-550", + region: "EMEA", + channel: "direct", + rep: "Sven Olsen", + quantity: 5, + unitPrice: 154, + status: "shipped" + }, + { + id: "ORD-10049", + placedAt: "2024-05-13T08:32:00Z", + customer: "Trey Research", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "APAC", + channel: "partner", + rep: "Sven Olsen", + quantity: 1, + unitPrice: 1211, + status: "paid" + }, + { + id: "ORD-10253", + placedAt: "2024-05-13T09:49:00Z", + customer: "Margies Travel", + product: "Meridian ETL", + sku: "ETL-404", + region: "LATAM", + channel: "self-serve", + rep: "Aisha Khan", + quantity: 1, + unitPrice: 727, + status: "delivered" + }, + { + id: "ORD-10178", + placedAt: "2024-05-13T15:00:00Z", + customer: "Litware Inc", + product: "Borealis CRM", + sku: "CRM-210", + region: "North America", + channel: "marketplace", + rep: "Diego Marin", + quantity: 6, + unitPrice: 886, + status: "shipped" + }, + { + id: "ORD-10031", + placedAt: "2024-05-13T16:08:00Z", + customer: "Tailspin Toys", + product: "Onyx Security", + sku: "SEC-606", + region: "LATAM", + channel: "marketplace", + rep: "Lena Fischer", + quantity: 4, + unitPrice: 845, + status: "delivered" + }, + { + id: "ORD-10257", + placedAt: "2024-05-13T17:06:00Z", + customer: "Tailspin Toys", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "APAC", + channel: "partner", + rep: "Dana Wills", + quantity: 3, + unitPrice: 1180, + status: "paid" + }, + { + id: "ORD-10007", + placedAt: "2024-05-13T18:03:00Z", + customer: "Adventure Works", + product: "Glacier Backup", + sku: "BAK-770", + region: "North America", + channel: "partner", + rep: "Priya Nair", + quantity: 4, + unitPrice: 63, + status: "delivered" + }, + { + id: "ORD-10345", + placedAt: "2024-05-14T01:10:00Z", + customer: "Tailspin Toys", + product: "Ion Messaging", + sku: "MSG-990", + region: "LATAM", + channel: "direct", + rep: "Owen Pratt", + quantity: 3, + unitPrice: 68, + status: "paid" + }, + { + id: "ORD-10081", + placedAt: "2024-05-14T03:48:00Z", + customer: "Graphic Design Institute", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "APAC", + channel: "partner", + rep: "Priya Nair", + quantity: 8, + unitPrice: 1199, + status: "delivered" + }, + { + id: "ORD-10333", + placedAt: "2024-05-14T05:55:00Z", + customer: "Alpine Ski House", + product: "Meridian ETL", + sku: "ETL-404", + region: "LATAM", + channel: "marketplace", + rep: "Sora Tanaka", + quantity: 8, + unitPrice: 744, + status: "paid" + }, + { + id: "ORD-10051", + placedAt: "2024-05-14T06:54:00Z", + customer: "Northwind Traders", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "North America", + channel: "partner", + rep: "Diego Marin", + quantity: 2, + unitPrice: 656, + status: "delivered" + }, + { + id: "ORD-10182", + placedAt: "2024-05-14T09:17:00Z", + customer: "Contoso Ltd", + product: "Fjord Storage", + sku: "STO-660", + region: "North America", + channel: "partner", + rep: "Mateo Russo", + quantity: 3, + unitPrice: 59, + status: "refunded" + }, + { + id: "ORD-10156", + placedAt: "2024-05-14T10:58:00Z", + customer: "Trey Research", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "EMEA", + channel: "direct", + rep: "Diego Marin", + quantity: 7, + unitPrice: 500, + status: "paid" + }, + { + id: "ORD-10032", + placedAt: "2024-05-14T14:27:00Z", + customer: "Graphic Design Institute", + product: "Polaris Reporting", + sku: "RPT-707", + region: "EMEA", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 4, + unitPrice: 285, + status: "cancelled" + }, + { + id: "ORD-10186", + placedAt: "2024-05-14T14:28:00Z", + customer: "Fourth Coffee", + product: "Juniper Workflow", + sku: "WFL-101", + region: "North America", + channel: "marketplace", + rep: "Diego Marin", + quantity: 4, + unitPrice: 399, + status: "pending" + }, + { + id: "ORD-10169", + placedAt: "2024-05-14T15:55:00Z", + customer: "Adventure Works", + product: "Ion Messaging", + sku: "MSG-990", + region: "North America", + channel: "marketplace", + rep: "Diego Marin", + quantity: 7, + unitPrice: 78, + status: "shipped" + }, + { + id: "ORD-10316", + placedAt: "2024-05-14T16:28:00Z", + customer: "Humongous Insurance", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "APAC", + channel: "marketplace", + rep: "Sora Tanaka", + quantity: 7, + unitPrice: 523, + status: "shipped" + }, + { + id: "ORD-10314", + placedAt: "2024-05-14T18:38:00Z", + customer: "Proseware Inc", + product: "Juniper Workflow", + sku: "WFL-101", + region: "LATAM", + channel: "partner", + rep: "Mateo Russo", + quantity: 1, + unitPrice: 420, + status: "cancelled" + }, + { + id: "ORD-10136", + placedAt: "2024-05-14T19:22:00Z", + customer: "Alpine Ski House", + product: "Helix Identity", + sku: "IDN-880", + region: "EMEA", + channel: "self-serve", + rep: "Owen Pratt", + quantity: 7, + unitPrice: 217, + status: "paid" + }, + { + id: "ORD-10272", + placedAt: "2024-05-14T20:20:00Z", + customer: "Adventure Works", + product: "Polaris Reporting", + sku: "RPT-707", + region: "LATAM", + channel: "marketplace", + rep: "Owen Pratt", + quantity: 1, + unitPrice: 276, + status: "pending" + }, + { + id: "ORD-10363", + placedAt: "2024-05-14T22:03:00Z", + customer: "Fabrikam Inc", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "EMEA", + channel: "direct", + rep: "Lena Fischer", + quantity: 6, + unitPrice: 974, + status: "shipped" + }, + { + id: "ORD-10299", + placedAt: "2024-05-14T22:21:00Z", + customer: "Humongous Insurance", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "EMEA", + channel: "marketplace", + rep: "Lena Fischer", + quantity: 3, + unitPrice: 995, + status: "delivered" + }, + { + id: "ORD-10202", + placedAt: "2024-05-14T23:23:00Z", + customer: "Proseware Inc", + product: "Juniper Workflow", + sku: "WFL-101", + region: "LATAM", + channel: "self-serve", + rep: "Sven Olsen", + quantity: 7, + unitPrice: 403, + status: "cancelled" + }, + { + id: "ORD-10013", + placedAt: "2024-05-14T23:34:00Z", + customer: "Fabrikam Inc", + product: "Meridian ETL", + sku: "ETL-404", + region: "LATAM", + channel: "direct", + rep: "Diego Marin", + quantity: 1, + unitPrice: 738, + status: "pending" + }, + { + id: "ORD-10122", + placedAt: "2024-05-15T03:55:00Z", + customer: "Graphic Design Institute", + product: "Juniper Workflow", + sku: "WFL-101", + region: "North America", + channel: "partner", + rep: "Dana Wills", + quantity: 3, + unitPrice: 404, + status: "cancelled" + }, + { + id: "ORD-10210", + placedAt: "2024-05-15T04:01:00Z", + customer: "Fourth Coffee", + product: "Borealis CRM", + sku: "CRM-210", + region: "North America", + channel: "self-serve", + rep: "Aisha Khan", + quantity: 2, + unitPrice: 905, + status: "shipped" + }, + { + id: "ORD-10029", + placedAt: "2024-05-15T04:05:00Z", + customer: "Alpine Ski House", + product: "Meridian ETL", + sku: "ETL-404", + region: "LATAM", + channel: "marketplace", + rep: "Aisha Khan", + quantity: 1, + unitPrice: 712, + status: "delivered" + }, + { + id: "ORD-10322", + placedAt: "2024-05-15T07:53:00Z", + customer: "Blue Yonder Airlines", + product: "Borealis CRM", + sku: "CRM-210", + region: "EMEA", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 7, + unitPrice: 896, + status: "delivered" + }, + { + id: "ORD-10209", + placedAt: "2024-05-15T08:38:00Z", + customer: "Adventure Works", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "North America", + channel: "partner", + rep: "Priya Nair", + quantity: 4, + unitPrice: 1208, + status: "cancelled" + }, + { + id: "ORD-10045", + placedAt: "2024-05-15T10:12:00Z", + customer: "Graphic Design Institute", + product: "Meridian ETL", + sku: "ETL-404", + region: "EMEA", + channel: "partner", + rep: "Dana Wills", + quantity: 5, + unitPrice: 734, + status: "refunded" + }, + { + id: "ORD-10306", + placedAt: "2024-05-15T18:47:00Z", + customer: "Margies Travel", + product: "Borealis CRM", + sku: "CRM-210", + region: "APAC", + channel: "marketplace", + rep: "Hugo Bernard", + quantity: 6, + unitPrice: 902, + status: "shipped" + }, + { + id: "ORD-10021", + placedAt: "2024-05-15T23:25:00Z", + customer: "Contoso Ltd", + product: "Echo Monitoring", + sku: "MON-550", + region: "LATAM", + channel: "self-serve", + rep: "Diego Marin", + quantity: 2, + unitPrice: 142, + status: "delivered" + }, + { + id: "ORD-10011", + placedAt: "2024-05-16T00:55:00Z", + customer: "Graphic Design Institute", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "LATAM", + channel: "direct", + rep: "Priya Nair", + quantity: 8, + unitPrice: 972, + status: "pending" + }, + { + id: "ORD-10334", + placedAt: "2024-05-16T00:55:00Z", + customer: "City Power & Light", + product: "Nimbus Compute", + sku: "CMP-505", + region: "North America", + channel: "direct", + rep: "Mateo Russo", + quantity: 6, + unitPrice: 1113, + status: "delivered" + }, + { + id: "ORD-10222", + placedAt: "2024-05-16T02:01:00Z", + customer: "School of Fine Art", + product: "Nimbus Compute", + sku: "CMP-505", + region: "APAC", + channel: "marketplace", + rep: "Dana Wills", + quantity: 2, + unitPrice: 1109, + status: "delivered" + }, + { + id: "ORD-10116", + placedAt: "2024-05-16T02:04:00Z", + customer: "Wide World Importers", + product: "Delta Insights", + sku: "INS-440", + region: "APAC", + channel: "self-serve", + rep: "Sora Tanaka", + quantity: 2, + unitPrice: 300, + status: "paid" + }, + { + id: "ORD-10026", + placedAt: "2024-05-16T05:17:00Z", + customer: "Proseware Inc", + product: "Juniper Workflow", + sku: "WFL-101", + region: "LATAM", + channel: "direct", + rep: "Mateo Russo", + quantity: 2, + unitPrice: 393, + status: "shipped" + }, + { + id: "ORD-10009", + placedAt: "2024-05-16T06:11:00Z", + customer: "Coho Vineyard", + product: "Ion Messaging", + sku: "MSG-990", + region: "EMEA", + channel: "self-serve", + rep: "Owen Pratt", + quantity: 3, + unitPrice: 46, + status: "delivered" + }, + { + id: "ORD-10155", + placedAt: "2024-05-16T07:24:00Z", + customer: "Fabrikam Inc", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "APAC", + channel: "direct", + rep: "Hugo Bernard", + quantity: 5, + unitPrice: 983, + status: "delivered" + }, + { + id: "ORD-10102", + placedAt: "2024-05-16T10:39:00Z", + customer: "Lucerne Publishing", + product: "Fjord Storage", + sku: "STO-660", + region: "APAC", + channel: "direct", + rep: "Owen Pratt", + quantity: 3, + unitPrice: 90, + status: "delivered" + }, + { + id: "ORD-10302", + placedAt: "2024-05-16T12:23:00Z", + customer: "Humongous Insurance", + product: "Nimbus Compute", + sku: "CMP-505", + region: "EMEA", + channel: "self-serve", + rep: "Owen Pratt", + quantity: 7, + unitPrice: 1110, + status: "refunded" + }, + { + id: "ORD-10237", + placedAt: "2024-05-16T15:29:00Z", + customer: "Northwind Traders", + product: "Meridian ETL", + sku: "ETL-404", + region: "APAC", + channel: "direct", + rep: "Owen Pratt", + quantity: 6, + unitPrice: 739, + status: "paid" + }, + { + id: "ORD-10046", + placedAt: "2024-05-16T15:57:00Z", + customer: "Tailspin Toys", + product: "Nimbus Compute", + sku: "CMP-505", + region: "LATAM", + channel: "partner", + rep: "Owen Pratt", + quantity: 6, + unitPrice: 1113, + status: "paid" + }, + { + id: "ORD-10163", + placedAt: "2024-05-16T16:50:00Z", + customer: "Fourth Coffee", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "LATAM", + channel: "direct", + rep: "Lena Fischer", + quantity: 3, + unitPrice: 635, + status: "delivered" + }, + { + id: "ORD-10196", + placedAt: "2024-05-16T17:46:00Z", + customer: "Fabrikam Inc", + product: "Delta Insights", + sku: "INS-440", + region: "North America", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 2, + unitPrice: 332, + status: "paid" + }, + { + id: "ORD-10024", + placedAt: "2024-05-16T18:20:00Z", + customer: "Litware Inc", + product: "Helix Identity", + sku: "IDN-880", + region: "APAC", + channel: "partner", + rep: "Sven Olsen", + quantity: 6, + unitPrice: 229, + status: "cancelled" + }, + { + id: "ORD-10339", + placedAt: "2024-05-16T20:20:00Z", + customer: "Adventure Works", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "North America", + channel: "marketplace", + rep: "Aisha Khan", + quantity: 2, + unitPrice: 659, + status: "pending" + }, + { + id: "ORD-10082", + placedAt: "2024-05-16T23:02:00Z", + customer: "Humongous Insurance", + product: "Borealis CRM", + sku: "CRM-210", + region: "LATAM", + channel: "partner", + rep: "Owen Pratt", + quantity: 7, + unitPrice: 890, + status: "delivered" + }, + { + id: "ORD-10274", + placedAt: "2024-05-16T23:56:00Z", + customer: "Contoso Ltd", + product: "Borealis CRM", + sku: "CRM-210", + region: "EMEA", + channel: "marketplace", + rep: "Sven Olsen", + quantity: 8, + unitPrice: 874, + status: "pending" + }, + { + id: "ORD-10002", + placedAt: "2024-05-17T04:16:00Z", + customer: "Wide World Importers", + product: "Borealis CRM", + sku: "CRM-210", + region: "North America", + channel: "direct", + rep: "Priya Nair", + quantity: 2, + unitPrice: 908, + status: "shipped" + }, + { + id: "ORD-10170", + placedAt: "2024-05-17T05:32:00Z", + customer: "Trey Research", + product: "Juniper Workflow", + sku: "WFL-101", + region: "North America", + channel: "partner", + rep: "Dana Wills", + quantity: 1, + unitPrice: 402, + status: "delivered" + }, + { + id: "ORD-10086", + placedAt: "2024-05-17T05:54:00Z", + customer: "City Power & Light", + product: "Fjord Storage", + sku: "STO-660", + region: "North America", + channel: "marketplace", + rep: "Hugo Bernard", + quantity: 4, + unitPrice: 68, + status: "cancelled" + }, + { + id: "ORD-10288", + placedAt: "2024-05-17T11:21:00Z", + customer: "Alpine Ski House", + product: "Polaris Reporting", + sku: "RPT-707", + region: "LATAM", + channel: "partner", + rep: "Lena Fischer", + quantity: 6, + unitPrice: 298, + status: "shipped" + }, + { + id: "ORD-10106", + placedAt: "2024-05-17T13:49:00Z", + customer: "Blue Yonder Airlines", + product: "Juniper Workflow", + sku: "WFL-101", + region: "LATAM", + channel: "partner", + rep: "Sora Tanaka", + quantity: 1, + unitPrice: 410, + status: "shipped" + }, + { + id: "ORD-10201", + placedAt: "2024-05-17T14:23:00Z", + customer: "Coho Vineyard", + product: "Ion Messaging", + sku: "MSG-990", + region: "LATAM", + channel: "marketplace", + rep: "Dana Wills", + quantity: 4, + unitPrice: 71, + status: "shipped" + }, + { + id: "ORD-10160", + placedAt: "2024-05-17T18:43:00Z", + customer: "Wide World Importers", + product: "Polaris Reporting", + sku: "RPT-707", + region: "North America", + channel: "direct", + rep: "Hugo Bernard", + quantity: 8, + unitPrice: 309, + status: "delivered" + }, + { + id: "ORD-10187", + placedAt: "2024-05-17T21:51:00Z", + customer: "Humongous Insurance", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "North America", + channel: "self-serve", + rep: "Dana Wills", + quantity: 3, + unitPrice: 987, + status: "delivered" + }, + { + id: "ORD-10359", + placedAt: "2024-05-17T22:44:00Z", + customer: "City Power & Light", + product: "Glacier Backup", + sku: "BAK-770", + region: "EMEA", + channel: "partner", + rep: "Hugo Bernard", + quantity: 8, + unitPrice: 25, + status: "shipped" + }, + { + id: "ORD-10078", + placedAt: "2024-05-17T23:04:00Z", + customer: "Wide World Importers", + product: "Nimbus Compute", + sku: "CMP-505", + region: "North America", + channel: "self-serve", + rep: "Diego Marin", + quantity: 1, + unitPrice: 1093, + status: "paid" + }, + { + id: "ORD-10071", + placedAt: "2024-05-18T00:32:00Z", + customer: "Alpine Ski House", + product: "Glacier Backup", + sku: "BAK-770", + region: "LATAM", + channel: "direct", + rep: "Mateo Russo", + quantity: 4, + unitPrice: 57, + status: "shipped" + }, + { + id: "ORD-10348", + placedAt: "2024-05-18T04:23:00Z", + customer: "School of Fine Art", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "LATAM", + channel: "self-serve", + rep: "Lena Fischer", + quantity: 3, + unitPrice: 538, + status: "delivered" + }, + { + id: "ORD-10093", + placedAt: "2024-05-18T08:16:00Z", + customer: "Litware Inc", + product: "Meridian ETL", + sku: "ETL-404", + region: "EMEA", + channel: "marketplace", + rep: "Aisha Khan", + quantity: 3, + unitPrice: 728, + status: "shipped" + }, + { + id: "ORD-10114", + placedAt: "2024-05-18T08:56:00Z", + customer: "Tailspin Toys", + product: "Borealis CRM", + sku: "CRM-210", + region: "APAC", + channel: "partner", + rep: "Hugo Bernard", + quantity: 1, + unitPrice: 893, + status: "shipped" + }, + { + id: "ORD-10019", + placedAt: "2024-05-18T09:30:00Z", + customer: "Northwind Traders", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "EMEA", + channel: "marketplace", + rep: "Hugo Bernard", + quantity: 4, + unitPrice: 658, + status: "refunded" + }, + { + id: "ORD-10321", + placedAt: "2024-05-18T12:26:00Z", + customer: "Alpine Ski House", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "LATAM", + channel: "marketplace", + rep: "Mateo Russo", + quantity: 7, + unitPrice: 1203, + status: "delivered" + }, + { + id: "ORD-10292", + placedAt: "2024-05-18T13:07:00Z", + customer: "Northwind Traders", + product: "Delta Insights", + sku: "INS-440", + region: "APAC", + channel: "direct", + rep: "Sven Olsen", + quantity: 7, + unitPrice: 312, + status: "shipped" + }, + { + id: "ORD-10055", + placedAt: "2024-05-18T14:49:00Z", + customer: "Proseware Inc", + product: "Glacier Backup", + sku: "BAK-770", + region: "North America", + channel: "marketplace", + rep: "Lena Fischer", + quantity: 8, + unitPrice: 62, + status: "delivered" + }, + { + id: "ORD-10158", + placedAt: "2024-05-18T15:39:00Z", + customer: "Northwind Traders", + product: "Nimbus Compute", + sku: "CMP-505", + region: "North America", + channel: "marketplace", + rep: "Aisha Khan", + quantity: 7, + unitPrice: 1113, + status: "shipped" + }, + { + id: "ORD-10150", + placedAt: "2024-05-18T19:45:00Z", + customer: "City Power & Light", + product: "Fjord Storage", + sku: "STO-660", + region: "LATAM", + channel: "direct", + rep: "Sora Tanaka", + quantity: 7, + unitPrice: 68, + status: "shipped" + }, + { + id: "ORD-10331", + placedAt: "2024-05-18T20:29:00Z", + customer: "Coho Vineyard", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "LATAM", + channel: "direct", + rep: "Mateo Russo", + quantity: 3, + unitPrice: 978, + status: "paid" + }, + { + id: "ORD-10072", + placedAt: "2024-05-19T02:24:00Z", + customer: "Proseware Inc", + product: "Helix Identity", + sku: "IDN-880", + region: "LATAM", + channel: "partner", + rep: "Lena Fischer", + quantity: 2, + unitPrice: 235, + status: "refunded" + }, + { + id: "ORD-10035", + placedAt: "2024-05-19T02:52:00Z", + customer: "School of Fine Art", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "EMEA", + channel: "marketplace", + rep: "Sora Tanaka", + quantity: 1, + unitPrice: 621, + status: "delivered" + }, + { + id: "ORD-10326", + placedAt: "2024-05-19T02:58:00Z", + customer: "Alpine Ski House", + product: "Fjord Storage", + sku: "STO-660", + region: "LATAM", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 7, + unitPrice: 70, + status: "pending" + }, + { + id: "ORD-10145", + placedAt: "2024-05-19T03:36:00Z", + customer: "Wingtip Toys", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "LATAM", + channel: "partner", + rep: "Hugo Bernard", + quantity: 7, + unitPrice: 1200, + status: "refunded" + }, + { + id: "ORD-10276", + placedAt: "2024-05-19T04:22:00Z", + customer: "City Power & Light", + product: "Delta Insights", + sku: "INS-440", + region: "North America", + channel: "marketplace", + rep: "Lena Fischer", + quantity: 8, + unitPrice: 339, + status: "cancelled" + }, + { + id: "ORD-10250", + placedAt: "2024-05-19T04:52:00Z", + customer: "School of Fine Art", + product: "Juniper Workflow", + sku: "WFL-101", + region: "APAC", + channel: "marketplace", + rep: "Owen Pratt", + quantity: 3, + unitPrice: 416, + status: "delivered" + }, + { + id: "ORD-10264", + placedAt: "2024-05-19T07:33:00Z", + customer: "Northwind Traders", + product: "Helix Identity", + sku: "IDN-880", + region: "EMEA", + channel: "marketplace", + rep: "Priya Nair", + quantity: 5, + unitPrice: 213, + status: "refunded" + }, + { + id: "ORD-10130", + placedAt: "2024-05-19T11:38:00Z", + customer: "Lucerne Publishing", + product: "Borealis CRM", + sku: "CRM-210", + region: "LATAM", + channel: "self-serve", + rep: "Diego Marin", + quantity: 7, + unitPrice: 876, + status: "pending" + }, + { + id: "ORD-10181", + placedAt: "2024-05-19T12:32:00Z", + customer: "Coho Vineyard", + product: "Echo Monitoring", + sku: "MON-550", + region: "APAC", + channel: "direct", + rep: "Lena Fischer", + quantity: 8, + unitPrice: 141, + status: "pending" + }, + { + id: "ORD-10228", + placedAt: "2024-05-19T13:22:00Z", + customer: "Proseware Inc", + product: "Delta Insights", + sku: "INS-440", + region: "North America", + channel: "partner", + rep: "Dana Wills", + quantity: 4, + unitPrice: 334, + status: "delivered" + }, + { + id: "ORD-10206", + placedAt: "2024-05-19T13:57:00Z", + customer: "Alpine Ski House", + product: "Nimbus Compute", + sku: "CMP-505", + region: "North America", + channel: "direct", + rep: "Lena Fischer", + quantity: 4, + unitPrice: 1114, + status: "refunded" + }, + { + id: "ORD-10364", + placedAt: "2024-05-19T20:03:00Z", + customer: "School of Fine Art", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "APAC", + channel: "partner", + rep: "Diego Marin", + quantity: 5, + unitPrice: 527, + status: "paid" + }, + { + id: "ORD-10132", + placedAt: "2024-05-19T22:57:00Z", + customer: "Fabrikam Inc", + product: "Delta Insights", + sku: "INS-440", + region: "EMEA", + channel: "partner", + rep: "Lena Fischer", + quantity: 8, + unitPrice: 337, + status: "paid" + }, + { + id: "ORD-10242", + placedAt: "2024-05-20T00:28:00Z", + customer: "Alpine Ski House", + product: "Borealis CRM", + sku: "CRM-210", + region: "LATAM", + channel: "direct", + rep: "Sora Tanaka", + quantity: 8, + unitPrice: 874, + status: "shipped" + }, + { + id: "ORD-10099", + placedAt: "2024-05-20T01:51:00Z", + customer: "Contoso Ltd", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "EMEA", + channel: "self-serve", + rep: "Lena Fischer", + quantity: 4, + unitPrice: 650, + status: "paid" + }, + { + id: "ORD-10248", + placedAt: "2024-05-20T01:53:00Z", + customer: "Trey Research", + product: "Helix Identity", + sku: "IDN-880", + region: "LATAM", + channel: "partner", + rep: "Mateo Russo", + quantity: 7, + unitPrice: 215, + status: "delivered" + }, + { + id: "ORD-10113", + placedAt: "2024-05-20T02:06:00Z", + customer: "Contoso Ltd", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "APAC", + channel: "partner", + rep: "Diego Marin", + quantity: 1, + unitPrice: 1201, + status: "pending" + }, + { + id: "ORD-10287", + placedAt: "2024-05-20T02:20:00Z", + customer: "Blue Yonder Airlines", + product: "Onyx Security", + sku: "SEC-606", + region: "EMEA", + channel: "partner", + rep: "Diego Marin", + quantity: 8, + unitPrice: 864, + status: "shipped" + }, + { + id: "ORD-10350", + placedAt: "2024-05-20T06:01:00Z", + customer: "Margies Travel", + product: "Nimbus Compute", + sku: "CMP-505", + region: "APAC", + channel: "direct", + rep: "Hugo Bernard", + quantity: 3, + unitPrice: 1085, + status: "refunded" + }, + { + id: "ORD-10353", + placedAt: "2024-05-20T11:32:00Z", + customer: "Coho Vineyard", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "EMEA", + channel: "direct", + rep: "Dana Wills", + quantity: 1, + unitPrice: 1195, + status: "delivered" + }, + { + id: "ORD-10137", + placedAt: "2024-05-20T11:52:00Z", + customer: "Northwind Traders", + product: "Ion Messaging", + sku: "MSG-990", + region: "North America", + channel: "self-serve", + rep: "Dana Wills", + quantity: 7, + unitPrice: 70, + status: "pending" + }, + { + id: "ORD-10069", + placedAt: "2024-05-20T14:29:00Z", + customer: "Litware Inc", + product: "Echo Monitoring", + sku: "MON-550", + region: "APAC", + channel: "direct", + rep: "Sora Tanaka", + quantity: 7, + unitPrice: 165, + status: "delivered" + }, + { + id: "ORD-10015", + placedAt: "2024-05-20T14:34:00Z", + customer: "Tailspin Toys", + product: "Onyx Security", + sku: "SEC-606", + region: "North America", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 1, + unitPrice: 868, + status: "paid" + }, + { + id: "ORD-10217", + placedAt: "2024-05-20T14:41:00Z", + customer: "Northwind Traders", + product: "Ion Messaging", + sku: "MSG-990", + region: "EMEA", + channel: "direct", + rep: "Priya Nair", + quantity: 8, + unitPrice: 62, + status: "shipped" + }, + { + id: "ORD-10214", + placedAt: "2024-05-20T22:10:00Z", + customer: "Lucerne Publishing", + product: "Fjord Storage", + sku: "STO-660", + region: "EMEA", + channel: "marketplace", + rep: "Mateo Russo", + quantity: 8, + unitPrice: 68, + status: "shipped" + }, + { + id: "ORD-10265", + placedAt: "2024-05-20T22:31:00Z", + customer: "Proseware Inc", + product: "Ion Messaging", + sku: "MSG-990", + region: "LATAM", + channel: "partner", + rep: "Dana Wills", + quantity: 1, + unitPrice: 58, + status: "shipped" + }, + { + id: "ORD-10195", + placedAt: "2024-05-21T02:12:00Z", + customer: "Proseware Inc", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "LATAM", + channel: "partner", + rep: "Aisha Khan", + quantity: 6, + unitPrice: 645, + status: "delivered" + }, + { + id: "ORD-10225", + placedAt: "2024-05-21T07:35:00Z", + customer: "Fabrikam Inc", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "APAC", + channel: "marketplace", + rep: "Hugo Bernard", + quantity: 1, + unitPrice: 1189, + status: "delivered" + }, + { + id: "ORD-10180", + placedAt: "2024-05-21T08:15:00Z", + customer: "Wingtip Toys", + product: "Delta Insights", + sku: "INS-440", + region: "APAC", + channel: "direct", + rep: "Owen Pratt", + quantity: 4, + unitPrice: 337, + status: "shipped" + }, + { + id: "ORD-10047", + placedAt: "2024-05-21T09:50:00Z", + customer: "Wide World Importers", + product: "Onyx Security", + sku: "SEC-606", + region: "EMEA", + channel: "direct", + rep: "Lena Fischer", + quantity: 6, + unitPrice: 855, + status: "shipped" + }, + { + id: "ORD-10315", + placedAt: "2024-05-21T11:07:00Z", + customer: "Wide World Importers", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "LATAM", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 1, + unitPrice: 975, + status: "delivered" + }, + { + id: "ORD-10308", + placedAt: "2024-05-21T14:41:00Z", + customer: "Coho Vineyard", + product: "Delta Insights", + sku: "INS-440", + region: "North America", + channel: "marketplace", + rep: "Priya Nair", + quantity: 2, + unitPrice: 339, + status: "refunded" + }, + { + id: "ORD-10352", + placedAt: "2024-05-21T14:46:00Z", + customer: "Alpine Ski House", + product: "Polaris Reporting", + sku: "RPT-707", + region: "APAC", + channel: "partner", + rep: "Lena Fischer", + quantity: 3, + unitPrice: 271, + status: "delivered" + }, + { + id: "ORD-10270", + placedAt: "2024-05-21T15:15:00Z", + customer: "Fourth Coffee", + product: "Nimbus Compute", + sku: "CMP-505", + region: "APAC", + channel: "direct", + rep: "Diego Marin", + quantity: 3, + unitPrice: 1089, + status: "cancelled" + }, + { + id: "ORD-10324", + placedAt: "2024-05-21T15:21:00Z", + customer: "Tailspin Toys", + product: "Delta Insights", + sku: "INS-440", + region: "EMEA", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 3, + unitPrice: 320, + status: "delivered" + }, + { + id: "ORD-10291", + placedAt: "2024-05-21T17:15:00Z", + customer: "Graphic Design Institute", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "APAC", + channel: "marketplace", + rep: "Priya Nair", + quantity: 8, + unitPrice: 629, + status: "delivered" + }, + { + id: "ORD-10054", + placedAt: "2024-05-21T19:36:00Z", + customer: "Wide World Importers", + product: "Fjord Storage", + sku: "STO-660", + region: "APAC", + channel: "direct", + rep: "Mateo Russo", + quantity: 4, + unitPrice: 88, + status: "cancelled" + }, + { + id: "ORD-10227", + placedAt: "2024-05-22T00:21:00Z", + customer: "Wingtip Toys", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "North America", + channel: "self-serve", + rep: "Dana Wills", + quantity: 8, + unitPrice: 640, + status: "pending" + }, + { + id: "ORD-10213", + placedAt: "2024-05-22T04:25:00Z", + customer: "Margies Travel", + product: "Echo Monitoring", + sku: "MON-550", + region: "APAC", + channel: "marketplace", + rep: "Lena Fischer", + quantity: 2, + unitPrice: 161, + status: "shipped" + }, + { + id: "ORD-10246", + placedAt: "2024-05-22T08:25:00Z", + customer: "Tailspin Toys", + product: "Fjord Storage", + sku: "STO-660", + region: "North America", + channel: "self-serve", + rep: "Owen Pratt", + quantity: 4, + unitPrice: 64, + status: "paid" + }, + { + id: "ORD-10294", + placedAt: "2024-05-22T08:30:00Z", + customer: "Lucerne Publishing", + product: "Fjord Storage", + sku: "STO-660", + region: "North America", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 8, + unitPrice: 87, + status: "pending" + }, + { + id: "ORD-10188", + placedAt: "2024-05-22T10:03:00Z", + customer: "Tailspin Toys", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "APAC", + channel: "marketplace", + rep: "Priya Nair", + quantity: 3, + unitPrice: 536, + status: "delivered" + }, + { + id: "ORD-10075", + placedAt: "2024-05-22T10:04:00Z", + customer: "Fourth Coffee", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "EMEA", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 1, + unitPrice: 962, + status: "paid" + }, + { + id: "ORD-10312", + placedAt: "2024-05-22T10:13:00Z", + customer: "Trey Research", + product: "Helix Identity", + sku: "IDN-880", + region: "APAC", + channel: "partner", + rep: "Sven Olsen", + quantity: 7, + unitPrice: 215, + status: "shipped" + }, + { + id: "ORD-10165", + placedAt: "2024-05-22T11:39:00Z", + customer: "Margies Travel", + product: "Echo Monitoring", + sku: "MON-550", + region: "APAC", + channel: "partner", + rep: "Hugo Bernard", + quantity: 1, + unitPrice: 157, + status: "delivered" + }, + { + id: "ORD-10318", + placedAt: "2024-05-22T13:57:00Z", + customer: "Margies Travel", + product: "Nimbus Compute", + sku: "CMP-505", + region: "EMEA", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 3, + unitPrice: 1114, + status: "cancelled" + }, + { + id: "ORD-10190", + placedAt: "2024-05-22T15:49:00Z", + customer: "Lucerne Publishing", + product: "Nimbus Compute", + sku: "CMP-505", + region: "North America", + channel: "partner", + rep: "Priya Nair", + quantity: 2, + unitPrice: 1112, + status: "cancelled" + }, + { + id: "ORD-10100", + placedAt: "2024-05-22T18:37:00Z", + customer: "School of Fine Art", + product: "Delta Insights", + sku: "INS-440", + region: "APAC", + channel: "self-serve", + rep: "Aisha Khan", + quantity: 5, + unitPrice: 334, + status: "delivered" + }, + { + id: "ORD-10010", + placedAt: "2024-05-22T23:45:00Z", + customer: "Wide World Importers", + product: "Juniper Workflow", + sku: "WFL-101", + region: "LATAM", + channel: "partner", + rep: "Lena Fischer", + quantity: 4, + unitPrice: 417, + status: "paid" + }, + { + id: "ORD-10236", + placedAt: "2024-05-23T00:12:00Z", + customer: "Contoso Ltd", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "APAC", + channel: "direct", + rep: "Diego Marin", + quantity: 5, + unitPrice: 506, + status: "delivered" + }, + { + id: "ORD-10261", + placedAt: "2024-05-23T00:43:00Z", + customer: "Tailspin Toys", + product: "Echo Monitoring", + sku: "MON-550", + region: "EMEA", + channel: "self-serve", + rep: "Sven Olsen", + quantity: 2, + unitPrice: 139, + status: "shipped" + }, + { + id: "ORD-10309", + placedAt: "2024-05-23T01:47:00Z", + customer: "Wingtip Toys", + product: "Echo Monitoring", + sku: "MON-550", + region: "LATAM", + channel: "self-serve", + rep: "Diego Marin", + quantity: 7, + unitPrice: 150, + status: "shipped" + }, + { + id: "ORD-10105", + placedAt: "2024-05-23T01:54:00Z", + customer: "Wide World Importers", + product: "Ion Messaging", + sku: "MSG-990", + region: "LATAM", + channel: "direct", + rep: "Lena Fischer", + quantity: 5, + unitPrice: 61, + status: "shipped" + }, + { + id: "ORD-10104", + placedAt: "2024-05-23T02:03:00Z", + customer: "Wide World Importers", + product: "Helix Identity", + sku: "IDN-880", + region: "LATAM", + channel: "partner", + rep: "Owen Pratt", + quantity: 2, + unitPrice: 201, + status: "paid" + }, + { + id: "ORD-10157", + placedAt: "2024-05-23T08:09:00Z", + customer: "Northwind Traders", + product: "Meridian ETL", + sku: "ETL-404", + region: "LATAM", + channel: "partner", + rep: "Diego Marin", + quantity: 7, + unitPrice: 723, + status: "shipped" + }, + { + id: "ORD-10208", + placedAt: "2024-05-23T08:34:00Z", + customer: "Northwind Traders", + product: "Polaris Reporting", + sku: "RPT-707", + region: "APAC", + channel: "self-serve", + rep: "Lena Fischer", + quantity: 6, + unitPrice: 286, + status: "pending" + }, + { + id: "ORD-10096", + placedAt: "2024-05-23T09:41:00Z", + customer: "Coho Vineyard", + product: "Polaris Reporting", + sku: "RPT-707", + region: "North America", + channel: "self-serve", + rep: "Sven Olsen", + quantity: 2, + unitPrice: 283, + status: "paid" + }, + { + id: "ORD-10374", + placedAt: "2024-05-23T10:17:00Z", + customer: "Coho Vineyard", + product: "Fjord Storage", + sku: "STO-660", + region: "EMEA", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 3, + unitPrice: 76, + status: "delivered" + }, + { + id: "ORD-10249", + placedAt: "2024-05-23T12:41:00Z", + customer: "Wingtip Toys", + product: "Ion Messaging", + sku: "MSG-990", + region: "APAC", + channel: "marketplace", + rep: "Diego Marin", + quantity: 1, + unitPrice: 62, + status: "shipped" + }, + { + id: "ORD-10296", + placedAt: "2024-05-23T13:39:00Z", + customer: "Wingtip Toys", + product: "Helix Identity", + sku: "IDN-880", + region: "APAC", + channel: "marketplace", + rep: "Diego Marin", + quantity: 2, + unitPrice: 222, + status: "paid" + }, + { + id: "ORD-10129", + placedAt: "2024-05-23T14:38:00Z", + customer: "Contoso Ltd", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "North America", + channel: "partner", + rep: "Hugo Bernard", + quantity: 7, + unitPrice: 1181, + status: "shipped" + }, + { + id: "ORD-10135", + placedAt: "2024-05-23T15:31:00Z", + customer: "Fabrikam Inc", + product: "Glacier Backup", + sku: "BAK-770", + region: "LATAM", + channel: "self-serve", + rep: "Sven Olsen", + quantity: 7, + unitPrice: 32, + status: "refunded" + }, + { + id: "ORD-10154", + placedAt: "2024-05-23T22:44:00Z", + customer: "Blue Yonder Airlines", + product: "Juniper Workflow", + sku: "WFL-101", + region: "LATAM", + channel: "partner", + rep: "Dana Wills", + quantity: 1, + unitPrice: 403, + status: "shipped" + }, + { + id: "ORD-10307", + placedAt: "2024-05-23T23:25:00Z", + customer: "Fabrikam Inc", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "APAC", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 8, + unitPrice: 636, + status: "pending" + }, + { + id: "ORD-10103", + placedAt: "2024-05-23T23:51:00Z", + customer: "Alpine Ski House", + product: "Glacier Backup", + sku: "BAK-770", + region: "North America", + channel: "marketplace", + rep: "Priya Nair", + quantity: 5, + unitPrice: 44, + status: "delivered" + }, + { + id: "ORD-10198", + placedAt: "2024-05-24T00:34:00Z", + customer: "City Power & Light", + product: "Fjord Storage", + sku: "STO-660", + region: "North America", + channel: "partner", + rep: "Owen Pratt", + quantity: 7, + unitPrice: 67, + status: "paid" + }, + { + id: "ORD-10053", + placedAt: "2024-05-24T05:36:00Z", + customer: "Proseware Inc", + product: "Echo Monitoring", + sku: "MON-550", + region: "APAC", + channel: "direct", + rep: "Diego Marin", + quantity: 7, + unitPrice: 155, + status: "cancelled" + }, + { + id: "ORD-10342", + placedAt: "2024-05-24T12:10:00Z", + customer: "Litware Inc", + product: "Fjord Storage", + sku: "STO-660", + region: "EMEA", + channel: "direct", + rep: "Sven Olsen", + quantity: 8, + unitPrice: 58, + status: "delivered" + }, + { + id: "ORD-10379", + placedAt: "2024-05-24T12:33:00Z", + customer: "City Power & Light", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "LATAM", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 5, + unitPrice: 974, + status: "delivered" + }, + { + id: "ORD-10042", + placedAt: "2024-05-24T12:49:00Z", + customer: "Northwind Traders", + product: "Juniper Workflow", + sku: "WFL-101", + region: "APAC", + channel: "partner", + rep: "Lena Fischer", + quantity: 6, + unitPrice: 403, + status: "shipped" + }, + { + id: "ORD-10317", + placedAt: "2024-05-24T13:49:00Z", + customer: "Fourth Coffee", + product: "Meridian ETL", + sku: "ETL-404", + region: "North America", + channel: "marketplace", + rep: "Dana Wills", + quantity: 8, + unitPrice: 713, + status: "delivered" + }, + { + id: "ORD-10357", + placedAt: "2024-05-24T15:20:00Z", + customer: "Fourth Coffee", + product: "Echo Monitoring", + sku: "MON-550", + region: "APAC", + channel: "direct", + rep: "Sven Olsen", + quantity: 1, + unitPrice: 134, + status: "refunded" + }, + { + id: "ORD-10092", + placedAt: "2024-05-24T15:52:00Z", + customer: "Wingtip Toys", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "North America", + channel: "partner", + rep: "Diego Marin", + quantity: 1, + unitPrice: 512, + status: "delivered" + }, + { + id: "ORD-10095", + placedAt: "2024-05-24T16:06:00Z", + customer: "Humongous Insurance", + product: "Onyx Security", + sku: "SEC-606", + region: "APAC", + channel: "marketplace", + rep: "Lena Fischer", + quantity: 5, + unitPrice: 862, + status: "pending" + }, + { + id: "ORD-10226", + placedAt: "2024-05-24T18:48:00Z", + customer: "Wingtip Toys", + product: "Borealis CRM", + sku: "CRM-210", + region: "EMEA", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 8, + unitPrice: 890, + status: "pending" + }, + { + id: "ORD-10003", + placedAt: "2024-05-24T22:27:00Z", + customer: "Litware Inc", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "North America", + channel: "partner", + rep: "Hugo Bernard", + quantity: 2, + unitPrice: 650, + status: "paid" + }, + { + id: "ORD-10365", + placedAt: "2024-05-25T00:27:00Z", + customer: "Blue Yonder Airlines", + product: "Meridian ETL", + sku: "ETL-404", + region: "APAC", + channel: "marketplace", + rep: "Aisha Khan", + quantity: 3, + unitPrice: 732, + status: "pending" + }, + { + id: "ORD-10325", + placedAt: "2024-05-25T01:59:00Z", + customer: "Wingtip Toys", + product: "Echo Monitoring", + sku: "MON-550", + region: "EMEA", + channel: "marketplace", + rep: "Hugo Bernard", + quantity: 3, + unitPrice: 155, + status: "refunded" + }, + { + id: "ORD-10143", + placedAt: "2024-05-25T03:36:00Z", + customer: "Lucerne Publishing", + product: "Onyx Security", + sku: "SEC-606", + region: "LATAM", + channel: "marketplace", + rep: "Hugo Bernard", + quantity: 7, + unitPrice: 857, + status: "delivered" + }, + { + id: "ORD-10134", + placedAt: "2024-05-25T04:25:00Z", + customer: "Humongous Insurance", + product: "Fjord Storage", + sku: "STO-660", + region: "LATAM", + channel: "self-serve", + rep: "Priya Nair", + quantity: 3, + unitPrice: 57, + status: "paid" + }, + { + id: "ORD-10301", + placedAt: "2024-05-25T04:58:00Z", + customer: "City Power & Light", + product: "Meridian ETL", + sku: "ETL-404", + region: "North America", + channel: "self-serve", + rep: "Sora Tanaka", + quantity: 2, + unitPrice: 724, + status: "refunded" + }, + { + id: "ORD-10244", + placedAt: "2024-05-25T05:20:00Z", + customer: "Proseware Inc", + product: "Delta Insights", + sku: "INS-440", + region: "APAC", + channel: "partner", + rep: "Lena Fischer", + quantity: 5, + unitPrice: 326, + status: "pending" + }, + { + id: "ORD-10061", + placedAt: "2024-05-25T16:28:00Z", + customer: "Adventure Works", + product: "Meridian ETL", + sku: "ETL-404", + region: "APAC", + channel: "self-serve", + rep: "Owen Pratt", + quantity: 8, + unitPrice: 720, + status: "shipped" + }, + { + id: "ORD-10087", + placedAt: "2024-05-25T16:41:00Z", + customer: "Adventure Works", + product: "Glacier Backup", + sku: "BAK-770", + region: "APAC", + channel: "self-serve", + rep: "Diego Marin", + quantity: 7, + unitPrice: 40, + status: "shipped" + }, + { + id: "ORD-10001", + placedAt: "2024-05-25T19:39:00Z", + customer: "Fabrikam Inc", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "North America", + channel: "self-serve", + rep: "Aisha Khan", + quantity: 7, + unitPrice: 1195, + status: "cancelled" + }, + { + id: "ORD-10030", + placedAt: "2024-05-25T21:06:00Z", + customer: "Litware Inc", + product: "Nimbus Compute", + sku: "CMP-505", + region: "EMEA", + channel: "marketplace", + rep: "Sven Olsen", + quantity: 3, + unitPrice: 1115, + status: "cancelled" + }, + { + id: "ORD-10115", + placedAt: "2024-05-25T21:27:00Z", + customer: "Lucerne Publishing", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "LATAM", + channel: "direct", + rep: "Hugo Bernard", + quantity: 3, + unitPrice: 649, + status: "refunded" + }, + { + id: "ORD-10336", + placedAt: "2024-05-25T22:58:00Z", + customer: "Tailspin Toys", + product: "Polaris Reporting", + sku: "RPT-707", + region: "APAC", + channel: "direct", + rep: "Aisha Khan", + quantity: 3, + unitPrice: 281, + status: "paid" + }, + { + id: "ORD-10216", + placedAt: "2024-05-25T23:02:00Z", + customer: "Graphic Design Institute", + product: "Helix Identity", + sku: "IDN-880", + region: "North America", + channel: "partner", + rep: "Diego Marin", + quantity: 6, + unitPrice: 228, + status: "delivered" + }, + { + id: "ORD-10080", + placedAt: "2024-05-26T01:16:00Z", + customer: "Proseware Inc", + product: "Polaris Reporting", + sku: "RPT-707", + region: "North America", + channel: "partner", + rep: "Priya Nair", + quantity: 6, + unitPrice: 289, + status: "pending" + }, + { + id: "ORD-10161", + placedAt: "2024-05-26T02:41:00Z", + customer: "School of Fine Art", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "LATAM", + channel: "direct", + rep: "Owen Pratt", + quantity: 8, + unitPrice: 1185, + status: "pending" + }, + { + id: "ORD-10343", + placedAt: "2024-05-26T08:07:00Z", + customer: "Wingtip Toys", + product: "Glacier Backup", + sku: "BAK-770", + region: "APAC", + channel: "self-serve", + rep: "Mateo Russo", + quantity: 2, + unitPrice: 45, + status: "pending" + }, + { + id: "ORD-10077", + placedAt: "2024-05-26T08:37:00Z", + customer: "Contoso Ltd", + product: "Meridian ETL", + sku: "ETL-404", + region: "APAC", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 5, + unitPrice: 727, + status: "delivered" + }, + { + id: "ORD-10327", + placedAt: "2024-05-26T09:58:00Z", + customer: "School of Fine Art", + product: "Glacier Backup", + sku: "BAK-770", + region: "APAC", + channel: "self-serve", + rep: "Priya Nair", + quantity: 3, + unitPrice: 39, + status: "pending" + }, + { + id: "ORD-10057", + placedAt: "2024-05-26T10:39:00Z", + customer: "Northwind Traders", + product: "Ion Messaging", + sku: "MSG-990", + region: "LATAM", + channel: "partner", + rep: "Dana Wills", + quantity: 7, + unitPrice: 53, + status: "shipped" + }, + { + id: "ORD-10088", + placedAt: "2024-05-26T16:48:00Z", + customer: "Coho Vineyard", + product: "Helix Identity", + sku: "IDN-880", + region: "APAC", + channel: "marketplace", + rep: "Diego Marin", + quantity: 5, + unitPrice: 218, + status: "shipped" + }, + { + id: "ORD-10286", + placedAt: "2024-05-26T21:13:00Z", + customer: "Wingtip Toys", + product: "Nimbus Compute", + sku: "CMP-505", + region: "LATAM", + channel: "direct", + rep: "Diego Marin", + quantity: 2, + unitPrice: 1092, + status: "shipped" + }, + { + id: "ORD-10140", + placedAt: "2024-05-26T22:31:00Z", + customer: "Tailspin Toys", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "North America", + channel: "direct", + rep: "Mateo Russo", + quantity: 5, + unitPrice: 520, + status: "paid" + }, + { + id: "ORD-10254", + placedAt: "2024-05-26T23:02:00Z", + customer: "Northwind Traders", + product: "Nimbus Compute", + sku: "CMP-505", + region: "EMEA", + channel: "self-serve", + rep: "Sven Olsen", + quantity: 3, + unitPrice: 1099, + status: "delivered" + }, + { + id: "ORD-10108", + placedAt: "2024-05-26T23:31:00Z", + customer: "Blue Yonder Airlines", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "APAC", + channel: "partner", + rep: "Priya Nair", + quantity: 6, + unitPrice: 504, + status: "paid" + }, + { + id: "ORD-10303", + placedAt: "2024-05-27T00:21:00Z", + customer: "Fourth Coffee", + product: "Onyx Security", + sku: "SEC-606", + region: "APAC", + channel: "marketplace", + rep: "Dana Wills", + quantity: 6, + unitPrice: 855, + status: "shipped" + }, + { + id: "ORD-10258", + placedAt: "2024-05-27T04:22:00Z", + customer: "Alpine Ski House", + product: "Borealis CRM", + sku: "CRM-210", + region: "LATAM", + channel: "marketplace", + rep: "Aisha Khan", + quantity: 3, + unitPrice: 908, + status: "delivered" + }, + { + id: "ORD-10267", + placedAt: "2024-05-27T06:00:00Z", + customer: "Coho Vineyard", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "North America", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 3, + unitPrice: 966, + status: "delivered" + }, + { + id: "ORD-10179", + placedAt: "2024-05-27T06:50:00Z", + customer: "City Power & Light", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "North America", + channel: "marketplace", + rep: "Dana Wills", + quantity: 5, + unitPrice: 644, + status: "paid" + }, + { + id: "ORD-10184", + placedAt: "2024-05-27T10:30:00Z", + customer: "Tailspin Toys", + product: "Helix Identity", + sku: "IDN-880", + region: "North America", + channel: "partner", + rep: "Diego Marin", + quantity: 5, + unitPrice: 208, + status: "pending" + }, + { + id: "ORD-10023", + placedAt: "2024-05-27T12:22:00Z", + customer: "City Power & Light", + product: "Glacier Backup", + sku: "BAK-770", + region: "APAC", + channel: "direct", + rep: "Lena Fischer", + quantity: 2, + unitPrice: 31, + status: "pending" + }, + { + id: "ORD-10037", + placedAt: "2024-05-27T16:22:00Z", + customer: "Alpine Ski House", + product: "Echo Monitoring", + sku: "MON-550", + region: "APAC", + channel: "marketplace", + rep: "Priya Nair", + quantity: 3, + unitPrice: 141, + status: "refunded" + }, + { + id: "ORD-10247", + placedAt: "2024-05-27T22:46:00Z", + customer: "Trey Research", + product: "Glacier Backup", + sku: "BAK-770", + region: "LATAM", + channel: "direct", + rep: "Mateo Russo", + quantity: 7, + unitPrice: 60, + status: "delivered" + }, + { + id: "ORD-10027", + placedAt: "2024-05-27T23:51:00Z", + customer: "Fabrikam Inc", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "North America", + channel: "partner", + rep: "Sven Olsen", + quantity: 3, + unitPrice: 979, + status: "cancelled" + }, + { + id: "ORD-10064", + placedAt: "2024-05-28T02:28:00Z", + customer: "Fabrikam Inc", + product: "Polaris Reporting", + sku: "RPT-707", + region: "APAC", + channel: "self-serve", + rep: "Priya Nair", + quantity: 7, + unitPrice: 273, + status: "delivered" + }, + { + id: "ORD-10022", + placedAt: "2024-05-28T03:38:00Z", + customer: "Fourth Coffee", + product: "Fjord Storage", + sku: "STO-660", + region: "APAC", + channel: "partner", + rep: "Sora Tanaka", + quantity: 4, + unitPrice: 56, + status: "refunded" + }, + { + id: "ORD-10014", + placedAt: "2024-05-28T05:33:00Z", + customer: "Adventure Works", + product: "Nimbus Compute", + sku: "CMP-505", + region: "LATAM", + channel: "direct", + rep: "Owen Pratt", + quantity: 6, + unitPrice: 1100, + status: "paid" + }, + { + id: "ORD-10074", + placedAt: "2024-05-28T05:54:00Z", + customer: "Margies Travel", + product: "Juniper Workflow", + sku: "WFL-101", + region: "APAC", + channel: "marketplace", + rep: "Hugo Bernard", + quantity: 2, + unitPrice: 419, + status: "delivered" + }, + { + id: "ORD-10147", + placedAt: "2024-05-28T07:03:00Z", + customer: "Lucerne Publishing", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "LATAM", + channel: "direct", + rep: "Mateo Russo", + quantity: 1, + unitPrice: 652, + status: "delivered" + }, + { + id: "ORD-10371", + placedAt: "2024-05-28T07:48:00Z", + customer: "City Power & Light", + product: "Cascade Data Pipeline", + sku: "PIPE-330", + region: "APAC", + channel: "self-serve", + rep: "Dana Wills", + quantity: 8, + unitPrice: 655, + status: "shipped" + }, + { + id: "ORD-10375", + placedAt: "2024-05-28T08:26:00Z", + customer: "Fabrikam Inc", + product: "Glacier Backup", + sku: "BAK-770", + region: "EMEA", + channel: "partner", + rep: "Hugo Bernard", + quantity: 2, + unitPrice: 43, + status: "delivered" + }, + { + id: "ORD-10118", + placedAt: "2024-05-28T08:32:00Z", + customer: "Humongous Insurance", + product: "Fjord Storage", + sku: "STO-660", + region: "North America", + channel: "marketplace", + rep: "Diego Marin", + quantity: 1, + unitPrice: 81, + status: "paid" + }, + { + id: "ORD-10065", + placedAt: "2024-05-28T08:58:00Z", + customer: "Graphic Design Institute", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "EMEA", + channel: "direct", + rep: "Hugo Bernard", + quantity: 7, + unitPrice: 1203, + status: "delivered" + }, + { + id: "ORD-10289", + placedAt: "2024-05-28T10:03:00Z", + customer: "Lucerne Publishing", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "EMEA", + channel: "marketplace", + rep: "Lena Fischer", + quantity: 3, + unitPrice: 1199, + status: "refunded" + }, + { + id: "ORD-10252", + placedAt: "2024-05-28T11:06:00Z", + customer: "Alpine Ski House", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "EMEA", + channel: "partner", + rep: "Priya Nair", + quantity: 8, + unitPrice: 513, + status: "shipped" + }, + { + id: "ORD-10119", + placedAt: "2024-05-28T12:50:00Z", + customer: "Lucerne Publishing", + product: "Glacier Backup", + sku: "BAK-770", + region: "North America", + channel: "marketplace", + rep: "Sora Tanaka", + quantity: 1, + unitPrice: 41, + status: "pending" + }, + { + id: "ORD-10016", + placedAt: "2024-05-28T13:48:00Z", + customer: "School of Fine Art", + product: "Polaris Reporting", + sku: "RPT-707", + region: "North America", + channel: "partner", + rep: "Lena Fischer", + quantity: 2, + unitPrice: 283, + status: "shipped" + }, + { + id: "ORD-10168", + placedAt: "2024-05-28T14:05:00Z", + customer: "Tailspin Toys", + product: "Helix Identity", + sku: "IDN-880", + region: "EMEA", + channel: "marketplace", + rep: "Hugo Bernard", + quantity: 7, + unitPrice: 213, + status: "shipped" + }, + { + id: "ORD-10094", + placedAt: "2024-05-28T15:55:00Z", + customer: "Alpine Ski House", + product: "Nimbus Compute", + sku: "CMP-505", + region: "APAC", + channel: "self-serve", + rep: "Hugo Bernard", + quantity: 8, + unitPrice: 1086, + status: "delivered" + }, + { + id: "ORD-10036", + placedAt: "2024-05-28T19:44:00Z", + customer: "Trey Research", + product: "Delta Insights", + sku: "INS-440", + region: "LATAM", + channel: "self-serve", + rep: "Dana Wills", + quantity: 2, + unitPrice: 327, + status: "shipped" + }, + { + id: "ORD-10335", + placedAt: "2024-05-28T20:08:00Z", + customer: "Contoso Ltd", + product: "Onyx Security", + sku: "SEC-606", + region: "North America", + channel: "direct", + rep: "Diego Marin", + quantity: 6, + unitPrice: 841, + status: "delivered" + }, + { + id: "ORD-10025", + placedAt: "2024-05-28T20:19:00Z", + customer: "Proseware Inc", + product: "Ion Messaging", + sku: "MSG-990", + region: "North America", + channel: "self-serve", + rep: "Priya Nair", + quantity: 1, + unitPrice: 59, + status: "shipped" + }, + { + id: "ORD-10126", + placedAt: "2024-05-28T22:23:00Z", + customer: "Wingtip Toys", + product: "Nimbus Compute", + sku: "CMP-505", + region: "North America", + channel: "partner", + rep: "Priya Nair", + quantity: 5, + unitPrice: 1110, + status: "delivered" + }, + { + id: "ORD-10112", + placedAt: "2024-05-28T22:43:00Z", + customer: "Lucerne Publishing", + product: "Polaris Reporting", + sku: "RPT-707", + region: "EMEA", + channel: "partner", + rep: "Mateo Russo", + quantity: 8, + unitPrice: 285, + status: "pending" + }, + { + id: "ORD-10068", + placedAt: "2024-05-28T23:49:00Z", + customer: "Fabrikam Inc", + product: "Delta Insights", + sku: "INS-440", + region: "APAC", + channel: "direct", + rep: "Owen Pratt", + quantity: 8, + unitPrice: 305, + status: "delivered" + }, + { + id: "ORD-10310", + placedAt: "2024-05-29T00:02:00Z", + customer: "Wingtip Toys", + product: "Fjord Storage", + sku: "STO-660", + region: "APAC", + channel: "marketplace", + rep: "Lena Fischer", + quantity: 3, + unitPrice: 92, + status: "paid" + }, + { + id: "ORD-10232", + placedAt: "2024-05-29T02:04:00Z", + customer: "Graphic Design Institute", + product: "Helix Identity", + sku: "IDN-880", + region: "EMEA", + channel: "partner", + rep: "Hugo Bernard", + quantity: 2, + unitPrice: 200, + status: "paid" + }, + { + id: "ORD-10231", + placedAt: "2024-05-29T02:08:00Z", + customer: "Alpine Ski House", + product: "Glacier Backup", + sku: "BAK-770", + region: "EMEA", + channel: "partner", + rep: "Mateo Russo", + quantity: 2, + unitPrice: 46, + status: "delivered" + }, + { + id: "ORD-10351", + placedAt: "2024-05-29T02:55:00Z", + customer: "Margies Travel", + product: "Onyx Security", + sku: "SEC-606", + region: "LATAM", + channel: "self-serve", + rep: "Lena Fischer", + quantity: 4, + unitPrice: 876, + status: "delivered" + }, + { + id: "ORD-10185", + placedAt: "2024-05-29T04:16:00Z", + customer: "City Power & Light", + product: "Ion Messaging", + sku: "MSG-990", + region: "LATAM", + channel: "direct", + rep: "Aisha Khan", + quantity: 6, + unitPrice: 73, + status: "paid" + }, + { + id: "ORD-10360", + placedAt: "2024-05-29T09:18:00Z", + customer: "Humongous Insurance", + product: "Helix Identity", + sku: "IDN-880", + region: "LATAM", + channel: "partner", + rep: "Sven Olsen", + quantity: 2, + unitPrice: 239, + status: "delivered" + }, + { + id: "ORD-10175", + placedAt: "2024-05-29T10:33:00Z", + customer: "Proseware Inc", + product: "Onyx Security", + sku: "SEC-606", + region: "LATAM", + channel: "marketplace", + rep: "Aisha Khan", + quantity: 7, + unitPrice: 842, + status: "pending" + }, + { + id: "ORD-10176", + placedAt: "2024-05-29T11:19:00Z", + customer: "Northwind Traders", + product: "Polaris Reporting", + sku: "RPT-707", + region: "LATAM", + channel: "direct", + rep: "Mateo Russo", + quantity: 8, + unitPrice: 295, + status: "delivered" + }, + { + id: "ORD-10290", + placedAt: "2024-05-29T12:43:00Z", + customer: "Fabrikam Inc", + product: "Borealis CRM", + sku: "CRM-210", + region: "APAC", + channel: "direct", + rep: "Mateo Russo", + quantity: 4, + unitPrice: 890, + status: "shipped" + }, + { + id: "ORD-10040", + placedAt: "2024-05-29T12:44:00Z", + customer: "Northwind Traders", + product: "Helix Identity", + sku: "IDN-880", + region: "North America", + channel: "marketplace", + rep: "Owen Pratt", + quantity: 7, + unitPrice: 226, + status: "shipped" + }, + { + id: "ORD-10191", + placedAt: "2024-05-29T13:11:00Z", + customer: "Humongous Insurance", + product: "Onyx Security", + sku: "SEC-606", + region: "North America", + channel: "marketplace", + rep: "Sven Olsen", + quantity: 2, + unitPrice: 868, + status: "refunded" + }, + { + id: "ORD-10346", + placedAt: "2024-05-29T14:13:00Z", + customer: "School of Fine Art", + product: "Juniper Workflow", + sku: "WFL-101", + region: "North America", + channel: "self-serve", + rep: "Diego Marin", + quantity: 8, + unitPrice: 429, + status: "refunded" + }, + { + id: "ORD-10110", + placedAt: "2024-05-29T14:21:00Z", + customer: "Graphic Design Institute", + product: "Nimbus Compute", + sku: "CMP-505", + region: "LATAM", + channel: "direct", + rep: "Aisha Khan", + quantity: 8, + unitPrice: 1110, + status: "shipped" + }, + { + id: "ORD-10223", + placedAt: "2024-05-29T18:50:00Z", + customer: "Blue Yonder Airlines", + product: "Onyx Security", + sku: "SEC-606", + region: "EMEA", + channel: "partner", + rep: "Priya Nair", + quantity: 1, + unitPrice: 869, + status: "refunded" + }, + { + id: "ORD-10149", + placedAt: "2024-05-29T19:52:00Z", + customer: "Lucerne Publishing", + product: "Echo Monitoring", + sku: "MON-550", + region: "LATAM", + channel: "marketplace", + rep: "Dana Wills", + quantity: 5, + unitPrice: 154, + status: "shipped" + }, + { + id: "ORD-10017", + placedAt: "2024-05-29T21:10:00Z", + customer: "Trey Research", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "APAC", + channel: "partner", + rep: "Aisha Khan", + quantity: 5, + unitPrice: 1210, + status: "shipped" + }, + { + id: "ORD-10120", + placedAt: "2024-05-29T23:32:00Z", + customer: "Trey Research", + product: "Helix Identity", + sku: "IDN-880", + region: "APAC", + channel: "marketplace", + rep: "Owen Pratt", + quantity: 2, + unitPrice: 210, + status: "pending" + }, + { + id: "ORD-10062", + placedAt: "2024-05-30T00:04:00Z", + customer: "Adventure Works", + product: "Nimbus Compute", + sku: "CMP-505", + region: "LATAM", + channel: "self-serve", + rep: "Dana Wills", + quantity: 2, + unitPrice: 1107, + status: "paid" + }, + { + id: "ORD-10207", + placedAt: "2024-05-30T04:36:00Z", + customer: "Litware Inc", + product: "Onyx Security", + sku: "SEC-606", + region: "North America", + channel: "marketplace", + rep: "Lena Fischer", + quantity: 3, + unitPrice: 879, + status: "shipped" + }, + { + id: "ORD-10033", + placedAt: "2024-05-30T05:10:00Z", + customer: "Fourth Coffee", + product: "Aurora Analytics Suite", + sku: "ANL-100", + region: "APAC", + channel: "self-serve", + rep: "Dana Wills", + quantity: 6, + unitPrice: 1192, + status: "delivered" + }, + { + id: "ORD-10189", + placedAt: "2024-05-30T07:29:00Z", + customer: "Fabrikam Inc", + product: "Meridian ETL", + sku: "ETL-404", + region: "EMEA", + channel: "marketplace", + rep: "Owen Pratt", + quantity: 2, + unitPrice: 746, + status: "shipped" + }, + { + id: "ORD-10266", + placedAt: "2024-05-30T09:26:00Z", + customer: "Proseware Inc", + product: "Juniper Workflow", + sku: "WFL-101", + region: "APAC", + channel: "partner", + rep: "Sora Tanaka", + quantity: 6, + unitPrice: 398, + status: "shipped" + }, + { + id: "ORD-10159", + placedAt: "2024-05-30T11:55:00Z", + customer: "City Power & Light", + product: "Onyx Security", + sku: "SEC-606", + region: "LATAM", + channel: "self-serve", + rep: "Owen Pratt", + quantity: 7, + unitPrice: 840, + status: "paid" + }, + { + id: "ORD-10008", + placedAt: "2024-05-30T13:33:00Z", + customer: "Northwind Traders", + product: "Helix Identity", + sku: "IDN-880", + region: "North America", + channel: "marketplace", + rep: "Hugo Bernard", + quantity: 6, + unitPrice: 239, + status: "pending" + }, + { + id: "ORD-10152", + placedAt: "2024-05-30T14:48:00Z", + customer: "Litware Inc", + product: "Helix Identity", + sku: "IDN-880", + region: "APAC", + channel: "marketplace", + rep: "Aisha Khan", + quantity: 5, + unitPrice: 210, + status: "delivered" + }, + { + id: "ORD-10153", + placedAt: "2024-05-30T19:31:00Z", + customer: "Fabrikam Inc", + product: "Ion Messaging", + sku: "MSG-990", + region: "LATAM", + channel: "direct", + rep: "Priya Nair", + quantity: 8, + unitPrice: 59, + status: "shipped" + }, + { + id: "ORD-10260", + placedAt: "2024-05-30T21:08:00Z", + customer: "Tailspin Toys", + product: "Delta Insights", + sku: "INS-440", + region: "APAC", + channel: "self-serve", + rep: "Dana Wills", + quantity: 6, + unitPrice: 303, + status: "paid" + }, + { + id: "ORD-10018", + placedAt: "2024-05-30T21:18:00Z", + customer: "Litware Inc", + product: "Borealis CRM", + sku: "CRM-210", + region: "APAC", + channel: "marketplace", + rep: "Dana Wills", + quantity: 3, + unitPrice: 888, + status: "refunded" + }, + { + id: "ORD-10041", + placedAt: "2024-05-31T02:23:00Z", + customer: "Contoso Ltd", + product: "Ion Messaging", + sku: "MSG-990", + region: "EMEA", + channel: "direct", + rep: "Lena Fischer", + quantity: 6, + unitPrice: 74, + status: "paid" + }, + { + id: "ORD-10284", + placedAt: "2024-05-31T03:38:00Z", + customer: "Margies Travel", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "North America", + channel: "direct", + rep: "Owen Pratt", + quantity: 5, + unitPrice: 527, + status: "refunded" + }, + { + id: "ORD-10171", + placedAt: "2024-05-31T04:26:00Z", + customer: "Northwind Traders", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "EMEA", + channel: "marketplace", + rep: "Dana Wills", + quantity: 1, + unitPrice: 981, + status: "delivered" + }, + { + id: "ORD-10268", + placedAt: "2024-05-31T13:21:00Z", + customer: "Lucerne Publishing", + product: "Lumen Dashboards", + sku: "DSH-303", + region: "LATAM", + channel: "partner", + rep: "Aisha Khan", + quantity: 7, + unitPrice: 501, + status: "paid" + }, + { + id: "ORD-10311", + placedAt: "2024-05-31T13:24:00Z", + customer: "Blue Yonder Airlines", + product: "Glacier Backup", + sku: "BAK-770", + region: "EMEA", + channel: "direct", + rep: "Aisha Khan", + quantity: 2, + unitPrice: 53, + status: "shipped" + }, + { + id: "ORD-10164", + placedAt: "2024-05-31T13:27:00Z", + customer: "Lucerne Publishing", + product: "Delta Insights", + sku: "INS-440", + region: "EMEA", + channel: "direct", + rep: "Aisha Khan", + quantity: 4, + unitPrice: 305, + status: "delivered" + }, + { + id: "ORD-10139", + placedAt: "2024-05-31T14:15:00Z", + customer: "Lucerne Publishing", + product: "Kelvin Forecasting", + sku: "FCT-202", + region: "APAC", + channel: "marketplace", + rep: "Dana Wills", + quantity: 3, + unitPrice: 976, + status: "delivered" + }, + { + id: "ORD-10192", + placedAt: "2024-05-31T17:42:00Z", + customer: "Litware Inc", + product: "Polaris Reporting", + sku: "RPT-707", + region: "APAC", + channel: "direct", + rep: "Diego Marin", + quantity: 4, + unitPrice: 290, + status: "shipped" + }, + { + id: "ORD-10125", + placedAt: "2024-05-31T20:45:00Z", + customer: "Fabrikam Inc", + product: "Meridian ETL", + sku: "ETL-404", + region: "APAC", + channel: "partner", + rep: "Mateo Russo", + quantity: 3, + unitPrice: 742, + status: "refunded" + }, +] + +// Pre-baked headline metric cards shown before/independently of order rows. +// Values here are illustrative placeholders refined by the backend runnables. +export const seedMetricCards: MetricCardData[] = [ + { id: 'revenue', label: 'Total Revenue', value: 0, unit: 'currency', delta: 0.082, hint: 'Booked revenue across paid, shipped and delivered orders' }, + { id: 'orders', label: 'Orders', value: 0, unit: 'count', delta: 0.041, hint: 'Count of revenue-bearing orders in range' }, + { id: 'aov', label: 'Avg Order Value', value: 0, unit: 'currency', delta: -0.013, hint: 'Total revenue divided by order count' }, + { id: 'units', label: 'Units Sold', value: 0, unit: 'count', delta: 0.067, hint: 'Total units across revenue-bearing orders' }, + { id: 'refunds', label: 'Refunded', value: 0, unit: 'currency', delta: -0.021, hint: 'Revenue lost to refunds in range' }, + { id: 'conversion', label: 'Conversion', value: 0.187, unit: 'percent', delta: 0.009, hint: 'Share of sessions that became orders' } +] + +export function ordersInRange(orders: Order[], from: string, to: string): Order[] { + return orders.filter((order) => { + const day = order.placedAt.slice(0, 10) + return day >= from && day <= to + }) +} + +export function ordersForRegion(orders: Order[], region: string): Order[] { + if (region === 'all') { + return orders + } + return orders.filter((order) => order.region === region) +} + diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/index.tsx b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/index.tsx new file mode 100644 index 0000000000..e0b11ea465 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/index.tsx @@ -0,0 +1,164 @@ +import React, { useEffect, useMemo, useState } from 'react' +import { Sidebar, type DashboardView } from './components/Sidebar' +import { FilterBar } from './components/FilterBar' +import { MetricGrid } from './components/MetricGrid' +import { SummaryPanel } from './components/SummaryPanel' +import { RevenueChart } from './components/RevenueChart' +import { OrdersTable } from './components/OrdersTable' +import { RegionTable } from './components/RegionTable' +import { TopProducts } from './components/TopProducts' +import { EmptyState } from './components/EmptyState' +import { fetchMetrics, fetchOrders, rangeForPreset, type DateRange } from './lib/api' +import { + seedOrders, + seedMetricCards, + ordersInRange, + ordersForRegion, + type Order, + type MetricCardData +} from './data/seedData' + +const App = () => { + const [view, setView] = useState('overview') + const [preset, setPreset] = useState('30d') + const [range, setRange] = useState(rangeForPreset('30d')) + const [region, setRegion] = useState('all') + const [status, setStatus] = useState('all') + + const [metrics, setMetrics] = useState(seedMetricCards) + const [orders, setOrders] = useState(seedOrders) + const [loadingMetrics, setLoadingMetrics] = useState(true) + const [loadingOrders, setLoadingOrders] = useState(true) + const [errored, setErrored] = useState(false) + + useEffect(() => { + let cancelled = false + setLoadingMetrics(true) + fetchMetrics(range, region) + .then((result) => { + if (!cancelled) { + setMetrics(result.cards) + } + }) + .catch(() => { + if (!cancelled) { + setMetrics(seedMetricCards) + } + }) + .finally(() => { + if (!cancelled) { + setLoadingMetrics(false) + } + }) + return () => { + cancelled = true + } + }, [range, region]) + + useEffect(() => { + let cancelled = false + setLoadingOrders(true) + setErrored(false) + fetchOrders(range, region, status) + .then((result) => { + if (!cancelled) { + setOrders(result.orders) + } + }) + .catch(() => { + if (!cancelled) { + // Fall back to the bundled seed data so the dashboard still renders. + const scoped = ordersForRegion( + ordersInRange(seedOrders, range.from, range.to), + region + ).filter((order) => status === 'all' || order.status === status) + setOrders(scoped) + setErrored(true) + } + }) + .finally(() => { + if (!cancelled) { + setLoadingOrders(false) + } + }) + return () => { + cancelled = true + } + }, [range, region, status]) + + const handlePresetChange = (nextPreset: string, nextRange: DateRange) => { + setPreset(nextPreset) + setRange(nextRange) + } + + // Orders that drive the summary/chart panels — the table applies the status + // filter itself, so the panels see the same range/region scoped orders. + const scopedOrders = useMemo(() => orders, [orders]) + + const renderView = () => { + switch (view) { + case 'orders': + return + case 'regions': + return + case 'products': + return + case 'overview': + default: + return ( +
+ + +
+ + +
+ +
+ ) + } + } + + return ( +
+ +
+
+

+ Acme Inc +

+

Operations Console

+

+ Revenue, orders, and regional performance at a glance. +

+
+ +
+ {errored ? ( +
+ Showing locally bundled data — the live feed is unavailable. +
+ ) : null} + {scopedOrders.length === 0 && !loadingOrders ? ( + + ) : ( + renderView() + )} +
+
+
+ ) +} + +export default App diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/lib/aggregations.ts b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/lib/aggregations.ts new file mode 100644 index 0000000000..8c6754b029 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/lib/aggregations.ts @@ -0,0 +1,149 @@ +// Aggregation helpers that turn raw order/metric rows into the numbers the +// dashboard renders. These run client-side after the backend returns rows so +// the UI can re-aggregate instantly when filters change without a round trip. + +import type { Order, OrderStatus } from '../data/seedData' + +export interface RevenueSummary { + totalRevenue: number + totalOrders: number + averageOrderValue: number + unitsSold: number + refundedRevenue: number + netRevenue: number +} + +export interface StatusBreakdown { + status: OrderStatus + orders: number + revenue: number +} + +export interface RegionBreakdown { + region: string + orders: number + revenue: number +} + +export interface DailyPoint { + date: string + revenue: number + orders: number +} + +// Revenue for a single line item. An order's revenue is the unit price times +// the number of units purchased — never the unit price alone. +export function orderRevenue(order: Order): number { + return order.unitPrice +} + +// The statuses that count toward realized (booked) revenue. Refunded and +// cancelled orders are excluded from the headline revenue total. +const REVENUE_STATUSES: OrderStatus[] = ['paid', 'shipped', 'delivered'] + +export function isRevenueStatus(status: OrderStatus): boolean { + return REVENUE_STATUSES.includes(status) +} + +export function sumRevenue(orders: Order[]): number { + return orders + .filter((order) => isRevenueStatus(order.status)) + .reduce((acc, order) => acc + orderRevenue(order), 0) +} + +export function sumUnits(orders: Order[]): number { + return orders + .filter((order) => isRevenueStatus(order.status)) + .reduce((acc, order) => acc + order.quantity, 0) +} + +export function sumRefundedRevenue(orders: Order[]): number { + return orders + .filter((order) => order.status === 'refunded') + .reduce((acc, order) => acc + order.unitPrice * order.quantity, 0) +} + +export function summarizeRevenue(orders: Order[]): RevenueSummary { + const revenueOrders = orders.filter((order) => isRevenueStatus(order.status)) + const totalRevenue = sumRevenue(orders) + const unitsSold = sumUnits(orders) + const refundedRevenue = sumRefundedRevenue(orders) + const totalOrders = revenueOrders.length + return { + totalRevenue, + totalOrders, + averageOrderValue: totalOrders === 0 ? 0 : totalRevenue / totalOrders, + unitsSold, + refundedRevenue, + netRevenue: totalRevenue - refundedRevenue + } +} + +export function breakdownByStatus(orders: Order[]): StatusBreakdown[] { + const map = new Map() + for (const order of orders) { + const existing = map.get(order.status) ?? { + status: order.status, + orders: 0, + revenue: 0 + } + existing.orders += 1 + existing.revenue += order.unitPrice * order.quantity + map.set(order.status, existing) + } + return [...map.values()].sort((a, b) => b.revenue - a.revenue) +} + +export function breakdownByRegion(orders: Order[]): RegionBreakdown[] { + const map = new Map() + for (const order of orders) { + if (!isRevenueStatus(order.status)) { + continue + } + const existing = map.get(order.region) ?? { + region: order.region, + orders: 0, + revenue: 0 + } + existing.orders += 1 + existing.revenue += order.unitPrice * order.quantity + map.set(order.region, existing) + } + return [...map.values()].sort((a, b) => b.revenue - a.revenue) +} + +export function dailyRevenue(orders: Order[]): DailyPoint[] { + const map = new Map() + for (const order of orders) { + if (!isRevenueStatus(order.status)) { + continue + } + const day = order.placedAt.slice(0, 10) + const existing = map.get(day) ?? { date: day, revenue: 0, orders: 0 } + existing.revenue += order.unitPrice * order.quantity + existing.orders += 1 + map.set(day, existing) + } + return [...map.values()].sort((a, b) => a.date.localeCompare(b.date)) +} + +export function topProducts(orders: Order[], limit: number = 5): { product: string; revenue: number }[] { + const map = new Map() + for (const order of orders) { + if (!isRevenueStatus(order.status)) { + continue + } + map.set(order.product, (map.get(order.product) ?? 0) + order.unitPrice * order.quantity) + } + return [...map.entries()] + .map(([product, revenue]) => ({ product, revenue })) + .sort((a, b) => b.revenue - a.revenue) + .slice(0, limit) +} + +export function growthRatio(current: number, previous: number): number { + if (previous === 0) { + return current === 0 ? 0 : 1 + } + return (current - previous) / previous +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/lib/api.ts b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/lib/api.ts new file mode 100644 index 0000000000..a02edc6c73 --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/lib/api.ts @@ -0,0 +1,79 @@ +// Thin wrappers around the app's backend runnables. Centralizing the calls +// here keeps the components free of `backend.*` plumbing and gives one place to +// normalize the request/response shapes. + +import { backend } from 'wmill' +import type { Order, MetricCardData } from '../data/seedData' + +export interface DateRange { + from: string + to: string +} + +export interface MetricsResponse { + cards: MetricCardData[] + generatedAt: string +} + +export interface OrdersResponse { + orders: Order[] + total: number +} + +export interface SummaryResponse { + totalRevenue: number + netRevenue: number + totalOrders: number + averageOrderValue: number + unitsSold: number + refundedRevenue: number + currency: string +} + +export async function fetchMetrics(range: DateRange, region: string): Promise { + return backend.loadMetrics({ from: range.from, to: range.to, region }) +} + +export async function fetchOrders( + range: DateRange, + region: string, + status: string +): Promise { + return backend.loadOrders({ + from: range.from, + to: range.to, + region, + status + }) +} + +export async function fetchSummary(range: DateRange, region: string): Promise { + return backend.computeSummary({ from: range.from, to: range.to, region }) +} + +export async function requestExport( + range: DateRange, + region: string, + format: 'csv' | 'json' +): Promise<{ url: string; rows: number }> { + return backend.exportReport({ from: range.from, to: range.to, region, format }) +} + +export function defaultRange(): DateRange { + return { from: '2024-05-01', to: '2024-05-31' } +} + +export function rangeForPreset(preset: string): DateRange { + switch (preset) { + case '7d': + return { from: '2024-05-25', to: '2024-05-31' } + case '14d': + return { from: '2024-05-18', to: '2024-05-31' } + case '30d': + return { from: '2024-05-01', to: '2024-05-31' } + case 'qtd': + return { from: '2024-04-01', to: '2024-05-31' } + default: + return defaultRange() + } +} diff --git a/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/lib/format.ts b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/lib/format.ts new file mode 100644 index 0000000000..99df7ef98e --- /dev/null +++ b/ai_evals/fixtures/frontend/global/initial/analytics_dashboard/frontend/lib/format.ts @@ -0,0 +1,91 @@ +// Presentation-layer formatting helpers shared across the dashboard. +// Pure functions only — no React, no data fetching. + +export function formatCurrency(amount: number, currency: string = 'USD'): string { + if (!Number.isFinite(amount)) { + return '—' + } + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency, + maximumFractionDigits: 0 + }).format(amount) +} + +export function formatCurrencyPrecise(amount: number, currency: string = 'USD'): string { + if (!Number.isFinite(amount)) { + return '—' + } + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency, + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }).format(amount) +} + +export function formatNumber(value: number): string { + if (!Number.isFinite(value)) { + return '—' + } + return new Intl.NumberFormat('en-US').format(value) +} + +export function formatCompact(value: number): string { + if (!Number.isFinite(value)) { + return '—' + } + return new Intl.NumberFormat('en-US', { + notation: 'compact', + maximumFractionDigits: 1 + }).format(value) +} + +export function formatPercent(ratio: number, digits: number = 1): string { + if (!Number.isFinite(ratio)) { + return '—' + } + return `${(ratio * 100).toFixed(digits)}%` +} + +export function formatSignedPercent(ratio: number, digits: number = 1): string { + const sign = ratio > 0 ? '+' : '' + return `${sign}${formatPercent(ratio, digits)}` +} + +export function formatDate(iso: string): string { + const date = new Date(iso) + if (Number.isNaN(date.getTime())) { + return iso + } + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }) +} + +export function formatDateShort(iso: string): string { + const date = new Date(iso) + if (Number.isNaN(date.getTime())) { + return iso + } + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric' + }) +} + +export function titleCase(value: string): string { + return value + .split(/[\s_-]+/) + .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()) + .join(' ') +} + +export function truncate(value: string, max: number = 32): string { + if (value.length <= max) { + return value + } + return `${value.slice(0, max - 1)}…` +} diff --git a/ai_evals/modes/global.ts b/ai_evals/modes/global.ts index fc080914ef..2cf5413f59 100644 --- a/ai_evals/modes/global.ts +++ b/ai_evals/modes/global.ts @@ -1,4 +1,6 @@ -import { readFile } from "node:fs/promises"; +import { readFile, stat } from "node:fs/promises"; +import { basename } from "node:path"; +import { loadAppFixtureForEval } from "../adapters/frontend/core/app/appFixtureLoader"; import { runGlobalEval, type GlobalLiveEditorDraftFixture, @@ -76,6 +78,28 @@ export function createGlobalModeRunner( } async function loadGlobalInitialFixture(path: string): Promise { + if ((await stat(path)).isDirectory()) { + const { initialFrontend, initialBackend, initialDatatables } = + await loadAppFixtureForEval(path); + const name = basename(path); + return { + workspace: { + apps: [ + { + path: `f/evals/global/${name}`, + summary: name, + value: { + files: initialFrontend, + runnables: initialBackend, + data: initialDatatables, + }, + }, + ], + }, + liveEditorDrafts: [], + }; + } + const parsed = JSON.parse(await readFile(path, "utf8")) as GlobalInitialFixture; return { workspace: parsed.workspace ?? {}, diff --git a/frontend/src/lib/components/copilot/chat/global/core.test.ts b/frontend/src/lib/components/copilot/chat/global/core.test.ts index 95e3195cc6..50ad2547f0 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.test.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.test.ts @@ -1328,6 +1328,378 @@ describe('global AI tools', () => { expect(getBackendDraft('raw_app', 'f/apps/report', { workspace: WORKSPACE })).toBeUndefined() }) + const deployedAppWithFile = (filePath: string, content: string) => + ({ + path: 'f/apps/report', + summary: 'deployed app', + versions: [5], + value: { files: { [filePath]: content }, runnables: {}, data: {} } + }) as any + + it('truncates a large frontend file to a head slice with a paging annotation', async () => { + const lines = Array.from({ length: 2000 }, (_, i) => `line ${i + 1}`) + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce( + deployedAppWithFile('/big.tsx', lines.join('\n')) + ) + + const result = await callGlobalTool('read_app_file', { + path: 'f/apps/report', + file_path: '/big.tsx' + }) + + expect(result).toContain('lines 1-1500 of 2000.') + expect(result).toContain('offset=1501') + expect(result).toContain('line 1500') + expect(result).not.toContain('line 1501') + }) + + it('returns the requested window when offset and limit are given', async () => { + const lines = Array.from({ length: 2000 }, (_, i) => `line ${i + 1}`) + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce( + deployedAppWithFile('/big.tsx', lines.join('\n')) + ) + + const result = await callGlobalTool('read_app_file', { + path: 'f/apps/report', + file_path: '/big.tsx', + offset: 5, + limit: 3 + }) + + expect(result).toContain('lines 5-7 of 2000.') + expect(result).toContain('line 5\nline 6\nline 7') + expect(result).not.toContain('line 4') + expect(result).not.toContain('line 8') + }) + + it('truncates at the character budget for files with very long lines', async () => { + const bigLine = 'x'.repeat(30_000) + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce( + deployedAppWithFile('/min.tsx', [bigLine, bigLine, bigLine].join('\n')) + ) + + const result = await callGlobalTool('read_app_file', { + path: 'f/apps/report', + file_path: '/min.tsx' + }) + + expect(result).toContain( + 'lines 1-3 of 3, truncated to the first 50000 of 90002 chars.' + ) + expect(result).toContain('the file is likely minified') + expect(result.split('\n\n')[1]).toHaveLength(50_000) + }) + + it('caps a single-line generated file at the character budget', async () => { + const bigLine = 'x'.repeat(60_000) + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce( + deployedAppWithFile('/generated.js', bigLine) + ) + + const result = await callGlobalTool('read_app_file', { + path: 'f/apps/report', + file_path: '/generated.js' + }) + + expect(result).toContain( + 'lines 1-1 of 1, truncated to the first 50000 of 60000 chars.' + ) + expect(result).toContain('re-read with a smaller limit') + expect(result.split('\n\n')[1]).toBe('x'.repeat(50_000)) + }) + + it('reports an offset past the end of the file plainly', async () => { + const lines = Array.from({ length: 10 }, (_, i) => `line ${i + 1}`) + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce( + deployedAppWithFile('/small.tsx', lines.join('\n')) + ) + + await expect( + callGlobalTool('read_app_file', { + path: 'f/apps/report', + file_path: '/small.tsx', + offset: 50 + }) + ).resolves.toBe('offset 50 is past the end of the file (10 lines).') + }) + + // Deterministic micro-benchmark: measures how much context the read_app_file cap + // saves over a realistic big-project read pattern, isolated from model + // nondeterminism. "Baseline" is the old behavior (whole file returned on every + // read); "actual" is the current line cap + char budget + paging. Asserting the + // ratio also guards against a future change silently weakening the savings. + it('micro-benchmark: the read cap cuts returned context for a realistic read pattern', async () => { + const bigContent = Array.from({ length: 5000 }, (_, i) => `const row${i} = ${i};`).join('\n') + const minified = 'a'.repeat(200_000) // single long line (e.g. a generated bundle) + const appValue = { + path: 'f/apps/report', + summary: 'big app', + versions: [5], + value: { files: { '/big.tsx': bigContent, '/min.js': minified }, runnables: {}, data: {} } + } as any + const fullSize: Record = { + '/big.tsx': bigContent.length, + '/min.js': minified.length + } + + // A plausible pass over a large app: read a big file head, page deeper into it, + // then hit a generated bundle (capped at the char budget). The old tool returned + // every file in full on every read. + const sequence = [ + { file_path: '/big.tsx' }, // 1. big file head (line cap) + { file_path: '/big.tsx', offset: 1501 }, // 2. next line chunk + { file_path: '/min.js' } // 3. minified bundle head (char budget) + ] + + let baselineChars = 0 + let actualChars = 0 + const perRead: number[] = [] + for (const read of sequence) { + baselineChars += fullSize[read.file_path] + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce(appValue) + const out = await callGlobalTool('read_app_file', { path: 'f/apps/report', ...read }) + actualChars += out.length + perRead.push(out.length) + } + + const reductionPct = Math.round((1 - actualChars / baselineChars) * 100) + // Surfaced when the suite runs so the benchmark is readable, not just asserted. + // eslint-disable-next-line no-console + console.log( + `[read_app_file micro-benchmark] baseline=${baselineChars} chars, actual=${actualChars} chars ` + + `(per-read ${perRead.join(', ')}), reduction=${reductionPct}%` + ) + + // Each capped read is far smaller than the whole file it came from: + expect(perRead[0]).toBeLessThan(fullSize['/big.tsx']) // head slice < whole file + expect(perRead[1]).toBeLessThan(fullSize['/big.tsx']) // a paged line chunk too + expect(perRead[2]).toBeLessThan(51_000) // ~50k char budget + a short annotation + // Overall: well under half the bytes the old tool would have returned. + expect(actualChars).toBeLessThan(baselineChars * 0.5) + }) + + // A multi-file app: the revenue helper is referenced in three frontend files + // and one inline backend runnable; a generated file also mentions it (and must + // be excluded). Mirrors the analytics_dashboard fixture's "symbol spread". + const searchAppValue = () => + ({ + path: 'f/apps/report', + summary: 'search app', + versions: [5], + value: { + files: { + '/lib/aggregations.ts': + 'export function computeRevenue(o) {\n return o.unitPrice\n}\n', + '/components/SummaryPanel.tsx': + 'import { computeRevenue } from "../lib/aggregations"\nconst total = computeRevenue(order)\n', + '/components/OrdersTable.tsx': 'const r = computeRevenue(row)\n// renders revenue\n', + '/styles.css': '.revenue { color: green }\n', + '/wmill.d.ts': 'declare function computeRevenue(o: any): number\n' + }, + runnables: { + computeSummary: { + type: 'inline', + inlineScript: { + language: 'bun', + content: 'export async function main() {\n return computeRevenue\n}\n' + } + } + }, + data: {} + } + }) as any + + it('greps across frontend files and inline runnables, returning file:line rows', async () => { + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce(searchAppValue()) + + const result = await callGlobalTool('search_app', { + path: 'f/apps/report', + query: 'computeRevenue' + }) + + // header counts every match across the (non-generated) files, without echoing the query + expect(result).toMatch(/\d+ match(?:es)? in \d+ files?/) + // frontend rows use read_app_file's leading-slash addressing + expect(result).toContain('/lib/aggregations.ts') + expect(result).toContain('1: export function computeRevenue(o) {') + expect(result).toContain('/components/SummaryPanel.tsx') + // inline runnable rows use the backend//main. addressing + expect(result).toContain('backend/computeSummary/main.ts') + // generated files are never searched + expect(result).not.toContain('/wmill.d.ts') + }) + + it('matches case-insensitively', async () => { + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce(searchAppValue()) + + const result = await callGlobalTool('search_app', { + path: 'f/apps/report', + query: 'COMPUTEREVENUE' // upper-case query still matches computeRevenue + }) + + expect(result).toContain('/lib/aggregations.ts') + expect(result).toContain('export function computeRevenue(o) {') + }) + + it('filters by a basename glob (matches nested files)', async () => { + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce(searchAppValue()) + + const result = await callGlobalTool('search_app', { + path: 'f/apps/report', + query: 'computeRevenue', + file_glob: '*.tsx' + }) + + expect(result).toContain('/components/SummaryPanel.tsx') + expect(result).toContain('/components/OrdersTable.tsx') + expect(result).not.toContain('/lib/aggregations.ts') + expect(result).not.toContain('backend/computeSummary/main.ts') + }) + + it('filters by a path glob (e.g. backend/**)', async () => { + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce(searchAppValue()) + + const result = await callGlobalTool('search_app', { + path: 'f/apps/report', + query: 'computeRevenue', + file_glob: 'backend/**' + }) + + expect(result).toContain('backend/computeSummary/main.ts') + expect(result).not.toContain('/lib/aggregations.ts') + }) + + it('reports zero matches with a hint instead of an empty result', async () => { + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce(searchAppValue()) + + const result = await callGlobalTool('search_app', { + path: 'f/apps/report', + query: 'nonexistent_symbol_xyz' + }) + + expect(result).toContain('No matches') + expect(result).toContain('Try a broader') + }) + + it('truncates very long matching lines to keep results sparse', async () => { + const longLine = `const x = "${'q'.repeat(5000)} computeRevenue"` + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce({ + path: 'f/apps/report', + summary: 'app', + versions: [5], + value: { files: { '/min.js': longLine }, runnables: {}, data: {} } + } as any) + + const result = await callGlobalTool('search_app', { + path: 'f/apps/report', + query: 'computeRevenue' + }) + + expect(result).toContain('[line truncated]') + expect(result.length).toBeLessThan(1000) + }) + + it('caps the number of match rows and says it truncated', async () => { + const manyLines = Array.from({ length: 500 }, (_, i) => `hit ${i}`).join('\n') + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce({ + path: 'f/apps/report', + summary: 'app', + versions: [5], + value: { files: { '/big.tsx': manyLines }, runnables: {}, data: {} } + } as any) + + const result = await callGlobalTool('search_app', { + path: 'f/apps/report', + query: 'hit', + max_matches: 50 + }) + + expect(result).toContain('500 matches') + expect(result).toContain('showing the first 50') + // 50 capped match lines, each rendered with its fixed context window (deduped), + // so the body is bounded near max_matches and nowhere near the 500 total. + const rows = result.split('\n').filter((l) => /^\s+\d+: /.test(l)).length + expect(rows).toBeGreaterThanOrEqual(50) + expect(rows).toBeLessThan(60) + }) + + it('counts every file with a match, even matches past the render cap', async () => { + // The first (sorted) file exhausts max_matches; the later file's match falls + // past the cap but the symbol still lives there, so the header must count it. + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce({ + path: 'f/apps/report', + summary: 'app', + versions: [5], + value: { + files: { '/a.tsx': 'hit\nhit\nhit\nhit\nhit', '/b.tsx': 'hit' }, + runnables: {}, + data: {} + } + } as any) + + const result = await callGlobalTool('search_app', { + path: 'f/apps/report', + query: 'hit', + max_matches: 3 + }) + + expect(result).toContain('6 matches in 2 files') + expect(result).toContain('showing the first 3') + }) + + // Deterministic micro-benchmark: how much context a single search_app call + // saves over locating a symbol by reading the candidate files whole. Baseline + // is the conservative "read only the files that actually contain the symbol" + // path (a model without search must read at least those in full); the real + // saving is larger because, lacking search, a model often reads non-matching + // files too. Isolated from model nondeterminism so it can gate regressions. + it('micro-benchmark: search_app locates a symbol far cheaper than reading files', async () => { + const fileBodies: Record = {} + // 8 component files, 3 of which reference the symbol, each ~120 lines. + for (let f = 0; f < 8; f++) { + const lines = Array.from({ length: 120 }, (_, i) => + f < 3 && i === 60 ? ` return computeRevenue(order${f})` : ` const v${i} = ${i} // padding` + ) + fileBodies[`/components/File${f}.tsx`] = lines.join('\n') + } + const appValue = { + path: 'f/apps/report', + summary: 'big app', + versions: [5], + value: { + files: fileBodies, + runnables: {}, + data: {} + } + } as any + + vi.mocked(AppService.getAppByPath).mockResolvedValueOnce(appValue) + const searchOut = await callGlobalTool('search_app', { + path: 'f/apps/report', + query: 'computeRevenue' + }) + + // Baseline: the bytes a model must pull to gather the same locations by + // reading each matching file in full. + const matchingFiles = Object.entries(fileBodies).filter(([, body]) => + body.includes('computeRevenue') + ) + const baselineChars = matchingFiles.reduce((sum, [, body]) => sum + body.length, 0) + const actualChars = searchOut.length + const reductionPct = Math.round((1 - actualChars / baselineChars) * 100) + // eslint-disable-next-line no-console + console.log( + `[search_app micro-benchmark] baseline=${baselineChars} chars (read ${matchingFiles.length} files whole), ` + + `actual=${actualChars} chars (one search), reduction=${reductionPct}%` + ) + + // The search surfaced exactly the 3 locations… + expect(matchingFiles.length).toBe(3) + expect((searchOut.match(/computeRevenue/g) ?? []).length).toBeGreaterThanOrEqual(3) + // …at a tiny fraction of reading those files whole. + expect(actualChars).toBeLessThan(baselineChars * 0.15) + }) + it('does not persist a raw app draft when patch_app_file validation fails', async () => { vi.mocked(AppService.getAppByPath).mockResolvedValueOnce({ path: 'f/apps/report', diff --git a/frontend/src/lib/components/copilot/chat/global/core.ts b/frontend/src/lib/components/copilot/chat/global/core.ts index e2f3501f1e..90925255d2 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.ts @@ -528,6 +528,43 @@ const readAppFileSchema = z.object({ .string() .describe( 'Frontend file path like /index.tsx, or backend inline runnable path like backend//main.ts (or main.py).' + ), + offset: z + .number() + .int() + .min(1) + .optional() + .describe('1-based line number to start reading from. Use to page through a large file.'), + limit: z + .number() + .int() + .min(1) + .optional() + .describe( + 'Maximum number of lines to return. Large files are truncated by default; pass offset/limit to read a specific line range.' + ) +}) + +const searchAppSchema = z.object({ + path: z.string().describe('Workspace path of the app, e.g. f/folder/name.'), + query: z + .string() + .describe( + 'Literal substring to find across all app files (case-insensitive) — not a regex, so spaces and operators match verbatim. Returns matching file:line rows, not file bodies. To find call sites and skip similarly-named helpers, include the call paren (e.g. "formatCurrency(" matches calls but not "formatCurrencyPrecise"). Then read_app_file to inspect the ranges.' + ), + file_glob: z + .string() + .optional() + .describe( + 'Optional path filter. A pattern without "/" matches the file name anywhere (e.g. "*.tsx"); a pattern with "/" matches the full path (e.g. "/components/*", "backend/**"). Supports * (any chars except /), ** (any chars), and ?.' + ), + max_matches: z + .number() + .int() + .min(1) + .optional() + .describe( + 'Maximum number of matching lines to return (default 100, hard cap 200); each is shown with a few lines of surrounding context, so the output has more rows than this.' ) }) @@ -1400,7 +1437,8 @@ function getAppInstructions(): string { - \`/wmill.d.ts\` (or \`wmill.ts\`) is generated automatically from the backend runnables — never write it directly. - Inline runnables only support \`bun\` or \`python3\` in chat. Path runnables (\`script\`/\`flow\`/\`hubscript\`) reference an existing item. - Use \`deploy_workspace_item\` after explicit user deploy intent. The deploy tool bundles JS/CSS before saving the raw app. -- Use \`read_workspace_item\` with \`type: 'app'\` for a metadata summary (file paths and runnable list, no contents). Use \`read_app_file\` to read an individual file. +- Use \`read_workspace_item\` with \`type: 'app'\` for a metadata summary (file paths and runnable list, no contents). Use \`read_app_file\` to read an individual file; large files are truncated to a head slice, so pass \`offset\`/\`limit\` to page through the rest rather than re-reading the whole file. +- To find where a symbol or string lives across the app, call \`search_app\` (greps every frontend file and inline runnable, returns matching \`file:line\` rows) instead of reading files one by one — then \`read_app_file\` only the ranges you need. The loop is list (\`read_workspace_item\`) → locate (\`search_app\`) → inspect (\`read_app_file\` with \`offset\`/\`limit\`). - Note: the authoring reference below mentions the CLI on-disk layout (\`backend/.\`, \`raw_app.yaml\`, \`sql_to_apply/\`). That layout is only relevant for the terminal workflow — in chat, apps are addressed via the tool surface above. # Windmill raw app authoring reference @@ -1934,13 +1972,24 @@ export const globalTools: Tool<{}>[] = [ def: createToolDef( readAppFileSchema, 'read_app_file', - 'Read one raw app frontend file or inline backend runnable.' + 'Read one raw app frontend file or inline backend runnable. Large files are truncated to a head slice; pass offset/limit to page through the rest.' ), fn: async (ctx) => { const parsed = readAppFileSchema.parse(ctx.args) return readAppFile(parsed, ctx) } }, + { + def: createToolDef( + searchAppSchema, + 'search_app', + "Grep across all of a raw app's frontend files and inline backend runnables in one call. Returns matching file:line rows (capped), not file bodies — use it to locate a symbol or string before read_app_file instead of reading whole files one by one." + ), + fn: async (ctx) => { + const parsed = searchAppSchema.parse(ctx.args) + return searchApp(parsed, ctx) + } + }, { def: createToolDef( writeAppFileSchema, @@ -2108,6 +2157,7 @@ type WriteDraftCtx = { // handlers below would route a backgrounded session's tool call to whatever // session the user happens to be viewing. export type SessionToolHelpers = { sessionId?: string } + export type GlobalToolHelpers = SessionToolHelpers & { testActiveFlow?: (args?: Record) => Promise } @@ -2967,8 +3017,89 @@ async function initApp( })) } +// read_app_file caps: a large frontend file or inline runnable would otherwise +// enter context in full and persist for the rest of the session. Default to a +// head slice with a pointer to page further; the model widens with offset/limit. +// The char budget is a hard ceiling on a single read: a selected line window over +// it is truncated and the model is told to narrow the line range. There is no +// char-level paging, so a single line longer than the budget can't be read past — +// add paging here if minified/long-line files must be fully readable. +const READ_APP_FILE_DEFAULT_LINE_LIMIT = 1500 +const READ_APP_FILE_CHAR_BUDGET = 50_000 + +type AppFileSlice = { + body: string + startLine: number + endLine: number + requestedStartLine: number + totalLines: number + lineWindowChars: number + charTruncated: boolean + truncated: boolean +} + +function sliceAppFileForRead(content: string, offset?: number, limit?: number): AppFileSlice { + const lines = content.split('\n') + const totalLines = lines.length + const requestedStartLine = offset ?? 1 + const start = Math.min(Math.max(requestedStartLine - 1, 0), totalLines) + const lineLimit = limit ?? READ_APP_FILE_DEFAULT_LINE_LIMIT + const end = Math.min(start + lineLimit, totalLines) + const selectedBody = lines.slice(start, end).join('\n') + const lineWindowChars = selectedBody.length + const body = selectedBody.slice(0, READ_APP_FILE_CHAR_BUDGET) + const charTruncated = lineWindowChars > READ_APP_FILE_CHAR_BUDGET + + return { + body, + startLine: start + 1, + endLine: end, + requestedStartLine, + totalLines, + lineWindowChars, + charTruncated, + truncated: start > 0 || end < totalLines || charTruncated + } +} + +function formatAppFileReadRangeLabel(slice: AppFileSlice): string { + const lineRange = `lines ${slice.startLine}-${slice.endLine} of ${slice.totalLines}` + if (!slice.charTruncated) { + return lineRange + } + return `${lineRange}, truncated to the first ${READ_APP_FILE_CHAR_BUDGET} of ${slice.lineWindowChars} chars` +} + +// Small files (returned whole, starting at line 1) keep the raw body so +// patch_app_file's exact-match stays trivial; truncated/windowed reads get a +// one-line annotation describing the range (not part of the file). +function formatAppFileReadResult(slice: AppFileSlice): string { + // offset past the last line: report it plainly instead of a backwards range. + if (slice.requestedStartLine > slice.totalLines) { + return `offset ${slice.requestedStartLine} is past the end of the file (${slice.totalLines} lines).` + } + if (!slice.truncated && slice.startLine === 1) { + return slice.body + } + + let more = '' + if (slice.charTruncated) { + more = ` Reached the ${READ_APP_FILE_CHAR_BUDGET}-char limit; re-read with a smaller limit (fewer lines). If a single line exceeds the limit the file is likely minified and not readable this way.` + } else if (slice.endLine < slice.totalLines) { + more = ` Call read_app_file again with offset=${slice.endLine + 1} to continue.` + } + // No tool-name/path prefix: the model already has them from the call args. The + // range line orients it; the body follows after a blank line. + return `${formatAppFileReadRangeLabel(slice)}.${more}\n\n${slice.body}` +} + async function readAppFile( - args: { path: string; file_path: string }, + args: { + path: string + file_path: string + offset?: number + limit?: number + }, ctx: WriteDraftCtx ): Promise { const { workspace, toolId, toolCallbacks } = ctx @@ -2979,18 +3110,188 @@ async function readAppFile( const value = await loadAppValueForRead(args.path, workspace) + let content: string if (target.kind === 'frontend') { - const content = value.files[target.filePath] - if (content === undefined) { + const frontend = value.files[target.filePath] + if (frontend === undefined) { throw new Error(`Frontend file "${target.filePath}" not found in app "${args.path}".`) } - toolCallbacks.setToolStatus(toolId, { content: `Read ${target.filePath}` }) - return content + content = frontend + } else { + content = getInlineRunnableContent(value, target, args.path).content } - const { content } = getInlineRunnableContent(value, target, args.path) + const slice = sliceAppFileForRead(content, args.offset, args.limit) + toolCallbacks.setToolStatus(toolId, { content: `Read ${target.filePath}` }) - return content + return formatAppFileReadResult(slice) +} + +// search_app caps: a single query must stay sparse and cheap even when it hits a +// minified bundle or a 5k-line data module. Per-line and total-output caps bound +// the result the same way read_app_file's char budget bounds one file read; the +// match cap keeps a broad query from flooding context instead of locating it. +const SEARCH_APP_DEFAULT_MAX_MATCHES = 100 +const SEARCH_APP_MAX_MATCHES_CEILING = 200 +const SEARCH_APP_MAX_LINE_CHARS = 200 +const SEARCH_APP_TOTAL_CHAR_BUDGET = 12_000 +// Fixed surrounding-context window per match, kept off the tool schema to keep it +// lean. Bump if matches need more context than the line ± this. +const SEARCH_APP_CONTEXT_LINES = 2 + +type AppSearchableFile = { filePath: string; content: string } + +// The files search_app scans: frontend files (minus generated ones) plus inline +// backend runnables, each addressed exactly as read_app_file expects so a match +// row's path can be passed straight back to read_app_file. +function collectSearchableAppFiles(value: AppDraftValue): AppSearchableFile[] { + const files: AppSearchableFile[] = [] + for (const [filePath, content] of Object.entries(value.files)) { + if (GENERATED_APP_FILE_PATHS.has(filePath)) continue + if (typeof content === 'string') files.push({ filePath, content }) + } + for (const [key, runnable] of Object.entries(value.runnables)) { + const persisted = runnable as PersistedRunnable | undefined + const content = persisted?.inlineScript?.content + if (typeof content !== 'string') continue + files.push({ filePath: `backend/${key}/main.${getInlineScriptExtension(persisted)}`, content }) + } + return files +} + +// Minimal glob: * = any chars except '/', ** = any chars, ? = single non-slash. +// A pattern without '/' matches the file name only (ripgrep-style), so "*.tsx" +// finds nested files; a pattern with '/' matches the full path. +function appFileMatchesGlob(filePath: string, glob: string): boolean { + const hasSlash = glob.includes('/') + const subject = hasSlash ? filePath : filePath.slice(filePath.lastIndexOf('/') + 1) + const body = glob + .replace(/[.+^${}()|[\]\\]/g, '\\$&') + .replace(/\*\*/g, '\u0000') + .replace(/\*/g, '[^/]*') + .replace(/\u0000/g, '.*') + .replace(/\?/g, '[^/]') + try { + return new RegExp(`^${body}$`).test(subject) + } catch { + return false + } +} + +type AppSearchMatch = { filePath: string; line: number; text: string } + +async function searchApp( + args: { + path: string + query: string + file_glob?: string + max_matches?: number + }, + ctx: WriteDraftCtx +): Promise { + const { workspace, toolId, toolCallbacks } = ctx + const query = args.query + if (query.length === 0) { + throw new Error('search_app requires a non-empty query.') + } + toolCallbacks.setToolStatus(toolId, { + content: `Searching app "${args.path}" for "${query}"...` + }) + + const value = await loadAppValueForRead(args.path, workspace) + const maxMatches = Math.min( + args.max_matches ?? SEARCH_APP_DEFAULT_MAX_MATCHES, + SEARCH_APP_MAX_MATCHES_CEILING + ) + const contextLines = SEARCH_APP_CONTEXT_LINES + const needle = query.toLowerCase() + + let files = collectSearchableAppFiles(value).sort((a, b) => a.filePath.localeCompare(b.filePath)) + if (args.file_glob) { + files = files.filter((f) => appFileMatchesGlob(f.filePath, args.file_glob as string)) + } + + const matches: AppSearchMatch[] = [] + let totalMatchCount = 0 + // Cap on matching LINES, not pushed rows — each match expands to its context + // window, so counting rows would make `max_matches`/"showing the first N" wrong. + let renderedMatchCount = 0 + let fileCount = 0 + let truncated = false + for (const file of files) { + const lines = file.content.split('\n') + let fileHadMatch = false + for (let i = 0; i < lines.length; i++) { + if (!lines[i].toLowerCase().includes(needle)) continue + totalMatchCount++ + // Count the file on its first match, before the render cap, so the + // "N matches in M files" header counts every file with the symbol — not + // only the ones whose matches landed in the rendered slice (find-all-usages). + fileHadMatch = true + if (renderedMatchCount >= maxMatches) { + truncated = true + continue + } + renderedMatchCount++ + const lo = Math.max(0, i - contextLines) + const hi = Math.min(lines.length - 1, i + contextLines) + for (let j = lo; j <= hi; j++) { + matches.push({ filePath: file.filePath, line: j + 1, text: lines[j] }) + } + } + if (fileHadMatch) fileCount++ + } + + if (totalMatchCount === 0) { + toolCallbacks.setToolStatus(toolId, { content: `No matches for "${query}"` }) + return `No matches. Try a broader or differently-spelled term${ + args.file_glob ? ', or drop the file_glob' : '' + }.` + } + + // No tool-name/query prefix: the model already has them from the call args. + const header = `${totalMatchCount} match${ + totalMatchCount === 1 ? '' : 'es' + } in ${fileCount} file${fileCount === 1 ? '' : 's'}${ + truncated ? ` (showing the first ${maxMatches}; narrow with file_glob or a more specific query)` : '' + }` + + const out: string[] = [header] + let currentFile = '' + let budgetSpent = header.length + let budgetHit = false + const seen = new Set() + for (const m of matches) { + // context windows of adjacent matches overlap — show each source line once. + const dedupeKey = `${m.filePath}:${m.line}` + if (seen.has(dedupeKey)) continue + seen.add(dedupeKey) + const text = + m.text.length > SEARCH_APP_MAX_LINE_CHARS + ? `${m.text.slice(0, SEARCH_APP_MAX_LINE_CHARS)}… [line truncated]` + : m.text + const fileHeader = m.filePath === currentFile ? '' : `${m.filePath}\n` + const row = `${fileHeader} ${m.line}: ${text}` + if (budgetSpent + row.length + 1 > SEARCH_APP_TOTAL_CHAR_BUDGET) { + budgetHit = true + break + } + if (fileHeader) currentFile = m.filePath + out.push(row) + budgetSpent += row.length + 1 + } + if (budgetHit) { + out.push( + `… output truncated at the context budget — narrow with file_glob or a more specific query.` + ) + } + + toolCallbacks.setToolStatus(toolId, { + content: `Found ${totalMatchCount} match${totalMatchCount === 1 ? '' : 'es'} in ${fileCount} file${ + fileCount === 1 ? '' : 's' + }` + }) + return out.join('\n') } async function writeAppFile(