Class: Retailer::SiblingPriceRefresher
- Inherits:
-
Object
- Object
- Retailer::SiblingPriceRefresher
- Includes:
- CatalogConstants
- Defined in:
- app/services/retailer/sibling_price_refresher.rb
Overview
Service to synchronously refresh retailer prices for sibling catalog items.
Used by the repricer to ensure fresh external price data before deciding
whether to raise Amazon prices.
This uses the REALTIME (synchronous) Oxylabs API, not the batch webhook system.
It should only be called for items that are candidates for price increases
to minimize API calls.
Defined Under Namespace
Classes: Result
Constant Summary collapse
- MAX_SIBLINGS_TO_PROBE =
Maximum sibling items to probe in one call (to limit API costs)
10- FRESHNESS_THRESHOLD =
Only refresh probes that are older than this threshold.
Bumped from 6h to 24h in May 2026 after audit showed the 6h window was
being defeated by the ~10–15% probe failure rate: items that returned
failed/not_foundfrom the nightly batch checker had no
retailer_price_updated_atorurl_last_checkedupdate, so they were
treated as stale and re-probed by every Amazon pricing run. With
WebhookResultProcessornow stampingurl_last_checkedon every outcome
and the threshold at 24h, a single successful nightly batch satisfies
freshness for the next day's automation. Chronic-failure items are
bounded separately byRetailer::ProbeAutoSkipper. 24.hours
Constants included from CatalogConstants
CatalogConstants::ALL_MAIN_CATALOG_IDS, CatalogConstants::AMAZON_CATALOG_IDS, CatalogConstants::AMAZON_CA_CATALOG_IDS, CatalogConstants::AMAZON_EU_CATALOG_IDS, CatalogConstants::AMAZON_NA_SELLER_IDS, CatalogConstants::AMAZON_SC_BE_CATALOG_ID, CatalogConstants::AMAZON_SC_CATALOG_IDS, CatalogConstants::AMAZON_SC_CA_CATALOG_ID, CatalogConstants::AMAZON_SC_DE_CATALOG_ID, CatalogConstants::AMAZON_SC_ES_CATALOG_ID, CatalogConstants::AMAZON_SC_FR_CATALOG_ID, CatalogConstants::AMAZON_SC_IT_CATALOG_ID, CatalogConstants::AMAZON_SC_NL_CATALOG_ID, CatalogConstants::AMAZON_SC_PL_CATALOG_ID, CatalogConstants::AMAZON_SC_SE_CATALOG_ID, CatalogConstants::AMAZON_SC_UK_CATALOG_ID, CatalogConstants::AMAZON_SC_US_CATALOG_ID, CatalogConstants::AMAZON_SELLER_IDS, CatalogConstants::AMAZON_US_CATALOG_IDS, CatalogConstants::AMAZON_VC_CATALOG_IDS, CatalogConstants::AMAZON_VC_CA_CATALOG_ID, CatalogConstants::AMAZON_VC_CA_CATALOG_IDS, CatalogConstants::AMAZON_VC_DIRECT_FULFILLMENT_CATALOG_IDS, CatalogConstants::AMAZON_VC_US_CATALOG_IDS, CatalogConstants::AMAZON_VC_US_WASN4_CATALOG_ID, CatalogConstants::AMAZON_VC_US_WAX7V_CATALOG_ID, CatalogConstants::AMAZON_VC_WAT0F_CA_CATALOG_ID, CatalogConstants::AMAZON_VC_WAT4D_CA_CATALOG_ID, CatalogConstants::AMAZON_VENDOR_CODE_TO_CATALOG_ID, CatalogConstants::BESTBUY_CANADA, CatalogConstants::BUILD_COM, CatalogConstants::CANADIAN_TIRE, CatalogConstants::CA_CATALOG_ID, CatalogConstants::COSTCO_CANADA, CatalogConstants::COSTCO_CATALOGS, CatalogConstants::COSTCO_USA, CatalogConstants::EU_CATALOG_ID, CatalogConstants::HOME_DEPOT_CANADA, CatalogConstants::HOME_DEPOT_CATALOGS, CatalogConstants::HOME_DEPOT_USA, CatalogConstants::HOUZZ, CatalogConstants::LOCALE_TO_CATALOG, CatalogConstants::LOWES_CANADA, CatalogConstants::LOWES_USA, CatalogConstants::RONA_CANADA, CatalogConstants::US_CATALOG_ID, CatalogConstants::WALMART_CATALOGS, CatalogConstants::WALMART_SELLER_CANADA, CatalogConstants::WALMART_SELLER_USA, CatalogConstants::WAYFAIR_CANADA, CatalogConstants::WAYFAIR_CATALOGS, CatalogConstants::WAYFAIR_GERMANY, CatalogConstants::WAYFAIR_USA
Instance Attribute Summary collapse
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
-
#price_checker ⇒ Object
readonly
Returns the value of attribute price_checker.
Instance Method Summary collapse
-
#initialize(options = {}) ⇒ SiblingPriceRefresher
constructor
A new instance of SiblingPriceRefresher.
-
#refresh_for(catalog_item, force: false) ⇒ Result
Refresh sibling retailer prices for a given catalog item.
Methods included from CatalogConstants
amazon_catalog?, amazon_seller_catalog?, costco_catalog?, home_depot_catalog?, walmart_catalog?, wayfair_catalog?
Constructor Details
#initialize(options = {}) ⇒ SiblingPriceRefresher
Returns a new instance of SiblingPriceRefresher.
46 47 48 49 |
# File 'app/services/retailer/sibling_price_refresher.rb', line 46 def initialize( = {}) @price_checker = [:price_checker] || Retailer::PriceChecker.new @logger = [:logger] || Rails.logger end |
Instance Attribute Details
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
44 45 46 |
# File 'app/services/retailer/sibling_price_refresher.rb', line 44 def logger @logger end |
#price_checker ⇒ Object (readonly)
Returns the value of attribute price_checker.
44 45 46 |
# File 'app/services/retailer/sibling_price_refresher.rb', line 44 def price_checker @price_checker end |
Instance Method Details
#refresh_for(catalog_item, force: false) ⇒ Result
Refresh sibling retailer prices for a given catalog item.
Only refreshes items from catalogs with external_price_check_enabled.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'app/services/retailer/sibling_price_refresher.rb', line 57 def refresh_for(catalog_item, force: false) item_id = catalog_item.item_id store_id = catalog_item.catalog.store_id unless item_id @logger.warn "[SiblingPriceRefresher] No item_id for catalog_item #{catalog_item.id}" return Result.new(refreshed_count: 0, lowest_price: nil, prices: [], errors: ['No item_id']) end # Find sibling catalog items in non-Amazon catalogs with price checking enabled siblings = find_sibling_catalog_items(item_id, store_id, catalog_item.catalog_id) if siblings.empty? @logger.info "[SiblingPriceRefresher] No sibling catalog items found for item #{item_id}" return Result.new(refreshed_count: 0, lowest_price: nil, prices: [], errors: []) end # Filter to only items that need refresh (stale data) to_refresh = force ? siblings : siblings.select { |s| needs_refresh?(s) } @logger.info "[SiblingPriceRefresher] Found #{siblings.size} siblings, #{to_refresh.size} need refresh" # Limit to MAX_SIBLINGS_TO_PROBE to control costs to_refresh = to_refresh.first(MAX_SIBLINGS_TO_PROBE) # Synchronously probe each sibling refreshed_count = 0 prices = [] errors = [] to_refresh.each do |sibling| probe = @price_checker.check(sibling) if probe&.price_captured? refreshed_count += 1 prices << { catalog_item_id: sibling.id, catalog_name: sibling.catalog.name, price: probe.price, currency: probe.currency } @logger.info "[SiblingPriceRefresher] Refreshed #{sibling.catalog.name}: $#{probe.price}" elsif probe @logger.debug "[SiblingPriceRefresher] Probe completed but no price: #{probe.status}" end rescue StandardError => e @logger.error "[SiblingPriceRefresher] Error probing #{sibling.id}: #{e.}" errors << "#{sibling.catalog.name}: #{e.}" end # Get the lowest price from refreshed items + existing fresh data all_prices = collect_all_sibling_prices(item_id, store_id, catalog_item.catalog_id) lowest_price = all_prices.min Result.new( refreshed_count: refreshed_count, lowest_price: lowest_price, prices: prices, errors: errors ) end |