fix(customers): normalize structured ai product values

This commit is contained in:
wmq
2026-08-03 02:04:13 +08:00
parent 45b94374b0
commit a7c894138e
5 changed files with 65 additions and 14 deletions
+44 -7
View File
@@ -759,8 +759,14 @@ async function analyzeAndPersistConversation(tenantId, conversationId) {
...customer,
company: String(result.aiProfile?.companyName || '').trim() || customer.company,
language: String(result.aiProfile?.preferredLanguage || '').trim() || customer.language,
products: Array.from(new Set([...(customer.products || []), ...(result.products || [])])),
tags: Array.from(new Set([...(customer.tags || []), ...(result.tags || [])])),
products: Array.from(new Set(normalizedTextList(
[...(customer.products || []), ...(Array.isArray(result.products) ? result.products : [])],
['name', 'product', 'category'],
))),
tags: Array.from(new Set(normalizedTextList(
[...(customer.tags || []), ...(Array.isArray(result.tags) ? result.tags : [])],
['label', 'name', 'category'],
))),
intentScore,
intentLevel: result.intentLevel || customer.intentLevel || 'low',
stage: result.stage || customer.stage || 'initial',
@@ -777,7 +783,7 @@ async function analyzeAndPersistConversation(tenantId, conversationId) {
? result.aiProfile.relationshipEvidence.map(String).filter(Boolean).slice(0, 3)
: [],
summary: String(result.summary || ''),
nextActions: Array.isArray(result.nextActions) ? result.nextActions : [],
nextActions: normalizedTextList(result.nextActions, ['action', 'text', 'label', 'name']),
profileVersion: 3,
},
aiAnalyzedAt: new Date().toISOString(),
@@ -1125,6 +1131,18 @@ const kinds = {
customers: 'customers', opportunities: 'opportunities', tasks: 'tasks', risks: 'risks',
knowledge: 'knowledge',
}
function normalizedTextList(values, preferredKeys = []) {
if (!Array.isArray(values)) return []
return values.map((item) => {
if (typeof item === 'string' || typeof item === 'number') return String(item).trim()
if (!item || typeof item !== 'object') return ''
for (const key of preferredKeys) {
const value = item[key]
if (typeof value === 'string' || typeof value === 'number') return String(value).trim()
}
return ''
}).filter(Boolean)
}
function withDefaults(kind, body) {
if (kind === 'customers') {
const source = body && typeof body === 'object' ? body : {}
@@ -1135,15 +1153,15 @@ function withDefaults(kind, body) {
valueLevel: 'D', isKey: false, ownerId: null,
aiProfile: { summary: '', keyPoints: [], nextActions: [] },
...source,
products: Array.isArray(source.products) ? source.products : [],
tags: Array.isArray(source.tags) ? source.tags : [],
products: normalizedTextList(source.products, ['name', 'product', 'category']),
tags: normalizedTextList(source.tags, ['label', 'name', 'category']),
aiProfile: {
summary: '',
keyPoints: [],
nextActions: [],
...(source.aiProfile && typeof source.aiProfile === 'object' ? source.aiProfile : {}),
keyPoints: Array.isArray(source.aiProfile?.keyPoints) ? source.aiProfile.keyPoints : [],
nextActions: Array.isArray(source.aiProfile?.nextActions) ? source.aiProfile.nextActions : [],
keyPoints: normalizedTextList(source.aiProfile?.keyPoints, ['text', 'label', 'name']),
nextActions: normalizedTextList(source.aiProfile?.nextActions, ['action', 'text', 'label', 'name']),
},
}
return {
@@ -1155,10 +1173,26 @@ function withDefaults(kind, body) {
}
return body
}
async function normalizeLegacyCustomerResources() {
const { rows } = await pool.query(
"SELECT tenant_id, data FROM resources WHERE kind='customers'",
)
let updated = 0
for (const row of rows) {
const current = row.data
const normalized = withDefaults('customers', current)
if (JSON.stringify(current) === JSON.stringify(normalized)) continue
await putResource(String(row.tenant_id), 'customers', normalized)
updated++
}
if (updated) console.log(`Normalized ${updated} legacy customer records`)
}
for (const [path, kind] of Object.entries(kinds)) {
app.get(`/api/${path}`, async (req, res) => {
let list = await listResources(req.auth.tenantId, kind)
if (kind === 'customers') {
list = list.map((customer) => withDefaults('customers', customer))
list = await Promise.all(list.map(async (customer) => {
const country = resolveCustomerCountry(customer, customer)
if (
@@ -5650,6 +5684,9 @@ async function runMessagingIntegrityMaintenance() {
if (weeklyDue) await reconcileConnectedMessagingChannels({ full: true, refreshProfiles: true })
}
setTimeout(() => void normalizeLegacyCustomerResources().catch((error) => {
console.error(JSON.stringify({ message: 'Customer shape normalization failed', error: error.message }))
}), 1_000).unref?.()
setTimeout(() => void reconcileConnectedMessagingChannels(), 15_000).unref?.()
setInterval(
() => void reconcileConnectedMessagingChannels(),