• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DemocracyClub / UK-Polling-Stations / a88d934d-c215-40a2-a668-f424218e237e

24 Apr 2025 09:25AM UTC coverage: 73.995% (+0.3%) from 73.668%
a88d934d-c215-40a2-a668-f424218e237e

Pull #8615

circleci

polling-bot-4000
Import script for Barnet (2025-05-15) (closes #8613)
Pull Request #8615: Import script for Barnet (2025-05-15) (closes #8613)

4106 of 5549 relevant lines covered (74.0%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.33
/polling_stations/apps/data_finder/helpers/geocoders.py
1
import abc
1✔
2

3
from councils.models import Council
1✔
4
from django.conf import settings
1✔
5
from django.core.exceptions import ObjectDoesNotExist
1✔
6
from uk_geo_utils.geocoders import (
1✔
7
    AddressBaseGeocoder,
8
    CodesNotFoundException,
9
    OnspdGeocoder,
10
)
11
from uk_geo_utils.helpers import Postcode
1✔
12

13

14
class PostcodeError(Exception):
1✔
15
    pass
1✔
16

17

18
class BaseGeocoder(metaclass=abc.ABCMeta):
1✔
19
    def __init__(self, postcode):
1✔
20
        self.postcode = self.format_postcode(postcode)
1✔
21

22
    def format_postcode(self, postcode):
1✔
23
        return Postcode(postcode).without_space
1✔
24

25
    @abc.abstractmethod
1✔
26
    def geocode_point_only(self):
1✔
27
        pass
×
28

29
    @abc.abstractmethod
1✔
30
    def geocode(self):
1✔
31
        pass
×
32

33

34
class OnspdGeocoderAdapter(BaseGeocoder):
1✔
35
    def geocode(self):
1✔
36
        geocoder = OnspdGeocoder(self.postcode)
1✔
37
        centre = geocoder.centroid
1✔
38
        if not centre:
1✔
39
            raise PostcodeError("No location information")
×
40

41
        local_auth = geocoder.get_code("lad")
1✔
42
        error_values = [
1✔
43
            "L99999999",  # Channel Islands
44
            "M99999999",  # Isle of Man
45
            "",  # Terminated Postcode or other
46
        ]
47
        if not local_auth or local_auth in error_values:
1✔
48
            raise PostcodeError("No location information")
×
49

50
        return geocoder
1✔
51

52
    def geocode_point_only(self):
1✔
53
        geocoder = OnspdGeocoder(self.postcode)
×
54
        centre = geocoder.centroid
×
55
        if not centre:
×
56
            raise PostcodeError("No location information")
×
57
        return geocoder
×
58

59

60
class AddressBaseGeocoderAdapter(BaseGeocoder):
1✔
61
    def geocode(self):
1✔
62
        return AddressBaseGeocoder(self.postcode)
1✔
63

64
    def geocode_point_only(self):
1✔
65
        return AddressBaseGeocoder(self.postcode)
1✔
66

67

68
def geocode_point_only(postcode):
1✔
69
    geocoders = (AddressBaseGeocoderAdapter(postcode), OnspdGeocoderAdapter(postcode))
1✔
70
    for geocoder in geocoders:
1✔
71
        try:
1✔
72
            return geocoder.geocode_point_only()
1✔
73
        except ObjectDoesNotExist:
1✔
74
            # we couldn't find this postcode in AddressBase
75
            # this might be because
76
            # - The postcode isn't in AddressBase
77
            # - The postcode is in Northern Ireland
78
            # - AddressBase hasn't been imported
79
            # fall back to the next source
80
            continue
1✔
81
        except PostcodeError:
×
82
            # we were unable to geocode this postcode using ONSPD
83
            # re-raise the exception.
84
            # Note: in future we may want to fall back to yet another source
85
            raise
×
86

87
    # All of our attempts to geocode this failed. Raise a generic exception
88
    raise PostcodeError("Could not geocode from any source")
×
89

90

91
def geocode(postcode):
1✔
92
    geocoders = (AddressBaseGeocoderAdapter(postcode), OnspdGeocoderAdapter(postcode))
1✔
93
    for geocoder in geocoders:
1✔
94
        try:
1✔
95
            gc = geocoder.geocode()
1✔
96

97
            # Declare a new subclass of whatever type `gc` is on-the-fly
98
            # this allows us to monkey-patch `get_code()` with a function that
99
            # can translate old codes if our data source isn't up-to-date yet
100
            class PatchedGeocoder(type(gc)):
1✔
101
                def get_code(self, code_type, *args, **kwargs):
1✔
102
                    code = super().get_code(code_type, *args, **kwargs)
1✔
103
                    if code_type == "lad" and code in settings.OLD_TO_NEW_MAP:
1✔
104
                        return settings.OLD_TO_NEW_MAP[code]
1✔
105
                    return code
1✔
106

107
            return PatchedGeocoder(postcode)
1✔
108

109
        except ObjectDoesNotExist:
1✔
110
            # we couldn't find this postcode in AddressBase
111
            # this might be because
112
            # - The postcode isn't in AddressBase
113
            # - The postcode is in Northern Ireland
114
            # - AddressBase hasn't been imported
115
            # fall back to the next source
116
            continue
1✔
117
        except CodesNotFoundException:
×
118
            # we did find this postcode in AddressBase, but there were no
119
            # corresponding codes in the uprn/council lookup:
120
            # fall back to the next source
121
            continue
×
122
        except PostcodeError:
×
123
            # we were unable to geocode this postcode using ONSPD
124
            # re-raise the exception.
125
            # Note: in future we may want to fall back to yet another source
126
            raise
×
127

128
    # All of our attempts to geocode this failed. Raise a generic exception
129
    raise PostcodeError("Could not geocode from any source")
1✔
130

131

132
def get_council(geocode_result):
1✔
133
    try:
1✔
134
        return Council.objects.get(geography__gss=geocode_result.get_code("lad"))
1✔
135

136
    except Council.DoesNotExist:
1✔
137
        return Council.objects.get(geography__geography__covers=geocode_result.centroid)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc