fix(customers): normalize structured ai product values
This commit is contained in:
+44
-7
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user