Class: Assistant::ProductSpecToolBuilder
- Inherits:
-
Object
- Object
- Assistant::ProductSpecToolBuilder
- Defined in:
- app/services/assistant/product_spec_tool_builder.rb
Overview
Builds RubyLLM::Tool subclasses for product specification management.
All tools require item_manager role — enforced at the service level by
AssistantChatController#available_chat_services.
Provides 6 tools:
search_specs — find specs by token/name/grouping/blurb
get_spec — full spec detail + scope summary
get_spec_scope — enumerate affected items
update_spec — update spec fields in place
clone_spec_to_item — clone and isolate to a single SKU
clone_spec_to_product_line — clone and narrow to a PL ± PC
Usage (via ChatToolBuilder):
tools = Assistant::ProductSpecToolBuilder.tools(audit_context: { user_id: 42 })
Constant Summary collapse
- CRM_SPEC_URL =
"#{CRM_URL}/en-US/product_specifications".freeze
- SPEC_SYSTEM_GUIDE =
<<~GUIDE ## Product Specification System Guide ### What is a product specification? A ProductSpecification defines ONE data point (a "token") for items — e.g. `voltage`, `watts`, `width`, `coverage`, `sku`. Tokens are unique per scope. The rendered value is stored per-item in `rendered_product_specifications` JSONB. ### How a spec is latched to an item (scope types) **1. Item-specific** (most specific — wins over everything else) Direct link via `items_product_specifications` join table. Has direct `item_ids`. No `product_line_id` or `product_category_id`. **2. Product-line + category** (second priority) `product_line_id` AND `product_category_id` both set. Matches items whose PL ancestry includes spec.product_line_id AND whose PC ancestry includes spec.product_category_id. Resolution: most specific (deepest) PL × PC combination wins first. **3. Category-only** (third priority) `product_line_id` nil, `product_category_id` set. Matches all items in that PC (and descendants) regardless of PL. **4. Product-line-only** (fourth priority) `product_category_id` nil, `product_line_id` set. Matches all items in that PL ancestry. **SKU regexp filter** — optional `sku_regexp` further restricts scope to items whose SKU matches the regex. Applied at step 2–4 above. **X-group specs** (grouping starts with "X-", e.g. "X-Amazon") Only apply to the main item — not to kit component items. **Propagation enum** unrestricted(0) — resolves for any matching item item(1) — item-specific specs only product_line(2) — PL-scoped only **Resolution priority per token** When multiple specs share the same token, the most specific wins: item-specific → PL+PC (deepest match first) → secondary PL+PC → category-only → product-line-only Once a spec wins a token, no lower-priority spec for that token applies. ### Spec method types text — static value from `text_blurb` (may contain Liquid) calculate_* — computed from item fields (e.g. calculate_watts, calculate_ohms) sku, upc — pulled directly from item attributes (others) — see SAFE_METHODS list on ProductSpecification ### Units (the `units` field) `units` is a RubyUnits string that declares what unit the raw numeric value is in. It drives two things: 1. The display symbol appended to the raw value (via UnitHelper.unit_symbol): "in" → ″ (e.g. "23.6″") "ft" → ′ (e.g. "5′") "sqft" → ft² "degF" → °F (also "degf", case-insensitive) "degC" → °C "ohm" → Ω "W", "V", "A", "lb", "oz", "kg", "m", "cm", "mm", "sqm", etc. — appended with a space 2. Unit conversion: when viewing in the CA locale, RubyUnits converts values (e.g. inches → cm, lbs → kg). Formatters are skipped for non-English locales. Common unit values used in this catalog: "in", "ft", "lb", "oz", "W", "V", "A", "ohm", "sqft", "sqm", "degF", "degC", "m", "cm", "mm", "kg", "y" (years). ### Formatters (the `formatter` field) Formatters post-process the raw numeric value AFTER unit conversion. Only applied when `units` is set AND locale is English (en-*). Three formatters are available: FeetAndInches Converts an inch value to feet+inches display. Input: raw value in "in" units (e.g. text_blurb "20.5", units "in") Output: "1′ 8.5″" — feet + remaining inches Use for: widths, heights, lengths when you want the feet+inches breakdown. NOTE: if other specs for the same field show just "X″" (plain inches), they have NO formatter set. To make all specs consistent, either add FeetAndInches to all or remove it (set formatter to blank) from the outlier. PoundsAndOunces Converts an ounce value to "X lbs, Y oz" display. Input: raw value in "oz" or "lb" units Output: "10 lbs, 4 oz" Use for: weights displayed in lbs + oz breakdown. ClosestRational Approximates the raw number to its nearest simple fraction + unit symbol. Input: any numeric value + units Output: e.g. "1/2 in", "3/4 oz" Use for: measurements best expressed as fractions rather than decimals. No formatter (blank) Falls through to default RubyUnits formatting: the number is stripped of insignificant zeros, then the unit symbol is appended. e.g. units "in", raw "23.6" → "23.6″" This is the most common case for straightforward dimension specs. DIAGNOSIS PATTERN — "spec renders differently from siblings": 1. Use get_spec (by token + product_line_slug) to compare formatter fields. 2. The outlier likely has a formatter set that others don't (or vice versa). 3. Use update_spec with formatter: "" (empty string) to clear it, or set formatter: "FeetAndInches" to match the feet+inches siblings. 4. Use enqueue_spec_refresh to re-render affected items without waiting for the next background pass. ### Liquid variables in text_blurb When `has_liquid_blurb = true`, `text_blurb` is rendered as a Liquid template. Available variables include: {{ sku }} → item SKU {{ width }}, {{ length }} → other spec tokens by token name (output value) {{ width_raw }}, {{ width_units }} → raw number and unit string for any token {{ width_in_raw }}, {{ width_ft_raw }} → converted values in specific units (supports: ft, in, m, cm, mm, lb, oz, kg, g, sqft, sqm) {{ item_name }}, {{ short_description }}, {{ feature_1 }} … {{ feature_5 }} {{ calculate_watts }}, {{ calculate_ohms }}, {{ coverage_at_3_in }}, etc. (all SAFE_METHODS are callable as Liquid variables) ### Visibility enum internal(0) — only visible to internal CRM users hidden(1) — used in formulas but not shown open_visibility(2) — shown on the public product page ### Cloning strategy When you need a different value for a subset of items but want to keep the general spec for the rest: 1. clone_spec_to_item — creates an item-specific override for exactly one SKU 2. clone_spec_to_product_line — creates a more targeted PL or PL+PC spec that wins over the broader original due to higher specificity The original spec continues to apply to items not covered by the clone. ### Re-rendering After any spec change, `async_refresh_items` automatically enqueues `ItemAttributeWorker` for all affected items. Changes appear in `rendered_product_specifications` after those jobs complete (~minutes). GUIDE
Class Method Summary collapse
-
.read_tools ⇒ Array<RubyLLM::Tool>
Read-only spec tools — safe to expose to all authenticated users.
-
.tools(audit_context: {}) ⇒ Array<RubyLLM::Tool>
Build all product spec management tools (read + write).
-
.write_tools(audit_context: {}) ⇒ Array<RubyLLM::Tool>
Write tools — require item_manager role.
Class Method Details
.read_tools ⇒ Array<RubyLLM::Tool>
Read-only spec tools — safe to expose to all authenticated users.
163 164 165 166 167 168 169 |
# File 'app/services/assistant/product_spec_tool_builder.rb', line 163 def read_tools [ build_search_specs_tool, build_get_spec_tool, build_get_spec_scope_tool ] end |
.tools(audit_context: {}) ⇒ Array<RubyLLM::Tool>
Build all product spec management tools (read + write).
186 187 188 |
# File 'app/services/assistant/product_spec_tool_builder.rb', line 186 def tools(audit_context: {}) read_tools + write_tools(audit_context: audit_context) end |
.write_tools(audit_context: {}) ⇒ Array<RubyLLM::Tool>
Write tools — require item_manager role.
174 175 176 177 178 179 180 181 |
# File 'app/services/assistant/product_spec_tool_builder.rb', line 174 def write_tools(audit_context: {}) [ build_update_spec_tool(audit_context), build_clone_spec_to_item_tool(audit_context), build_clone_spec_to_product_line_tool(audit_context), build_enqueue_spec_refresh_tool ] end |