import type { ModelCategoryId, ModelRecord, ModelStatus, MonitoringRow, RankingStatus, } from "~/features/operations/modelData"; export type OperationsApiMode = "api"; export type OperationsModelListParams = { workspaceId?: string; bank?: string; category?: ModelCategoryId; status?: ModelStatus; keyword?: string; signal?: AbortSignal; }; export type OperationsModelDto = { model_instance_id: string; bank_name: string; model_category: ModelCategoryId; model_name: string; model_id: string; model_version: string; model_status: ModelStatus; last_iteration_date: string; ranking_result: RankingStatus; ks: number; psi: number; ks_mom_drop: number; secondary_hits_6m: number; is_wuji_bank: boolean; last_processed_at: string | null; previous_advice: string; common_model_name: string | null; }; export type MonthlyMonitoringResultDto = { model_instance_id: string; monitor_month: string; ranking_result: RankingStatus; ks: number; psi: number; ks_mom_drop: number; secondary_hits_6m: number; }; export type WorkbenchTone = "normal" | "warning" | "danger" | "primary"; export type OperationsWorkbenchKpi = { key: string; label: string; value: number; detail: string; target: string; tone: WorkbenchTone; }; export type OperationsWorkbenchItem = { id: string; tone: WorkbenchTone; title: string; detail: string; action_label: string; target: string; }; export type OperationsWorkbenchActivity = { occurred_at: string; actor: string; text: string; }; export type OperationsWorkbenchDto = { role: "admin" | "model_team" | "business_team"; alerts: string[]; kpis: OperationsWorkbenchKpi[]; todos: OperationsWorkbenchItem[]; watches: OperationsWorkbenchItem[]; activities: OperationsWorkbenchActivity[]; }; type ApiEnvelope = { data: T; meta?: Record; }; export class OperationsApiError extends Error { readonly status: number; readonly code?: string; constructor(message: string, status: number, code?: string) { super(message); this.name = "OperationsApiError"; this.status = status; this.code = code; } } export const operationsApiMode: OperationsApiMode = "api"; const API_BASE = "/api/v1/operations"; function requireWorkspaceId(workspaceId?: string): string { if (workspaceId) return workspaceId; throw new OperationsApiError("请先选择项目空间", 400, "WORKSPACE_REQUIRED"); } async function request(path: string, init: RequestInit = {}): Promise { const response = await fetch(`${API_BASE}${path}`, { ...init, credentials: "same-origin", headers: { Accept: "application/json", ...init.headers, }, }); const payload = await response.json().catch(() => ({})) as ApiEnvelope & { detail?: string | { code?: string; message?: string }; }; if (!response.ok) { const message = typeof payload.detail === "string" ? payload.detail : payload.detail?.message ?? `请求失败(HTTP ${response.status})`; throw new OperationsApiError(message, response.status, typeof payload.detail === "object" ? payload.detail.code : undefined); } return payload.data; } function toModelRecord(dto: OperationsModelDto): ModelRecord { return { bank: dto.bank_name, category: dto.model_category, name: dto.model_name, modelId: dto.model_id, version: dto.model_version, status: dto.model_status, iteratedAt: dto.last_iteration_date, ranking: dto.ranking_result, ks: dto.ks, psi: dto.psi, ksDrop: dto.ks_mom_drop, secondaryHits: dto.secondary_hits_6m, wuji: dto.is_wuji_bank, processedAt: dto.last_processed_at, previousAdvice: dto.previous_advice, commonModel: dto.common_model_name, }; } export async function listOperationsModels(params: OperationsModelListParams = {}): Promise { const query = new URLSearchParams(); query.set("workspace_id", requireWorkspaceId(params.workspaceId)); if (params.bank) query.set("bank", params.bank); if (params.category) query.set("category", params.category); if (params.status) query.set("status", params.status); if (params.keyword) query.set("keyword", params.keyword); const suffix = query.size ? `?${query.toString()}` : ""; const data = await request(`/models${suffix}`, { signal: params.signal }); return data.map(toModelRecord); } export async function getOperationsModel( modelId: string, workspaceId?: string, signal?: AbortSignal, ): Promise { const query = new URLSearchParams({ workspace_id: requireWorkspaceId(workspaceId) }); const data = await request(`/models/${encodeURIComponent(modelId)}?${query.toString()}`, { signal }); return toModelRecord(data); } export async function getMonthlyMonitoringResult( modelId: string, month: string, workspaceId?: string, signal?: AbortSignal, ): Promise { const model = await getOperationsModel(modelId, workspaceId, signal); const query = new URLSearchParams({ month, workspace_id: requireWorkspaceId(workspaceId), }); const data = await request( `/models/${encodeURIComponent(modelId)}/monitor-results?${query.toString()}`, { signal }, ); return { ...model, monitorMonth: data.monitor_month, ranking: data.ranking_result, ks: data.ks, psi: data.psi, ksDrop: data.ks_mom_drop, secondaryHits: data.secondary_hits_6m, }; } export async function getOperationsWorkbench( workspaceId?: string, signal?: AbortSignal, ): Promise { const query = new URLSearchParams({ workspace_id: requireWorkspaceId(workspaceId) }); return request(`/workbench?${query.toString()}`, { signal }); }