From f96b75b401a4ce6bceb3c9cf293aa46e854277fc Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Tue, 24 Aug 2021 14:36:14 +0100 Subject: [PATCH 1/8] Add method to get wards for custom polygon This will be used as part of the area aggregation in the following commits. --- app/broadcast_areas/models.py | 7 +++++++ tests/app/broadcast_areas/custom_polygons.py | 8 ++++++++ tests/app/broadcast_areas/test_models.py | 20 ++++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 tests/app/broadcast_areas/test_models.py diff --git a/app/broadcast_areas/models.py b/app/broadcast_areas/models.py index 6084cd7c9..ba61fb915 100644 --- a/app/broadcast_areas/models.py +++ b/app/broadcast_areas/models.py @@ -162,6 +162,13 @@ class CustomBroadcastArea(BaseBroadcastArea): simple_polygons = polygons + @cached_property + def overlapping_electoral_wards(self): + return [ + area for area in self.nearby_electoral_wards + if area.simple_polygons.intersects(self.polygons) + ] + @cached_property def nearby_electoral_wards(self): if not self.polygons: diff --git a/tests/app/broadcast_areas/custom_polygons.py b/tests/app/broadcast_areas/custom_polygons.py index 6faea8675..7e8ceda16 100644 --- a/tests/app/broadcast_areas/custom_polygons.py +++ b/tests/app/broadcast_areas/custom_polygons.py @@ -13,3 +13,11 @@ SKYE = [ [57.7334, -6.8280], [57.1004, -6.8280], ] + +SANTA_A = [ + [25.8890, 66.5500], + [25.8890, 66.551], + [25.8910, 66.551], + [25.8910, 66.5500], + [25.889, 66.55000], +] diff --git a/tests/app/broadcast_areas/test_models.py b/tests/app/broadcast_areas/test_models.py new file mode 100644 index 000000000..23b9933d2 --- /dev/null +++ b/tests/app/broadcast_areas/test_models.py @@ -0,0 +1,20 @@ +import pytest + +from app.broadcast_areas.models import CustomBroadcastArea +from tests.app.broadcast_areas.custom_polygons import BRISTOL, SANTA_A, SKYE + + +@pytest.mark.parametrize(('simple_polygon', 'expected_wards_length'), [ + (SKYE, 2), + (BRISTOL, 12), + (SANTA_A, 0) # does not overlap with UK +]) +def test_custom_broadcast_area_overlapping_electoral_wards( + simple_polygon, + expected_wards_length, +): + custom_area = CustomBroadcastArea( + name='foo', polygons=[simple_polygon] + ) + + assert len(custom_area.overlapping_electoral_wards) == expected_wards_length From 12978f0a9a0f35c11525eaf7cf5a9582a53a557f Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Tue, 24 Aug 2021 14:57:50 +0100 Subject: [PATCH 2/8] Simplify creating BroadcastMessage instances This avoids passing loads of unnecessary boilerplate values. --- tests/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 27785c65c..95bd17233 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -667,11 +667,11 @@ def find_element_by_tag_and_partial_text(page, tag, string): def broadcast_message_json( *, - id_, - service_id, - template_id, - status, - created_by_id, + id_=None, + service_id=None, + template_id=None, + status='draft', + created_by_id=None, starts_at=None, finishes_at=None, cancelled_at=None, From a210d87deec35c0709ca06ef165eb45ea08a3a88 Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Tue, 24 Aug 2021 15:01:53 +0100 Subject: [PATCH 3/8] Add function method to aggregate areas This is standalone for now - we'll plumb it in later. --- app/broadcast_areas/models.py | 4 ++ app/broadcast_areas/utils.py | 10 +++ tests/app/broadcast_areas/custom_polygons.py | 16 +++++ tests/app/broadcast_areas/test_utils.py | 71 ++++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 app/broadcast_areas/utils.py create mode 100644 tests/app/broadcast_areas/test_utils.py diff --git a/app/broadcast_areas/models.py b/app/broadcast_areas/models.py index ba61fb915..271da834f 100644 --- a/app/broadcast_areas/models.py +++ b/app/broadcast_areas/models.py @@ -127,6 +127,10 @@ class BroadcastArea(BaseBroadcastArea, SortableMixin): def ancestors(self): return list(self._ancestors_iterator) + @cached_property + def parent(self): + return next(iter(self.ancestors), None) + @property def _ancestors_iterator(self): id = self.id diff --git a/app/broadcast_areas/utils.py b/app/broadcast_areas/utils.py new file mode 100644 index 000000000..fc50d95c6 --- /dev/null +++ b/app/broadcast_areas/utils.py @@ -0,0 +1,10 @@ +def aggregate_areas(areas): + areas = _aggregate_wards_by_local_authority(areas) + return areas + + +def _aggregate_wards_by_local_authority(areas): + return { + area.parent if area.id.startswith('wd20-') + else area for area in areas + } diff --git a/tests/app/broadcast_areas/custom_polygons.py b/tests/app/broadcast_areas/custom_polygons.py index 7e8ceda16..03a0dcc47 100644 --- a/tests/app/broadcast_areas/custom_polygons.py +++ b/tests/app/broadcast_areas/custom_polygons.py @@ -21,3 +21,19 @@ SANTA_A = [ [25.8910, 66.5500], [25.889, 66.55000], ] + +BURFORD = [ + [51.8925, -1.7495], + [51.7372, -1.7825], + [51.7159, -1.5257], + [51.8866, -1.5037], + [51.8925, -1.7495], +] + +CHELTENHAM = [ + [51.9324, -2.1265], + [51.8633, -2.1306], + [51.8637, -2.0242], + [51.9328, -2.0221], + [51.9324, -2.1265], +] diff --git a/tests/app/broadcast_areas/test_utils.py b/tests/app/broadcast_areas/test_utils.py new file mode 100644 index 000000000..d9db48c0e --- /dev/null +++ b/tests/app/broadcast_areas/test_utils.py @@ -0,0 +1,71 @@ +import pytest + +from app.broadcast_areas.utils import aggregate_areas +from app.models.broadcast_message import BroadcastMessage +from tests import broadcast_message_json +from tests.app.broadcast_areas.custom_polygons import ( + BRISTOL, + BURFORD, + CHELTENHAM, + SANTA_A, + SKYE, +) + + +@pytest.mark.parametrize(('area_ids', 'expected_area_names'), [ + ( + [ + 'wd20-E05004294', # Hester’s Way, Cheltenham (electoral ward) + 'wd20-E05010981', # Painswick & Upton, Stroud (electoral ward) + ], [ + 'Cheltenham', # in Gloucestershire (upper tier authority) + 'Stroud', # in Gloucestershire + ], + ), + ( + [ + 'wd20-E05004294', # Hester’s Way, Cheltenham (electoral ward) + 'wd20-E05009372', # Hackney Central (electoral ward) + ], [ + 'Cheltenham', # in Gloucestershire (upper tier authority) + 'Hackney', # in Greater London* (DB doesn't know this) + ], + ), + ( + [ + 'lad20-E07000037', # High Peak (lower tier authority) + ], + [ + 'High Peak', # in Derbyshire (upper tier authority) + ] + ), + ( + [ + 'lad20-E07000037', # High Peak (lower tier authority) + 'lad20-E07000035', # Derbyshire Dales (lower tier authority) + ], + [ + 'Derbyshire Dales', # in Derbyshire (upper tier authority) + 'High Peak', # in Derbyshire + ] + ), + ( + [ + 'ctry19-E92000001', # England + ], + [ + 'England', + ] + ) +]) +def test_aggregate_areas( + area_ids, + expected_area_names, +): + broadcast_message = BroadcastMessage( + broadcast_message_json(area_ids=area_ids) + ) + + assert sorted( + area.name for area in aggregate_areas(broadcast_message.areas) + ) == expected_area_names From 2d912cef4284a207a5e526e14087e30dba425614 Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Tue, 24 Aug 2021 15:11:00 +0100 Subject: [PATCH 4/8] Support area aggregation for custom polygons --- app/broadcast_areas/utils.py | 16 ++++++++ tests/app/broadcast_areas/test_utils.py | 52 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/app/broadcast_areas/utils.py b/app/broadcast_areas/utils.py index fc50d95c6..26da631cb 100644 --- a/app/broadcast_areas/utils.py +++ b/app/broadcast_areas/utils.py @@ -1,8 +1,24 @@ +from app.broadcast_areas.models import CustomBroadcastArea + + def aggregate_areas(areas): + areas = _convert_custom_areas_to_wards(areas) areas = _aggregate_wards_by_local_authority(areas) return areas +def _convert_custom_areas_to_wards(areas): + results = set() + + for area in areas: + if type(area) == CustomBroadcastArea: + results |= set(area.overlapping_electoral_wards) + else: + results |= {area} + + return results + + def _aggregate_wards_by_local_authority(areas): return { area.parent if area.id.startswith('wd20-') diff --git a/tests/app/broadcast_areas/test_utils.py b/tests/app/broadcast_areas/test_utils.py index d9db48c0e..74e940fcc 100644 --- a/tests/app/broadcast_areas/test_utils.py +++ b/tests/app/broadcast_areas/test_utils.py @@ -69,3 +69,55 @@ def test_aggregate_areas( assert sorted( area.name for area in aggregate_areas(broadcast_message.areas) ) == expected_area_names + + +@pytest.mark.parametrize(('simple_polygons', 'expected_area_names'), [ + ( + [SKYE], [ + # Area covers a single unitary authority + 'Highland', + ] + ), + ( + [BRISTOL], [ + # Area covers a single unitary authority + 'Bristol, City of', + ] + ), + ( + [CHELTENHAM], [ + # Area covers three lower-tier authorities + # in Gloucestershire (upper tier authority) + 'Cheltenham', + 'Cotswold', + 'Tewkesbury', + ] + ), + ( + [BURFORD], [ + # Area covers two lower-tier authorities + # in Gloucestershire (upper tier authority) + 'Cotswold', + 'West Oxfordshire', + ], + ), + ( + [SANTA_A], [ + # Does not overlap with the UK + ], + ), +]) +def test_aggregate_areas_for_custom_polygons( + simple_polygons, + expected_area_names, +): + broadcast_message = BroadcastMessage( + broadcast_message_json( + area_ids=['derived from polygons'], + simple_polygons=simple_polygons + ) + ) + + assert sorted( + area.name for area in aggregate_areas(broadcast_message.areas) + ) == expected_area_names From bcfa21428f3161b1e3964671d1ffda720c71b358 Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Tue, 24 Aug 2021 15:49:46 +0100 Subject: [PATCH 5/8] Support aggregating local authority clusters This applies some heuristics to try and keep the overall list of areas short when many are selected in the same wider area. Currently we only have relationship information between upper and lower tier local authorities, so we can't / won't aggregate up to Greater London (it's own special thing) or whole countries. --- app/broadcast_areas/utils.py | 39 ++++++++++++ tests/app/broadcast_areas/custom_polygons.py | 16 +++++ tests/app/broadcast_areas/test_utils.py | 66 +++++++++++++++++++- 3 files changed, 119 insertions(+), 2 deletions(-) diff --git a/app/broadcast_areas/utils.py b/app/broadcast_areas/utils.py index 26da631cb..fdcc1f83a 100644 --- a/app/broadcast_areas/utils.py +++ b/app/broadcast_areas/utils.py @@ -1,9 +1,12 @@ +from collections import defaultdict + from app.broadcast_areas.models import CustomBroadcastArea def aggregate_areas(areas): areas = _convert_custom_areas_to_wards(areas) areas = _aggregate_wards_by_local_authority(areas) + areas = _aggregate_lower_tier_authorities(areas) return areas @@ -24,3 +27,39 @@ def _aggregate_wards_by_local_authority(areas): area.parent if area.id.startswith('wd20-') else area for area in areas } + + +def _aggregate_lower_tier_authorities(areas): + results = set() + clusters = _cluster_lower_tier_authorities(areas) + + for cluster in clusters: + # always keep lone area as itself + if len(cluster) == 1: + results |= set(cluster) + # aggregate multi-area cluster + elif len(cluster) > 3: + results |= {cluster[0].parent} + # aggregate many small clusters + elif len(clusters) > 1: + area = cluster[0] + results |= {area.parent or area} + # keep one small cluster in full + else: + results |= set(cluster) + + return results + + +def _cluster_lower_tier_authorities(areas): + result = defaultdict(lambda: []) + + for area in areas: + # group lower tier authorities by "county" + if area.id.startswith('lad20-') and area.parent: + result[area.parent] += [area] + # leave countries, unitary authorities as-is + else: + result[area] = [area] + + return result.values() diff --git a/tests/app/broadcast_areas/custom_polygons.py b/tests/app/broadcast_areas/custom_polygons.py index 03a0dcc47..e79360649 100644 --- a/tests/app/broadcast_areas/custom_polygons.py +++ b/tests/app/broadcast_areas/custom_polygons.py @@ -37,3 +37,19 @@ CHELTENHAM = [ [51.9328, -2.0221], [51.9324, -2.1265], ] + +CHELTENHAM_AND_GLOUCESTER = [ + [51.8820, -2.2920], + [51.8234, -2.2570], + [51.8883, -2.0262], + [51.9425, -2.0771], + [51.8820, -2.2920], +] + +SEVERN_ESTUARY = [ + [51.5719, -2.9388], + [51.4180, -2.7259], + [51.8493, -2.1958], + [51.8883, -2.3016], + [51.5719, -2.9388], +] diff --git a/tests/app/broadcast_areas/test_utils.py b/tests/app/broadcast_areas/test_utils.py index 74e940fcc..c95b75eff 100644 --- a/tests/app/broadcast_areas/test_utils.py +++ b/tests/app/broadcast_areas/test_utils.py @@ -7,12 +7,24 @@ from tests.app.broadcast_areas.custom_polygons import ( BRISTOL, BURFORD, CHELTENHAM, + CHELTENHAM_AND_GLOUCESTER, SANTA_A, + SEVERN_ESTUARY, SKYE, ) @pytest.mark.parametrize(('area_ids', 'expected_area_names'), [ + ( + [ + 'wd20-E05009336', # Whitechapel, Tower Hamlets (electoral ward) + 'wd20-E05009372', # Hackney Central, Hackney (electoral ward) + 'wd20-E05009374', # Hackney Wick, Hackney (electoral ward) + ], [ + 'Hackney', # in Greater London* (DB doesn't know this) + 'Tower Hamlets', # in Greater London* + ], + ), ( [ 'wd20-E05004294', # Hester’s Way, Cheltenham (electoral ward) @@ -31,6 +43,16 @@ from tests.app.broadcast_areas.custom_polygons import ( 'Hackney', # in Greater London* (DB doesn't know this) ], ), + ( + [ + 'wd20-E05004294', # Hester’s Way, Cheltenham (electoral ward) + 'wd20-E05010981', # Painswick & Upton, Stroud (electoral ward) + 'wd20-E05009372', # Hackney Central, Hackney (electoral ward) + ], [ + 'Gloucestershire', # upper tier authority + 'Hackney', # in Greater London* (DB doesn't know this) + ], + ), ( [ 'lad20-E07000037', # High Peak (lower tier authority) @@ -49,6 +71,17 @@ from tests.app.broadcast_areas.custom_polygons import ( 'High Peak', # in Derbyshire ] ), + ( + [ + 'lad20-E07000037', # High Peak (lower tier authority) + 'lad20-E07000035', # Derbyshire Dales (lower tier authority) + 'ctyua19-E10000028', # Staffordshire (upper tier authority) + ], + [ + 'Derbyshire', # upper tier authority + 'Staffordshire', + ] + ), ( [ 'ctry19-E92000001', # England @@ -84,6 +117,19 @@ def test_aggregate_areas( 'Bristol, City of', ] ), + ( + [SEVERN_ESTUARY], [ + # Area covers various lower-tier authorities + # with more than three in Gloucestershire so + # we aggregate them into that + 'Bristol, City of', + 'Gloucestershire', + 'Monmouthshire', + 'Newport', + 'North Somerset', + 'South Gloucestershire', + ] + ), ( [CHELTENHAM], [ # Area covers three lower-tier authorities @@ -93,14 +139,30 @@ def test_aggregate_areas( 'Tewkesbury', ] ), + ( + [CHELTENHAM_AND_GLOUCESTER], [ + # Area covers more than 3 lower-tier authorities + # in Gloucestershire so we aggregate them + 'Gloucestershire', + ] + ), ( [BURFORD], [ - # Area covers two lower-tier authorities - # in Gloucestershire (upper tier authority) + # Area covers one lower-tier authority in + # Gloucestershire and one in Oxfordshire (both + # upper tier authorities) 'Cotswold', 'West Oxfordshire', ], ), + ( + [CHELTENHAM_AND_GLOUCESTER, BURFORD], [ + # Area covers many lower-tier authorities + # in Gloucestershire but only one in Oxfordshire + 'Gloucestershire', + 'West Oxfordshire', + ], + ), ( [SANTA_A], [ # Does not overlap with the UK From de804805acbf9b5874fa00abd14cac633e85ed2c Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Tue, 24 Aug 2021 16:06:46 +0100 Subject: [PATCH 6/8] Refactor logic to identify types of areas I did consider whether to store this explicitly in the SQLite DB, but this is less effort for now and we can always switch to that more robust approach in future if we need to. --- app/broadcast_areas/models.py | 8 ++++++++ app/broadcast_areas/utils.py | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/broadcast_areas/models.py b/app/broadcast_areas/models.py index 271da834f..8d364e3c5 100644 --- a/app/broadcast_areas/models.py +++ b/app/broadcast_areas/models.py @@ -86,6 +86,14 @@ class BroadcastArea(BaseBroadcastArea, SortableMixin): def __init__(self, row): self.id, self.name, self._count_of_phones, self.library_id = row + @cached_property + def is_lower_tier_local_authority(self): + return self.id.startswith('lad20-') and self.parent + + @cached_property + def is_electoral_ward(self): + return self.id.startswith('wd20-') + @classmethod def from_row_with_simple_polygons(cls, row): instance = cls(row[:4]) diff --git a/app/broadcast_areas/utils.py b/app/broadcast_areas/utils.py index fdcc1f83a..217bf028e 100644 --- a/app/broadcast_areas/utils.py +++ b/app/broadcast_areas/utils.py @@ -24,7 +24,7 @@ def _convert_custom_areas_to_wards(areas): def _aggregate_wards_by_local_authority(areas): return { - area.parent if area.id.startswith('wd20-') + area.parent if area.is_electoral_ward else area for area in areas } @@ -56,7 +56,7 @@ def _cluster_lower_tier_authorities(areas): for area in areas: # group lower tier authorities by "county" - if area.id.startswith('lad20-') and area.parent: + if area.is_lower_tier_local_authority: result[area.parent] += [area] # leave countries, unitary authorities as-is else: From a94291d5a4a94ecb99c06466e5663dc157c3a36e Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Fri, 27 Aug 2021 14:41:00 +0100 Subject: [PATCH 7/8] Clarify comments for aggregation algorithm In response to [1] and [2]. [1]: https://github.com/alphagov/notifications-admin/pull/4003#discussion_r695919270 [2]: https://github.com/alphagov/notifications-admin/pull/4003#discussion_r695920213 --- app/broadcast_areas/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/broadcast_areas/utils.py b/app/broadcast_areas/utils.py index 217bf028e..cd4ec378d 100644 --- a/app/broadcast_areas/utils.py +++ b/app/broadcast_areas/utils.py @@ -34,17 +34,17 @@ def _aggregate_lower_tier_authorities(areas): clusters = _cluster_lower_tier_authorities(areas) for cluster in clusters: - # always keep lone area as itself + # always show a single area cluster as itself (aggregation isn't helpful) if len(cluster) == 1: results |= set(cluster) - # aggregate multi-area cluster + # aggregate a single cluster with lots of areas (too complex to show in full) elif len(cluster) > 3: results |= {cluster[0].parent} - # aggregate many small clusters + # if cluster is 2 or 3 areas, and there are more than 1 cluster, aggregate the cluster elif len(clusters) > 1: area = cluster[0] results |= {area.parent or area} - # keep one small cluster in full + # else keep single 2-3 areas cluster in full (easy enough to understand) else: results |= set(cluster) From 4bae4eec6cd85f10c1a22bd2d7645ad8e02b71e9 Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Fri, 27 Aug 2021 14:58:45 +0100 Subject: [PATCH 8/8] Start sending aggregate area names to API The aggregate names don't need to be sorted, but it helps to do this for the tests, since the implementation of sets may not be stable between machines and lead to sporadic test failures. I did also toy with sorting granular area names so they have a similar ordering, but this would take them out-of-order with IDs and really the important thing is that the ordering is stable. --- app/broadcast_areas/utils.py | 2 +- app/models/broadcast_message.py | 2 ++ tests/app/broadcast_areas/test_utils.py | 8 ++++---- tests/app/main/views/test_broadcast.py | 11 +++++++++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/broadcast_areas/utils.py b/app/broadcast_areas/utils.py index cd4ec378d..48766073d 100644 --- a/app/broadcast_areas/utils.py +++ b/app/broadcast_areas/utils.py @@ -7,7 +7,7 @@ def aggregate_areas(areas): areas = _convert_custom_areas_to_wards(areas) areas = _aggregate_wards_by_local_authority(areas) areas = _aggregate_lower_tier_authorities(areas) - return areas + return sorted(areas) def _convert_custom_areas_to_wards(areas): diff --git a/app/models/broadcast_message.py b/app/models/broadcast_message.py index 8b9878a8c..5e1a5deb4 100644 --- a/app/models/broadcast_message.py +++ b/app/models/broadcast_message.py @@ -11,6 +11,7 @@ from app.broadcast_areas.models import ( CustomBroadcastAreas, broadcast_area_libraries, ) +from app.broadcast_areas.utils import aggregate_areas from app.formatters import round_to_significant_figures from app.models import JSONModel, ModelList from app.models.user import User @@ -244,6 +245,7 @@ class BroadcastMessage(JSONModel): areas = { 'ids': self.area_ids, 'names': [area.name for area in self.areas], + 'aggregate_names': [area.name for area in aggregate_areas(self.areas)], 'simple_polygons': self.simple_polygons.as_coordinate_pairs_lat_long } diff --git a/tests/app/broadcast_areas/test_utils.py b/tests/app/broadcast_areas/test_utils.py index c95b75eff..8ed121995 100644 --- a/tests/app/broadcast_areas/test_utils.py +++ b/tests/app/broadcast_areas/test_utils.py @@ -99,9 +99,9 @@ def test_aggregate_areas( broadcast_message_json(area_ids=area_ids) ) - assert sorted( + assert [ area.name for area in aggregate_areas(broadcast_message.areas) - ) == expected_area_names + ] == expected_area_names @pytest.mark.parametrize(('simple_polygons', 'expected_area_names'), [ @@ -180,6 +180,6 @@ def test_aggregate_areas_for_custom_polygons( ) ) - assert sorted( + assert [ area.name for area in aggregate_areas(broadcast_message.areas) - ) == expected_area_names + ] == expected_area_names diff --git a/tests/app/main/views/test_broadcast.py b/tests/app/main/views/test_broadcast.py index 414b1502c..e50b0a858 100644 --- a/tests/app/main/views/test_broadcast.py +++ b/tests/app/main/views/test_broadcast.py @@ -1398,6 +1398,7 @@ def test_add_broadcast_area( 'areas': { 'ids': ['ctry19-E92000001', 'ctry19-S92000003', 'ctry19-W92000004'], 'names': ['England', 'Scotland', 'Wales'], + 'aggregate_names': ['England', 'Scotland', 'Wales'], 'simple_polygons': coordinates } }, @@ -1411,7 +1412,9 @@ def test_add_broadcast_area( }, { # wd20-S13002845 is ignored because the user chose ‘Select all…’ - 'ids': ['lad20-S12000033'], 'names': ['Aberdeen City'] + 'ids': ['lad20-S12000033'], + 'names': ['Aberdeen City'], + 'aggregate_names': ['Aberdeen City'] } ), ( @@ -1421,6 +1424,7 @@ def test_add_broadcast_area( { 'ids': ['wd20-S13002845', 'wd20-S13002836'], 'names': ['Bridge of Don', 'Airyhall/Broomhill/Garthdee'], + 'aggregate_names': ['Aberdeen City'], } ), )) @@ -1454,6 +1458,7 @@ def test_add_broadcast_sub_area_district_view( # These two areas are on the broadcast already expected_data['ids'] = ['ctry19-E92000001', 'ctry19-S92000003'] + expected_data['ids'] expected_data['names'] = ['England', 'Scotland'] + expected_data['names'] + expected_data['aggregate_names'] = sorted(['England', 'Scotland'] + expected_data['aggregate_names']) mock_update_broadcast_message.assert_called_once_with( service_id=SERVICE_ONE_ID, @@ -1504,7 +1509,8 @@ def test_add_broadcast_sub_area_county_view( ] + [ 'ctyua19-E10000016' ], - 'names': ['England', 'Scotland', 'Kent'] + 'names': ['England', 'Scotland', 'Kent'], + 'aggregate_names': ['England', 'Kent', 'Scotland'] } }, ) @@ -1545,6 +1551,7 @@ def test_remove_broadcast_area_page( 'areas': { 'simple_polygons': coordinates, 'names': ['Scotland'], + 'aggregate_names': ['Scotland'], 'ids': ['ctry19-S92000003'] }, },