Calibrate RFV frequency scoring
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m14s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m14s
This commit is contained in:
@@ -5,6 +5,7 @@ const {
|
||||
buildRfmClients,
|
||||
buildRfmSegments,
|
||||
buildDateFilter,
|
||||
getFrequencyScore,
|
||||
getPreviousDate,
|
||||
getRecencyScore,
|
||||
getRfmAnalytics,
|
||||
@@ -120,6 +121,15 @@ test('getRecencyScore uses fixed lifecycle boundaries', () => {
|
||||
assert.equal(getRecencyScore(366), 1);
|
||||
});
|
||||
|
||||
test('getFrequencyScore uses fixed historical order thresholds', () => {
|
||||
assert.equal(getFrequencyScore(0), 1);
|
||||
assert.equal(getFrequencyScore(1), 1);
|
||||
assert.equal(getFrequencyScore(2), 2);
|
||||
assert.equal(getFrequencyScore(4), 2);
|
||||
assert.equal(getFrequencyScore(5), 3);
|
||||
assert.equal(getFrequencyScore(100), 3);
|
||||
});
|
||||
|
||||
test('getRfmSegment maps the 3x3 RFM matrix', () => {
|
||||
assert.deepEqual(getRfmSegment(3, 3), { key: 'champions', label: 'Champions' });
|
||||
assert.deepEqual(getRfmSegment(2, 2), { key: 'need_attention', label: 'Precisam de Atenção' });
|
||||
@@ -143,7 +153,7 @@ test('buildRfmSegments summarizes segment count and revenue', () => {
|
||||
assert.equal(lost.totalRevenue, 25);
|
||||
});
|
||||
|
||||
test('buildRfmClients keeps a yesterday buyer in the same high-recency segment when it is the only qualifying client', () => {
|
||||
test('buildRfmClients keeps a repeat recent buyer as potential loyal across period filters', () => {
|
||||
const sevenDayClients = buildRfmClients([
|
||||
{ name: 'Cliente Ontem', phone: '1', monetary: 500, frequency: 2, quantityPurchased: 2, lastPurchaseDate: '2026-06-14', recencyDays: 1 }
|
||||
]);
|
||||
@@ -151,11 +161,42 @@ test('buildRfmClients keeps a yesterday buyer in the same high-recency segment w
|
||||
{ name: 'Cliente Ontem', phone: '1', monetary: 500, frequency: 2, quantityPurchased: 2, lastPurchaseDate: '2026-06-14', recencyDays: 0 }
|
||||
]);
|
||||
|
||||
assert.equal(sevenDayClients[0].segmentKey, 'champions');
|
||||
assert.equal(yesterdayClients[0].segmentKey, 'champions');
|
||||
assert.equal(sevenDayClients[0].segmentKey, 'potential_loyalists');
|
||||
assert.equal(yesterdayClients[0].segmentKey, 'potential_loyalists');
|
||||
assert.equal(sevenDayClients[0].phone, yesterdayClients[0].phone);
|
||||
});
|
||||
|
||||
test('buildRfmClients requires five orders and top value for a recent Champion', () => {
|
||||
const clients = buildRfmClients([
|
||||
{
|
||||
customerKey: 'potential',
|
||||
name: 'Potencial',
|
||||
monetary: 5000,
|
||||
frequency: 4,
|
||||
recencyDays: 10,
|
||||
rfmFrequency: 4,
|
||||
rfmMonetary: 5000,
|
||||
rfmMonetaryScore: 3
|
||||
},
|
||||
{
|
||||
customerKey: 'champion',
|
||||
name: 'Champion',
|
||||
monetary: 5000,
|
||||
frequency: 5,
|
||||
recencyDays: 10,
|
||||
rfmFrequency: 5,
|
||||
rfmMonetary: 5000,
|
||||
rfmMonetaryScore: 3
|
||||
}
|
||||
]);
|
||||
const byKey = new Map(clients.map(client => [client.customerKey, client]));
|
||||
|
||||
assert.equal(byKey.get('potential').frequencyScore, 2);
|
||||
assert.equal(byKey.get('potential').segmentKey, 'potential_loyalists');
|
||||
assert.equal(byKey.get('champion').frequencyScore, 3);
|
||||
assert.equal(byKey.get('champion').segmentKey, 'champions');
|
||||
});
|
||||
|
||||
test('buildRfmClients scores segments from RFM history when period totals are smaller', () => {
|
||||
const clients = buildRfmClients([
|
||||
{
|
||||
@@ -376,7 +417,9 @@ test('getRfmAnalytics classifies period buyers by history through the selected r
|
||||
assert.doesNotMatch(selectCalls[0].sql, /cliente_fone IS NOT NULL/);
|
||||
assert.match(selectCalls[1].sql, /\(\$1::date - MAX\(data_pedido_date\)\)::int/);
|
||||
assert.match(selectCalls[1].sql, /data_pedido_date <= \$1::date/);
|
||||
assert.match(selectCalls[1].sql, /PERCENT_RANK\(\) OVER \(ORDER BY frequency\)/);
|
||||
assert.match(selectCalls[1].sql, /WHEN frequency <= 1 THEN 1/);
|
||||
assert.match(selectCalls[1].sql, /WHEN frequency <= 4 THEN 2/);
|
||||
assert.doesNotMatch(selectCalls[1].sql, /PERCENT_RANK\(\) OVER \(ORDER BY frequency\)/);
|
||||
assert.match(selectCalls[1].sql, /PERCENT_RANK\(\) OVER \(ORDER BY monetary\)/);
|
||||
assert.match(selectCalls[1].sql, /customer_key = ANY\(\$2::text\[\]\)/);
|
||||
assert.doesNotMatch(selectCalls[1].sql, /cliente_fone IS NOT NULL/);
|
||||
|
||||
Reference in New Issue
Block a user