Tier 1: chat quality — fuzzy aliases, reply templates, dedup, rewriter
Foco: matar repetición y adaptar respuestas. Los handlers tenían ~30 strings hardcodeadas (3-7 lugares cada una). Aliases hacían substring exacto. - pg_trgm + GIN indexes en product_aliases / alias_product_mappings. Captura plurales, diminutivos, typos sin reglas. catalogRetrieval re-busca el snapshot con normalized_alias cuando el query original no rinde (vasio→vacio→Vacío). - reply_templates table + replyTemplates.js. 20 keys, 2-3 variantes c/u con DEFAULTS hardcodeados como fallback. pickVariant excluye las usadas en context.recent_replies (FIFO cap 8). Wired en idle/cart/cartHelpers/ shipping/payment/waiting. - failed_searches counter en context. count>=3 escala via humanFallback. Reset en cada add_to_cart exitoso. - storeContext.js: vars derivadas de getStoreConfig (delivery_zones, hours, zonas) listas para inyectar en templates cuando los datos se carguen. - replyRewriter.js: LLM call opcional (REPLY_REWRITER=1) que adapta el template al hilo conversacional. 1.5s timeout, fallback al template puro. Sólo activo en 8 slots semánticamente importantes. - 12 unit tests para replyTemplates (rotation, recency, FIFO, vars). 208 tests totales pasando. Plan completo: ~/.claude/plans/ok-creo-que-tiene-humming-sutton.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -535,32 +535,57 @@ export async function getDecryptedTenantEcommerceConfig({
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function searchProductAliases({ tenant_id, q = "", limit = 20 }) {
|
||||
export async function searchProductAliases({ tenant_id, q = "", limit = 20, threshold = 0.3 }) {
|
||||
const lim = Math.max(1, Math.min(200, parseInt(limit, 10) || 20));
|
||||
const query = String(q || "").trim();
|
||||
if (!query) return [];
|
||||
const normalized = query.toLowerCase();
|
||||
const like = `%${query}%`;
|
||||
const nlike = `%${normalized}%`;
|
||||
const sql = `
|
||||
select tenant_id, alias, normalized_alias, woo_product_id, category_hint, boost, metadata, updated_at
|
||||
select tenant_id, alias, normalized_alias, woo_product_id, category_hint, boost, metadata, updated_at,
|
||||
greatest(similarity(alias, $2), similarity(normalized_alias, $3)) as sim
|
||||
from product_aliases
|
||||
where tenant_id=$1
|
||||
and (alias ilike $2 or normalized_alias ilike $3)
|
||||
order by boost desc, updated_at desc
|
||||
where tenant_id = $1
|
||||
and (alias % $2 or normalized_alias % $3)
|
||||
order by sim desc, boost desc, updated_at desc
|
||||
limit $4
|
||||
`;
|
||||
const { rows } = await pool.query(sql, [tenant_id, like, nlike, lim]);
|
||||
return rows.map((r) => ({
|
||||
tenant_id: r.tenant_id,
|
||||
alias: r.alias,
|
||||
normalized_alias: r.normalized_alias,
|
||||
woo_product_id: r.woo_product_id,
|
||||
category_hint: r.category_hint,
|
||||
boost: r.boost,
|
||||
metadata: r.metadata,
|
||||
updated_at: r.updated_at,
|
||||
}));
|
||||
const { rows } = await pool.query(sql, [tenant_id, query, normalized, lim]);
|
||||
return rows
|
||||
.filter((r) => Number(r.sim) >= threshold)
|
||||
.map((r) => ({
|
||||
tenant_id: r.tenant_id,
|
||||
alias: r.alias,
|
||||
normalized_alias: r.normalized_alias,
|
||||
woo_product_id: r.woo_product_id,
|
||||
category_hint: r.category_hint,
|
||||
boost: r.boost,
|
||||
metadata: r.metadata,
|
||||
updated_at: r.updated_at,
|
||||
similarity: Number(r.sim),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function searchAliasProductMappings({ tenant_id, q = "", limit = 50, threshold = 0.3 }) {
|
||||
const lim = Math.max(1, Math.min(200, parseInt(limit, 10) || 50));
|
||||
const query = String(q || "").trim();
|
||||
if (!query) return [];
|
||||
const normalized = query.toLowerCase();
|
||||
const sql = `
|
||||
select alias, woo_product_id, score, similarity(alias, $2) as sim
|
||||
from alias_product_mappings
|
||||
where tenant_id = $1 and alias % $2
|
||||
order by sim desc, score desc
|
||||
limit $3
|
||||
`;
|
||||
const { rows } = await pool.query(sql, [tenant_id, normalized, lim]);
|
||||
return rows
|
||||
.filter((r) => Number(r.sim) >= threshold)
|
||||
.map((r) => ({
|
||||
alias: r.alias,
|
||||
woo_product_id: Number(r.woo_product_id),
|
||||
score: Number(r.score || 1),
|
||||
similarity: Number(r.sim),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getRecoRules({ tenant_id }) {
|
||||
|
||||
Reference in New Issue
Block a user