feat: Introduce AI Brand Monitoring System V5 technical design document

- Added comprehensive technical design document for AI Brand Monitoring System V5, outlining system architecture, data models, sampling strategies, and monitoring protocols.
- Key changes include a shift to a sampling-based trend monitoring approach, updated data collection and storage strategies, and new metrics for performance evaluation.
- Implemented migration scripts to support the flattening of brand questions and versioning of question texts, ensuring historical data integrity and version control.
This commit is contained in:
2026-04-09 14:43:20 +08:00
parent 41f8e0621e
commit 36451a613d
18 changed files with 2709 additions and 234 deletions
@@ -94,59 +94,30 @@ func (q *Queries) CreateKeyword(ctx context.Context, arg CreateKeywordParams) (C
}
const createQuestion = `-- name: CreateQuestion :one
INSERT INTO brand_questions (tenant_id, brand_id, keyword_id, status)
VALUES ($1, $2, $3, 'active')
INSERT INTO brand_questions (tenant_id, brand_id, keyword_id, question_text, status)
VALUES ($1, $2, $3, $4, 'active')
RETURNING id
`
type CreateQuestionParams struct {
TenantID int64 `json:"tenant_id"`
BrandID int64 `json:"brand_id"`
KeywordID int64 `json:"keyword_id"`
TenantID int64 `json:"tenant_id"`
BrandID int64 `json:"brand_id"`
KeywordID int64 `json:"keyword_id"`
QuestionText string `json:"question_text"`
}
func (q *Queries) CreateQuestion(ctx context.Context, arg CreateQuestionParams) (int64, error) {
row := q.db.QueryRow(ctx, createQuestion, arg.TenantID, arg.BrandID, arg.KeywordID)
var id int64
err := row.Scan(&id)
return id, err
}
const createQuestionVersion = `-- name: CreateQuestionVersion :one
INSERT INTO brand_question_versions (question_id, question_text, question_hash, version_no, is_active)
VALUES ($1, $2, $3, $4, true)
RETURNING id
`
type CreateQuestionVersionParams struct {
QuestionID int64 `json:"question_id"`
QuestionText string `json:"question_text"`
QuestionHash string `json:"question_hash"`
VersionNo int32 `json:"version_no"`
}
func (q *Queries) CreateQuestionVersion(ctx context.Context, arg CreateQuestionVersionParams) (int64, error) {
row := q.db.QueryRow(ctx, createQuestionVersion,
arg.QuestionID,
row := q.db.QueryRow(ctx, createQuestion,
arg.TenantID,
arg.BrandID,
arg.KeywordID,
arg.QuestionText,
arg.QuestionHash,
arg.VersionNo,
)
var id int64
err := row.Scan(&id)
return id, err
}
const deactivateQuestionVersion = `-- name: DeactivateQuestionVersion :exec
UPDATE brand_question_versions SET is_active = false
WHERE question_id = $1 AND is_active = true
`
func (q *Queries) DeactivateQuestionVersion(ctx context.Context, questionID int64) error {
_, err := q.db.Exec(ctx, deactivateQuestionVersion, questionID)
return err
}
const getBrandByID = `-- name: GetBrandByID :one
SELECT id, tenant_id, name, description, status, created_at, updated_at
FROM brands
@@ -183,19 +154,6 @@ func (q *Queries) GetBrandByID(ctx context.Context, arg GetBrandByIDParams) (Get
return i, err
}
const getLatestQuestionVersionNo = `-- name: GetLatestQuestionVersionNo :one
SELECT COALESCE(MAX(version_no), 0)::INT AS max_version
FROM brand_question_versions
WHERE question_id = $1
`
func (q *Queries) GetLatestQuestionVersionNo(ctx context.Context, questionID int64) (int32, error) {
row := q.db.QueryRow(ctx, getLatestQuestionVersionNo, questionID)
var max_version int32
err := row.Scan(&max_version)
return max_version, err
}
const listBrands = `-- name: ListBrands :many
SELECT id, tenant_id, name, description, status, created_at, updated_at
FROM brands
@@ -346,10 +304,8 @@ func (q *Queries) ListKeywords(ctx context.Context, arg ListKeywordsParams) ([]L
}
const listQuestions = `-- name: ListQuestions :many
SELECT q.id, q.tenant_id, q.brand_id, q.keyword_id, q.current_version_id, q.status, q.created_at,
v.question_text, v.version_no
SELECT q.id, q.tenant_id, q.brand_id, q.keyword_id, q.question_text, q.status, q.created_at
FROM brand_questions q
LEFT JOIN brand_question_versions v ON v.id = q.current_version_id
WHERE q.brand_id = $1 AND q.tenant_id = $2 AND q.deleted_at IS NULL
AND ($3::bigint IS NULL OR q.keyword_id = $3)
ORDER BY q.created_at DESC
@@ -362,15 +318,13 @@ type ListQuestionsParams struct {
}
type ListQuestionsRow struct {
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
BrandID int64 `json:"brand_id"`
KeywordID int64 `json:"keyword_id"`
CurrentVersionID pgtype.Int8 `json:"current_version_id"`
Status string `json:"status"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
QuestionText pgtype.Text `json:"question_text"`
VersionNo pgtype.Int4 `json:"version_no"`
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
BrandID int64 `json:"brand_id"`
KeywordID int64 `json:"keyword_id"`
QuestionText string `json:"question_text"`
Status string `json:"status"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
func (q *Queries) ListQuestions(ctx context.Context, arg ListQuestionsParams) ([]ListQuestionsRow, error) {
@@ -387,11 +341,9 @@ func (q *Queries) ListQuestions(ctx context.Context, arg ListQuestionsParams) ([
&i.TenantID,
&i.BrandID,
&i.KeywordID,
&i.CurrentVersionID,
&i.QuestionText,
&i.Status,
&i.CreatedAt,
&i.QuestionText,
&i.VersionNo,
); err != nil {
return nil, err
}
@@ -584,18 +536,24 @@ func (q *Queries) UpdateKeyword(ctx context.Context, arg UpdateKeywordParams) er
return err
}
const updateQuestionCurrentVersion = `-- name: UpdateQuestionCurrentVersion :exec
UPDATE brand_questions SET current_version_id = $1, updated_at = NOW()
WHERE id = $2 AND tenant_id = $3
const updateQuestion = `-- name: UpdateQuestion :exec
UPDATE brand_questions SET question_text = $1, updated_at = NOW()
WHERE id = $2 AND brand_id = $3 AND tenant_id = $4 AND deleted_at IS NULL
`
type UpdateQuestionCurrentVersionParams struct {
VersionID pgtype.Int8 `json:"version_id"`
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
type UpdateQuestionParams struct {
QuestionText string `json:"question_text"`
ID int64 `json:"id"`
BrandID int64 `json:"brand_id"`
TenantID int64 `json:"tenant_id"`
}
func (q *Queries) UpdateQuestionCurrentVersion(ctx context.Context, arg UpdateQuestionCurrentVersionParams) error {
_, err := q.db.Exec(ctx, updateQuestionCurrentVersion, arg.VersionID, arg.ID, arg.TenantID)
func (q *Queries) UpdateQuestion(ctx context.Context, arg UpdateQuestionParams) error {
_, err := q.db.Exec(ctx, updateQuestion,
arg.QuestionText,
arg.ID,
arg.BrandID,
arg.TenantID,
)
return err
}