From 21e97c428214e73c419e5ad0182cc19b2ace9636 Mon Sep 17 00:00:00 2001 From: SebastienReuiller Date: Tue, 12 Nov 2024 10:39:27 +0100 Subject: [PATCH] =?UTF-8?q?fix(Activit=C3=A9s=20des=20structures):=20ajout?= =?UTF-8?q?=20de=20la=20correspondance=20directe=20sur=20la=20ville,=20le?= =?UTF-8?q?=20d=C3=A9partement=20ou=20la=20r=C3=A9gion=20de=20la=20structu?= =?UTF-8?q?re=20(#1487)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../commands/create_siae_activities.py | 9 ++- lemarche/siaes/models.py | 66 ++++++++++-------- lemarche/siaes/tests/test_commands.py | 19 ++++- lemarche/tenders/tests/test_matching.py | 69 +++++++++++++++---- 4 files changed, 120 insertions(+), 43 deletions(-) diff --git a/lemarche/siaes/management/commands/create_siae_activities.py b/lemarche/siaes/management/commands/create_siae_activities.py index 43cb6e6b4..b16f0a146 100644 --- a/lemarche/siaes/management/commands/create_siae_activities.py +++ b/lemarche/siaes/management/commands/create_siae_activities.py @@ -126,8 +126,13 @@ def create_siae_activities(self, siae: Siae): continue case _: - self.stdout_warning(f"Unknown geo_range: {siae.geo_range}") - continue + # Create a SiaeActivity with no location to continue to match the sectors + siae_activity = SiaeActivity.objects.create( + siae=siae, + sector_group_id=sector_group_id, + presta_type=siae.presta_type, + geo_range=siae_constants.GEO_RANGE_ZONES, + ) siae_activity.sectors.set(siae.sectors.filter(group_id=sector_group_id)) self.stdout_info(f"Created {len(siae_sector_group_ids)} activities for {siae}") diff --git a/lemarche/siaes/models.py b/lemarche/siaes/models.py index 348447242..e1f6eca0e 100644 --- a/lemarche/siaes/models.py +++ b/lemarche/siaes/models.py @@ -1500,33 +1500,45 @@ def geo_range_in_perimeter_list(self, perimeters: models.QuerySet, include_count # Match siae activity with geo range zone and same perimeter conditions |= Q(Q(geo_range=siae_constants.GEO_RANGE_ZONES) & Q(locations=perimeter)) - if perimeter.kind == Perimeter.KIND_CITY: - # Match siae activity with geo range custom and siae city is in area - conditions |= Q( - Q(geo_range=siae_constants.GEO_RANGE_CUSTOM) - & Q(geo_range_custom_distance__gte=Distance("siae__coords", perimeter.coords) / 1000) - ) - - # Match the department that includes this city - conditions |= Q( - Q(geo_range=siae_constants.GEO_RANGE_ZONES) - & Q(locations__kind=Perimeter.KIND_DEPARTMENT) - & Q(locations__insee_code=perimeter.department_code) - ) - - # Match the region that includes this city - conditions |= Q( - Q(geo_range=siae_constants.GEO_RANGE_ZONES) - & Q(locations__kind=Perimeter.KIND_REGION) - & Q(locations__insee_code=f"R{perimeter.region_code}") - ) - if perimeter.kind == Perimeter.KIND_DEPARTMENT: - # Match the region that includes this department - conditions |= Q( - Q(geo_range=siae_constants.GEO_RANGE_ZONES) - & Q(locations__kind=Perimeter.KIND_REGION) - & Q(locations__insee_code=f"R{perimeter.region_code}") - ) + match perimeter.kind: + case Perimeter.KIND_CITY: + # Match siae activity with geo range custom and siae city is in area + conditions |= Q( + Q(geo_range=siae_constants.GEO_RANGE_CUSTOM) + & Q(geo_range_custom_distance__gte=Distance("siae__coords", perimeter.coords) / 1000) + ) + + # Match the department that includes this city + conditions |= Q( + Q(geo_range=siae_constants.GEO_RANGE_ZONES) + & Q(locations__kind=Perimeter.KIND_DEPARTMENT) + & Q(locations__insee_code=perimeter.department_code) + ) + + # Match the region that includes this city + conditions |= Q( + Q(geo_range=siae_constants.GEO_RANGE_ZONES) + & Q(locations__kind=Perimeter.KIND_REGION) + & Q(locations__insee_code=f"R{perimeter.region_code}") + ) + + # Try to match directly the siae city + conditions |= Q(siae__post_code__in=perimeter.post_codes) + + case Perimeter.KIND_DEPARTMENT: + # Match the region that includes this department + conditions |= Q( + Q(geo_range=siae_constants.GEO_RANGE_ZONES) + & Q(locations__kind=Perimeter.KIND_REGION) + & Q(locations__insee_code=f"R{perimeter.region_code}") + ) + + # Try to match directly the siae department + conditions |= Q(siae__department=perimeter.insee_code) + + case Perimeter.KIND_REGION: + # Try to match directly the siae region + conditions |= Q(siae__region=perimeter.name) if include_country_area: conditions = Q(geo_range=siae_constants.GEO_RANGE_COUNTRY) | conditions diff --git a/lemarche/siaes/tests/test_commands.py b/lemarche/siaes/tests/test_commands.py index 07f159a90..1da10b327 100644 --- a/lemarche/siaes/tests/test_commands.py +++ b/lemarche/siaes/tests/test_commands.py @@ -95,11 +95,21 @@ def test_create_activities(self): ) siae4.sectors.set([self.sector2, self.sector3]) + # without geo_range + siae5 = SiaeFactory( + is_active=True, + kind=siae_constants.KIND_EA, + presta_type=[siae_constants.PRESTA_DISP], + department="978", + region=self.region_name, + ) + siae5.sectors.set([self.sector3]) + call_command("create_siae_activities", dry_run=True) self.assertEqual(SiaeActivity.objects.count(), 0) call_command("create_siae_activities") - self.assertEqual(SiaeActivity.objects.count(), 2 + 1 + 1 + 2) + self.assertEqual(SiaeActivity.objects.count(), 2 + 1 + 1 + 2 + 1) siae1_activities = SiaeActivity.objects.filter(siae=siae1) self.assertEqual(siae1_activities.count(), 2) self.assertEqual(siae1_activities.filter(sectors__in=[self.sector1]).count(), 1) @@ -136,3 +146,10 @@ def test_create_activities(self): self.assertEqual(siae_activity.geo_range, siae_constants.GEO_RANGE_ZONES) self.assertEqual(siae_activity.locations.count(), 1) self.assertEqual(siae_activity.locations.first(), self.perimeter_department) + + siae5_activities = SiaeActivity.objects.filter(siae=siae5) + self.assertEqual(siae5_activities.count(), 1) + self.assertEqual(siae5_activities.filter(sectors__in=[self.sector3]).count(), 1) + self.assertEqual(siae5_activities.first().presta_type, [siae_constants.PRESTA_DISP]) + self.assertEqual(siae5_activities.first().geo_range, siae_constants.GEO_RANGE_ZONES) + self.assertEqual(siae5_activities.first().locations.count(), 0) diff --git a/lemarche/tenders/tests/test_matching.py b/lemarche/tenders/tests/test_matching.py index cdec15b38..3eb4163d4 100644 --- a/lemarche/tenders/tests/test_matching.py +++ b/lemarche/tenders/tests/test_matching.py @@ -1,3 +1,5 @@ +from timeit import default_timer as timer + from django.contrib.gis.geos import Point from django.test import TestCase @@ -72,25 +74,41 @@ def setUpTestData(cls): ) cls.siae_three_activity.sectors.add(cls.other_sector) + # siae with activity without locations to check if match directly city/department/region + cls.siae_four = SiaeFactory( + is_active=True, + kind=siae_constants.KIND_ESAT, + department="75", + region="Île-de-France", + post_code="75018", + ) + cls.siae_four_activity = SiaeActivityFactory( + siae=cls.siae_four, + sector_group=cls.sectors[0].group, + presta_type=[siae_constants.PRESTA_PREST, siae_constants.PRESTA_BUILD], + geo_range=siae_constants.GEO_RANGE_ZONES, + ) + cls.siae_four_activity.sectors.add(cls.sectors[i]) + def test_matching_siae_presta_type(self): tender = TenderFactory(presta_type=[], sectors=self.sectors, perimeters=self.perimeters) siae_found_list = Siae.objects.filter_with_tender_through_activities(tender) - self.assertEqual(len(siae_found_list), 2) + self.assertEqual(len(siae_found_list), 3) tender = TenderFactory( presta_type=[siae_constants.PRESTA_BUILD], sectors=self.sectors, perimeters=self.perimeters ) siae_found_list = Siae.objects.filter_with_tender_through_activities(tender) - self.assertEqual(len(siae_found_list), 2) + self.assertEqual(len(siae_found_list), 3) tender = TenderFactory( presta_type=[siae_constants.PRESTA_PREST], sectors=self.sectors, perimeters=self.perimeters ) siae_found_list = Siae.objects.filter_with_tender_through_activities(tender) - self.assertEqual(len(siae_found_list), 1) + self.assertEqual(len(siae_found_list), 2) def test_matching_siae_kind(self): tender = TenderFactory(siae_kind=[], sectors=self.sectors, perimeters=self.perimeters) siae_found_list = Siae.objects.filter_with_tender_through_activities(tender) - self.assertEqual(len(siae_found_list), 2) + self.assertEqual(len(siae_found_list), 3) tender = TenderFactory(siae_kind=[siae_constants.KIND_AI], sectors=self.sectors, perimeters=self.perimeters) siae_found_list = Siae.objects.filter_with_tender_through_activities(tender) self.assertEqual(len(siae_found_list), 1) @@ -100,7 +118,7 @@ def test_matching_siae_kind(self): perimeters=self.perimeters, ) siae_found_list = Siae.objects.filter_with_tender_through_activities(tender) - self.assertEqual(len(siae_found_list), 2) + self.assertEqual(len(siae_found_list), 3) tender = TenderFactory(siae_kind=[siae_constants.KIND_SEP], sectors=self.sectors, perimeters=self.perimeters) siae_found_list = Siae.objects.filter_with_tender_through_activities(tender) self.assertEqual(len(siae_found_list), 0) @@ -108,7 +126,7 @@ def test_matching_siae_kind(self): def test_matching_siae_sectors(self): tender = TenderFactory(sectors=self.sectors) siae_found_list = Siae.objects.filter_with_tender_through_activities(tender) - self.assertEqual(len(siae_found_list), 2) + self.assertEqual(len(siae_found_list), 3) def test_matching_siae_distance_location(self): # create SIAE in Tours @@ -170,7 +188,7 @@ def test_matching_siae_distance_location(self): tender.distance_location = None tender.save() siae_found_list = Siae.objects.filter_with_tender_through_activities(tender) - self.assertEqual(len(siae_found_list), 2) + self.assertEqual(len(siae_found_list), 3) self.assertIn(self.siae_one, siae_found_list) self.assertIn(self.siae_two, siae_found_list) @@ -199,7 +217,7 @@ def test_matching_siae_distance_location(self): perimeters=[self.perimeter_paris], # without effect too ) siae_found_list = Siae.objects.filter_with_tender_through_activities(tender) - self.assertEqual(len(siae_found_list), 3) + self.assertEqual(len(siae_found_list), 4) self.assertIn(self.siae_one, siae_found_list) self.assertIn(self.siae_two, siae_found_list) self.assertIn(siae_marseille, siae_found_list) @@ -217,11 +235,11 @@ def test_matching_siae_perimeters_custom(self): # tender perimeter custom with include_country_area = False tender_1 = TenderFactory(sectors=self.sectors, perimeters=self.perimeters) siae_found_list = Siae.objects.filter_with_tender_through_activities(tender_1) - self.assertEqual(len(siae_found_list), 2 + 0) + self.assertEqual(len(siae_found_list), 3 + 0) # tender perimeter custom with include_country_area = True tender_2 = TenderFactory(sectors=self.sectors, perimeters=self.perimeters, include_country_area=True) siae_found_list = Siae.objects.filter_with_tender_through_activities(tender_2) - self.assertEqual(len(siae_found_list), 2 + 1) + self.assertEqual(len(siae_found_list), 3 + 1) def test_matching_siae_country(self): # add Siae with geo_range_country @@ -253,7 +271,7 @@ def test_matching_siae_country(self): # add perimeters tender_2.perimeters.set(self.perimeters) siae_found_list_2 = Siae.objects.filter_with_tender_through_activities(tender_2) - self.assertEqual(len(siae_found_list_2), 2 + 2) + self.assertEqual(len(siae_found_list_2), 2 + 3) tender_2.is_country_area = True tender_2.save() siae_found_list_2 = Siae.objects.filter_with_tender_through_activities(tender_2) @@ -285,7 +303,7 @@ def test_matching_siae_perimeters_custom_2(self): # tender perimeter custom tender = TenderFactory(sectors=self.sectors, perimeters=self.perimeters) siae_found_list = Siae.objects.filter_with_tender_through_activities(tender) - self.assertEqual(len(siae_found_list), 2 + 1) + self.assertEqual(len(siae_found_list), 3 + 1) def test_matching_siae_perimeters_france(self): # tender france @@ -357,5 +375,30 @@ def test_with_no_contact_email(self): siae_activity.sectors.add(self.sectors[0]) siae_found_list = Siae.objects.filter_with_tender_through_activities(tender) - self.assertEqual(len(siae_found_list), 2 + 0) + self.assertEqual(len(siae_found_list), 3 + 0) self.assertNotIn(siae, siae_found_list) + + def test_performance(self): + # create 100 siaes with 10 activities each + for i in range(100): + siae = SiaeFactory(is_active=True, coords=Point(48.86385199985207, 2.337071483848432)) + for j in range(10): + siae_activity = SiaeActivityFactory( + siae=siae, + sector_group=self.sectors[j % 10].group, + presta_type=[siae_constants.PRESTA_PREST, siae_constants.PRESTA_BUILD], + with_zones_perimeter=True, + ) + siae_activity.locations.set([self.perimeter_paris]) + siae_activity.sectors.add(self.sectors[j % 10]) + + tender = TenderFactory(sectors=self.sectors, perimeters=self.perimeters) + + start_time = timer() + with self.assertNumQueries(6): + siae_found_list = Siae.objects.filter_with_tender_through_activities(tender) + self.assertEqual(len(siae_found_list), 100 + 3) + + end_time = timer() + duration = end_time - start_time + self.assertLess(duration, 0.5, f"Performance issue: took {duration:.4f} seconds")