File

src/orders/ingestion/validators/order.validator.ts

Index

Methods

Methods

validate
validate(orders: ExtractedOrder[])
Parameters :
Name Type Optional
orders ExtractedOrder[] No
Returns : ValidationResult
import { ExtractedOrder } from '../ai-extractor/claude.extractor';

export interface ValidationError {
  orderIndex: number;
  field: string;
  message: string;
  severity: 'error' | 'warning';
}

export interface ValidationResult {
  valid: boolean;
  errors: ValidationError[];
  warnings: ValidationError[];
  validOrders: ExtractedOrder[];
  rejectedCount: number;
}

const VALID_VEHICLE_TYPES = [
  'DRY_VAN',
  'REFRIGERATED',
  'FLATBED',
  'TANKER',
  'CURTAIN_SIDE',
  'BOX_TRUCK',
  'SPRINTER',
  'HAZMAT',
];

const VALID_PRIORITIES = ['LOW', 'NORMAL', 'HIGH', 'CRITICAL'];

const MAX_WEIGHT_KG = 44000;

export class OrderValidator {
  validate(orders: ExtractedOrder[]): ValidationResult {
    const errors: ValidationError[] = [];
    const warnings: ValidationError[] = [];
    const validOrders: ExtractedOrder[] = [];
    let rejectedCount = 0;

    for (let i = 0; i < orders.length; i++) {
      const order = orders[i];
      const orderErrors: ValidationError[] = [];

      // Origin is optional — many beer distribution files only have PLANT column
      // which may not always map. Destination is what truly matters.
      if (!order.origin_address) {
        warnings.push({
          orderIndex: i,
          field: 'origin_address',
          message: 'Origin address is missing',
          severity: 'warning',
        });
      }

      if (!order.destination_address) {
        orderErrors.push({
          orderIndex: i,
          field: 'destination_address',
          message: 'Destination address is required',
          severity: 'error',
        });
      }

      // Weight validation
      if (order.weight_kg !== null && order.weight_kg !== undefined) {
        if (order.weight_kg <= 0) {
          warnings.push({
            orderIndex: i,
            field: 'weight_kg',
            message: `Weight is non-positive (${order.weight_kg} kg)`,
            severity: 'warning',
          });
        }
        if (order.weight_kg > MAX_WEIGHT_KG) {
          orderErrors.push({
            orderIndex: i,
            field: 'weight_kg',
            message: `Weight exceeds max of ${MAX_WEIGHT_KG} kg (got ${order.weight_kg} kg)`,
            severity: 'error',
          });
        }
      }

      // Date validation
      if (order.pickup_date && order.delivery_date) {
        const pickup = new Date(order.pickup_date);
        const delivery = new Date(order.delivery_date);
        if (!isNaN(pickup.getTime()) && !isNaN(delivery.getTime())) {
          if (delivery < pickup) {
            warnings.push({
              orderIndex: i,
              field: 'delivery_date',
              message: 'Delivery date is before pickup date',
              severity: 'warning',
            });
          }
        }
      }

      // Vehicle type validation
      if (
        order.vehicle_type &&
        !VALID_VEHICLE_TYPES.includes(order.vehicle_type.toUpperCase())
      ) {
        warnings.push({
          orderIndex: i,
          field: 'vehicle_type',
          message: `Unknown vehicle type: "${order.vehicle_type}"`,
          severity: 'warning',
        });
        // Normalize to null if invalid
        order.vehicle_type = null;
      } else if (order.vehicle_type) {
        order.vehicle_type = order.vehicle_type.toUpperCase();
      }

      // Priority validation
      if (order.priority) {
        order.priority = order.priority.toUpperCase();
        if (!VALID_PRIORITIES.includes(order.priority)) {
          warnings.push({
            orderIndex: i,
            field: 'priority',
            message: `Unknown priority: "${order.priority}", defaulting to NORMAL`,
            severity: 'warning',
          });
          order.priority = 'NORMAL';
        }
      } else {
        order.priority = 'NORMAL';
      }

      // If there are hard errors, reject this order
      if (orderErrors.length > 0) {
        errors.push(...orderErrors);
        rejectedCount++;
      } else {
        validOrders.push(order);
      }
    }

    // Duplicate detection: same origin + dest + weight + pickup date
    const seen = new Map<string, number>();
    for (let i = 0; i < validOrders.length; i++) {
      const o = validOrders[i];
      const key = [
        o.origin_address?.toLowerCase(),
        o.destination_address?.toLowerCase(),
        o.weight_kg,
        o.pickup_date,
      ].join('|');

      if (seen.has(key)) {
        warnings.push({
          orderIndex: i,
          field: '_duplicate',
          message: `Possible duplicate of order ${seen.get(key)! + 1} (same origin, destination, weight, pickup date)`,
          severity: 'warning',
        });
      } else {
        seen.set(key, i);
      }
    }

    return {
      valid: errors.length === 0,
      errors,
      warnings,
      validOrders,
      rejectedCount,
    };
  }
}

results matching ""

    No results matching ""