Class: Assistant::ProductToolBuilder
- Inherits:
-
Object
- Object
- Assistant::ProductToolBuilder
- Defined in:
- app/services/assistant/product_tool_builder.rb
Overview
Builds RubyLLM::Tool subclasses for product catalog queries.
Provides 3 tools:
get_product — full rendered snapshot of a single SKU
search_products — semantic search with pricing + availability
browse_product_line — list products within a product line hierarchy
All tools are locale-aware (en-US = catalog 1, en-CA = catalog 2).
Pricing and availability flags mirror what ProductCatalogPresenter renders.
Usage (via ChatToolBuilder):
tools = Assistant::ProductToolBuilder.tools
Constant Summary collapse
- CRM_ITEM_URL =
URL for crm item.
"#{CRM_URL}/en-US/items".freeze
- CRM_PRODUCT_LINE_URL =
URL for crm product line (descriptions editor).
"#{CRM_URL}/product_lines".freeze
- WEB_PRODUCT_URL =
URL for web product.
"https://www.warmlyyours.com"- PRODUCT_DATA_GUIDE =
Data guide injected into tool descriptions so the LLM understands
the rendering flags and pricing cascade without a separate prompt. <<~GUIDE ## Product Data Guide ### Page title cascade (what renders as the <title> tag) seo_title → public_short_name → item_name effective_page_title is pre-computed for you. ### SEO description cascade (what renders as the <meta description> tag) seo_description → short_description → item description → item_name effective_seo_description is pre-computed for you. ### Pricing cascade effective_price = sale_price (when sale_price_in_effect = true) OR price All amounts are in the currency field (USD or CAD). msrp and retail_price are informational (not always present). ### Multi-catalog pricing (catalog_pricing[]) catalog_pricing lists pricing for all main WarmlyYours catalogs the SKU is in: catalog_id 1 → en-US (warmlyyours.com, USD) catalog_id 2 → en-CA (warmlyyours.com/en-CA, CAD) catalog_id 125 → EU (European catalog, EUR) Each entry includes: price, effective_price, sale_price_in_effect, map_price, sale_price_effective_date, sale_price_expiration_date, product_stock_status, item_is_web_accessible, catalog_item_state. Use catalog_pricing to compare US vs CA pricing or check sale windows across markets. ### Availability flags — what controls whether a product is purchasable item_is_web_accessible = true → product IS listed on the website product_stock_status = "InStock" or "OutOfStock" item_condition = "new" | "refurbished" | "damaged" item_is_discontinued = true → item record permanently discontinued store_item_is_discontinued = true → store item discontinued separately hide_from_feed = true → excluded from Google Shopping / feeds ### catalog_item_state — the product lifecycle state machine Each CatalogItem has a `state` that controls visibility and workflow. item_is_web_accessible = true ONLY when ALL of: - item.is_discontinued = false - store_item.is_discontinued = false - catalog_item.state NOT IN hidden_states States and their meaning: active — publicly listed AND purchasable. The goal state. active_hidden — purchasable by direct URL but NOT in search/navigation. Used for: kit components sold as part of a kit only, recently decommissioned products still fulfilling orders, trade-only items not shown publicly. pending_onboarding — needs setup/upload in customer or retailer systems. pending_discontinue — flagged for removal; unboarding in progress (non-primary catalogs). pending_client_review — submitted to a retailer/client; awaiting their acceptance. require_vendor_update — update is required in the retailer's catalog (action needed). pending_vendor_update — update sent to retailer; awaiting propagation. discontinued — permanently discontinued. Set by background job or explicit event. Cannot be ordered. Can be reactivated via `activate` event. invalid_catalog_item — data quality issue (e.g. missing required field). Must be corrected before reactivation. hidden_states (item_is_web_accessible = false): active_hidden, discontinued, pending_client_review, invalid_catalog_item, pending_onboarding Available events and valid transitions: activate discontinued → active hide_from_public active → active_hidden unboard active → pending_discontinue discontinue most states → discontinued (main WY catalogs: 1,2,125) active → pending_discontinue (third-party catalogs) ready_for_onboarding pending_client_review | discontinued → pending_onboarding items_are_onboarded pending_onboarding → active submitted_to_client_for_review invalid | discontinued | active → pending_client_review invalidate any → invalid_catalog_item item_specs_or_price_updated active | pending_vendor_update → require_vendor_update submitted_updates_to_client active | require_vendor_update → pending_vendor_update client_completed_updates require_vendor_update | pending_vendor_update → active Common workflows: Make product public: discontinued → activate → active Hide without discontinuing: active → hide_from_public → active_hidden Start wind-down (gentle): active → unboard → pending_discontinue → discontinue → discontinued Direct discontinue (WY): active → discontinue → discontinued Retailer onboarding: active → submitted_to_client_for_review → pending_client_review → ready_for_onboarding → pending_onboarding → items_are_onboarded → active ### allow_add_to_cart true when: item_is_web_accessible = true AND restricted_to_trade = false restricted_to_trade = true for TempZone Ruler Cable heating cables (these are trade-only; show "Contact us for a quote" instead of cart) ### item_condition: refurbished refurbished_item = true means this IS a refurbished SKU (sku ends in "-BTK"). has_new_version = true means there is a newer non-refurbished counterpart. Refurbished SKUs show a discount vs original_price. ### Item vs ProductLine hierarchy product_line = the direct ProductLine the item belongs to (primary) primary_pl_path_slugs = ltree path e.g. "floor_heating.tempzone.flex_roll" Ancestors: floor_heating → tempzone → flex_roll product_category = the product category (PC) ltree path pc_path_slugs = ltree path e.g. "goods.heating_elements.heating_mats" show_in_sales_portal — product line is shown in the public sales portal available_to_public — product line is available to public ### Specifications specifications are pre-rendered, grouped by grouping (e.g. "Electrical", "Dimensions"). Only "open_visibility" specs are shown publicly. Features (grouping = "Features" / "Highlights") are listed separately as features[]. ### Variants Products in the same item_grouping_identifier are variants of each other (e.g. different sizes of the same mat). variants[] lists all SKUs with prices. ### Smart services (PC path starts with "services") These are professional installation services, not physical goods. They have terms_and_conditions and potential surcharges instead of a normal cart. ### Kit items item_is_kit = true → this SKU is a kit composed of multiple component items. ### Successor / discontinue successor_sku — the recommended replacement when this item is discontinued. ### Website images (images[]) images[] contains WarmlyYours website image profiles (image_type starts with WYS_): WYS_CARD — listing / product-card thumbnail (not on PDP carousel or feeds) WYS_MAIN — main product image (also referenced as primary_image_url) WYS_I01–I15 — additional product images (gallery) Each entry has: image_type, url (full ImageKit CDN URL), locale. primary_image_url is the URL of the item's designated primary_image directly. ### Videos (videos[]) videos[] are DigitalAsset records of type Video linked to this item. Each entry has: id, title, category (e.g. "installation", "product", "training"), cloudflare_uid (for Cloudflare Stream embed), duration_seconds, locales. Videos with cloudflare_uid are streamable; others may be external links. ### Publications (publications[]) publications[] are linked Items of the "publication" type (PDFs, manuals, datasheets, installation guides). Each entry has: id, sku, title, category, locales (available language editions), url (download URL). These represent the technical documentation attached to this product. ### Model-specific support identification A family/model name is not an exact product identifier. When catalog evidence shows multiple configurations, ask for the exact label SKU or distinguishing configuration and wait before selecting documentation or parts. Never guess one candidate SKU. ### Mobility translations (translations{}) Item text fields (name, seo_title, seo_description, short_description, etc.) support multiple locales via Mobility (container backend with locale_accessors). translations{} shows non-default locale values that have been entered. Default locale is 'en' (English). Fields in the default locale are returned directly as the top-level seo_title, name, etc. fields. Non-default locales appear under their locale key, e.g.: "fr-CA": { name: "...", seo_title: "..." } To EDIT a translated field, write to the locale-specific accessor, e.g.: Mobility.with_locale(:'fr-CA') { item.seo_title = "..." } Column accessors also exist (e.g. item.seo_title_fr_ca), but Mobility.with_locale is the safe, backend-agnostic approach and correctly triggers callbacks. _en suffix is the default; e.g. item.name_en is the English name. European products (EU catalog 125) may have fr, de, nl, es, it etc. locales. Always check translations{} to see what already exists before overwriting. GUIDE
- CONTENT_EDITING_GUIDE =
Guide injected into the content-editing tools (item + product-line text).
<<~GUIDE ## Product Content Editing Guide There are two distinct marketing-copy surfaces — pick the right one: ### Product-line descriptions → update_product_line The CRM `?tab=descriptions` editor edits ProductLine fields: description_html — the main marketing description (rich HTML) short_description — brief summary feature_1..feature_5, tag_line, name, public_name, seo_title, seo_description, seo_support_* … (see editable_fields in the response) A product-line description is INHERITED by every item in the line that has no item-level override — so this is the efficient way to change copy for a whole line at once. ### Item (SKU) descriptions → update_product Item-level fields, used when ONE SKU needs copy different from its line: detailed_description_html — the item's own long description (HTML) short_description, name, public_short_name, seo_title, seo_description, feature_1..feature_5 … (see editable_fields in the response) ### Locales Every field above is per-locale (Mobility). Pass `locale` to target one (default "en"). Common: en, en-CA, fr-CA, plus EU (fr, de, nl, es, it). Omit `locale` on the get_* tools to see every locale at once. ### Workflow — ALWAYS read before you write 1. get_product_content / get_product_line_content — read the CURRENT copy for the locale you intend to edit. 2. Weave new wording in ORGANICALLY and send the FULL revised field value. These tools REPLACE the field; they do NOT append. 3. update_product / update_product_line to save. 4. Pass dry_run: true first to preview before/after without saving. Note: the public site is CDN-cached — edits can take a few minutes (or the next cache purge / deploy) to appear on warmlyyours.com. GUIDE
- CATALOG_ITEM_STATE_EVENTS =
CatalogItem state-machine events fireable via update_catalog_item_state.
%w[ activate hide_from_public unboard discontinue ready_for_onboarding items_are_onboarded submitted_to_client_for_review invalidate item_specs_or_price_updated submitted_updates_to_client client_completed_updates ].freeze
Class Method Summary collapse
-
.management_tools(audit_context: {}) ⇒ Array<RubyLLM::Tool>
Write tools for product lifecycle + content management — require item_manager role (gated by AssistantChatController#available_chat_services).
-
.tools ⇒ Array<RubyLLM::Tool>
Read-only product catalog tools — available to all authenticated users.
Class Method Details
.management_tools(audit_context: {}) ⇒ Array<RubyLLM::Tool>
Write tools for product lifecycle + content management — require
item_manager role (gated by AssistantChatController#available_chat_services).
update_catalog_item_state — fire a CatalogItem state-machine event
get_product_content — read an item's editable text (all locales)
update_product — edit an item's text fields in a locale
get_product_line_content — read a product line's editable text
update_product_line — edit a product line's text fields in a locale
264 265 266 267 268 269 270 271 272 |
# File 'app/services/assistant/product_tool_builder.rb', line 264 def management_tools(audit_context: {}) [ build_update_catalog_item_state_tool(audit_context), build_get_product_content_tool, build_update_product_content_tool(audit_context), build_get_product_line_content_tool, build_update_product_line_content_tool(audit_context) ] end |
.tools ⇒ Array<RubyLLM::Tool>
Read-only product catalog tools — available to all authenticated users.
243 244 245 246 247 248 249 250 251 |
# File 'app/services/assistant/product_tool_builder.rb', line 243 def tools [ build_list_product_lines_tool, build_get_product_tool, build_search_products_tool, build_browse_product_line_tool, build_get_heating_recommendation_tool ] end |