Class: Shipping::TrackingNumberRepair

Inherits:
Object
  • Object
show all
Defined in:
app/services/shipping/tracking_number_repair.rb

Overview

Suggests the number a hand-keyed tracking number was meant to be.

Every mistake we see in production is one keystroke away from a valid
number: 1Z typed as 12, a stray leading digit (14Z07EH…), a
trailing +, an O for a 0. Rather than guess at intent, generate
the small set of numbers those slips could have come from and let the
carrier's own check digit arbitrate.

The safety property that makes this usable on a shipping identifier:
a repair is offered only when exactly one candidate validates. The
carriers we repair (UPS, FedEx Ground, USPS IMpb, Canada Post,
Purolator) all carry a mod-10 check digit, so a wrong candidate passes
only ~1 time in 10; requiring a unique survivor removes the case where
two plausible readings both check out. Ambiguous input yields nil and
the operator retypes it.

Nothing here rewrites data on its own — TrackingNumberFormatValidator
puts the suggestion in the error message and the operator decides.
Carriers with no check digit (Canpar, Spee-Dee) are format-only, so a
repair there is a weaker claim; the unique-survivor rule still applies
but the operator is the one confirming against the label.

Examples:

Shipping::TrackingNumberRepair.new('UPS', '12299EW0378764725').suggestion
# => "1Z2299EW0378764725"

Constant Summary collapse

CONFUSIONS =

Digit/letter pairs that get confused reading a label or a screen.

{
  '0' => %w[O D], 'O' => %w[0], 'D' => %w[0],
  '1' => %w[I L], 'I' => %w[1], 'L' => %w[1],
  '2' => %w[Z], 'Z' => %w[2],
  '5' => %w[S], 'S' => %w[5],
  '6' => %w[G], 'G' => %w[6],
  '8' => %w[B], 'B' => %w[8]
}.freeze
MAX_LENGTH =

Longest input we'll try to repair. Above this the candidate set stops
being worth generating and the input isn't a parcel number anyway.

40
MARKETPLACE_REPAIR_CARRIERS =

Last-mile carriers a marketplace label may actually be.

['UPS', 'FedEx', 'USPS', 'Amazon Shipping'].freeze

Instance Method Summary collapse

Constructor Details

#initialize(carrier, value) ⇒ TrackingNumberRepair

Returns a new instance of TrackingNumberRepair.

Parameters:

  • carrier (String)

    free-form shipments.carrier value

  • value (String)

    the number as entered



49
50
51
52
# File 'app/services/shipping/tracking_number_repair.rb', line 49

def initialize(carrier, value)
  @carrier = Heatwave::Normalizers.shipping_carrier(carrier.to_s)
  @value = Heatwave::Normalizers.tracking_number(value.to_s).to_s
end

Instance Method Details

#suggestionString?

Returns the unique valid number this was probably meant
to be, or nil when there's no candidate or more than one.

Returns:

  • (String, nil)

    the unique valid number this was probably meant
    to be, or nil when there's no candidate or more than one



56
57
58
59
60
61
62
# File 'app/services/shipping/tracking_number_repair.rb', line 56

def suggestion
  return nil if @value.empty? || @value.length > MAX_LENGTH

  return unique_survivor(MARKETPLACE_REPAIR_CARRIERS.flat_map { |carrier| survivors_for(carrier) }) if TrackingNumberFormatValidator.marketplace_carrier?(@carrier)

  unique_survivor(survivors_for(@carrier))
end