190 lines
5.0 KiB
Go
190 lines
5.0 KiB
Go
package catalog
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/descrybe/descrybe-v2/apps/api/internal/company"
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
const enhanceInputHashKey = "enhance_input_hash"
|
|
|
|
// RepairWeakEnhanceHashesResult is the detailed report for weak-hash clearing.
|
|
type RepairWeakEnhanceHashesResult struct {
|
|
Cleared int64
|
|
Scanned int
|
|
ClearedRawIDs []uuid.UUID
|
|
}
|
|
|
|
// RepairWeakEnhanceHashes clears enhance_input_hash from field_sources and
|
|
// localized_content for processed products whose descriptions are weak /
|
|
// title-echo / filler / invent-synthesize (so the next enhance cannot hash-skip
|
|
// thin or heuristic priors).
|
|
// Returns the number of products updated.
|
|
func RepairWeakEnhanceHashes(ctx context.Context, pool *pgxpool.Pool, companyID uuid.UUID) (int64, error) {
|
|
res, err := RepairWeakEnhanceHashesDetailed(ctx, pool, companyID)
|
|
return res.Cleared, err
|
|
}
|
|
|
|
// RepairWeakEnhanceHashesDetailed is RepairWeakEnhanceHashes plus scan/raw-id detail
|
|
// for admin Fix A1 hygiene reporting.
|
|
func RepairWeakEnhanceHashesDetailed(ctx context.Context, pool *pgxpool.Pool, companyID uuid.UUID) (RepairWeakEnhanceHashesResult, error) {
|
|
var out RepairWeakEnhanceHashesResult
|
|
if pool == nil {
|
|
return out, fmt.Errorf("catalog pool not configured")
|
|
}
|
|
if companyID == uuid.Nil {
|
|
return out, ClientMsg("company_id is required")
|
|
}
|
|
tx, err := pool.Begin(ctx)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
|
|
out, err = repairWeakEnhanceHashesTx(ctx, tx, companyID)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return out, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// RepairWeakEnhanceHashes clears weak enhance skip hashes for the company.
|
|
func (s *Service) RepairWeakEnhanceHashes(ctx context.Context, companyID uuid.UUID) (int64, error) {
|
|
if s == nil || s.Pool == nil {
|
|
return 0, fmt.Errorf("catalog service not configured")
|
|
}
|
|
return RepairWeakEnhanceHashes(ctx, s.Pool, companyID)
|
|
}
|
|
|
|
func repairWeakEnhanceHashesTx(ctx context.Context, tx pgx.Tx, companyID uuid.UUID) (RepairWeakEnhanceHashesResult, error) {
|
|
var out RepairWeakEnhanceHashesResult
|
|
rows, err := tx.Query(ctx, `
|
|
SELECT id, raw_product_id,
|
|
COALESCE(name, ''),
|
|
COALESCE(description, ''),
|
|
COALESCE(processed_name, ''),
|
|
COALESCE(processed_description, ''),
|
|
COALESCE(field_sources, '{}'::jsonb),
|
|
COALESCE(localized_content, '{}'::jsonb)
|
|
FROM processed_products
|
|
WHERE company_id = $1`, companyID)
|
|
if err != nil {
|
|
return out, fmt.Errorf("list processed products: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
type pending struct {
|
|
id uuid.UUID
|
|
rawID uuid.UUID
|
|
fs []byte
|
|
loc []byte
|
|
}
|
|
var updates []pending
|
|
|
|
for rows.Next() {
|
|
out.Scanned++
|
|
var (
|
|
id, rawID uuid.UUID
|
|
name, desc, processedName, processedDesc string
|
|
fsRaw, locRaw []byte
|
|
)
|
|
if err := rows.Scan(&id, &rawID, &name, &desc, &processedName, &processedDesc, &fsRaw, &locRaw); err != nil {
|
|
return out, err
|
|
}
|
|
fs := map[string]any{}
|
|
if len(fsRaw) > 0 {
|
|
if err := json.Unmarshal(fsRaw, &fs); err != nil {
|
|
return out, err
|
|
}
|
|
}
|
|
if fs == nil {
|
|
fs = map[string]any{}
|
|
}
|
|
loc, err := company.DecodeLocalizedContent(locRaw)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
|
|
changed := false
|
|
primaryDesc := strings.TrimSpace(processedDesc)
|
|
if primaryDesc == "" {
|
|
primaryDesc = desc
|
|
}
|
|
primaryName := strings.TrimSpace(processedName)
|
|
if primaryName == "" {
|
|
primaryName = name
|
|
}
|
|
|
|
if _, has := fs[enhanceInputHashKey]; has {
|
|
if company.ShouldRefuseEnhanceHashSkip(primaryDesc, primaryName, name) {
|
|
delete(fs, enhanceInputHashKey)
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
for lang, fields := range loc {
|
|
d := strings.TrimSpace(fields.ProcessedDescription)
|
|
n := strings.TrimSpace(fields.ProcessedName)
|
|
if n == "" {
|
|
n = primaryName
|
|
}
|
|
if strings.TrimSpace(fields.EnhanceInputHash) == "" {
|
|
continue
|
|
}
|
|
if company.ShouldRefuseEnhanceHashSkip(d, n, primaryName, name) {
|
|
fields.EnhanceInputHash = ""
|
|
loc[lang] = fields
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
if !changed {
|
|
continue
|
|
}
|
|
fsBytes, err := json.Marshal(fs)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
locBytes, err := company.EncodeLocalizedContent(loc)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
updates = append(updates, pending{id: id, rawID: rawID, fs: fsBytes, loc: locBytes})
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return out, err
|
|
}
|
|
rows.Close()
|
|
|
|
seenRaw := map[uuid.UUID]struct{}{}
|
|
for _, u := range updates {
|
|
ct, err := tx.Exec(ctx, `
|
|
UPDATE processed_products
|
|
SET field_sources = $2::jsonb,
|
|
localized_content = $3::jsonb,
|
|
updated_at = now()
|
|
WHERE id = $1 AND company_id = $4`,
|
|
u.id, u.fs, u.loc, companyID)
|
|
if err != nil {
|
|
return out, fmt.Errorf("clear enhance hashes product=%s: %w", u.id, err)
|
|
}
|
|
if ct.RowsAffected() > 0 {
|
|
out.Cleared++
|
|
if _, ok := seenRaw[u.rawID]; !ok {
|
|
seenRaw[u.rawID] = struct{}{}
|
|
out.ClearedRawIDs = append(out.ClearedRawIDs, u.rawID)
|
|
}
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|