Module: Catalog::AmazonCompetitiveSignal

Defined in:
app/services/catalog/amazon_competitive_signal.rb

Overview

Reads the two competitive numbers out of an Amazon buy-box Summary payload
and says which of them is allowed to move a price.

They are not interchangeable, and treating them as one number is what this
module exists to stop:

  • CompetitivePriceThreshold is Amazon's benchmark for what counts as a
    competitive price on the ASIN, derived from the wider market. It is
    evidence, so it binds.
  • SuggestedLowerPricePlusShipping is Amazon asking us to charge less.
    Its whole purpose is to sit below what we currently charge, so it is a
    negotiating position, not evidence of anyone's price. It is advisory.

Until 2026-08-04 five call sites took [threshold, suggestion].compact.min.
Because the suggestion is by construction the lower of the two, it won
whenever Amazon sent one, and the repricer inherited Amazon's opinion instead
of the market's. On PTC120-7W-012-WY (B09YSZ5VVG, catalog item 43151) that
read as a competitor at $63.75 while the real benchmark was $71.25 — matching
Home Depot Canada to the cent — and NumberOfOffers was 1, so there was no
competitor on the listing at all.

It also closed a loop: AmazonBuyBoxService#fair_pricing_correction stepped
the price down toward the suggestion (71.24 → 67.68 → 64.30 over 08-01 and
08-03), and then AmazonPriceRaisingService refused to raise it because the
same suggestion now sat below the price, reporting the block to operators as
"an external retailer has a lower or equal price". 53 automated reductions
across two runs, $356.76 of price removed, 24 of the affected listings with no
competing offer.

Class Method Summary collapse

Class Method Details

.advisory_suggestion(summary) ⇒ Float?

Amazon's suggested lower price. Reportable, never a reason to move a price.

Parameters:

  • summary (Hash, ActiveSupport::HashWithIndifferentAccess, nil)

Returns:

  • (Float, nil)


57
58
59
60
61
62
# File 'app/services/catalog/amazon_competitive_signal.rb', line 57

def advisory_suggestion(summary)
  return nil if summary.blank?

  value = summary.dig(:SuggestedLowerPricePlusShipping, :Amount)&.to_f
  value if value&.positive?
end

.binding_threshold(summary) ⇒ Float?

The competitive number a price decision may act on.

Zero is treated as absent: Amazon omits these keys rather than sending 0,
so a 0.0 here means a malformed or empty payload, and letting it through
would present a $0 floor as the market rate.

Parameters:

  • summary (Hash, ActiveSupport::HashWithIndifferentAccess, nil)

    the
    payload.Summary object. Callers parse with with_indifferent_access;
    a plain string-keyed Hash will not dig correctly.

Returns:

  • (Float, nil)

    nil when Amazon sent no usable benchmark



46
47
48
49
50
51
# File 'app/services/catalog/amazon_competitive_signal.rb', line 46

def binding_threshold(summary)
  return nil if summary.blank?

  value = summary.dig(:CompetitivePriceThreshold, :Amount)&.to_f
  value if value&.positive?
end