Files
Aakash BakhleandGitHub 102f19fe41 feat(captain): add FAQ suggestion data model (1/3) (#14977)
Resolved conversations need a separate suggestion layer so repeated FAQ
signals can be grouped without creating untrusted knowledge entries.
This PR adds the persistence foundation only; it introduces no
user-facing behavior by itself.

## Closes

-
[CW-7495](https://linear.app/chatwoot/issue/CW-7495/backend-llm-changes-to-make-conversation-faqs-as-signalssuggestions)
(stacked PR 1/3; the issue is complete after the full stack lands)

## What changed

- Added `captain_faq_suggestions` with question, answer, embedding,
source count, and review status.
- Added `captain_faq_observations` to retain conversation-level signals.
- Added Captain assistant, account, and conversation associations.
- Added vector and lookup indexes for semantic grouping.

## How to test

This layer has no standalone UI behavior. Apply the migration and
confirm Captain assistants can persist open FAQ suggestions with
attached conversation observations.
2026-07-14 14:31:08 +05:30

43 lines
1.4 KiB
Ruby

# == Schema Information
#
# Table name: captain_faq_observations
#
# id :bigint not null, primary key
# generated_answer :text not null
# generated_question :string not null
# language :string default("en"), not null
# status :integer default("attached"), not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# conversation_id :bigint not null
# faq_suggestion_id :bigint
#
class Captain::FaqObservation < ApplicationRecord
self.table_name = 'captain_faq_observations'
belongs_to :account
belongs_to :conversation, class_name: '::Conversation'
belongs_to :faq_suggestion, class_name: 'Captain::FaqSuggestion', optional: true, inverse_of: :observations
enum status: { attached: 0, discarded: 1 }
validates :generated_question, :generated_answer, :language, presence: true
validates :faq_suggestion, presence: true, if: :attached?
validate :faq_suggestion_belongs_to_account
before_validation :ensure_account
private
def ensure_account
self.account = conversation&.account
end
def faq_suggestion_belongs_to_account
return if faq_suggestion.blank? || faq_suggestion.account_id == account_id
errors.add(:faq_suggestion, :invalid)
end
end