Class: Feed::OpenaiCommerce::CatalogFeedGenerator
- Inherits:
-
Feed::OpenaiAds::CatalogFeedGenerator
- Object
- Feed::OpenaiAds::CatalogFeedGenerator
- Feed::OpenaiCommerce::CatalogFeedGenerator
- Defined in:
- app/services/feed/openai_commerce/catalog_feed_generator.rb
Overview
Builds the OpenAI Commerce (non-Ads) product feed — the discovery feed that
keeps our catalog surfaced in ChatGPT search with accurate pricing,
availability, and ratings — as a single CSV for delivery via SFTP, file
upload, or hosted URL (https://developers.openai.com/commerce/specs).
Subclasses Feed::OpenaiAds::CatalogFeedGenerator: the catalog scoping,
presenter pipeline, and review-stats injection are identical; only the column
set (commerce-spec names, not GMC names) and the presenter class differ.
Instant Checkout was sunset in March 2026, so is_eligible_checkout is
always false and the checkout-only merchant fields are omitted.
Deliberately omitted spec-optional fields:
q_and_a— our FAQs are support articles (ArticleFaq), not clean Q/A
pairs; dumping them risks junk in the feed.shipping— no per-item rate matrix to expresscountry:service:price.return_deadline_in_days— our policy is "no time limits", which the
positive-integer field cannot express;accepts_returns+return_policy
carry the signal.model_3d_url,pickup_method/pickup_sla,geo_price/geo_availability,
popularity_score/return_rate,marketplace_seller,size_system,
gender/age_group— not applicable to our catalog.
Usage:
Feed::OpenaiCommerce::CatalogFeedGenerator.new.call(output_file_path: path)
Constant Summary collapse
- COLUMNS =
Ordered CSV columns from the commerce flat-file spec. Required fields:
the eligibility flags, item_id, title, description, url, brand, image_url,
price, availability, seller_name, seller_url, return_policy,
target_countries, store_country. Everything else enriches discovery. { # OpenAI Flags 'is_eligible_search' => ->(p) { p.eligible_search? }, 'is_eligible_checkout' => ->(_p) { false }, # Instant Checkout sunset 2026-03 # Basic Product Data 'item_id' => ->(p) { p.id }, 'gtin' => ->(p) { p.upc }, 'mpn' => ->(p) { p.mpn }, 'title' => ->(p) { p.title }, 'description' => ->(p) { p.description }, 'url' => ->(p) { p.url }, # Item Information 'brand' => ->(p) { p.brand }, 'condition' => ->(p) { p.condition }, 'product_category' => ->(p) { p.product_category_path }, 'dimensions' => ->(p) { p.dimensions_in }, 'weight' => ->(p) { p.item_weight }, 'item_weight_unit' => ->(p) { p.item_weight_unit }, # Media 'image_url' => ->(p) { p.image_link }, 'additional_image_urls' => ->(p) { p.additional_image_link }, 'video_url' => ->(p) { p.video_url }, # Price & Promotions 'price' => ->(p) { p.price_with_currency }, 'sale_price' => ->(p) { p.sale_price_with_currency if p.sale_price_in_effect? }, 'sale_price_start_date' => ->(p) { p.sale_start_date }, 'sale_price_end_date' => ->(p) { p.sale_end_date }, # Availability & Inventory 'availability' => ->(p) { p.availability }, # Variants 'group_id' => ->(p) { p.item_group_name }, 'listing_has_variations' => ->(p) { p.has_variants? }, 'variant_dict' => ->(p) { p.variant_dict }, 'item_group_title' => ->(p) { p.item_group_title }, 'color' => ->(p) { p.color_info }, 'size' => ->(p) { p.size_info }, 'offer_id' => ->(p) { p.offer_id }, # Fulfillment 'is_digital' => ->(_p) { false }, # Merchant Info + Returns 'seller_name' => ->(_p) { 'WarmlyYours' }, 'seller_url' => ->(_p) { Feed::OpenaiCommerce::ProductPresenter::SELLER_URL }, 'return_policy' => ->(_p) { Feed::OpenaiCommerce::ProductPresenter::RETURN_POLICY_URL }, 'accepts_returns' => ->(_p) { true }, # Geo Tagging 'target_countries' => ->(p) { p.ships_from_country_iso }, 'store_country' => ->(_p) { 'US' }, # Reviews and Q&A 'review_count' => ->(p) { p.review_count }, 'star_rating' => ->(p) { p. }, 'store_review_count' => ->(p) { p.store_review_count }, 'store_star_rating' => ->(p) { p. }, 'reviews' => ->(p) { p.review_entries_json } }.freeze
- REVIEW_ENTRIES_PER_LINE =
Number of review entries embedded per product line in the
reviewscolumn. 5
Instance Method Summary collapse
-
#assign_review_stats(presenters, store_stats: ReviewsIo.store_review_stats) ⇒ void
Adds the spec's
reviewslist on top of the aggregate stats: top reviews per product line, memoized per line so variant rows share one lookup. - #presenter_class ⇒ Object
Instance Method Details
#assign_review_stats(presenters, store_stats: ReviewsIo.store_review_stats) ⇒ void
This method returns an undefined value.
Adds the spec's reviews list on top of the aggregate stats: top reviews
per product line, memoized per line so variant rows share one lookup.
97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'app/services/feed/openai_commerce/catalog_feed_generator.rb', line 97 def assign_review_stats(presenters, store_stats: ReviewsIo.store_review_stats) super entries_by_line = {} presenters.each do |presenter| product_line = presenter.item.primary_product_line next unless product_line entries_by_line[product_line.id] ||= fetch_review_entries(product_line) presenter.review_entries_json = entries_by_line[product_line.id] end end |
#presenter_class ⇒ Object
90 |
# File 'app/services/feed/openai_commerce/catalog_feed_generator.rb', line 90 def presenter_class = Feed::OpenaiCommerce::ProductPresenter |