Class: Catalog::AmazonBuyBoxService
- Inherits:
-
BaseService
- Object
- BaseService
- Catalog::AmazonBuyBoxService
- Defined in:
- app/services/catalog/amazon_buy_box_service.rb
Overview
The single decision authority for one Amazon listing's Buy Box.
A listing is only ever in one of two situations, and each has exactly one
response:
Lost the Buy Box → corrective action, immediately. An ANY_OFFER_CHANGED
SQS notification tells us within seconds; #correct works the signal ladder
below down to a price that can win it back.
Holding the Buy Box → maintenance, once a day. #maintain creeps the
price toward its target while the raising service's stability and ceiling
gates allow.
Callers name the situation rather than passing a trigger. The notification
knows it is reporting a loss and calls #correct; the daily pass uses
#call, which routes by the listing's own Buy Box status. Nothing takes a
"which caller am I" flag, so the two responses cannot drift apart.
Before this, the rules were spread across the nightly service, an hourly
recovery sweep, and an event handler that could only revert a probationary
raise — so a Buy Box lost at 00:36 CT sat untouched until 06:15.
Mechanics (stepping, undercutting, floors, flagging) stay in
AmazonPriceLoweringService and AmazonPriceRaisingService.
This service decides whether to act and what price to aim at; those decide
how to get there.
Corrective signal ladder — first signal that yields a target wins:
- Amazon's own competitive benchmark (CompetitivePriceThreshold). Amazon
telling us what it considers competitive is the strongest source of
truth we have. Its SuggestedLowerPricePlusShipping is NOT part of this
rung — that field asks us to charge less by construction, so honouring
it walked prices down and then blocked the recovery
(AmazonCompetitiveSignal). - A live competing seller offer on the ASIN (Amazon's own seller IDs
excluded — we don't race Vendor Central down). - An external retailer in the same market with a verified-fresh lower
price (see RETAILER_FRESHNESS). Reference retailers bind when set. - Nothing to go on — hand to the lowering service's Buy Box probe.
Every target is still bounded by the floor
(Models::CatalogItemAmazonHelper#amazon_minimum_seller_allowed_price_with_tax, which carries
MAP), the per-item rule direction, and the step/cooldown limits.
Defined Under Namespace
Constant Summary collapse
- RETAILER_FRESHNESS =
An external retailer price older than this is not evidence of anything.
Borrowed from the refresher rather than restated, so the two freshness
gates cannot drift apart. Retailer::SiblingPriceRefresher::FRESHNESS_THRESHOLD
- LOWERING_COOLDOWN =
Minimum gap between two automated reductions on the same listing.
ANY_OFFER_CHANGED fires on every competitor move (~200/day across the
catalog), so without this an event-driven reaction would walk a price
down all afternoon on a single competitor's testing. Catalog::AmazonPriceLoweringService::PROBING_COOLDOWN_HOURS.hours
- CORRECTIVE_ACTOR =
PaperTrail actor for corrective price changes, so a Buy-Box rescue is
distinguishable from the daily maintenance creep in an item's history. 'Catalog::AmazonBuyBoxService (corrective)'- FAIR_PRICING_ACTOR =
A fair-pricing step down is neither a rescue nor a creep — it happens while
we are winning. Its own actor so "why did this price fall while we held the
Buy Box" is answerable from the item's history alone. 'Catalog::AmazonBuyBoxService (fair pricing)'
Instance Attribute Summary
Attributes inherited from BaseService
Instance Method Summary collapse
-
#call(catalog_item) ⇒ Decision
The daily pass: maintain what is winning, correct what is not.
-
#correct(catalog_item) ⇒ Decision
Corrective.
-
#initialize(lowering_service: nil, raising_service: nil) ⇒ AmazonBuyBoxService
constructor
A new instance of AmazonBuyBoxService.
-
#maintain(catalog_item) ⇒ Decision
Maintenance.
Methods inherited from BaseService
#log_debug, #log_error, #log_info, #log_warning, #logger, #process, #tagged_logger
Constructor Details
#initialize(lowering_service: nil, raising_service: nil) ⇒ AmazonBuyBoxService
Returns a new instance of AmazonBuyBoxService.
84 85 86 87 88 |
# File 'app/services/catalog/amazon_buy_box_service.rb', line 84 def initialize(lowering_service: nil, raising_service: nil) super() @lowering_service = lowering_service || Catalog::AmazonPriceLoweringService.new @raising_service = raising_service || Catalog::AmazonPriceRaisingService.new end |
Instance Method Details
#call(catalog_item) ⇒ Decision
The daily pass: maintain what is winning, correct what is not.
Only the scheduled run should use this. An event knows which situation it
is reporting and must say so by calling #correct directly — routing a
loss through here would let a stale won: false event (or an up_only
item, which ignores the Buy Box) come out the maintenance side and raise
a price in response to a loss.
100 101 102 103 104 105 106 |
# File 'app/services/catalog/amazon_buy_box_service.rb', line 100 def call(catalog_item) # up_only climbs regardless of the Buy Box — a loss is not its concern, so # on the daily pass it maintains either way. return maintain(catalog_item) if catalog_item.is_amz_buy_box_winner || catalog_item.reprice_rule_up_only? correct(catalog_item) end |
#correct(catalog_item) ⇒ Decision
Corrective. The Buy Box is lost, so work the signal ladder down to a price
that can win it back. Meant to run the moment we hear about the loss, and
safe to call on a stale loss event: a rule that cannot lower simply
declines, and nothing here can raise a price.
137 138 139 140 141 142 |
# File 'app/services/catalog/amazon_buy_box_service.rb', line 137 def correct(catalog_item) gate = common_gate(catalog_item) return Decision.new(action: :no_action, reason: gate) if gate correct_unguarded(catalog_item) end |
#maintain(catalog_item) ⇒ Decision
Maintenance. We hold the Buy Box, so the price either has to come down to
Amazon's fair-pricing threshold or creep toward its target — gated by the
rule and the raising service's own stability and ceiling checks. Slow by
design; a daily pass is the natural cadence.
115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'app/services/catalog/amazon_buy_box_service.rb', line 115 def maintain(catalog_item) gate = common_gate(catalog_item) return Decision.new(action: :no_action, reason: gate) if gate correction = fair_pricing_correction(catalog_item) return correction if correction return Decision.new(action: :no_action, reason: :raising_disabled_by_rule) unless catalog_item.reprice_allows_raising? result = @raising_service.process(catalog_item) action = result.action == :price_increased ? :price_raised : :no_action Decision.new(action: action, reason: result.reason, price: result.try(:new_price), source: :maintenance, detail: result) end |