uk_bin_collection 0.145.0__py3-none-any.whl → 0.146.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uk_bin_collection/map.html +33 -23
- uk_bin_collection/tests/check_selenium_url_in_input.json.py +15 -8
- uk_bin_collection/tests/input.json +29 -1
- uk_bin_collection/uk_bin_collection/common.py +10 -6
- uk_bin_collection/uk_bin_collection/councils/GooglePublicCalendarCouncil.py +6 -4
- uk_bin_collection/uk_bin_collection/councils/GreatYarmouthBoroughCouncil.py +23 -13
- uk_bin_collection/uk_bin_collection/councils/KnowsleyMBCouncil.py +40 -13
- uk_bin_collection/uk_bin_collection/councils/MidUlsterDistrictCouncil.py +17 -13
- uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py +1 -0
- {uk_bin_collection-0.145.0.dist-info → uk_bin_collection-0.146.0.dist-info}/METADATA +1 -1
- {uk_bin_collection-0.145.0.dist-info → uk_bin_collection-0.146.0.dist-info}/RECORD +14 -14
- {uk_bin_collection-0.145.0.dist-info → uk_bin_collection-0.146.0.dist-info}/LICENSE +0 -0
- {uk_bin_collection-0.145.0.dist-info → uk_bin_collection-0.146.0.dist-info}/WHEEL +0 -0
- {uk_bin_collection-0.145.0.dist-info → uk_bin_collection-0.146.0.dist-info}/entry_points.txt +0 -0
uk_bin_collection/map.html
CHANGED
@@ -37,23 +37,37 @@
|
|
37
37
|
const coveredLADs = new Set();
|
38
38
|
const integrationByLAD24CD = {};
|
39
39
|
|
40
|
-
for (const entry of Object.
|
41
|
-
const
|
42
|
-
const
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
40
|
+
for (const [moduleName, entry] of Object.entries(integrationData)) {
|
41
|
+
const wikiName = entry.wiki_name || moduleName;
|
42
|
+
const wikiUrl = `https://github.com/robbrad/UKBinCollectionData/wiki/Councils#${slugify(wikiName)}`;
|
43
|
+
|
44
|
+
// Case 1: Direct LAD24CD
|
45
|
+
if (entry.LAD24CD) {
|
46
|
+
const code = entry.LAD24CD?.replace(/[^\x20-\x7E]/g, '').trim();
|
47
|
+
if (code) {
|
48
|
+
coveredLADs.add(code);
|
49
|
+
integrationByLAD24CD[code] = { wiki_name: wikiName, url: wikiUrl };
|
50
|
+
} else {
|
51
|
+
console.warn("⚠️ Entry with bad LAD24CD:", moduleName, entry);
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
// Case 2: Shared modules with multiple LADs
|
56
|
+
if (Array.isArray(entry.supported_councils_LAD24CD)) {
|
57
|
+
for (const codeRaw of entry.supported_councils_LAD24CD) {
|
58
|
+
const code = codeRaw?.replace(/[^\x20-\x7E]/g, '').trim();
|
59
|
+
if (code) {
|
60
|
+
coveredLADs.add(code);
|
61
|
+
// Only overwrite if not already present (prefer specific match)
|
62
|
+
if (!integrationByLAD24CD[code]) {
|
63
|
+
integrationByLAD24CD[code] = { wiki_name: wikiName, url: wikiUrl };
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
52
67
|
}
|
53
68
|
}
|
54
69
|
|
55
|
-
console.log("
|
56
|
-
console.log("📦 Final set of covered LADs:", [...coveredLADs]);
|
70
|
+
console.log("📦 Final covered LADs:", [...coveredLADs]);
|
57
71
|
|
58
72
|
return fetch('Local_Authority_Boundaries.geojson')
|
59
73
|
.then(res => res.json())
|
@@ -74,7 +88,6 @@
|
|
74
88
|
const name = feature.properties.LAD24NM;
|
75
89
|
const covered = coveredLADs.has(code);
|
76
90
|
|
77
|
-
console.log("🌍 GeoJSON Feature:", code, name);
|
78
91
|
if (!code) console.warn("⚠️ Missing LAD24CD in GeoJSON feature:", feature);
|
79
92
|
if (!covered && code) console.warn("❌ Not covered LAD:", code, name);
|
80
93
|
|
@@ -90,14 +103,11 @@
|
|
90
103
|
}
|
91
104
|
}).addTo(map);
|
92
105
|
|
93
|
-
//
|
94
|
-
const missing =
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
missing.push(`${code} - ${f.properties.LAD24NM}`);
|
99
|
-
}
|
100
|
-
});
|
106
|
+
// Debug missing LADs
|
107
|
+
const missing = geojson.features
|
108
|
+
.map(f => [f.properties.LAD24CD?.trim(), f.properties.LAD24NM])
|
109
|
+
.filter(([code]) => !coveredLADs.has(code))
|
110
|
+
.map(([code, name]) => `${code} - ${name}`);
|
101
111
|
console.warn("🛠️ Missing LADs in input.json:", missing);
|
102
112
|
});
|
103
113
|
})
|
@@ -23,10 +23,14 @@ def get_council_files(repo, branch):
|
|
23
23
|
name = item["name"]
|
24
24
|
if name.endswith(".py"):
|
25
25
|
council_name = name.replace(".py", "")
|
26
|
-
councils[council_name] = item[
|
26
|
+
councils[council_name] = item[
|
27
|
+
"url"
|
28
|
+
] # 'url' gives API-based content URL
|
27
29
|
return councils
|
28
30
|
else:
|
29
|
-
raise ValueError(
|
31
|
+
raise ValueError(
|
32
|
+
"Expected a list from the GitHub response but got something else."
|
33
|
+
)
|
30
34
|
else:
|
31
35
|
print(f"Failed to fetch councils from files: {response.content}")
|
32
36
|
return {}
|
@@ -39,7 +43,9 @@ def get_council_file_content(api_url):
|
|
39
43
|
We'll use the latter, decode base64, and return the text.
|
40
44
|
"""
|
41
45
|
# Example: https://api.github.com/repos/robbrad/UKBinCollectionData/contents/...
|
42
|
-
response = requests.get(
|
46
|
+
response = requests.get(
|
47
|
+
api_url, headers={"Accept": "application/vnd.github.v3+json"}
|
48
|
+
)
|
43
49
|
if response.status_code == 200:
|
44
50
|
file_json = response.json()
|
45
51
|
# file_json["content"] is base64-encoded
|
@@ -82,8 +88,8 @@ def council_needs_update(council_name, json_data, council_file_content):
|
|
82
88
|
# If the council isn't in the JSON at all, we can't do the check
|
83
89
|
# (or we assume no JSON data => no web_driver?).
|
84
90
|
council_data = json_data.get(council_name, {})
|
85
|
-
web_driver_missing =
|
86
|
-
create_webdriver_present =
|
91
|
+
web_driver_missing = "web_driver" not in council_data
|
92
|
+
create_webdriver_present = "create_webdriver" in council_file_content
|
87
93
|
|
88
94
|
return web_driver_missing and create_webdriver_present
|
89
95
|
|
@@ -165,20 +171,21 @@ def main(repo="robbrad/UKBinCollectionData", branch="master"):
|
|
165
171
|
# 4) Print results
|
166
172
|
table_data = []
|
167
173
|
headers = ["Council Name", "In Files", "In JSON", "Needs Update?", "Discrepancies"]
|
174
|
+
|
168
175
|
# Sort councils so that ones with the highest discrepancy or update appear first
|
169
176
|
# Then alphabetical if tie:
|
170
177
|
def sort_key(item):
|
171
178
|
# item is (council_name, data_dict)
|
172
179
|
return (
|
173
|
-
item[1]["needs_update"],
|
180
|
+
item[1]["needs_update"], # sort by needs_update (False < True)
|
174
181
|
item[1]["discrepancies_count"], # then by discrepancies
|
175
|
-
item[0],
|
182
|
+
item[0], # then by name
|
176
183
|
)
|
177
184
|
|
178
185
|
# We'll sort descending for "needs_update", so invert the boolean or reverse later
|
179
186
|
sorted_councils = sorted(
|
180
187
|
all_councils_data.items(),
|
181
|
-
key=lambda x: (not x[1]["needs_update"], x[1]["discrepancies_count"], x[0])
|
188
|
+
key=lambda x: (not x[1]["needs_update"], x[1]["discrepancies_count"], x[0]),
|
182
189
|
)
|
183
190
|
|
184
191
|
for council, presence in sorted_councils:
|
@@ -985,7 +985,35 @@
|
|
985
985
|
"wiki_name": "Google Calendar (Public)",
|
986
986
|
"wiki_note": "The URL should be the public ics file URL for the public Google calendar. See https://support.google.com/calendar/answer/37083?sjid=7202815583021446882-EU. Councils that currently need this are Trafford.",
|
987
987
|
"supported_councils": [
|
988
|
-
"TraffordCouncil"
|
988
|
+
"TraffordCouncil",
|
989
|
+
"ClackmannanshireCouncil",
|
990
|
+
"HavantBoroughCouncil",
|
991
|
+
"NorthWarwickshireBoroughCouncil",
|
992
|
+
"NewryMourneAndDownDistrictCouncil",
|
993
|
+
"EastDunbartonshireCouncil",
|
994
|
+
"PendleBoroughCouncil",
|
995
|
+
"TorfaenCountyBoroughCouncil",
|
996
|
+
"EastHampshireCountyCouncil",
|
997
|
+
"RibbleValleyCouncil",
|
998
|
+
"BrentwoodBoroughCouncil",
|
999
|
+
"IsleOfWightCouncil",
|
1000
|
+
"WestmorlAndFurnessCouncil",
|
1001
|
+
"DerryAndStrabaneDistrictCouncil",
|
1002
|
+
"NorwichCityCouncil"
|
1003
|
+
],
|
1004
|
+
"supported_councils_LAD24CD": [
|
1005
|
+
"E06000046",
|
1006
|
+
"E07000068",
|
1007
|
+
"E07000085",
|
1008
|
+
"E07000090",
|
1009
|
+
"E07000124",
|
1010
|
+
"E07000218",
|
1011
|
+
"E08000009",
|
1012
|
+
"N09000005",
|
1013
|
+
"N09000010",
|
1014
|
+
"S12000005",
|
1015
|
+
"S12000045",
|
1016
|
+
"W06000020"
|
989
1017
|
]
|
990
1018
|
},
|
991
1019
|
"GraveshamBoroughCouncil": {
|
@@ -177,7 +177,11 @@ def is_working_day(date_to_check: datetime, region: Region = Region.ENG) -> bool
|
|
177
177
|
:param region: The UK nation to check. Defaults to ENG.
|
178
178
|
:return: Bool - true if a working day (non-holiday, Mon-Fri).
|
179
179
|
"""
|
180
|
-
return
|
180
|
+
return (
|
181
|
+
False
|
182
|
+
if is_holiday(date_to_check, region) or is_weekend(date_to_check)
|
183
|
+
else True
|
184
|
+
)
|
181
185
|
|
182
186
|
|
183
187
|
def get_next_working_day(date: datetime, region: Region = Region.ENG) -> datetime:
|
@@ -232,7 +236,7 @@ def get_next_occurrence_from_day_month(date: datetime) -> datetime:
|
|
232
236
|
|
233
237
|
# Check if the target date has already occurred this year
|
234
238
|
if (target_month < current_month) or (
|
235
|
-
|
239
|
+
target_month == current_month and target_day < current_day
|
236
240
|
):
|
237
241
|
date = pd.to_datetime(date) + pd.DateOffset(years=1)
|
238
242
|
|
@@ -315,10 +319,10 @@ def contains_date(string, fuzzy=False) -> bool:
|
|
315
319
|
|
316
320
|
|
317
321
|
def create_webdriver(
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
+
web_driver: str = None,
|
323
|
+
headless: bool = True,
|
324
|
+
user_agent: str = None,
|
325
|
+
session_name: str = None,
|
322
326
|
) -> webdriver.Chrome:
|
323
327
|
"""
|
324
328
|
Create and return a Chrome WebDriver configured for optional headless operation.
|
@@ -29,9 +29,11 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
29
29
|
if not event.summary or not event.start:
|
30
30
|
continue
|
31
31
|
|
32
|
-
bindata["bins"].append(
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
bindata["bins"].append(
|
33
|
+
{
|
34
|
+
"type": event.summary,
|
35
|
+
"collectionDate": event.start.date().strftime(date_format),
|
36
|
+
}
|
37
|
+
)
|
36
38
|
|
37
39
|
return bindata
|
@@ -10,6 +10,7 @@ from datetime import datetime
|
|
10
10
|
from uk_bin_collection.uk_bin_collection.common import *
|
11
11
|
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
12
12
|
|
13
|
+
|
13
14
|
# import the wonderful Beautiful Soup and the URL grabber
|
14
15
|
class CouncilClass(AbstractGetBinDataClass):
|
15
16
|
"""
|
@@ -31,7 +32,7 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
31
32
|
check_postcode(user_postcode)
|
32
33
|
|
33
34
|
driver = create_webdriver(web_driver, headless, None, __name__)
|
34
|
-
|
35
|
+
|
35
36
|
driver.get(url)
|
36
37
|
|
37
38
|
wait = WebDriverWait(driver, 10)
|
@@ -78,19 +79,23 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
78
79
|
time.sleep(5)
|
79
80
|
# Wait for the specified div to be present
|
80
81
|
target_div = WebDriverWait(driver, 10).until(
|
81
|
-
EC.presence_of_element_located(
|
82
|
+
EC.presence_of_element_located(
|
83
|
+
(By.ID, "WASTECOLLECTIONCALENDARV2_LOOKUP_SHOWSCHEDULE")
|
84
|
+
)
|
82
85
|
)
|
83
86
|
|
84
87
|
soup = BeautifulSoup(driver.page_source, "html.parser")
|
85
88
|
|
86
89
|
bin_data = {"bins": []}
|
87
|
-
next_collections =
|
90
|
+
next_collections = (
|
91
|
+
{}
|
92
|
+
) # Dictionary to store the next collection for each bin type
|
88
93
|
|
89
94
|
bin_types = {
|
90
95
|
"bulky": "Bulky Collection",
|
91
96
|
"green": "Recycling",
|
92
97
|
"black": "General Waste",
|
93
|
-
"brown": "Garden Waste"
|
98
|
+
"brown": "Garden Waste",
|
94
99
|
}
|
95
100
|
|
96
101
|
for div in soup.select(".collection-area"):
|
@@ -108,21 +113,26 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
108
113
|
|
109
114
|
# Determine bin type from alt or description
|
110
115
|
description = detail.get_text(separator=" ", strip=True).lower()
|
111
|
-
alt_text = img[
|
116
|
+
alt_text = img["alt"].lower()
|
112
117
|
|
113
118
|
for key, name in bin_types.items():
|
114
119
|
if key in alt_text or key in description:
|
115
120
|
# Format date as dd/mm/yyyy
|
116
121
|
formatted_date = date_obj.strftime("%d/%m/%Y")
|
117
|
-
bin_entry = {
|
118
|
-
|
119
|
-
"collectionDate": formatted_date
|
120
|
-
}
|
121
|
-
|
122
|
+
bin_entry = {"type": name, "collectionDate": formatted_date}
|
123
|
+
|
122
124
|
# Only keep the earliest date for each bin type
|
123
|
-
if
|
125
|
+
if (
|
126
|
+
name not in next_collections
|
127
|
+
or date_obj
|
128
|
+
< datetime.strptime(
|
129
|
+
next_collections[name]["collectionDate"], "%d/%m/%Y"
|
130
|
+
)
|
131
|
+
):
|
124
132
|
next_collections[name] = bin_entry
|
125
|
-
print(
|
133
|
+
print(
|
134
|
+
f"Found next collection for {name}: {formatted_date}"
|
135
|
+
) # Debug output
|
126
136
|
break
|
127
137
|
|
128
138
|
# Add the next collections to the bin_data
|
@@ -134,7 +144,7 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
134
144
|
finally:
|
135
145
|
if driver:
|
136
146
|
driver.quit()
|
137
|
-
|
147
|
+
|
138
148
|
print("\nFinal bin data:")
|
139
149
|
print(bin_data) # Debug output
|
140
150
|
return bin_data
|
@@ -26,12 +26,19 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
26
26
|
driver = create_webdriver(web_driver, headless, None, __name__)
|
27
27
|
driver.set_window_size(1920, 1080) # 👈 ensure full viewport
|
28
28
|
|
29
|
-
driver.get(
|
29
|
+
driver.get(
|
30
|
+
"https://www.knowsley.gov.uk/bins-waste-and-recycling/your-household-bins/putting-your-bins-out"
|
31
|
+
)
|
30
32
|
|
31
33
|
# Dismiss cookie popup if it exists
|
32
34
|
try:
|
33
35
|
accept_cookies = WebDriverWait(driver, 10).until(
|
34
|
-
EC.element_to_be_clickable(
|
36
|
+
EC.element_to_be_clickable(
|
37
|
+
(
|
38
|
+
By.XPATH,
|
39
|
+
"//a[contains(@class, 'agree-button') and contains(text(), 'Accept all cookies')]",
|
40
|
+
)
|
41
|
+
)
|
35
42
|
)
|
36
43
|
accept_cookies.click()
|
37
44
|
time.sleep(1)
|
@@ -41,7 +48,10 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
41
48
|
# Step 1: Click "Search by postcode"
|
42
49
|
search_btn = WebDriverWait(driver, 60).until(
|
43
50
|
EC.element_to_be_clickable(
|
44
|
-
(
|
51
|
+
(
|
52
|
+
By.XPATH,
|
53
|
+
"//a[contains(text(), 'Search by postcode to find out when your bins are emptied')]",
|
54
|
+
)
|
45
55
|
)
|
46
56
|
)
|
47
57
|
search_btn.send_keys(Keys.RETURN)
|
@@ -49,14 +59,20 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
49
59
|
# Step 2: Enter postcode
|
50
60
|
postcode_box = WebDriverWait(driver, 60).until(
|
51
61
|
EC.presence_of_element_located(
|
52
|
-
(
|
62
|
+
(
|
63
|
+
By.XPATH,
|
64
|
+
"//label[contains(text(), 'Please enter the post code')]/following-sibling::input",
|
65
|
+
)
|
53
66
|
)
|
54
67
|
)
|
55
68
|
postcode_box.send_keys(user_postcode)
|
56
69
|
|
57
70
|
postcode_search_btn = WebDriverWait(driver, 60).until(
|
58
71
|
EC.element_to_be_clickable(
|
59
|
-
(
|
72
|
+
(
|
73
|
+
By.XPATH,
|
74
|
+
"//label[contains(text(), 'Please enter the post code')]/parent::div/following-sibling::button",
|
75
|
+
)
|
60
76
|
)
|
61
77
|
)
|
62
78
|
postcode_search_btn.send_keys(Keys.RETURN)
|
@@ -64,7 +80,10 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
64
80
|
# Step 3: Select address from results
|
65
81
|
address_selection_button = WebDriverWait(driver, 60).until(
|
66
82
|
EC.element_to_be_clickable(
|
67
|
-
(
|
83
|
+
(
|
84
|
+
By.XPATH,
|
85
|
+
f"//span[contains(text(), '{user_paon}')]/ancestor::li//button",
|
86
|
+
)
|
68
87
|
)
|
69
88
|
)
|
70
89
|
address_selection_button.send_keys(Keys.RETURN)
|
@@ -77,9 +96,13 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
77
96
|
)
|
78
97
|
|
79
98
|
bin_info_container = driver.find_element(
|
80
|
-
By.XPATH,
|
99
|
+
By.XPATH,
|
100
|
+
"//label[contains(text(), 'collection')]/ancestor::div[contains(@class, 'mx-dataview-content')]",
|
101
|
+
)
|
81
102
|
|
82
|
-
soup = BeautifulSoup(
|
103
|
+
soup = BeautifulSoup(
|
104
|
+
bin_info_container.get_attribute("innerHTML"), "html.parser"
|
105
|
+
)
|
83
106
|
|
84
107
|
for group in soup.find_all("div", class_="form-group"):
|
85
108
|
label = group.find("label")
|
@@ -93,14 +116,18 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
93
116
|
if "bin next collection date" in label_text.lower():
|
94
117
|
bin_type = label_text.split(" bin")[0]
|
95
118
|
try:
|
96
|
-
collection_date = datetime.strptime(
|
119
|
+
collection_date = datetime.strptime(
|
120
|
+
value_text, "%A %d/%m/%Y"
|
121
|
+
).strftime("%d/%m/%Y")
|
97
122
|
except ValueError:
|
98
123
|
continue
|
99
124
|
|
100
|
-
bindata["bins"].append(
|
101
|
-
|
102
|
-
|
103
|
-
|
125
|
+
bindata["bins"].append(
|
126
|
+
{
|
127
|
+
"type": bin_type,
|
128
|
+
"collectionDate": collection_date,
|
129
|
+
}
|
130
|
+
)
|
104
131
|
|
105
132
|
bindata["bins"].sort(
|
106
133
|
key=lambda x: datetime.strptime(x["collectionDate"], "%d/%m/%Y")
|
@@ -5,12 +5,13 @@ from selenium.webdriver.common.by import By
|
|
5
5
|
from selenium.webdriver.support import expected_conditions as EC
|
6
6
|
from selenium.webdriver.support.ui import Select, WebDriverWait
|
7
7
|
|
8
|
-
#import selenium keys
|
8
|
+
# import selenium keys
|
9
9
|
from selenium.webdriver.common.keys import Keys
|
10
10
|
|
11
11
|
from uk_bin_collection.uk_bin_collection.common import *
|
12
12
|
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
13
13
|
|
14
|
+
|
14
15
|
# import the wonderful Beautiful Soup and the URL grabber
|
15
16
|
class CouncilClass(AbstractGetBinDataClass):
|
16
17
|
"""
|
@@ -28,7 +29,7 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
28
29
|
check_postcode(user_postcode)
|
29
30
|
user_paon = kwargs.get("paon")
|
30
31
|
check_paon(user_paon)
|
31
|
-
|
32
|
+
|
32
33
|
headless = kwargs.get("headless")
|
33
34
|
web_driver = kwargs.get("web_driver")
|
34
35
|
driver = create_webdriver(web_driver, headless, None, __name__)
|
@@ -54,13 +55,10 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
54
55
|
pass
|
55
56
|
|
56
57
|
postcode_input = wait.until(
|
57
|
-
EC.presence_of_element_located(
|
58
|
-
(By.ID, 'postcode-search-input')
|
59
|
-
)
|
58
|
+
EC.presence_of_element_located((By.ID, "postcode-search-input"))
|
60
59
|
)
|
61
60
|
postcode_input.send_keys(user_postcode)
|
62
61
|
|
63
|
-
|
64
62
|
# Wait for the element to be clickable
|
65
63
|
postcode_search_btn = wait.until(
|
66
64
|
EC.element_to_be_clickable(
|
@@ -101,7 +99,9 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
101
99
|
date_text = date_span.text.strip()
|
102
100
|
current_year = datetime.now().year
|
103
101
|
full_date = f"{date_text} {current_year}" # e.g., "18 Apr 2025"
|
104
|
-
collection_date = datetime.strptime(full_date, "%d %b %Y").strftime(
|
102
|
+
collection_date = datetime.strptime(full_date, "%d %b %Y").strftime(
|
103
|
+
date_format
|
104
|
+
)
|
105
105
|
else:
|
106
106
|
collection_date = None
|
107
107
|
except Exception as e:
|
@@ -115,13 +115,17 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
115
115
|
bin_title_div = bin_block.select_one("div.bin-title")
|
116
116
|
if bin_title_div:
|
117
117
|
bin_type = bin_title_div.get_text(strip=True)
|
118
|
-
data["bins"].append(
|
119
|
-
|
120
|
-
|
121
|
-
|
118
|
+
data["bins"].append(
|
119
|
+
{
|
120
|
+
"type": bin_type,
|
121
|
+
"collectionDate": collection_date,
|
122
|
+
}
|
123
|
+
)
|
122
124
|
|
123
125
|
# 3. Optional: sort bins by collectionDate
|
124
|
-
data["bins"].sort(
|
126
|
+
data["bins"].sort(
|
127
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
128
|
+
)
|
125
129
|
|
126
130
|
except Exception as e:
|
127
131
|
# Here you can log the exception if needed
|
@@ -132,4 +136,4 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
132
136
|
# This block ensures that the driver is closed regardless of an exception
|
133
137
|
if driver:
|
134
138
|
driver.quit()
|
135
|
-
return data
|
139
|
+
return data
|
@@ -2,6 +2,7 @@ from bs4 import BeautifulSoup
|
|
2
2
|
from uk_bin_collection.uk_bin_collection.common import *
|
3
3
|
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
4
4
|
|
5
|
+
|
5
6
|
# import the wonderful Beautiful Soup and the URL grabber
|
6
7
|
class CouncilClass(AbstractGetBinDataClass):
|
7
8
|
"""
|
@@ -1,11 +1,11 @@
|
|
1
1
|
uk_bin_collection/Local_Authority_Boundaries.geojson,sha256=_j-hUiL0--t2ewd_s29-j7_AKRlhagRMmOhXyco-B6I,1175922
|
2
2
|
uk_bin_collection/README.rst,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
uk_bin_collection/map.html,sha256=
|
4
|
-
uk_bin_collection/tests/check_selenium_url_in_input.json.py,sha256=
|
3
|
+
uk_bin_collection/map.html,sha256=qKc4Lscv1eSIc0M3vDofzZf5w9_eslEYpz1-kK4tup0,4346
|
4
|
+
uk_bin_collection/tests/check_selenium_url_in_input.json.py,sha256=lf-JT7vvaSfvgbrfOhzrhfSzJqL82WajlRqo1GqfcMM,7875
|
5
5
|
uk_bin_collection/tests/council_feature_input_parity.py,sha256=DO6Mk4ImYgM5ZCZ-cutwz5RoYYWZRLYx2tr6zIs_9Rc,3843
|
6
6
|
uk_bin_collection/tests/features/environment.py,sha256=VQZjJdJI_kZn08M0j5cUgvKT4k3iTw8icJge1DGOkoA,127
|
7
7
|
uk_bin_collection/tests/features/validate_council_outputs.feature,sha256=SJK-Vc737hrf03tssxxbeg_JIvAH-ddB8f6gU1LTbuQ,251
|
8
|
-
uk_bin_collection/tests/input.json,sha256=
|
8
|
+
uk_bin_collection/tests/input.json,sha256=hJ_W7-yiUwGRA2V9WVQVXCV3UfaBFfbdaIbxm3c7hIM,133302
|
9
9
|
uk_bin_collection/tests/output.schema,sha256=ZwKQBwYyTDEM4G2hJwfLUVM-5v1vKRvRK9W9SS1sd18,1086
|
10
10
|
uk_bin_collection/tests/step_defs/step_helpers/file_handler.py,sha256=Ygzi4V0S1MIHqbdstUlIqtRIwnynvhu4UtpweJ6-5N8,1474
|
11
11
|
uk_bin_collection/tests/step_defs/test_validate_council.py,sha256=VZ0a81sioJULD7syAYHjvK_-nT_Rd36tUyzPetSA0gk,3475
|
@@ -14,7 +14,7 @@ uk_bin_collection/tests/test_common_functions.py,sha256=cCUwXKGijmsvTLz0KoaedXkp
|
|
14
14
|
uk_bin_collection/tests/test_conftest.py,sha256=qI_zgGjNOnwE9gmZUiuirL1SYz3TFw5yfGFgT4T3aG4,1100
|
15
15
|
uk_bin_collection/tests/test_get_data.py,sha256=sFJz_Fd6o-1r2gdmzY52JGwVi0Of_mDzvYSoc7a3RUw,7239
|
16
16
|
uk_bin_collection/uk_bin_collection/collect_data.py,sha256=dB7wWXsJX4fm5bIf84lexkvHIcO54CZ3JPxqmS-60YY,4654
|
17
|
-
uk_bin_collection/uk_bin_collection/common.py,sha256=
|
17
|
+
uk_bin_collection/uk_bin_collection/common.py,sha256=izotgwavB08pUWisNL3wqcBrE9E1-bdrq-v6YKyriDE,11034
|
18
18
|
uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py,sha256=Je8VwVLK9KnYl9vqf2gWJ7ZYDgUq3A7caDiIzk5Xof8,4194
|
19
19
|
uk_bin_collection/uk_bin_collection/councils/AberdeenshireCouncil.py,sha256=aO1CSdyqa8oAD0fB79y1Q9bikAWCP_JFa7CsyTa2j9s,1655
|
20
20
|
uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py,sha256=ppbrmm-MzB1wOulK--CU_0j4P-djNf3ozMhHnmQFqLo,1511
|
@@ -131,9 +131,9 @@ uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py,sha256=SRCgYhYs
|
|
131
131
|
uk_bin_collection/uk_bin_collection/councils/GedlingBoroughCouncil.py,sha256=XzfFMCwclh9zAJgsbaj4jywjdiH0wPaFicaVsLrN3ms,2297
|
132
132
|
uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py,sha256=9Rk9KfcglJvRhh4373OfRX-fwywE2xgwx5KejqzV5fE,3399
|
133
133
|
uk_bin_collection/uk_bin_collection/councils/GloucesterCityCouncil.py,sha256=67D8rbhn0t4rsCSJRTXZVtHmph2wT6rJiexNWKOnMok,4625
|
134
|
-
uk_bin_collection/uk_bin_collection/councils/GooglePublicCalendarCouncil.py,sha256=
|
134
|
+
uk_bin_collection/uk_bin_collection/councils/GooglePublicCalendarCouncil.py,sha256=1YLxRgiAyOi6ekPYUMJ_F8dKsx62lAeOi1NmT47E8zo,1216
|
135
135
|
uk_bin_collection/uk_bin_collection/councils/GraveshamBoroughCouncil.py,sha256=ueQ9xFiTxMUBTGV9VjtySHA1EFWliTM0AeNePBIG9ho,4568
|
136
|
-
uk_bin_collection/uk_bin_collection/councils/GreatYarmouthBoroughCouncil.py,sha256=
|
136
|
+
uk_bin_collection/uk_bin_collection/councils/GreatYarmouthBoroughCouncil.py,sha256=lEtV-zxApiWXEJUEedaNiwBA3Oo1x8uM7m7GtRKfT_8,5469
|
137
137
|
uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py,sha256=9pVrmQhZcK2AD8gX8mNvP--L4L9KaY6L3B822VX6fec,5695
|
138
138
|
uk_bin_collection/uk_bin_collection/councils/GwyneddCouncil.py,sha256=eK2KkY1NbIxVtBruQYSNPA0J7fuzMik5it02dFbKYV0,1855
|
139
139
|
uk_bin_collection/uk_bin_collection/councils/HackneyCouncil.py,sha256=vO3ugk5fcdkYTslXNKmpJC84-ZHqrdFqW8zfX7TSiTQ,3104
|
@@ -157,7 +157,7 @@ uk_bin_collection/uk_bin_collection/councils/IslingtonCouncil.py,sha256=xavzL6ZI
|
|
157
157
|
uk_bin_collection/uk_bin_collection/councils/KingsLynnandWestNorfolkBC.py,sha256=Shj18R-7NW4ivqJJFVJOLmf-EeN6hXP2Of30oI-SeAQ,1932
|
158
158
|
uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py,sha256=iZ7njIxccCGBhUUWWd9Azh7cxUAKaofebCm3lo-TuxA,3543
|
159
159
|
uk_bin_collection/uk_bin_collection/councils/KirkleesCouncil.py,sha256=WPM7koIqK5Wz-iT9Mds6AptihGZtl4KZhkVTcT9cx_c,2762
|
160
|
-
uk_bin_collection/uk_bin_collection/councils/KnowsleyMBCouncil.py,sha256=
|
160
|
+
uk_bin_collection/uk_bin_collection/councils/KnowsleyMBCouncil.py,sha256=PDC907dxifFSAuto-vLJLEPBJrxTqNsfoD07__iKiwM,5269
|
161
161
|
uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py,sha256=FmHT6oyD4BwWuhxA80PHnGA7HPrLuyjP_54Cg8hT6k4,2537
|
162
162
|
uk_bin_collection/uk_bin_collection/councils/LeedsCityCouncil.py,sha256=VWdhw6qvCTj3EhFHf046xPWgc6szeFW2Xbt6W2J0e6w,4371
|
163
163
|
uk_bin_collection/uk_bin_collection/councils/LeicesterCityCouncil.py,sha256=o3kE8sjThQa4_AvSK5NH8VH7jWFO9MMPgoqLOTjyh0w,1851
|
@@ -185,7 +185,7 @@ uk_bin_collection/uk_bin_collection/councils/MidAndEastAntrimBoroughCouncil.py,s
|
|
185
185
|
uk_bin_collection/uk_bin_collection/councils/MidDevonCouncil.py,sha256=8MxqGgOJVseMkrTmEMT0EyDW7UMbXMoa5ZcJ2nD55Ew,3367
|
186
186
|
uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py,sha256=h6M-v5jVYe7OlQ47Vf-0pEgECZLOOacK3_XE6zbpsM4,6329
|
187
187
|
uk_bin_collection/uk_bin_collection/councils/MidSussexDistrictCouncil.py,sha256=AZgC9wmDLEjUOtIFvf0ehF5LHturXTH4DkE3ioPSVBA,6254
|
188
|
-
uk_bin_collection/uk_bin_collection/councils/MidUlsterDistrictCouncil.py,sha256=
|
188
|
+
uk_bin_collection/uk_bin_collection/councils/MidUlsterDistrictCouncil.py,sha256=ev_T8LoI9tZzkymmAD9yEk2leKc4Y0Cqsde56aAJPa8,5114
|
189
189
|
uk_bin_collection/uk_bin_collection/councils/MiddlesbroughCouncil.py,sha256=BiYexiZj-9PxRnB7sYRy0G-72s3L9jfh2vd1Y2NQwtg,4223
|
190
190
|
uk_bin_collection/uk_bin_collection/councils/MidlothianCouncil.py,sha256=-VKvdIhrs859-YqxsNMzRWm2alP1avBR1_J8O9gJnYw,6725
|
191
191
|
uk_bin_collection/uk_bin_collection/councils/MiltonKeynesCityCouncil.py,sha256=7e2pGBLCw24pNItHeI9jkxQ3rEOZ4WC4zVlbvKYGdXE,2600
|
@@ -329,11 +329,11 @@ uk_bin_collection/uk_bin_collection/councils/WychavonDistrictCouncil.py,sha256=Y
|
|
329
329
|
uk_bin_collection/uk_bin_collection/councils/WyreCouncil.py,sha256=QpCkmRSQmJo0RLsjXoCYPDcoxuDzG_00qNV0AHTDmXo,3000
|
330
330
|
uk_bin_collection/uk_bin_collection/councils/WyreForestDistrictCouncil.py,sha256=3b7WzBXdYub6j13sqDL3jlqgICKmNyQaF4KxRxOMHWk,2000
|
331
331
|
uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py,sha256=I2kBYMlsD4bIdsvmoSzBjJAvTTi6yPfJa8xjJx1ys2w,1490
|
332
|
-
uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py,sha256=
|
332
|
+
uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py,sha256=QD4v4xpsEE0QheR_fGaNOIRMc2FatcUfKkkhAhseyVU,1159
|
333
333
|
uk_bin_collection/uk_bin_collection/create_new_council.py,sha256=m-IhmWmeWQlFsTZC4OxuFvtw5ZtB8EAJHxJTH4O59lQ,1536
|
334
334
|
uk_bin_collection/uk_bin_collection/get_bin_data.py,sha256=YvmHfZqanwrJ8ToGch34x-L-7yPe31nB_x77_Mgl_vo,4545
|
335
|
-
uk_bin_collection-0.
|
336
|
-
uk_bin_collection-0.
|
337
|
-
uk_bin_collection-0.
|
338
|
-
uk_bin_collection-0.
|
339
|
-
uk_bin_collection-0.
|
335
|
+
uk_bin_collection-0.146.0.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
|
336
|
+
uk_bin_collection-0.146.0.dist-info/METADATA,sha256=HDx_ThJ-YHiak_qHaFdhBG4hOT-xeil2DR5LO4V_kAA,19858
|
337
|
+
uk_bin_collection-0.146.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
338
|
+
uk_bin_collection-0.146.0.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
|
339
|
+
uk_bin_collection-0.146.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
{uk_bin_collection-0.145.0.dist-info → uk_bin_collection-0.146.0.dist-info}/entry_points.txt
RENAMED
File without changes
|