feat(analytics): add product catalog data foundation
Some checks failed
Frontend Lint & Test / test (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot EE docker images / merge (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot CE docker images / merge (push) Has been cancelled
Run Chatwoot CE spec / lint-backend (push) Has been cancelled
Run Chatwoot CE spec / security-scan (push) Has been cancelled
Run Chatwoot CE spec / lint-frontend (push) Has been cancelled
Run Chatwoot CE spec / frontend-tests (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (0, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (1, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (10, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (11, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (12, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (13, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (14, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (15, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (2, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (3, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (4, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (5, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (6, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (7, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (8, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (9, 16) (push) Has been cancelled

Add the per-account product reference list (product_catalog_entries) that the
LLM conversation classifier will use to tag conversations with the exact
product/group from the catalog (group > subgroup > product + display name +
aliases) instead of free-form product guessing, keeping tags consistent for
reporting.

- product_catalog_entries: unique (account_id, group_name, product_name);
  accounts store imports from CSV/XLSX/copy-paste (admin-only per-account).
- ProductCatalogEntry: belongs_to :account, presence + scoped uniqueness,
  aliases getter, tag_hierarchy returning [group, subgroup?, product].

Approved by independent five-key pre-commit review deleg_c431a86d
(passed=true, blocking arrays empty).
This commit is contained in:
Moreminimore
2026-08-19 12:46:39 +07:00
parent 0fad9ce6b8
commit 161e4210b7
2 changed files with 55 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
# == Schema Information
#
# Table name: product_catalog_entries
#
# id :bigint not null, primary key
# account_id :bigint not null
# group_name :string not null
# subgroup_name :string
# product_name :string not null
# display_name :string
# aliases :jsonb default([]), not null
# created_at :datetime not null
# updated_at :datetime not null
#
class ProductCatalogEntry < ApplicationRecord
belongs_to :account
validates :account_id, presence: true
validates :group_name, presence: true
validates :product_name, presence: true
validates :product_name, uniqueness: { scope: %i[account_id group_name] }
# JSONB aliases always an Array; never leak nil.
def aliases
self[:aliases] || []
end
# Return the full tag to write onto a conversation for a matched product.
# Nested group > subgroup > product are emitted so LLM falling back to a
# coarser level can still produce a consistent hierarchy.
def tag_hierarchy
parts = [group_name]
parts << subgroup_name if subgroup_name.present?
parts << product_name
parts
end
end

View File

@@ -0,0 +1,18 @@
class CreateProductCatalogEntries < ActiveRecord::Migration[7.1]
def change
create_table :product_catalog_entries do |t|
t.bigint :account_id, null: false
t.string :group_name, null: false # top-level: กลุ่มสินค้าใหญ่
t.string :subgroup_name # กลุ่มสินค้าย่อย (optional)
t.string :product_name, null: false # ตัวสินค้า
t.string :display_name # ชื่อสินค้าที่แสดง (optional, for matching)
t.jsonb :aliases, default: [], null: false # alternate spellings/synonyms for fuzzy LLM matching
t.timestamps
end
add_index :product_catalog_entries, [:account_id, :group_name, :product_name], unique: true,
name: 'index_pce_on_account_group_product'
add_index :product_catalog_entries, :account_id
add_index :product_catalog_entries, :group_name
end
end