Class: ListingIssues::RetailerProbeAdapter
- Inherits:
-
BaseAdapter
- Object
- BaseAdapter
- ListingIssues::RetailerProbeAdapter
- Defined in:
- app/services/listing_issues/retailer_probe_adapter.rb
Overview
Cross-retailer listing issues derived from our OWN daily Oxylabs probe
(Retailer::PriceChecker → CatalogItemRetailerProbe). This is the only
"is the listing live" signal a scrape-only retailer (Home Depot, Rona,
Costco, … — no channel API to ask) has, and it applies identically wherever
a catalog is probed, so the counts are directly comparable retailer to
retailer. Complements the channel-sourced adapters
(WayfairAdapter#not_live_issue, AmazonAdapter#issues_from_suppression):
those trust what the marketplace API says; this trusts our own observation.
Reads stored probe rows only — never probes — so it can't block and stays
consistent with whatever the last probe run recorded. Everything is computed
as memoized id Sets from CatalogItemRetailerProbe's current-state scopes, so
the bulk sweep does a handful of set queries, not one per item.
Two severities, split by CONFIDENCE:
-
critical — the retailer page LOADED (ruling out a scraper/network blip)
but the product is broken: not on the page / a wrong product
(not_found / product_mismatch), or the retailer shows it out of stock
while OUR inventory says in stock (a stuck/broken listing, not a real
stockout). Self-verified live revenue loss. -
error —
not_confirmable: a human setskip_url_checkson the item, so
nothing will ever re-confirm the listing. Lower confidence
than critical — the evidence is frozen at whatever we last saw and can no
longer be refreshed — but a real "we've given up confirming this" signal.This claims EVERY opted-out item, including ones whose last probe
loaded the page and found the product missing. Those look like critical
not_buyableevidence, but we stopped probing them, so the finding can't
be re-confirmed and doesn't belong in a metric of what is failing now.
Keeping them here is also what lets the /retailers dashboard count each
problem once:not_buyableandout_of_stock_mismatchexist only for
actively-probed items and are therefore fully represented by the Scrape
Failures / Out of Stock Online columns, so
Retailer::DailyComplianceReport::COLUMN_BACKED_ISSUE_CODES excludes
them from the Listing Issues count while these stay. -
error —
probe_stale: no probe row at all inside
CatalogItemRetailerProbe::STALE_PROBE_DAYS. Every signal above reads a
probe row (orskip_url_checks), so an item that stops being probed
entirely is invisible to all
of them — it silently keeps serving whatever price its last probe wrote.
Last in the precedence chain: a recent probe that found something wrong
is always the better signal.
Constant Summary collapse
- PROVIDER =
Provider key stamped on issues produced by this adapter.
'retailer_probe'
Instance Method Summary collapse
-
#candidate_item_ids ⇒ Array<Integer>
Active, probe-eligible items currently in any flagged probe state, plus any item already carrying an open issue (so a recovered listing auto-resolves).
-
#eager_load ⇒ Array
Associations the bulk sweep eager-loads before #issues_for.
-
#initialize(item_ids: nil) ⇒ RetailerProbeAdapter
constructor
A new instance of RetailerProbeAdapter.
- #issues_for(catalog_item) ⇒ Array<ListingIssues::Issue>
- #provider ⇒ String
Constructor Details
#initialize(item_ids: nil) ⇒ RetailerProbeAdapter
Returns a new instance of RetailerProbeAdapter.
57 58 59 |
# File 'app/services/listing_issues/retailer_probe_adapter.rb', line 57 def initialize(item_ids: nil) @item_ids = item_ids end |
Instance Method Details
#candidate_item_ids ⇒ Array<Integer>
Active, probe-eligible items currently in any flagged probe state, plus any
item already carrying an open issue (so a recovered listing auto-resolves).
In item-scoped mode the open-issue lookup is unscoped, so intersect here.
68 69 70 71 72 |
# File 'app/services/listing_issues/retailer_probe_adapter.rb', line 68 def candidate_item_ids ids = (scrape_failure_ids.to_a + stock_mismatch_ids.to_a + skipped_ids.to_a + exhausted_ids.to_a + stale_probe_ids.to_a + open_issue_item_ids).uniq @item_ids ? ids & @item_ids.map(&:to_i) : ids end |
#eager_load ⇒ Array
Returns associations the bulk sweep eager-loads before #issues_for.
94 95 96 |
# File 'app/services/listing_issues/retailer_probe_adapter.rb', line 94 def eager_load [:catalog] end |
#issues_for(catalog_item) ⇒ Array<ListingIssues::Issue>
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'app/services/listing_issues/retailer_probe_adapter.rb', line 76 def issues_for(catalog_item) return [] unless catalog_item.state == 'active' id = catalog_item.id # Exhaustion outranks the per-probe classification. An exhausted item will # nearly always ALSO look like a scrape failure — that is how it got there # — so classifying it as not_buyable would make the escalation # unreachable in exactly the case it exists for. return [needs_manual_confirmation_issue(catalog_item)] if exhausted_ids.include?(id) return [not_buyable_issue(catalog_item)] if scrape_failure_ids.include?(id) return [out_of_stock_mismatch_issue(catalog_item)] if stock_mismatch_ids.include?(id) return [not_confirmable_issue(catalog_item)] if skipped_ids.include?(id) return [probe_stale_issue(catalog_item)] if stale_probe_ids.include?(id) [] end |
#provider ⇒ String
62 |
# File 'app/services/listing_issues/retailer_probe_adapter.rb', line 62 def provider = PROVIDER |