import type { SchedulingSuggestion, CandidateVehicle } from './types'; function csvCell(v: string | number | null | undefined): string { if (v === null || v === undefined) return ''; const s = typeof v === 'number' ? String(v) : v; if (/[",\n\r]/.test(s)) return `"${s.replace(/"/g, '""')}"`; return s; } function pickTopCandidate(s: SchedulingSuggestion): CandidateVehicle | null { if (s.candidates.length === 0) return null; const sameRegion = s.candidates.filter(c => c.isSameRegion); const pool = sameRegion.length > 0 ? sameRegion : s.candidates; return pool.find(c => c.canQualifyAfterSwap) ?? pool[0]; } function pctString(rate: number): string { return (rate * 100).toFixed(1) + '%'; } function typeLabel(s: SchedulingSuggestion): string { return s.type === 'replace_qualified' ? '里程高·换下' : '里程低·换走'; } const HEADERS = [ '车牌号', '业务部门', '业务负责人', '客户', '车型', '运营区域', '调度类型', '当前年里程(km)', '年度考核(km)', '年度完成率', '客户30日均(km)', '客户7日均(km)', '剩余天数', '最优候选车牌', '候选当前里程(km)', '候选替换后预估(km)', '候选可达标', '候选区域', '干预状态', ] as const; export function buildSuggestionsCsv(suggestions: SchedulingSuggestion[]): string { const rows: string[] = [HEADERS.map(csvCell).join(',')]; for (const s of suggestions) { const v = s.currentVehicle; const top = pickTopCandidate(s); const notifStatus = s.candidates.find(c => c.notificationStatus === 'executed') ? '已执行' : s.candidates.find(c => c.notificationStatus === 'sent') ? '待执行' : ''; rows.push([ csvCell(v.plateNumber), csvCell(v.department ?? ''), csvCell(v.manager ?? ''), csvCell(v.customer ?? ''), csvCell(v.vehicleType), csvCell(v.region), csvCell(typeLabel(s)), csvCell(Math.round(v.currentYearMileage)), csvCell(Math.round(v.yearTarget)), csvCell(pctString(v.completionRate)), csvCell(Math.round(v.customerAvgDaily)), csvCell(Math.round(v.customerAvgDaily7d)), csvCell(v.daysLeft), csvCell(top?.plateNumber ?? ''), csvCell(top ? Math.round(top.totalMileage) : ''), csvCell(top ? Math.round(top.predictedAfterSwap) : ''), csvCell(top ? (top.canQualifyAfterSwap ? '是' : '否') : ''), csvCell(top?.region ?? ''), csvCell(notifStatus), ].join(',')); } return rows.join('\r\n'); } export function downloadCsv(filename: string, csv: string): void { // UTF-8 BOM so Excel opens Chinese characters correctly const blob = new Blob(['\uFEFF', csv], { type: 'text/csv;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } export function exportSuggestionsCsv(suggestions: SchedulingSuggestion[], prefix = '调度建议'): void { const now = new Date(); const y = now.getFullYear(); const m = String(now.getMonth() + 1).padStart(2, '0'); const d = String(now.getDate()).padStart(2, '0'); const hh = String(now.getHours()).padStart(2, '0'); const mm = String(now.getMinutes()).padStart(2, '0'); const csv = buildSuggestionsCsv(suggestions); downloadCsv(`${prefix}_${y}${m}${d}_${hh}${mm}.csv`, csv); }