def test_mutliple_groups(self): """ https://github.com/betagouv/peps/issues/16 We need to ensure no practices belonging to the same practice group are selected. """ rumex = Weed.objects.filter(display_text='Rumex').first() chardon = Weed.objects.filter( display_text='Chardon des champs').first() answers = { "problem": "GLYPHOSATE", "glyphosate": "VIVACES,COUVERTS", "weeds": "{0},{1}".format(str(chardon.external_id), str(rumex.external_id)), "tillage": "TRAVAIL_PROFOND", } engine = Engine(answers, [], []) results = engine.calculate_results() suggestions = engine.get_suggestions(results) suggested_groups = [] for practice in map(lambda x: x.practice, suggestions): practice_groups = list(practice.practice_groups.all()) for group in practice_groups: self.assertNotIn(group, suggested_groups) suggested_groups.append(group)
def test_pests_problem_type(self): """ If the user says their problem are weeds (adventices), the three suggestions must address weeds. """ ble = SimulatorCulture.objects.filter(display_text='Blé dur').first() mais = SimulatorCulture.objects.filter(display_text='Maïs').first() chanvre = SimulatorCulture.objects.filter( display_text='Chanvre').first() answers = { "problem": "DESHERBAGE", "rotation": [ble.external_id, chanvre.external_id, mais.external_id] } engine = Engine(answers, [], []) practices = engine.calculate_results() response_items = engine.get_suggestions(practices) # We ensure there are three suggestions, and all three address weeds self.assertEqual(len(response_items), 3) for response_item in response_items: suggestion = response_item.practice self.assertIn(Problem['DESHERBAGE'].value, suggestion.problems_addressed)
def test_weed_fields(self): """ Three form fields can contain weed information: weeds, perennials and weedsGlyphosate. All must be treated the same way by the engine. """ practice_title = "Faucher une culture fourragère" rumex = Weed.objects.filter(display_text='Rumex').first() chardon = Weed.objects.filter( display_text='Chardon des champs').first() ble = SimulatorCulture.objects.filter(display_text='Blé dur').first() answers = { "problem": "DESHERBAGE", "weeds": "{0},{1}".format(str(chardon.external_id), str(rumex.external_id)), "rotation": [ble.external_id], "cattle": "Oui" } engine = Engine(answers, [], []) results = engine.calculate_results() result_weeds = next( filter(lambda x: x.practice.title == practice_title, results)) answers = { "problem": "DESHERBAGE", "perennials": "{0},{1}".format(str(chardon.external_id), str(rumex.external_id)), "rotation": [ble.external_id], "cattle": "Oui" } engine = Engine(answers, [], []) results = engine.calculate_results() result_perennials = next( filter(lambda x: x.practice.title == practice_title, results)) self.assertEqual(result_weeds.weight, result_perennials.weight) answers = { "problem": "DESHERBAGE", "weedsGlyphosate": "{0},{1}".format(str(chardon.external_id), str(rumex.external_id)), "rotation": [ble.external_id], "cattle": "Oui" } engine = Engine(answers, [], []) results = engine.calculate_results() result_weeds_glypho = next( filter(lambda x: x.practice.title == practice_title, results)) self.assertEqual(result_weeds.weight, result_weeds_glypho.weight)
def test_suggestion_rankings(self): """ We check that the weight of the proposed suggestions is correct """ answers = { "problem": "MALADIES_FONGIQUES", "rotation": [], "department": "01" } engine = Engine(answers, [], []) practices = engine.calculate_results() suggestions = engine.get_suggestions(practices) # There should be two practices with weight 1.5 self.assertEqual(len(suggestions), 3) weights = list(map(lambda x: x.weight, suggestions)) self.assertEqual(len(list(filter(lambda x: x == 1.5, weights))), 2)
def test_culture_multipliers(self): """ A practice can be more (or less) useful to address certain cultures, this is specified in the culture_multipliers field of the practice model. The practice "Semer l'inter-rang pour réduire la place disponible aux adventices" has a multiplier for the culture COLZA, here we check that said multiplier is taken into account by the engine. """ practice_title = "Semer l'inter-rang pour réduire la place disponible aux adventices" colza = SimulatorCulture.objects.filter(display_text='Colza').first() colza_multiplier = 1.2 # First we check the weignt without using COLZA in the response answers = {"problem": "DESHERBAGE", "rotation": []} engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) initial_weight = result.weight # Now we add COLZA in the response and get the results answers = {"problem": "DESHERBAGE", "rotation": [colza.external_id]} engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) new_weight = result.weight # We need to make sure the new weight has taken into account the # multiplier for COLZA self.assertEqual(new_weight, initial_weight * colza_multiplier)
def test_problem_glyphosate(self): """ If a user chooses glyphosate as their problem, the practices that target glyphosate should have a higher score. An exemple of these practices is: "Défanner les pommes des terre avec un produit de biocontrôle" """ practice_title = 'Défanner les pommes des terre avec un produit de biocontrôle' pomme_de_terre = SimulatorCulture.objects.filter( display_text='Pomme de terre').first() # First we make a request without specifying glyphosate as the main problem answers = { "problem": "DESHERBAGE", "rotation": [pomme_de_terre.external_id] } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) initial_weight = result.weight # First we make a request without specifying glyphosate as the main problem answers = { "problem": "GLYPHOSATE", "rotation": [pomme_de_terre.external_id] } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) self.assertGreater(result.weight, 0) self.assertGreater(result.weight, initial_weight)
def test_cultures_weight(self): """ If the user is already growing a culture, like Chanvre in this example, the engine should set practices introducing to that culture to zero. """ practice_title = 'Introduire le chanvre dans la rotation' chanvre = SimulatorCulture.objects.filter( display_text='Chanvre').first() # If we have a problem with weeds we expect to have the chanvre practice # with a ranking above 0 answers = {"problem": "DESHERBAGE", "department": "01"} engine = Engine(answers, [], []) practices = engine.calculate_results() chanvre_practice = next( filter(lambda x: x.practice.title == practice_title, practices)) self.assertGreater(chanvre_practice.weight, 0) # However, if the user says they already have chanvre in their rotation, # the same practice will now have 0 as weight answers = { "problem": "DESHERBAGE", "rotation": [chanvre.external_id], "department": "01" } engine = Engine(answers, [], []) practices = engine.calculate_results() chanvre_practice = next( filter(lambda x: x.practice.title == practice_title, practices)) self.assertEqual(chanvre_practice.weight, 0.0)
def test_pest_whitelist(self): """ A practice can have a limited number of whitelisted pests it can be applied to. As an example, practice "Lutter contre la pyrale du maïs au moyen de lâchers de trichogrammes" can only be relevant for pyrale. """ practice_title = "Lutter contre la pyrale du maïs au moyen de lâchers de trichogrammes" pyrales = Pest.objects.filter(display_text='Pyrales').first() mais = SimulatorCulture.objects.filter(display_text='Maïs').first() # First we check the weignt without using PYRALES. We expect the weight to be # zero since the whitelist is not upheld answers = { "problem": "RAVAGEURS", "rotation": [mais.external_id], } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) self.assertEqual(result.weight, 0) # Now we add PYRALES. The same practice should have a non-zero weight answers = { "problem": "RAVAGEURS", "pests": "{0}".format(pyrales.external_id), "rotation": [mais.external_id], } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) self.assertTrue(result.weight > 0)
def test_blacklist_types(self): """ It is possible to blacklist entire practice types. This test ensures that practices belonging to blacklisted practice types have a score of zero. """ ble = SimulatorCulture.objects.filter(display_text='Blé dur').first() mais = SimulatorCulture.objects.filter(display_text='Maïs').first() chanvre = SimulatorCulture.objects.filter( display_text='Chanvre').first() # We make a call to get suggestions answers = { "problem": "DESHERBAGE", "rotation": [ble.external_id, chanvre.external_id, mais.external_id] } engine = Engine(answers, [], []) practices = engine.calculate_results() response_items = engine.get_suggestions(practices) # We get the first suggestion's practice type. We will blacklist it # later and ensure no practices of the same type are proposed, and that # they are all set to zero. blacklisted_practice_type = str( list(response_items[0].practice.types.all())[0].id) engine = Engine(answers, [], [blacklisted_practice_type]) practices = engine.calculate_results() response_items = engine.get_suggestions(practices) # Now let's verify that all the practices belonging to that type # have a score of zero. for practice_item in practices: practice_types_ids = list( map(lambda x: str(x.id), practice_item.practice.types.all())) if blacklisted_practice_type in practice_types_ids: self.assertEqual(practice_item.weight, 0.0)
def test_blacklist_practices(self): """ It is possible to blacklist individual practices, this test ensures that blacklisted practices end up with a score of zero. """ ble = SimulatorCulture.objects.filter(display_text='Blé dur').first() mais = SimulatorCulture.objects.filter(display_text='Maïs').first() chanvre = SimulatorCulture.objects.filter( display_text='Chanvre').first() # We make a call to get suggestions answers = { "problem": "DESHERBAGE", "rotation": [ble.external_id, chanvre.external_id, mais.external_id] } engine = Engine(answers, [], []) practices = engine.calculate_results() response_items = engine.get_suggestions(practices) # We get the first suggestion - we will blacklist it later and ensure # it is no longer proposed. blacklisted_suggestion_id = str(response_items[0].practice.id) engine = Engine(answers, [blacklisted_suggestion_id], []) practices = engine.calculate_results() response_items = engine.get_suggestions(practices) # Now let's verify that the suggestions no longer include the # blacklisted practice suggested_ids = list(map(lambda x: str(x.practice.id), response_items)) self.assertNotIn(blacklisted_suggestion_id, suggested_ids) # The blacklisted practice should have a score of zero blacklisted_response_item = next( filter(lambda x: str(x.practice.id) == blacklisted_suggestion_id, practices)) self.assertEqual(blacklisted_response_item.weight, 0.0)
def test_balanced_rotation(self): """ Summer cultures should be counted with the automn ones when calculating the balance of the rotation. """ # Printemps betterave = 'recKDNdfSiV33djzf' pomme_de_terre = 'recURJ9JQS9u6OHva' orge_printemps = 'recEEz6LZ3MRDdV99' # Automne ble_hiver = 'recmm8lo1bGXCYSA3' # Summer colza = 'recZj4cTO0dwcYhbe' # This practice balances the sowing period and should be proposed when having an unbalanced rotation practice_name = "Favoriser l'alternance de cultures à semis de printemps et d'automne" # In this example, all cultures are spring cultures, so it should be unbalanced answers = { "problem": "DESHERBAGE", "tillage": "TRAVAIL_PROFOND", "rotation": [betterave, pomme_de_terre, orge_printemps], } engine = Engine(answers, [], []) unbalanced_result = next( filter(lambda x: x.practice.title == practice_name, engine.calculate_results())) # Now we balance it out, having 3 spring, 1 automn and 1 summer (automn and # summer count together) answers = { "problem": "DESHERBAGE", "tillage": "TRAVAIL_PROFOND", "rotation": [betterave, pomme_de_terre, orge_printemps, ble_hiver, colza], } engine = Engine(answers, [], []) balanced_result = next( filter(lambda x: x.practice.title == practice_name, engine.calculate_results())) self.assertGreater(unbalanced_result.weight, balanced_result.weight)
def test_large_rotation(self): """ If there are more than six cultures in the rotation of a user, practices with added cultures should be handicaped. """ practice_name = 'Faucher une culture fourragère' mais = 'recsPtaEneeYVoEWx' tournesol = 'rec5MHmc9xIgAg8ha' soja = 'recwHs4aAiZc9okg9' ble = 'recuVebqXEqCg8kK0' orge = 'recfGVtMZSz05Rfl8' ble_hiver = 'recmm8lo1bGXCYSA3' colza = 'recZj4cTO0dwcYhbe' rumex = 'rec2wnpJOAJzUFe5v' # When having 6 or less cultures, there is no handicap answers = { 'problem': 'DESHERBAGE', 'rotation': [mais, tournesol, ble, orge], 'weeds': rumex, "cattle": "Oui", } engine = Engine(answers, [], []) result = next( filter(lambda x: x.practice.title == practice_name, engine.calculate_results())) initial_weight = result.weight # When having 6 or more, a handicap of 0.9 will be applied to cultures that # add a new culture to the rotation answers = { 'problem': 'DESHERBAGE', 'rotation': [mais, tournesol, ble, orge, soja, ble_hiver, colza], 'weeds': rumex, "cattle": "Oui", } engine = Engine(answers, [], []) result = next( filter(lambda x: x.practice.title == practice_name, engine.calculate_results())) large_rotation_weight = result.weight self.assertEqual(initial_weight * 0.9, large_rotation_weight)
def test_weed_whitelist(self): """ A practice can have a limited number of whitelisted weeds it can be applied to. As an example, practice "Faucher une culture fourragère " can only be relevant for Rumex. """ practice_title = "Faucher une culture fourragère" rumex = Weed.objects.filter(display_text='Rumex').first() chardon = Weed.objects.filter( display_text='Chardon des champs').first() ble = SimulatorCulture.objects.filter(display_text='Blé dur').first() # First we check the weignt without using RUMEX. We expect the weight to be # zero since the whitelist is not upheld answers = { "problem": "DESHERBAGE", "rotation": [ble.external_id], "cattle": "Oui" } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) self.assertEqual(result.weight, 0) # Now we add RUMEX. The same practice should have a non-zero weight answers = { "problem": "DESHERBAGE", "weeds": "{0},{1}".format(str(chardon.external_id), str(rumex.external_id)), "rotation": [ble.external_id], "cattle": "Oui" } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) self.assertTrue(result.weight > 0)
def test_small_rotation(self): """ If there are three or less cultures in the rotation practices that extend the rotation should be boosted by 1.1. """ practice_name = 'Introduire une culture étouffant les adventices' mais = 'recsPtaEneeYVoEWx' tournesol = 'rec5MHmc9xIgAg8ha' soja = 'recwHs4aAiZc9okg9' orge = 'recfGVtMZSz05Rfl8' raygrass = 'recjzIBqwGkton9Ed' # When having more than 3 cultures, there is no bonus answers = { 'problem': 'DESHERBAGE', 'rotation': [mais, tournesol, soja, orge], 'weeds': raygrass, "cattle": "Oui", } engine = Engine(answers, [], []) result = next( filter(lambda x: x.practice.title == practice_name, engine.calculate_results())) initial_weight = result.weight # When having 3 or less cultures, the bonus is 1.1 answers = { 'problem': 'DESHERBAGE', 'rotation': [mais, tournesol, soja], 'weeds': raygrass, "cattle": "Oui", } engine = Engine(answers, [], []) result = next( filter(lambda x: x.practice.title == practice_name, engine.calculate_results())) small_rotation_weight = result.weight self.assertEqual(initial_weight * 1.1, small_rotation_weight)
def test_glyphosate_multiplier(self): """ Certain practices have a specific multiplier depending on the use of glyphosate that the user has. For example, the practice "Déchaumages répétés" has a multiplier. """ glyphosate_bonus = 1.4 practice_title = 'Déchaumages répétés' rumex = Weed.objects.filter(display_text='Rumex').first() lin_hiver = SimulatorCulture.objects.filter( display_text='Lin hiver').first() # First we make a request without specifying the use of glyphosate answers = { "problem": "GLYPHOSATE", "weeds": "{0}".format(str(rumex.external_id)), "tillage": "TRAVAIL_PROFOND", "rotation": [lin_hiver.external_id], } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) initial_weight = result.weight # First we make a request without specifying glyphosate as the main problem answers = { "problem": "GLYPHOSATE", "glyphosate": "VIVACES", "weeds": "{0}".format(str(rumex.external_id)), "tillage": "TRAVAIL_PROFOND", "rotation": [lin_hiver.external_id], } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) self.assertGreater(result.weight, 0) self.assertEqual(result.weight, initial_weight * glyphosate_bonus)
def test_pest_multipliers(self): """ A practice can be more (or less) useful to address certain pests, this is specified in the pest_multipliers field of the practice model. The practice "Associer un colza avec un couvert de légumineuses compagnes" has a multiplier for the pest Charançon, here we check that said multiplier is taken into account by the engine. """ practice_title = "Associer un colza avec un couvert de légumineuses compagnes" charancon = Pest.objects.filter(display_text='Charançons').first() colza = SimulatorCulture.objects.filter(display_text='Colza').first() charancon_multiplier = 1.21 # First we check the weignt without using CHARANCONS in the response answers = { "problem": "RAVAGEURS", "rotation": [colza.external_id], } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) initial_weight = result.weight # Now we add CHARANCONS in the response and get the results answers = { "problem": "RAVAGEURS", "pests": "{0}".format(str(charancon.external_id)), "rotation": [colza.external_id], } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) new_weight = result.weight # We need to make sure the new weight has taken into account the # multiplier for CHARANCONS self.assertEqual(new_weight, initial_weight * charancon_multiplier)
def test_livestock_need(self): """ Certain practices can only be suggested when the user has livestock. If they don't, they should be set to zero. One example of this is "Faire pâturer les couverts et les repousses" """ practice_title = 'Faire pâturer les couverts et les repousses' rumex = Weed.objects.filter(display_text='Rumex').first() chardon = Weed.objects.filter( display_text='Chardon des champs').first() # First we try with livestock, the score should be greater than zero answers = { "problem": "GLYPHOSATE", "weeds": "{0},{1}".format(str(rumex.external_id), str(chardon.external_id)), "cattle": "Oui", } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) self.assertGreater(result.weight, 0) # Now we try without cattle, the score should be zero answers = { "problem": "GLYPHOSATE", "weeds": "{0},{1}".format(str(chardon.external_id), str(rumex.external_id)), } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) self.assertEqual(result.weight, 0)
def test_culture_whitelist(self): """ A practice can have a limited number of whitelisted cultures it can be applied to. As an example, practice "Détruire les résidus de cannes de maïs" can only be relevant for MAIS. """ practice_title = "Détruire les résidus de cannes de maïs" pyrales = Pest.objects.filter(display_text='Pyrales').first() ble = SimulatorCulture.objects.filter(display_text='Blé dur').first() mais = SimulatorCulture.objects.filter(display_text='Maïs').first() # First we check the weight without using MAIS. We expect the weight to be # zero since the whitelist is not upheld answers = { "problem": "RAVAGEURS", "pests": "{0}".format(str(pyrales.external_id)), "tillage": "TRAVAIL_DU_SOL", "rotation": [ble.external_id] } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) self.assertEqual(result.weight, 0) # Now we add PYRALES. The same practice should have a non-zero weight answers = { "problem": "RAVAGEURS", "pests": "{0}".format(str(pyrales.external_id)), "tillage": "TRAVAIL_DU_SOL", "rotation": [ble.external_id, mais.external_id] } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) self.assertTrue(result.weight > 0)
def test_weed_multipliers(self): """ A practice can be more (or less) useful to address certain weeds, this is specified in the weed_multipliers field of the practice model. The practice "Profiter de l'action des auxiliaires sur le puceron de l'épi" has a multiplier for the weed Chardon, here we check that said multiplier is taken into account by the engine. """ practice_title = "Profiter de l'action des auxiliaires sur le puceron de l'épi" chardon_multiplier = 0.6 chardon = Weed.objects.filter( display_text='Chardon des champs').first() ble = SimulatorCulture.objects.filter(display_text='Blé dur').first() # First we check the weignt without using CHARDON in the response answers = {"problem": "DESHERBAGE", "rotation": [ble.external_id]} engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) initial_weight = result.weight # Now we add CHARDON in the response and get the results answers = { "problem": "DESHERBAGE", "weeds": "{0}".format(str(chardon.external_id)), "rotation": [ble.external_id], } engine = Engine(answers, [], []) results = engine.calculate_results() result = next( filter(lambda x: x.practice.title == practice_title, results)) new_weight = result.weight # We need to make sure the new weight has taken into account the # multiplier for CHARDON self.assertEqual(new_weight, initial_weight * chardon_multiplier)
def test_unbalanced_rotation(self): """ If the user has <=25% of cultures of either spring or fall, they will need to balance their rotation. We must propose practices that help balance this out. """ # Spring cultures: mais = 'recsPtaEneeYVoEWx' tournesol = 'rec5MHmc9xIgAg8ha' soja = 'recwHs4aAiZc9okg9' # Fall cultures: ble = 'recuVebqXEqCg8kK0' orge = 'recfGVtMZSz05Rfl8' # Summer cultures colza = 'recZj4cTO0dwcYhbe' # This practice balances the sowing period and should be proposed when having an unbalanced rotation practice_name = 'Favoriser l\'alternance de cultures à semis de printemps et d\'automne' # In this example, we have only 25% of fall cultures: an unbalanced rotation answers = { "problem": "DESHERBAGE", "tillage": "TRAVAIL_PROFOND", "rotation": [mais, tournesol, soja, ble], } engine = Engine(answers, [], []) unbalanced_result = next( filter(lambda x: x.practice.title == practice_name, engine.calculate_results())) # Now we balance it out, having half fall, half spring rotation answers = { "problem": "DESHERBAGE", "tillage": "TRAVAIL_PROFOND", "rotation": [mais, tournesol, orge, ble], } engine = Engine(answers, [], []) balanced_result = next( filter(lambda x: x.practice.title == practice_name, engine.calculate_results())) # The practice weight when the rotation was unbalanced must be higher self.assertGreater(unbalanced_result.weight, balanced_result.weight) # Note that the threshold is 25%, so having an unbalanced rotation of 2-1 (33%) # should count as a balanced rotation. We need to account here for the short-rotation # bonis though. answers = { "problem": "DESHERBAGE", "tillage": "TRAVAIL_PROFOND", "rotation": [mais, tournesol, ble], } engine = Engine(answers, [], []) short_rotation_bonus = 1.1 result = next( filter(lambda x: x.practice.title == practice_name, engine.calculate_results())) self.assertEqual(balanced_result.weight * short_rotation_bonus, result.weight) # We should only look at spring and fall cultures when we apply this logic. # In this case we have 40% fall, 40% spring, and 20% end-of-summer. Despite # having a sowing period of less than 25%, since it is not fall nor spring we # still consider this a balanced culture. answers = { "problem": "DESHERBAGE", "tillage": "TRAVAIL_PROFOND", "rotation": [mais, tournesol, ble, orge, colza], } engine = Engine(answers, [], []) result = next( filter(lambda x: x.practice.title == practice_name, engine.calculate_results())) self.assertEqual(balanced_result.weight, result.weight) # In this case we have 25% summer, 25% spring and 50% fall. This should # be considered an unbalanced rotation because spring is <= 25% answers = { "problem": "DESHERBAGE", "tillage": "TRAVAIL_PROFOND", "rotation": [tournesol, ble, orge, colza], } engine = Engine(answers, [], []) result = next( filter(lambda x: x.practice.title == practice_name, engine.calculate_results())) self.assertEqual(unbalanced_result.weight, result.weight)
def test_tillage_types(self): """ Tillage (travail de sol) can be deep or shallow. The practices that need deep tillage should only be proposed if the user can do deep tillage. Same goes for shallow tillage. """ deep_tillage_practice_title = 'Positionner un labour stratégiquement' shallow_tillage_practice_title = 'Désherbage mécanique en plein en début de saison pour cultures de printemps' rumex = Weed.objects.filter(display_text='Rumex').first() ble = SimulatorCulture.objects.filter(display_text='Blé dur').first() lin_hiver = SimulatorCulture.objects.filter( display_text='Lin hiver').first() ble_printemps = SimulatorCulture.objects.filter( display_text='Blé tendre de printemps').first() # If the user can't do any tillage, both practices should be at zero score answers = { "problem": "GLYPHOSATE", "weeds": "{0}".format(str(rumex.external_id)), "tillage": None, "rotation": [ ble.external_id, lin_hiver.external_id, ble_printemps.external_id ], } engine = Engine(answers, [], []) results = engine.calculate_results() deep_tillage_result = next( filter(lambda x: x.practice.title == deep_tillage_practice_title, results)) shallow_tillage_result = next( filter( lambda x: x.practice.title == shallow_tillage_practice_title, results)) self.assertEqual(deep_tillage_result.weight, 0) self.assertEqual(shallow_tillage_result.weight, 0) # If the user can do shallow tillage, the shallow tillage practice # should be above zero, whereas the deep tillage practice should be at zero answers = { "problem": "GLYPHOSATE", "weeds": "{0}".format(str(rumex.external_id)), "tillage": 'TRAVAIL_DU_SOL', "rotation": [ ble.external_id, lin_hiver.external_id, ble_printemps.external_id ], } engine = Engine(answers, [], []) results = engine.calculate_results() deep_tillage_result = next( filter(lambda x: x.practice.title == deep_tillage_practice_title, results)) shallow_tillage_result = next( filter( lambda x: x.practice.title == shallow_tillage_practice_title, results)) self.assertEqual(deep_tillage_result.weight, 0) self.assertGreater(shallow_tillage_result.weight, 0) # If the user can do deep tillage, both practices should be above zero answers = { "problem": "GLYPHOSATE", "weeds": "{0}".format(str(rumex.external_id)), "tillage": 'TRAVAIL_PROFOND', "rotation": [ ble.external_id, lin_hiver.external_id, ble_printemps.external_id ], } engine = Engine(answers, [], []) results = engine.calculate_results() deep_tillage_result = next( filter(lambda x: x.practice.title == deep_tillage_practice_title, results)) shallow_tillage_result = next( filter( lambda x: x.practice.title == shallow_tillage_practice_title, results)) self.assertGreater(deep_tillage_result.weight, 0) self.assertGreater(shallow_tillage_result.weight, 0)
class Bot(object): def __init__(self, url, driver_path=None, browser_name=None, opponent="Friend"): super().__init__() self.url = url self.driver_path = driver_path self.driver = None self.browser_name = None self.opponent = opponent self.engine = None self.last_move_tracker = None self.match_end = False def set_driver_path(self, path, browser_name): self.driver_path = path self.browser_name = browser_name def set_opponent(self, opponent): self.opponent = opponent def create_browser(self): if self.browser_name.lower() == "chrome": self.driver = webdriver.Chrome(executable_path=self.driver_path) else: self.driver = webdriver.Firefox() def close_driver(self): self.driver.close() def create_engine(self): self.engine = Engine() self.engine.set_engine(skill_level=1) # Default = stockfish def click_coords(self, coords): y, x = coords action = webdriver.common.action_chains.ActionChains(self.driver) action.move_to_element_with_offset(self.board_web_element, y, x) action.click() action.perform() print("Clicked:", coords) return True def click_accept(self): try: button = self.driver.find_element_by_xpath("//form[@class='accept']/button") button.click() return True except Exception as e: print(e) return False def create_board(self, board, my_colour): self.main_board = Board(board, my_colour) self.main_board.set_current_board_image() # Set screenshot image def make_move(self): move = self.engine.get_best_move() print("BOT: Best move -", move) # Make move on GUI pp_piece = None # Pawn promotion piece if len(move) == 5: pp_piece = move[-1] move = move[:-1] # Get coordinates of rank/file (old_square, new_square) old_fr, new_fr = self.main_board.from_engine_split_fr(move) # Split string to old, new old_square = self.main_board.from_engine_convert(old_fr) # Co-ordinates to click new_square = self.main_board.from_engine_convert(new_fr) # Co-ordinates to click # Click old_square self.click_coords(old_square) time.sleep(0.1) # Click new_square self.click_coords(new_square) # Select new piece if pawn promoted if pp_piece: width, height = new_square if pp_piece.lower() == "q": pass # Click again same square elif pp_piece.lower() == "n": height = height + self.main_board.square_height # Click 1 square down elif pp_piece.lower() == "r": height = height + (self.main_board.square_height*2) # Click 2 squares down elif pp_piece.lower() == "b": height = height + (self.main_board.square_height*3) # Click 3 squares down time.sleep(0.2) self.click_coords( (height, width) ) move += pp_piece # Update engine with new move self.update_engine(move) # Update bot with last move self.last_move_tracker = new_fr print("My move finished.\n") def update_engine(self, move): self.engine.move(move) def start(self): print("Bot starting...") self.create_browser() self.driver.get(self.url) if self.opponent == "friend": print("Playing against a friend") if not self.click_accept(): print("No accept button so: Spectating") return elif self.opponent == "computer": print("Playing against computer") elif self.opponent == "player": print("Playing against a player") my_colour_text = self.driver.find_element_by_xpath("//div[contains(@class, 'cg-wrap')]").get_attribute("class") my_colour = re.search(r'orientation-(\w+) ', my_colour_text).group(1) print("I am",my_colour) if my_colour == "black": print("Flip the board") flip_btn = self.driver.find_element_by_xpath("//button[contains(@title, 'Flip board')]").click() # Find main board in browser self.board_web_element = self.driver.find_element_by_tag_name('cg-board') # Create a board object self.create_board(self.board_web_element, my_colour) # Create engine self.create_engine() # self.engine.set_engine_skill_level(5) # If white then: Move first if self.main_board.my_turn(): self.make_move() self.main_board.update_turn() while True: # If game ended (Checkmate) then break if self.match_end: break time.sleep(3) # Sleep for 3 seconds try: # Get last move move_list_parent = self.driver.find_element_by_tag_name('l4x') # Does not exist if there are no moves i.e. white first last_move = move_list_parent.find_element_by_xpath("//u8t[contains(@class, 'a1t')]").text print("Ultimate last move", last_move) castle_move = None pawn_promotion = None new_pp_piece = None if cst_n := last_move.count('-'): # O-O or O-O-O print("Castle") if cst_n == 1: castle_move = "e1g1" if self.main_board.turn == "white" else "e8g8" # If white turn: O-O: e1g1, if black: e8g8 elif cst_n == 2: castle_move = "e1c1" if self.main_board.turn == "white" else "e8c8" # If white turn: O-O-O: e1c1, if black: e8c8 elif pawn_promotion := last_move.count('='): # b8=Q print("Pawn promotion") last_move = last_move.strip('+=') # If check with new piece new_pp_piece = last_move[-1] last_move = last_move[:-1] elif any(x in last_move for x in ('+','#')): # Bxf7+ print("Check") if '#' in last_move: self.match_end = True break last_move = last_move.strip('+#')[-2:] print("Last move:",last_move) # Whose turn it is turn_text = self.driver.find_element_by_xpath("//div[contains(@class, 'rclock-turn__text')]").text print(turn_text) if turn_text == "Your turn": # Update board with their latest move self.main_board.set_current_board_image() self.main_board.update_turn() # If move is castle then set opponent move as one generated previously if castle_move: opponent_move = castle_move else: opponent_move = self.main_board.to_engine_get_fr() if pawn_promotion: opponent_move += new_pp_piece # Update engine self.update_engine(opponent_move) # I finally make my move self.make_move() # My move # Update board with my latest move self.main_board.set_current_board_image() self.main_board.update_turn() # Set last move # self.last_move_tracker = last_move elif turn_text == "Waiting for opponent": pass # print("Waiting for their move") else: print("Error: User turn text is wrong.")
def get_results(answers, practice_blacklist, type_blacklist): engine = Engine(answers, practice_blacklist, type_blacklist) practices = engine.calculate_results() suggestions = engine.get_suggestions(practices) return (practices, suggestions)
def create_engine(self): self.engine = Engine() self.engine.set_engine(skill_level=1) # Default = stockfish