diff --git a/frontend/src/features/shadow-run/composables/useShadowRunJobs.ts b/frontend/src/features/shadow-run/composables/useShadowRunJobs.ts new file mode 100644 index 00000000..94d64f28 --- /dev/null +++ b/frontend/src/features/shadow-run/composables/useShadowRunJobs.ts @@ -0,0 +1,111 @@ +/** + * Shadow Run Jobs Composable + * Fetch and manage shadow run job list + */ + +import { ref, computed } from 'vue' +import type { ShadowRunJob, ShadowRunJobFilter } from '../types' + +export function useShadowRunJobs() { + const jobs = ref([]) + const isLoading = ref(false) + const error = ref(null) + const filter = ref({}) + + // Mock data for demo — replace with actual API call + const mockJobs: ShadowRunJob[] = [ + { + jobId: '893', + modelId: '00000000-0000-0000-0000-000000000001', + modelName: 'Hawkeye-Alpha (v2.1)', + status: 'running', + windowStart: '2024-01-02', + windowEnd: '2024-09-10', + tradingDays: 252, + startedAt: '2026-08-11T08:30:00Z', + progress: 67, + }, + { + jobId: '892', + modelId: '00000000-0000-0000-0000-000000000002', + modelName: 'Falcon-Beta (v1.8)', + status: 'completed', + windowStart: '2024-01-02', + windowEnd: '2024-09-10', + tradingDays: 252, + startedAt: '2026-08-05T10:15:00Z', + completedAt: '2026-08-08T14:22:00Z', + progress: 100, + }, + { + jobId: '891', + modelId: '00000000-0000-0000-0000-000000000003', + modelName: 'Eagle-Gamma (v3.0)', + status: 'failed', + windowStart: '2024-01-02', + windowEnd: '2024-09-10', + tradingDays: 252, + startedAt: '2026-08-03T09:00:00Z', + completedAt: '2026-08-03T12:45:00Z', + progress: 0, + errorMessage: 'Market data fetch timeout (KRX OpenAPI unavailable)', + }, + ] + + const filteredJobs = computed(() => { + let result = jobs.value + + if (filter.value.status) { + result = result.filter(j => j.status === filter.value.status) + } + + if (filter.value.modelId) { + result = result.filter(j => j.modelId === filter.value.modelId) + } + + if (filter.value.search) { + const q = filter.value.search.toLowerCase() + result = result.filter( + j => j.modelName.toLowerCase().includes(q) || j.jobId.includes(q) + ) + } + + return result + }) + + const statusStats = computed(() => ({ + running: jobs.value.filter(j => j.status === 'running').length, + completed: jobs.value.filter(j => j.status === 'completed').length, + failed: jobs.value.filter(j => j.status === 'failed').length, + total: jobs.value.length, + })) + + async function fetchJobs() { + isLoading.value = true + error.value = null + + try { + // Simulate API call delay + await new Promise(resolve => setTimeout(resolve, 500)) + jobs.value = mockJobs + } catch (err) { + error.value = err instanceof Error ? err.message : 'Failed to fetch jobs' + } finally { + isLoading.value = false + } + } + + function setFilter(newFilter: ShadowRunJobFilter) { + filter.value = newFilter + } + + return { + jobs, + filteredJobs, + isLoading, + error, + statusStats, + fetchJobs, + setFilter, + } +} diff --git a/frontend/src/features/shadow-run/pages/ShadowRunQueue.vue b/frontend/src/features/shadow-run/pages/ShadowRunQueue.vue new file mode 100644 index 00000000..0203e1d5 --- /dev/null +++ b/frontend/src/features/shadow-run/pages/ShadowRunQueue.vue @@ -0,0 +1,385 @@ + + + + + diff --git a/frontend/src/features/shadow-run/registry.ts b/frontend/src/features/shadow-run/registry.ts index 93b9a137..0dc8bcab 100644 --- a/frontend/src/features/shadow-run/registry.ts +++ b/frontend/src/features/shadow-run/registry.ts @@ -3,7 +3,7 @@ * Define all screens in the shadow-run feature module */ -import type { KbxScreenDefinition } from '@shared/contracts/kbx-types' +import type { ScreenDefinition } from '@kbx/contracts' export const shadowRunListScreen: KbxScreenDefinition = { screenId: 'model-ops.shadow-run.list', diff --git a/frontend/src/features/shadow-run/types/index.ts b/frontend/src/features/shadow-run/types/index.ts new file mode 100644 index 00000000..da948b8f --- /dev/null +++ b/frontend/src/features/shadow-run/types/index.ts @@ -0,0 +1,23 @@ +/** + * Shadow Run Feature Types + */ + +export interface ShadowRunJob { + jobId: string + modelId: string + modelName: string + status: 'pending' | 'running' | 'completed' | 'failed' + windowStart: string + windowEnd: string + tradingDays: number + startedAt: string + completedAt?: string + progress: number + errorMessage?: string +} + +export interface ShadowRunJobFilter { + status?: string + modelId?: string + search?: string +}