summaryrefslogtreecommitdiff
path: root/addons/crm/tests
diff options
context:
space:
mode:
authorDavid Beguin <dbe@odoo.com>2020-05-18 07:05:34 +0000
committerDavid Beguin <dbe@odoo.com>2020-09-14 14:26:30 +0000
commit7edc12e366659d226d56197e09bd65d17c80bd2f (patch)
tree6a809f0d8c90da00a4c3b2d46e3cb6768d93004f /addons/crm/tests
parentcd291b79eb2d2df80899867263ec71438ab8fe87 (diff)
[IMP] crm: add test for PLS live increment.
Setting a lead to won or lost should not impact others leads' probability until they are modified. Tests have been added to ensure that. Task ID: 2254543 closes odoo/odoo#57635 Signed-off-by: Toufik Benjaa (tbe) <tbe@odoo.com>
Diffstat (limited to 'addons/crm/tests')
-rw-r--r--addons/crm/tests/test_crm_pls.py278
1 files changed, 270 insertions, 8 deletions
diff --git a/addons/crm/tests/test_crm_pls.py b/addons/crm/tests/test_crm_pls.py
index 304bdce8868..9d74fac2938 100644
--- a/addons/crm/tests/test_crm_pls.py
+++ b/addons/crm/tests/test_crm_pls.py
@@ -20,6 +20,37 @@ class TestCRMPLS(TransactionCase):
'team_id': team_id
}
+ def generate_leads_with_tags(self, tag_ids):
+ Lead = self.env['crm.lead']
+ team_id = self.env['crm.team'].create({
+ 'name': 'blup',
+ }).id
+
+ leads_to_create = []
+ for i in range(150):
+ if i < 50: # tag 1
+ leads_to_create.append({
+ 'name': 'lead_tag_%s' % str(i),
+ 'tag_ids': [(4, tag_ids[0])],
+ 'team_id': team_id
+ })
+ elif i < 100: # tag 2
+ leads_to_create.append({
+ 'name': 'lead_tag_%s' % str(i),
+ 'tag_ids': [(4, tag_ids[1])],
+ 'team_id': team_id
+ })
+ else: # tag 1 and 2
+ leads_to_create.append({
+ 'name': 'lead_tag_%s' % str(i),
+ 'tag_ids': [(6, 0, tag_ids)],
+ 'team_id': team_id
+ })
+
+ leads_with_tags = Lead.create(leads_to_create)
+
+ return leads_with_tags
+
def test_predictive_lead_scoring(self):
""" We test here computation of lead probability based on PLS Bayes.
We will use 3 different values for each possible variables:
@@ -33,28 +64,35 @@ class TestCRMPLS(TransactionCase):
Note : We assume here that original bayes computation is correct
as we don't compute manually the probabilities."""
Lead = self.env['crm.lead']
+ LeadScoringFrequency = self.env['crm.lead.scoring.frequency']
state_values = ['correct', 'incorrect', None]
source_ids = self.env['utm.source'].search([], limit=3).ids
state_ids = self.env['res.country.state'].search([], limit=3).ids
country_ids = self.env['res.country'].search([], limit=3).ids
stage_ids = self.env['crm.stage'].search([], limit=3).ids
+ won_stage_id = self.env['crm.stage'].search([('is_won', '=', True)], limit=1).id
team_ids = self.env['crm.team'].create([{'name': 'Team Test 1'}, {'name': 'Team Test 2'}]).ids
# create bunch of lost and won crm_lead
leads_to_create = []
# for team 1
for i in range(3):
- leads_to_create.append(self._get_lead_values(team_ids[0], 'team_1_%s' % str(i), country_ids[i], state_ids[i], state_values[i], state_values[i], source_ids[i], stage_ids[i]))
+ leads_to_create.append(
+ self._get_lead_values(team_ids[0], 'team_1_%s' % str(i), country_ids[i], state_ids[i], state_values[i], state_values[i], source_ids[i], stage_ids[i]))
leads_to_create.append(
self._get_lead_values(team_ids[0], 'team_1_%s' % str(3), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[2], stage_ids[1]))
+ leads_to_create.append(
+ self._get_lead_values(team_ids[0], 'team_1_%s' % str(4), country_ids[1], state_ids[1], state_values[1], state_values[0], source_ids[1], stage_ids[0]))
# for team 2
leads_to_create.append(
- self._get_lead_values(team_ids[1], 'team_2_%s' % str(0), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[1], stage_ids[2]))
+ self._get_lead_values(team_ids[1], 'team_2_%s' % str(5), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[1], stage_ids[2]))
+ leads_to_create.append(
+ self._get_lead_values(team_ids[1], 'team_2_%s' % str(6), country_ids[0], state_ids[1], state_values[0], state_values[1], source_ids[2], stage_ids[1]))
leads_to_create.append(
- self._get_lead_values(team_ids[1], 'team_2_%s' % str(1), country_ids[0], state_ids[1], state_values[0], state_values[1], source_ids[2], stage_ids[1]))
+ self._get_lead_values(team_ids[1], 'team_2_%s' % str(7), country_ids[0], state_ids[2], state_values[0], state_values[1], source_ids[2], stage_ids[0]))
leads_to_create.append(
- self._get_lead_values(team_ids[1], 'team_2_%s' % str(2), country_ids[0], state_ids[2], state_values[0], state_values[1], source_ids[2], stage_ids[0]))
+ self._get_lead_values(team_ids[1], 'team_2_%s' % str(8), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[2], stage_ids[1]))
leads_to_create.append(
- self._get_lead_values(team_ids[1], 'team_2_%s' % str(3), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[2], stage_ids[1]))
+ self._get_lead_values(team_ids[1], 'team_2_%s' % str(9), country_ids[1], state_ids[0], state_values[1], state_values[0], source_ids[1], stage_ids[1]))
leads = Lead.create(leads_to_create)
@@ -68,10 +106,11 @@ class TestCRMPLS(TransactionCase):
leads[1].action_set_lost()
leads[2].action_set_won()
# for Team 2
- leads[4].action_set_lost()
leads[5].action_set_lost()
- leads[6].action_set_won()
+ leads[6].action_set_lost()
+ leads[7].action_set_won()
+ # A. Test Full Rebuild
# rebuild frequencies table and recompute automated_probability for all leads.
Lead._cron_update_automated_probabilities()
@@ -79,4 +118,227 @@ class TestCRMPLS(TransactionCase):
leads.invalidate_cache()
self.assertEqual(tools.float_compare(leads[3].automated_probability, 33.49, 2), 0)
- self.assertEqual(tools.float_compare(leads[7].automated_probability, 7.74, 2), 0)
+ self.assertEqual(tools.float_compare(leads[8].automated_probability, 7.74, 2), 0)
+
+ # Test frequencies
+ lead_4_stage_0_freq = LeadScoringFrequency.search([('team_id', '=', leads[4].team_id.id), ('variable', '=', 'stage_id'), ('value', '=', stage_ids[0])])
+ lead_4_stage_won_freq = LeadScoringFrequency.search([('team_id', '=', leads[4].team_id.id), ('variable', '=', 'stage_id'), ('value', '=', won_stage_id)])
+ lead_4_country_freq = LeadScoringFrequency.search([('team_id', '=', leads[4].team_id.id), ('variable', '=', 'country_id'), ('value', '=', leads[4].country_id.id)])
+ lead_4_email_state_freq = LeadScoringFrequency.search([('team_id', '=', leads[4].team_id.id), ('variable', '=', 'email_state'), ('value', '=', str(leads[4].email_state))])
+
+ lead_9_stage_0_freq = LeadScoringFrequency.search([('team_id', '=', leads[9].team_id.id), ('variable', '=', 'stage_id'), ('value', '=', stage_ids[0])])
+ lead_9_stage_won_freq = LeadScoringFrequency.search([('team_id', '=', leads[9].team_id.id), ('variable', '=', 'stage_id'), ('value', '=', won_stage_id)])
+ lead_9_country_freq = LeadScoringFrequency.search([('team_id', '=', leads[9].team_id.id), ('variable', '=', 'country_id'), ('value', '=', leads[9].country_id.id)])
+ lead_9_email_state_freq = LeadScoringFrequency.search([('team_id', '=', leads[9].team_id.id), ('variable', '=', 'email_state'), ('value', '=', str(leads[9].email_state))])
+
+ self.assertEqual(lead_4_stage_0_freq.won_count, 1.1)
+ self.assertEqual(lead_4_stage_won_freq.won_count, 1.1)
+ self.assertEqual(lead_4_country_freq.won_count, 0.1)
+ self.assertEqual(lead_4_email_state_freq.won_count, 1.1)
+ self.assertEqual(lead_4_stage_0_freq.lost_count, 2.1)
+ self.assertEqual(lead_4_stage_won_freq.lost_count, 0.1)
+ self.assertEqual(lead_4_country_freq.lost_count, 1.1)
+ self.assertEqual(lead_4_email_state_freq.lost_count, 2.1)
+
+ self.assertEqual(lead_9_stage_0_freq.won_count, 1.1)
+ self.assertEqual(lead_9_stage_won_freq.won_count, 1.1)
+ self.assertEqual(lead_9_country_freq.won_count, 0.0) # frequency does not exist
+ self.assertEqual(lead_9_email_state_freq.won_count, 1.1)
+ self.assertEqual(lead_9_stage_0_freq.lost_count, 2.1)
+ self.assertEqual(lead_9_stage_won_freq.lost_count, 0.1)
+ self.assertEqual(lead_9_country_freq.lost_count, 0.0) # frequency does not exist
+ self.assertEqual(lead_9_email_state_freq.lost_count, 2.1)
+
+ # B. Test Live Increment
+ leads[4].action_set_lost()
+ leads[9].action_set_won()
+
+ # re-get frequencies that did not exists before
+ lead_9_country_freq = LeadScoringFrequency.search([('team_id', '=', leads[9].team_id.id), ('variable', '=', 'country_id'), ('value', '=', leads[9].country_id.id)])
+
+ # B.1. Test frequencies - team 1 should not impact team 2
+ self.assertEqual(lead_4_stage_0_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_stage_won_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_country_freq.won_count, 0.1) # unchanged
+ self.assertEqual(lead_4_email_state_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_stage_0_freq.lost_count, 3.1) # + 1
+ self.assertEqual(lead_4_stage_won_freq.lost_count, 0.1) # unchanged - consider stages with <= sequence when lost
+ self.assertEqual(lead_4_country_freq.lost_count, 2.1) # + 1
+ self.assertEqual(lead_4_email_state_freq.lost_count, 3.1) # + 1
+
+ self.assertEqual(lead_9_stage_0_freq.won_count, 2.1) # + 1
+ self.assertEqual(lead_9_stage_won_freq.won_count, 2.1) # + 1 - consider every stages when won
+ self.assertEqual(lead_9_country_freq.won_count, 1.1) # + 1
+ self.assertEqual(lead_9_email_state_freq.won_count, 2.1) # + 1
+ self.assertEqual(lead_9_stage_0_freq.lost_count, 2.1) # unchanged
+ self.assertEqual(lead_9_stage_won_freq.lost_count, 0.1) # unchanged
+ self.assertEqual(lead_9_country_freq.lost_count, 0.1) # unchanged (did not exists before)
+ self.assertEqual(lead_9_email_state_freq.lost_count, 2.1) # unchanged
+
+ # Propabilities of other leads should not be impacted as only modified lead are recomputed.
+ self.assertEqual(tools.float_compare(leads[3].automated_probability, 33.49, 2), 0)
+ self.assertEqual(tools.float_compare(leads[8].automated_probability, 7.74, 2), 0)
+
+ self.assertEqual(leads[3].is_automated_probability, True)
+ self.assertEqual(leads[8].is_automated_probability, True)
+
+ # Restore -> Should decrease lost
+ leads[4].toggle_active()
+ self.assertEqual(lead_4_stage_0_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_stage_won_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_country_freq.won_count, 0.1) # unchanged
+ self.assertEqual(lead_4_email_state_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_stage_0_freq.lost_count, 2.1) # - 1
+ self.assertEqual(lead_4_stage_won_freq.lost_count, 0.1) # unchanged - consider stages with <= sequence when lost
+ self.assertEqual(lead_4_country_freq.lost_count, 1.1) # - 1
+ self.assertEqual(lead_4_email_state_freq.lost_count, 2.1) # - 1
+
+ self.assertEqual(lead_9_stage_0_freq.won_count, 2.1) # unchanged
+ self.assertEqual(lead_9_stage_won_freq.won_count, 2.1) # unchanged
+ self.assertEqual(lead_9_country_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_9_email_state_freq.won_count, 2.1) # unchanged
+ self.assertEqual(lead_9_stage_0_freq.lost_count, 2.1) # unchanged
+ self.assertEqual(lead_9_stage_won_freq.lost_count, 0.1) # unchanged
+ self.assertEqual(lead_9_country_freq.lost_count, 0.1) # unchanged
+ self.assertEqual(lead_9_email_state_freq.lost_count, 2.1) # unchanged
+
+ # set to won stage -> Should increase won
+ leads[4].stage_id = won_stage_id
+ self.assertEqual(lead_4_stage_0_freq.won_count, 2.1) # + 1
+ self.assertEqual(lead_4_stage_won_freq.won_count, 2.1) # + 1
+ self.assertEqual(lead_4_country_freq.won_count, 1.1) # + 1
+ self.assertEqual(lead_4_email_state_freq.won_count, 2.1) # + 1
+ self.assertEqual(lead_4_stage_0_freq.lost_count, 2.1) # unchanged
+ self.assertEqual(lead_4_stage_won_freq.lost_count, 0.1) # unchanged
+ self.assertEqual(lead_4_country_freq.lost_count, 1.1) # unchanged
+ self.assertEqual(lead_4_email_state_freq.lost_count, 2.1) # unchanged
+
+ # Archive (was won, now lost) -> Should decrease won and increase lost
+ leads[4].toggle_active()
+ self.assertEqual(lead_4_stage_0_freq.won_count, 1.1) # - 1
+ self.assertEqual(lead_4_stage_won_freq.won_count, 1.1) # - 1
+ self.assertEqual(lead_4_country_freq.won_count, 0.1) # - 1
+ self.assertEqual(lead_4_email_state_freq.won_count, 1.1) # - 1
+ self.assertEqual(lead_4_stage_0_freq.lost_count, 3.1) # + 1
+ self.assertEqual(lead_4_stage_won_freq.lost_count, 1.1) # consider stages with <= sequence when lostand as stage is won.. even won_stage lost_count is increased by 1
+ self.assertEqual(lead_4_country_freq.lost_count, 2.1) # + 1
+ self.assertEqual(lead_4_email_state_freq.lost_count, 3.1) # + 1
+
+ # Move to original stage -> Should do nothing (as lead is still lost)
+ leads[4].stage_id = stage_ids[0]
+ self.assertEqual(lead_4_stage_0_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_stage_won_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_country_freq.won_count, 0.1) # unchanged
+ self.assertEqual(lead_4_email_state_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_stage_0_freq.lost_count, 3.1) # unchanged
+ self.assertEqual(lead_4_stage_won_freq.lost_count, 1.1) # unchanged
+ self.assertEqual(lead_4_country_freq.lost_count, 2.1) # unchanged
+ self.assertEqual(lead_4_email_state_freq.lost_count, 3.1) # unchanged
+
+ # Restore -> Should decrease lost - at the end, frequencies should be like first frequencyes tests (except for 0.0 -> 0.1)
+ leads[4].toggle_active()
+ self.assertEqual(lead_4_stage_0_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_stage_won_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_country_freq.won_count, 0.1) # unchanged
+ self.assertEqual(lead_4_email_state_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_stage_0_freq.lost_count, 2.1) # - 1
+ self.assertEqual(lead_4_stage_won_freq.lost_count, 1.1) # unchanged - consider stages with <= sequence when lost
+ self.assertEqual(lead_4_country_freq.lost_count, 1.1) # - 1
+ self.assertEqual(lead_4_email_state_freq.lost_count, 2.1) # - 1
+
+ # Probabilities should only be recomputed after modifying the lead itself.
+ leads[3].stage_id = stage_ids[0] # probability should only change a bit as frequencies are almost the same (except 0.0 -> 0.1)
+ leads[8].stage_id = stage_ids[0] # probability should change quite a lot
+
+ # Test frequencies (should not have changed)
+ self.assertEqual(lead_4_stage_0_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_stage_won_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_country_freq.won_count, 0.1) # unchanged
+ self.assertEqual(lead_4_email_state_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_4_stage_0_freq.lost_count, 2.1) # unchanged
+ self.assertEqual(lead_4_stage_won_freq.lost_count, 1.1) # unchanged
+ self.assertEqual(lead_4_country_freq.lost_count, 1.1) # unchanged
+ self.assertEqual(lead_4_email_state_freq.lost_count, 2.1) # unchanged
+
+ self.assertEqual(lead_9_stage_0_freq.won_count, 2.1) # unchanged
+ self.assertEqual(lead_9_stage_won_freq.won_count, 2.1) # unchanged
+ self.assertEqual(lead_9_country_freq.won_count, 1.1) # unchanged
+ self.assertEqual(lead_9_email_state_freq.won_count, 2.1) # unchanged
+ self.assertEqual(lead_9_stage_0_freq.lost_count, 2.1) # unchanged
+ self.assertEqual(lead_9_stage_won_freq.lost_count, 0.1) # unchanged
+ self.assertEqual(lead_9_country_freq.lost_count, 0.1) # unchanged
+ self.assertEqual(lead_9_email_state_freq.lost_count, 2.1) # unchanged
+
+ # Continue to test probability computation
+ leads[3].probability = 40
+
+ self.assertEqual(leads[3].is_automated_probability, False)
+ self.assertEqual(leads[8].is_automated_probability, True)
+
+ self.assertEqual(tools.float_compare(leads[3].automated_probability, 20.87, 2), 0)
+ self.assertEqual(tools.float_compare(leads[8].automated_probability, 2.43, 2), 0)
+ self.assertEqual(tools.float_compare(leads[3].probability, 40, 2), 0)
+ self.assertEqual(tools.float_compare(leads[8].probability, 2.43, 2), 0)
+
+ # ----------------------------------------------
+ # Test tag_id frequencies and probability impact
+ # ----------------------------------------------
+
+ tag_ids = self.env['crm.tag'].create([
+ {'name': "Tag_test_1"},
+ {'name': "Tag_test_2"},
+ ]).ids
+ # tag_ids = self.env['crm.tag'].search([], limit=2).ids
+ leads_with_tags = self.generate_leads_with_tags(tag_ids)
+
+ leads_with_tags[:30].action_set_lost() # 60% lost on tag 1
+ leads_with_tags[31:50].action_set_won() # 40% won on tag 1
+ leads_with_tags[50:90].action_set_lost() # 80% lost on tag 2
+ leads_with_tags[91:100].action_set_won() # 20% won on tag 2
+ leads_with_tags[100:135].action_set_lost() # 70% lost on tag 1 and 2
+ leads_with_tags[136:150].action_set_won() # 30% won on tag 1 and 2
+ # tag 1 : won = 19+14 / lost = 30+35
+ # tag 2 : won = 9+14 / lost = 40+35
+
+ tag_1_freq = LeadScoringFrequency.search([('variable', '=', 'tag_id'), ('value', '=', tag_ids[0])])
+ tag_2_freq = LeadScoringFrequency.search([('variable', '=', 'tag_id'), ('value', '=', tag_ids[1])])
+ self.assertEqual(tools.float_compare(tag_1_freq.won_count, 33.1, 1), 0)
+ self.assertEqual(tools.float_compare(tag_1_freq.lost_count, 65.1, 1), 0)
+ self.assertEqual(tools.float_compare(tag_2_freq.won_count, 23.1, 1), 0)
+ self.assertEqual(tools.float_compare(tag_2_freq.lost_count, 75.1, 1), 0)
+
+ # Force recompute - A priori, no need to do this as, for each won / lost, we increment tag frequency.
+ Lead._cron_update_automated_probabilities()
+ leads_with_tags.invalidate_cache()
+
+ lead_tag_1 = leads_with_tags[30]
+ lead_tag_2 = leads_with_tags[90]
+ lead_tag_1_2 = leads_with_tags[135]
+
+ self.assertEqual(tools.float_compare(lead_tag_1.automated_probability, 33.69, 2), 0)
+ self.assertEqual(tools.float_compare(lead_tag_2.automated_probability, 23.51, 2), 0)
+ self.assertEqual(tools.float_compare(lead_tag_1_2.automated_probability, 28.05, 2), 0)
+
+ lead_tag_1.tag_ids = [(5, 0, 0)] # remove all tags
+ lead_tag_1_2.tag_ids = [(3, tag_ids[1], 0)] # remove tag 2
+
+ self.assertEqual(tools.float_compare(lead_tag_1.automated_probability, 28.6, 2), 0)
+ self.assertEqual(tools.float_compare(lead_tag_2.automated_probability, 23.51, 2), 0) # no impact
+ self.assertEqual(tools.float_compare(lead_tag_1_2.automated_probability, 33.69, 2), 0)
+
+ lead_tag_1.tag_ids = [(4, tag_ids[1])] # add tag 2
+ lead_tag_2.tag_ids = [(4, tag_ids[0])] # add tag 1
+ lead_tag_1_2.tag_ids = [(3, tag_ids[0]), (4, tag_ids[1])] # remove tag 1 / add tag 2
+
+ self.assertEqual(tools.float_compare(lead_tag_1.automated_probability, 23.51, 2), 0)
+ self.assertEqual(tools.float_compare(lead_tag_2.automated_probability, 28.05, 2), 0)
+ self.assertEqual(tools.float_compare(lead_tag_1_2.automated_probability, 23.51, 2), 0)
+
+ # go back to initial situation
+ lead_tag_1.tag_ids = [(3, tag_ids[1]), (4, tag_ids[0])] # remove tag 2 / add tag 1
+ lead_tag_2.tag_ids = [(3, tag_ids[0])] # remove tag 1
+ lead_tag_1_2.tag_ids = [(4, tag_ids[0])] # add tag 1
+
+ self.assertEqual(tools.float_compare(lead_tag_1.automated_probability, 33.69, 2), 0)
+ self.assertEqual(tools.float_compare(lead_tag_2.automated_probability, 23.51, 2), 0)
+ self.assertEqual(tools.float_compare(lead_tag_1_2.automated_probability, 28.05, 2), 0)