export type FetchRequestStatus = | "pending" | "processing" | "paused" | "raw_expenses_done" | "enriched_done" | "completed" | "failed"; export interface FileSource { path: string; format: string; raw_lines?: string[]; txn_blocks?: Record; txn_dicts?: Record[]; txn_dict_count?: number; txn_dicts_count?: number; } export interface EmailSource { format: string; from_email?: string; subject?: string; raw_terms?: string[]; txn_dict_count?: number; txn_dicts_count?: number; } export interface FetchRequestCreate { source: FileSource | EmailSource; account_name: string; payor_username?: string; start_date?: string; end_date?: string; } export interface FetchRequestUpdate { status?: FetchRequestStatus; error_message?: string | null; } export interface FetchRequest extends FetchRequestCreate { id: string; status: FetchRequestStatus; fingerprint: string; completed_at?: string | null; error_message?: string | null; retry_count?: number; created_at: string; } export interface UploadResult { original_filename: string; saved_as: string; content_type: string; url: string; absolute_path: string; } export interface AmbiguityCandidate { amount: number; balance: number; } export interface PendingAmbiguity { id: string; fetch_request: string; step_index?: number; line: string; ocr_amount: number; ocr_balance: number; prev_balance: number; candidates: AmbiguityCandidate[]; chosen?: AmbiguityCandidate | null; resolved_at?: string | null; status: "pending" | "resolved"; created_at: string; } export interface ResolveAmbiguityPayload { chosen: { amount: number; balance: number; }; } export type SSEEventStep = | "load_content" | "raw_lines" | "txn_blocks" | "txn_dicts" | "resume_extract" | "extract" | "paused" | "complete" | "enrich" | "save_expenses" | "pipeline"; export type SSEEventStatus = | "started" | "completed" | "skipped" | "paused" | "progress" | "failed"; export interface ProgressMessage { lines?: number; blocks?: number; count?: number; unit?: string; raw_ocr_line?: string; error?: string; } export interface SSEEvent { step: SSEEventStep; status: SSEEventStatus; message: ProgressMessage; } export interface FetchRequestFilters { status?: FetchRequestStatus[]; account_name?: string; source_type?: "file" | "email"; } export function formatApiError(err: any): string { if (!err?.response) return err?.message || "Request failed"; const data = err.response.data; const status = err.response.status; if (status === 422 && Array.isArray(data?.detail)) { return data.detail.map((d: any) => { const field = d.loc?.filter((s: string) => s !== "body").pop() || "field"; if (d.type === "value_error.missing") return `Missing: ${field}`; return `${field}: ${d.msg}`; }).join("; "); } if (typeof data?.detail === "string") return data.detail; return `Request failed (${status})`; } export const RETRY_MAX = 3;