Files

190 lines
5.0 KiB
Go
Raw Permalink Normal View History

2026-08-16 16:57:36 +02:00
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 /
2026-08-16 21:35:48 +02:00
// title-echo / filler / invent-synthesize (so the next enhance cannot hash-skip
// thin or heuristic priors).
2026-08-16 16:57:36 +02:00
// 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 {
2026-08-16 21:35:48 +02:00
if company.ShouldRefuseEnhanceHashSkip(primaryDesc, primaryName, name) {
2026-08-16 16:57:36 +02:00
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
}
2026-08-16 21:35:48 +02:00
if company.ShouldRefuseEnhanceHashSkip(d, n, primaryName, name) {
2026-08-16 16:57:36 +02:00
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
}