#!/usr/bin/env tsx /** * 履约状态只读审计。 * * 用法: * npm run audit:fulfillment * npm run audit:fulfillment -- --strict * * --strict 会在关键异常大于 0 时返回失败,适合部署后和生产巡检。 */ import { closeDb, query } from '../src/db/client.js' const strict = process.argv.includes('--strict') type CountRow = { label: string count: number } async function loadCounts(): Promise { const result = await query<{ pending_items: number classification: number configuration: number carrier: number manual_review: number after_sales_pending: number state_inconsistent: number assigned_missing_carrier: number }>( ` SELECT COUNT(*) FILTER (WHERE issue_code <> '')::int AS pending_items, COUNT(*) FILTER (WHERE issue_category = 'classification')::int AS classification, COUNT(*) FILTER (WHERE issue_category = 'configuration')::int AS configuration, COUNT(*) FILTER (WHERE issue_category = 'carrier')::int AS carrier, COUNT(*) FILTER (WHERE issue_category = 'manual_review')::int AS manual_review, COUNT(*) FILTER ( WHERE issue_category = 'after_sales' AND fulfillment_status = 'pending' )::int AS after_sales_pending, COUNT(*) FILTER (WHERE issue_code = 'state_inconsistent')::int AS state_inconsistent, COUNT(*) FILTER ( WHERE fulfillment_status = 'assigned' AND carrier_status = 'missing' )::int AS assigned_missing_carrier FROM v_fulfillment_item_state `, ) const row = result.rows[0] return [ { label: '待处理订单项', count: Number(row?.pending_items || 0) }, { label: '未分类', count: Number(row?.classification || 0) }, { label: '配置待补', count: Number(row?.configuration || 0) }, { label: '载体异常', count: Number(row?.carrier || 0) }, { label: '人工复核', count: Number(row?.manual_review || 0) }, { label: '售后关闭仍进入履约队列', count: Number(row?.after_sales_pending || 0) }, { label: '状态不一致', count: Number(row?.state_inconsistent || 0) }, { label: '已分配但无有效载体', count: Number(row?.assigned_missing_carrier || 0) }, ] } async function loadIssueBreakdown(): Promise { const result = await query<{ label: string; count: number }>( ` SELECT issue_code AS label, COUNT(*)::int AS count FROM v_fulfillment_item_state WHERE issue_code <> '' GROUP BY issue_code ORDER BY count DESC, issue_code `, ) return result.rows } async function loadRecentDuplicateAttempts(): Promise { const result = await query<{ label: string; count: number }>( ` SELECT '近 24 小时重复尝试订单项' AS label, COUNT(*)::int AS count FROM ( SELECT order_item_id, unit_index FROM fulfillment_tasks WHERE created_at >= NOW() - INTERVAL '24 hours' GROUP BY order_item_id, unit_index HAVING COUNT(*) > 1 ) duplicate `, ) return result.rows } async function loadShadowModelDiffs(): Promise { const result = await query<{ missing_fulfillments: number fulfillment_state_diffs: number missing_attempts: number missing_open_issues: number unlinked_events: number }>( ` SELECT COUNT(*) FILTER (WHERE fulfillment.id IS NULL)::int AS missing_fulfillments, COUNT(*) FILTER ( WHERE fulfillment.id IS NOT NULL AND ROW( fulfillment.route_status, fulfillment.channel, fulfillment.carrier_status, fulfillment.current_attempt_type, fulfillment.current_attempt_source_id, fulfillment.issue_code, fulfillment.issue_category ) IS DISTINCT FROM ROW( CASE WHEN state.route_status = 'configured' THEN 'configured' ELSE 'unrouted' END, state.intended_channel, state.carrier_status, state.current_attempt_type, NULLIF(state.current_attempt_id, 0), state.issue_code, state.issue_category ) )::int AS fulfillment_state_diffs, ( SELECT COUNT(*)::int FROM ( SELECT task.order_item_id FROM fulfillment_tasks task JOIN fulfillments fulfillment ON fulfillment.order_item_id = task.order_item_id WHERE NOT EXISTS ( SELECT 1 FROM fulfillment_attempts attempt WHERE attempt.attempt_type = 'task' AND attempt.source_id = task.id ) UNION ALL SELECT work_order.order_item_id FROM work_orders work_order JOIN fulfillments fulfillment ON fulfillment.order_item_id = work_order.order_item_id WHERE work_order.order_item_id IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM fulfillment_attempts attempt WHERE attempt.attempt_type = 'work_order' AND attempt.source_id = work_order.id ) ) missing )::int AS missing_attempts, COUNT(*) FILTER ( WHERE state.issue_code <> '' AND NOT EXISTS ( SELECT 1 FROM fulfillment_issues issue WHERE issue.fulfillment_id = fulfillment.id AND issue.issue_code = state.issue_code AND issue.status <> 'resolved' ) )::int AS missing_open_issues, ( SELECT COUNT(*)::int FROM fulfillment_state_events event JOIN v_fulfillment_item_state active_state ON active_state.order_item_id = event.order_item_id WHERE event.fulfillment_id IS NULL ) AS unlinked_events FROM v_fulfillment_item_state state LEFT JOIN fulfillments fulfillment ON fulfillment.order_item_id = state.order_item_id `, ) const row = result.rows[0] return [ { label: '影子履约缺失', count: Number(row?.missing_fulfillments || 0) }, { label: '影子履约状态差异', count: Number(row?.fulfillment_state_diffs || 0) }, { label: '影子执行尝试缺失', count: Number(row?.missing_attempts || 0) }, { label: '影子开放异常缺失', count: Number(row?.missing_open_issues || 0) }, { label: '履约事件未关联影子记录', count: Number(row?.unlinked_events || 0) }, ] } async function main(): Promise { const [counts, issues, duplicates, shadowDiffs] = await Promise.all([ loadCounts(), loadIssueBreakdown(), loadRecentDuplicateAttempts(), loadShadowModelDiffs(), ]) console.info('[fulfillment-audit] 总览') for (const row of counts) { console.info(` ${row.label}: ${row.count}`) } console.info('[fulfillment-audit] 异常分布') if (issues.length === 0) { console.info(' 无') } else { for (const row of issues) { console.info(` ${row.label}: ${row.count}`) } } console.info('[fulfillment-audit] 尝试审计') for (const row of duplicates) { console.info(` ${row.label}: ${row.count}`) } console.info('[fulfillment-audit] 影子模型差异') for (const row of shadowDiffs) { console.info(` ${row.label}: ${row.count}`) } const critical = new Map(counts.map((row) => [row.label, Number(row.count || 0)])) const duplicateCount = Number(duplicates[0]?.count || 0) const shadowDiffCount = shadowDiffs.reduce((sum, row) => sum + Number(row.count || 0), 0) const hasCritical = (critical.get('状态不一致') || 0) > 0 || (critical.get('售后关闭仍进入履约队列') || 0) > 0 || (critical.get('已分配但无有效载体') || 0) > 0 || duplicateCount > 0 || shadowDiffCount > 0 if (strict && hasCritical) { process.exitCode = 1 console.error('[fulfillment-audit] strict 模式检测到关键异常') } } try { await main() } finally { await closeDb() }