/** 前端类型定义 — 与 PRD / API 契约一致 */ import type { Priority1DisplayLine } from "@/modules/priority1/quote-storage"; import type { FlockDisplayLine } from "@/modules/flock/quote-storage"; export type QuotePageStatus = | "idle" | "resolving_address" | "validating" | "processing" | "success" | "fallback" | "manual_followup" | "error" | "expired"; export type DataPageStatus = "loading" | "success" | "empty" | "error"; /** MotherShip 已知 Tab;API 可能出现未注册的新 service_level */ export type KnownServiceLevel = "standard" | "guaranteed" | "dedicated"; export type ServiceLevel = KnownServiceLevel | (string & {}); export type RateOption = "lowest" | "fastest" | "bestValue" | (string & {}); export type UnitWeight = "lb" | "kg"; export type UnitDim = "in" | "cm"; export type DisplayUnit = "imperial" | "metric"; export type CargoType = | "general_freight" | "machinery" | "furniture" | "electronics" | "building_materials" | "auto_parts" | "food_nonperishable" | "apparel" | "other"; export interface AddressInput { street: string; city: string; state: string; zip: string; place_id: string; formatted_address: string; selected_from_suggestions: boolean; mothership_option_id?: string; mothership_display_label?: string; selected_from_mothership?: boolean; } export interface MothershipAddressCandidate { option_id: string; display_label: string; formatted_address: string; street: string; city: string; state: string; zip: string; selectable?: boolean; unavailable_reason?: string; } export interface MothershipCandidatesResult { pickup_candidates: MothershipAddressCandidate[]; delivery_candidates: MothershipAddressCandidate[]; requires_selection: boolean; quote_session_id?: string; } export interface QuoteRequestBody { request_id: string; customer_id: string; pickup_address: AddressInput; delivery_address: AddressInput; weight: { value: number; unit: UnitWeight }; dimensions: { length: number; width: number; height: number; unit: UnitDim; }; pallet_count: number; cargo_type: CargoType; service_level?: ServiceLevel; rate_option?: RateOption; display_unit?: DisplayUnit; quote_session_id?: string; /** MotherShip 登录态:提货附加服务 id(cfs/liftgate/…) */ pickup_accessorials?: string[]; /** MotherShip 登录态:派送附加服务 id */ delivery_accessorials?: string[]; /** MotherShip 登录态:可提货日期 YYYY-MM-DD */ ready_date?: string; /** MotherShip 登录态:可提货时刻,如 3:00 PM */ ready_time?: string; /** MotherShip 登录态:多行货物(与官网 cargo-add-button 对应) */ cargo_lines?: Array<{ cargo_type: string; quantity: number; weight_lb: number; length_in: number; width_in: number; height_in: number; }>; /** MotherShip 登录态:二级 Details 补充字段 */ mothership_details?: { pickup: { company_name?: string; suite?: string; contact_first?: string; contact_last?: string; contact_email?: string; contact_phone?: string; reference?: string; notes?: string; opens_at?: string; closes_at?: string; }; delivery: { company_name?: string; suite?: string; contact_first?: string; contact_last?: string; contact_email?: string; contact_phone?: string; reference?: string; notes?: string; opens_at?: string; closes_at?: string; }; request_delivery_appointment?: boolean; fba_number?: string; fba_po_number?: string; cargo?: Array<{ piece_count_type?: string; piece_count_qty?: number; description?: string; nmfc?: string; hazmat?: boolean; alcohol?: boolean; tobacco?: boolean; }>; }; } export type QuoteStatus = "processing" | "done" | "failed" | "expired"; export type SourceType = "rpa" | "cache" | "stale"; export interface QuoteItem { service_level: ServiceLevel; rate_option: RateOption; carrier: string; transit_days: string; transit_description: string; raw_freight: number; surcharges: number; raw_total: number; markup_percent: number; markup_amount: number; final_total: number; breakdown: Array<{ item: string; amount: number }>; } export type Priority1QuoteLineDetail = Priority1DisplayLine; export interface Priority1QuotePayload { display_mode: "ltl_cards" | "ftl_calendar"; lines: Priority1DisplayLine[]; shipment_meta?: { shipment_type?: string; pickup_date?: string; commodity?: string; origin_zip?: string; destination_zip?: string; weight_lb?: number; }; } export interface FlockQuotePayload { reference?: string | null; lines: FlockDisplayLine[]; shipment_meta?: { pickup_date?: string; origin_zip?: string; destination_zip?: string; total_weight_lb?: number; pallet_count?: number; }; } export interface QuoteDetail { quote_id: string; request_id: string; status: QuoteStatus; valid_until?: string | null; source_type?: SourceType | null; is_realtime?: boolean; confidence_score?: number | null; currency: string; quotes?: QuoteItem[]; priority1?: Priority1QuotePayload; flock?: FlockQuotePayload; error_code?: string | null; error_message?: string | null; /** Flock RPA 执行阶段(processing 时由 Worker 回写) */ rpa_stage?: string | null; rpa_stage_label?: string | null; } export interface CreateQuoteResult { quote_id: string; status: "processing" | "done"; } /** Priority1 模拟填表提交体 */ export interface Priority1QuoteRequestBody { request_id: string; customer_id: string; priority1_input: Record; } /** Flock Freight 查价提交体 */ export interface FlockQuoteRequestBody { request_id: string; customer_id: string; flock_input: Record; } export type HostCustomerStatus = "active" | "disabled"; export type ServiceApiKeySummary = { key_id: string; key_prefix: string; is_active: boolean; permissions: string[]; created_at: string; }; export type HostCustomerRecord = { customer_id: string; name: string; status: HostCustomerStatus; remark: string | null; embed_password_set: boolean; active_api_key: string | null; created_at: string; updated_at: string; api_keys: ServiceApiKeySummary[]; }; export type CreateHostCustomerResponse = HostCustomerRecord & { api_key: string; }; export type MarkupType = "percent" | "fixed"; export interface MarkupConfig { customer_id: string; markup_type: MarkupType; markup_percent: number; markup_fixed_amount: number | null; operator_id: string; remark: string | null; updated_at: string; } export type AlertType = | "PRICE_DEVIATION" | "STALE_FALLBACK" | "RPA_FAILED" | "RPA_CAPTCHA" | "STRUCT_CHANGE" | "SESSION_EXPIRED" | "SECURITY" | "MANUAL_FALLBACK"; export interface AlertRecord { id: string; alert_type: AlertType; quote_id: string | null; cargo_hash: string | null; detail_json: Record | null; status: "open" | "resolved"; resolver_id: string | null; resolved_at: string | null; created_at: string; root_cause?: string; customer_id?: string | null; pickup_customer?: string; pickup_selected?: string; delivery_customer?: string; delivery_selected?: string; cargo_summary?: string; } export type QuoteQueryOutcome = "processing" | "success" | "failed" | "stale"; export interface QuoteQueryLogRecord { quote_id: string; request_id: string; customer_id: string; outcome: QuoteQueryOutcome; failure_reason: string | null; error_code: string | null; source_type: string | null; tier_count: number | null; pickup_customer: string; pickup_selected: string; delivery_customer: string; delivery_selected: string; cargo_summary: string; created_at: string; completed_at: string | null; } export type ErrorCode = | "VALIDATION_FAILED" | "UNAUTHORIZED" | "FORBIDDEN" | "QUOTE_NOT_FOUND" | "RATE_LIMITED" | "QUOTE_UNAVAILABLE" | "QUOTE_TIMEOUT" | "INTERNAL_ERROR"; export interface ApiOk { code: 0; message: string; data: T; } export interface ApiErr { code: ErrorCode | string; message: string; data: null; } export type ApiResponse = ApiOk | ApiErr; export interface AdminUser { user_id: string; role: "admin"; } export interface LoginResponse { token: string; user: AdminUser; } export interface PaginatedResult { list: T[]; total: number; page: number; size: number; } export interface MetricsDashboard { api_success_rate: number; rpa_success_rate: number; realtime_rate: number; cache_hit_rate: number; p95_latency_ms: number; queue_depth: number; open_alerts_count: number; } export interface RpaStatus { workers: Array<{ worker_id: string; last_seen_ms: number | null; paused_seconds: number; online: boolean; }>; queue_depth: number; circuit_open: boolean; success_rate_24h: number; } export interface QueueStats { queue_name: string; waiting: number; active: number; delayed: number; failed: number; completed: number; depth: number; }