uk_bin_collection 0.134.3__py3-none-any.whl → 0.135.1__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/tests/check_selenium_url_in_input.json.py +209 -0
- uk_bin_collection/tests/input.json +49 -4
- uk_bin_collection/uk_bin_collection/councils/AmberValleyBoroughCouncil.py +60 -0
- uk_bin_collection/uk_bin_collection/councils/BolsoverCouncil.py +298 -0
- uk_bin_collection/uk_bin_collection/councils/CherwellDistrictCouncil.py +75 -0
- uk_bin_collection/uk_bin_collection/councils/ConwyCountyBorough.py +11 -3
- uk_bin_collection/uk_bin_collection/councils/CotswoldDistrictCouncil.py +3 -5
- uk_bin_collection/uk_bin_collection/councils/DerbyshireDalesDistrictCouncil.py +54 -50
- uk_bin_collection/uk_bin_collection/councils/EpsomandEwellBoroughCouncil.py +86 -0
- uk_bin_collection/uk_bin_collection/councils/GloucesterCityCouncil.py +1 -1
- uk_bin_collection/uk_bin_collection/councils/LeedsCityCouncil.py +2 -1
- uk_bin_collection/uk_bin_collection/councils/MiddlesbroughCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/NeathPortTalbotCouncil.py +2 -0
- uk_bin_collection/uk_bin_collection/councils/RedcarandClevelandCouncil.py +108 -0
- uk_bin_collection/uk_bin_collection/councils/RunnymedeBoroughCouncil.py +54 -0
- uk_bin_collection/uk_bin_collection/councils/SunderlandCityCouncil.py +21 -15
- uk_bin_collection/uk_bin_collection/councils/TendringDistrictCouncil.py +1 -1
- uk_bin_collection/uk_bin_collection/councils/TorridgeDistrictCouncil.py +1 -35
- {uk_bin_collection-0.134.3.dist-info → uk_bin_collection-0.135.1.dist-info}/METADATA +1 -1
- {uk_bin_collection-0.134.3.dist-info → uk_bin_collection-0.135.1.dist-info}/RECORD +23 -15
- {uk_bin_collection-0.134.3.dist-info → uk_bin_collection-0.135.1.dist-info}/LICENSE +0 -0
- {uk_bin_collection-0.134.3.dist-info → uk_bin_collection-0.135.1.dist-info}/WHEEL +0 -0
- {uk_bin_collection-0.134.3.dist-info → uk_bin_collection-0.135.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,209 @@
|
|
1
|
+
import json
|
2
|
+
import requests
|
3
|
+
import sys
|
4
|
+
import base64
|
5
|
+
from tabulate import tabulate
|
6
|
+
|
7
|
+
|
8
|
+
def get_council_files(repo, branch):
|
9
|
+
"""
|
10
|
+
Get a list of all .py council files in the 'councils' directory
|
11
|
+
from the GitHub repo (via API), plus a mapping from council name
|
12
|
+
to the file's GitHub 'download_url' or 'contents_url'.
|
13
|
+
"""
|
14
|
+
url = f"https://api.github.com/repos/{repo}/contents/uk_bin_collection/uk_bin_collection/councils?ref={branch}"
|
15
|
+
print(f"Fetching council files from: {url}")
|
16
|
+
response = requests.get(url, headers={"Accept": "application/vnd.github.v3+json"})
|
17
|
+
if response.status_code == 200:
|
18
|
+
data = response.json()
|
19
|
+
# data should be a list of items in that folder
|
20
|
+
if isinstance(data, list):
|
21
|
+
councils = {}
|
22
|
+
for item in data:
|
23
|
+
name = item["name"]
|
24
|
+
if name.endswith(".py"):
|
25
|
+
council_name = name.replace(".py", "")
|
26
|
+
councils[council_name] = item["url"] # 'url' gives API-based content URL
|
27
|
+
return councils
|
28
|
+
else:
|
29
|
+
raise ValueError("Expected a list from the GitHub response but got something else.")
|
30
|
+
else:
|
31
|
+
print(f"Failed to fetch councils from files: {response.content}")
|
32
|
+
return {}
|
33
|
+
|
34
|
+
|
35
|
+
def get_council_file_content(api_url):
|
36
|
+
"""
|
37
|
+
Given the API URL for a file in GitHub, fetch its content (decoded).
|
38
|
+
The 'download_url' is direct raw, but the 'url' is the API URL for the content.
|
39
|
+
We'll use the latter, decode base64, and return the text.
|
40
|
+
"""
|
41
|
+
# Example: https://api.github.com/repos/robbrad/UKBinCollectionData/contents/...
|
42
|
+
response = requests.get(api_url, headers={"Accept": "application/vnd.github.v3+json"})
|
43
|
+
if response.status_code == 200:
|
44
|
+
file_json = response.json()
|
45
|
+
# file_json["content"] is base64-encoded
|
46
|
+
content = file_json.get("content", "")
|
47
|
+
decoded = base64.b64decode(content).decode("utf-8")
|
48
|
+
return decoded
|
49
|
+
else:
|
50
|
+
print(f"Failed to fetch file content: {response.content}")
|
51
|
+
return ""
|
52
|
+
|
53
|
+
|
54
|
+
def get_input_json_data(repo, branch):
|
55
|
+
"""
|
56
|
+
Fetch the entire input.json from GitHub and return it as a Python dict.
|
57
|
+
"""
|
58
|
+
url = f"https://api.github.com/repos/{repo}/contents/uk_bin_collection/tests/input.json?ref={branch}"
|
59
|
+
print(f"Fetching input JSON from: {url}")
|
60
|
+
response = requests.get(url, headers={"Accept": "application/vnd.github.v3+json"})
|
61
|
+
if response.status_code == 200:
|
62
|
+
try:
|
63
|
+
file_json = response.json()
|
64
|
+
content = file_json.get("content", "")
|
65
|
+
decoded = base64.b64decode(content).decode("utf-8")
|
66
|
+
data = json.loads(decoded)
|
67
|
+
return data
|
68
|
+
except json.JSONDecodeError as e:
|
69
|
+
print(f"JSON decoding error: {e}")
|
70
|
+
raise
|
71
|
+
else:
|
72
|
+
print(f"Failed to fetch input JSON: {response.content}")
|
73
|
+
return {}
|
74
|
+
|
75
|
+
|
76
|
+
def council_needs_update(council_name, json_data, council_file_content):
|
77
|
+
"""
|
78
|
+
Check if the given council needs an update:
|
79
|
+
- We say 'needs update' if 'web_driver' is missing in the JSON,
|
80
|
+
BUT the script uses 'create_webdriver' in code.
|
81
|
+
"""
|
82
|
+
# If the council isn't in the JSON at all, we can't do the check
|
83
|
+
# (or we assume no JSON data => no web_driver?).
|
84
|
+
council_data = json_data.get(council_name, {})
|
85
|
+
web_driver_missing = ("web_driver" not in council_data)
|
86
|
+
create_webdriver_present = ("create_webdriver" in council_file_content)
|
87
|
+
|
88
|
+
return web_driver_missing and create_webdriver_present
|
89
|
+
|
90
|
+
|
91
|
+
def compare_councils(file_council_dict, json_data):
|
92
|
+
"""
|
93
|
+
Compare councils in files vs councils in JSON, check for needs_update,
|
94
|
+
and gather everything for final tabulation.
|
95
|
+
|
96
|
+
Returns:
|
97
|
+
- all_councils_data: dict keyed by council name:
|
98
|
+
{
|
99
|
+
"in_files": bool,
|
100
|
+
"in_json": bool,
|
101
|
+
"discrepancies_count": int,
|
102
|
+
"needs_update": bool
|
103
|
+
}
|
104
|
+
- any_discrepancies_found: bool (if any differences in in_files vs in_json)
|
105
|
+
- any_updates_needed: bool (if any council needs update)
|
106
|
+
"""
|
107
|
+
file_councils = set(file_council_dict.keys())
|
108
|
+
json_councils = set(json_data.keys())
|
109
|
+
|
110
|
+
all_councils = file_councils.union(json_councils)
|
111
|
+
all_council_data = {}
|
112
|
+
|
113
|
+
any_discrepancies_found = False
|
114
|
+
any_updates_needed = False
|
115
|
+
|
116
|
+
for council in all_councils:
|
117
|
+
in_files = council in file_councils
|
118
|
+
in_json = council in json_councils
|
119
|
+
# Count how many are False
|
120
|
+
discrepancies_count = [in_files, in_json].count(False)
|
121
|
+
|
122
|
+
# If the file is in the repo, fetch its content for checking
|
123
|
+
content = ""
|
124
|
+
if in_files:
|
125
|
+
file_api_url = file_council_dict[council]
|
126
|
+
content = get_council_file_content(file_api_url)
|
127
|
+
|
128
|
+
# Evaluate "needs_update" only if the file is in place
|
129
|
+
# (If there's no file, you might consider it "False" by default)
|
130
|
+
needs_update = False
|
131
|
+
if in_files:
|
132
|
+
needs_update = council_needs_update(council, json_data, content)
|
133
|
+
|
134
|
+
if discrepancies_count > 0:
|
135
|
+
any_discrepancies_found = True
|
136
|
+
if needs_update:
|
137
|
+
any_updates_needed = True
|
138
|
+
|
139
|
+
all_council_data[council] = {
|
140
|
+
"in_files": in_files,
|
141
|
+
"in_json": in_json,
|
142
|
+
"discrepancies_count": discrepancies_count,
|
143
|
+
"needs_update": needs_update,
|
144
|
+
}
|
145
|
+
|
146
|
+
return all_council_data, any_discrepancies_found, any_updates_needed
|
147
|
+
|
148
|
+
|
149
|
+
def main(repo="robbrad/UKBinCollectionData", branch="master"):
|
150
|
+
print(f"Starting comparison for repo: {repo}, branch: {branch}")
|
151
|
+
|
152
|
+
# 1) Get council file data (dict: { council_name: content_api_url, ... })
|
153
|
+
file_council_dict = get_council_files(repo, branch)
|
154
|
+
|
155
|
+
# 2) Get the entire JSON data
|
156
|
+
json_data = get_input_json_data(repo, branch)
|
157
|
+
|
158
|
+
# 3) Compare
|
159
|
+
(
|
160
|
+
all_councils_data,
|
161
|
+
discrepancies_found,
|
162
|
+
updates_needed,
|
163
|
+
) = compare_councils(file_council_dict, json_data)
|
164
|
+
|
165
|
+
# 4) Print results
|
166
|
+
table_data = []
|
167
|
+
headers = ["Council Name", "In Files", "In JSON", "Needs Update?", "Discrepancies"]
|
168
|
+
# Sort councils so that ones with the highest discrepancy or update appear first
|
169
|
+
# Then alphabetical if tie:
|
170
|
+
def sort_key(item):
|
171
|
+
# item is (council_name, data_dict)
|
172
|
+
return (
|
173
|
+
item[1]["needs_update"], # sort by needs_update (False < True)
|
174
|
+
item[1]["discrepancies_count"], # then by discrepancies
|
175
|
+
item[0], # then by name
|
176
|
+
)
|
177
|
+
|
178
|
+
# We'll sort descending for "needs_update", so invert the boolean or reverse later
|
179
|
+
sorted_councils = sorted(
|
180
|
+
all_councils_data.items(),
|
181
|
+
key=lambda x: (not x[1]["needs_update"], x[1]["discrepancies_count"], x[0])
|
182
|
+
)
|
183
|
+
|
184
|
+
for council, presence in sorted_councils:
|
185
|
+
row = [
|
186
|
+
council,
|
187
|
+
"✔" if presence["in_files"] else "✘",
|
188
|
+
"✔" if presence["in_json"] else "✘",
|
189
|
+
"Yes" if presence["needs_update"] else "No",
|
190
|
+
presence["discrepancies_count"],
|
191
|
+
]
|
192
|
+
table_data.append(row)
|
193
|
+
|
194
|
+
print(tabulate(table_data, headers=headers, tablefmt="grid"))
|
195
|
+
|
196
|
+
# 5) Determine exit code:
|
197
|
+
# If any discrepancies OR any council needs updates -> fail
|
198
|
+
if discrepancies_found or updates_needed:
|
199
|
+
print("Some discrepancies found or updates are needed. Failing workflow.")
|
200
|
+
sys.exit(1)
|
201
|
+
else:
|
202
|
+
print("No discrepancies found and no updates needed. Workflow successful.")
|
203
|
+
|
204
|
+
|
205
|
+
if __name__ == "__main__":
|
206
|
+
# Optional CLI args: python script.py <repo> <branch>
|
207
|
+
repo_arg = sys.argv[1] if len(sys.argv) > 1 else "robbrad/UKBinCollectionData"
|
208
|
+
branch_arg = sys.argv[2] if len(sys.argv) > 2 else "master"
|
209
|
+
main(repo_arg, branch_arg)
|
@@ -18,6 +18,12 @@
|
|
18
18
|
"wiki_name": "Adur and Worthing Councils",
|
19
19
|
"wiki_note": "Replace XXXXXXXX with your UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it."
|
20
20
|
},
|
21
|
+
"AmberValleyBoroughCouncil": {
|
22
|
+
"url": "https://ambervalley.gov.uk",
|
23
|
+
"uprn": "100030026621",
|
24
|
+
"wiki_name": "Amber Valley Borough Council",
|
25
|
+
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
|
26
|
+
},
|
21
27
|
"AntrimAndNewtonabbeyCouncil": {
|
22
28
|
"url": "https://antrimandnewtownabbey.gov.uk/residents/bins-recycling/bins-schedule/?Id=643",
|
23
29
|
"wiki_command_url_override": "https://antrimandnewtownabbey.gov.uk/residents/bins-recycling/bins-schedule/?Id=XXXX",
|
@@ -197,6 +203,12 @@
|
|
197
203
|
"wiki_name": "Blaenau Gwent County Borough Council",
|
198
204
|
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
|
199
205
|
},
|
206
|
+
"BolsoverCouncil": {
|
207
|
+
"url": "https://bolsover.gov.uk",
|
208
|
+
"uprn": "100030066827",
|
209
|
+
"wiki_name": "Bolsover Council",
|
210
|
+
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
|
211
|
+
},
|
200
212
|
"BoltonCouncil": {
|
201
213
|
"postcode": "BL1 5PQ",
|
202
214
|
"skip_get_url": true,
|
@@ -391,6 +403,12 @@
|
|
391
403
|
"wiki_name": "Cheltenham Borough Council",
|
392
404
|
"wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search)."
|
393
405
|
},
|
406
|
+
"CherwellDistrictCouncil": {
|
407
|
+
"uprn": "100121292407",
|
408
|
+
"url": "https://www.cherwell.gov.uk",
|
409
|
+
"wiki_name": "Cherwell District Council",
|
410
|
+
"wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
|
411
|
+
},
|
394
412
|
"CheshireEastCouncil": {
|
395
413
|
"url": "https://online.cheshireeast.gov.uk/MyCollectionDay/SearchByAjax/GetBartecJobList?uprn=100012791226&onelineaddress=3%20COBBLERS%20YARD,%20SK9%207DZ&_=1689413260149",
|
396
414
|
"wiki_command_url_override": "https://online.cheshireeast.gov.uk/MyCollectionDay/SearchByAjax/GetBartecJobList?uprn=XXXXXXXX&onelineaddress=XXXXXXXX&_=1689413260149",
|
@@ -440,11 +458,10 @@
|
|
440
458
|
"wiki_note": "Pass the house name/number in the house number parameter, wrapped in double quotes."
|
441
459
|
},
|
442
460
|
"ConwyCountyBorough": {
|
443
|
-
"postcode": "LL30 2DF",
|
444
461
|
"uprn": "100100429249",
|
445
|
-
"url": "https://www.conwy.gov.uk
|
462
|
+
"url": "https://www.conwy.gov.uk",
|
446
463
|
"wiki_name": "Conwy County Borough Council",
|
447
|
-
"wiki_note": "
|
464
|
+
"wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
|
448
465
|
},
|
449
466
|
"CopelandBoroughCouncil": {
|
450
467
|
"uprn": "100110734613",
|
@@ -535,7 +552,6 @@
|
|
535
552
|
"skip_get_url": true,
|
536
553
|
"uprn": "10070102161",
|
537
554
|
"url": "https://www.derbyshiredales.gov.uk/",
|
538
|
-
"web_driver": "http://selenium:4444",
|
539
555
|
"wiki_name": "Derbyshire Dales District Council",
|
540
556
|
"wiki_note": "Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search)."
|
541
557
|
},
|
@@ -709,6 +725,12 @@
|
|
709
725
|
"wiki_name": "Epping Forest District Council",
|
710
726
|
"wiki_note": "Replace the postcode in the URL with your own."
|
711
727
|
},
|
728
|
+
"EpsomandEwellBoroughCouncil": {
|
729
|
+
"uprn": "100061349083",
|
730
|
+
"url": "https://www.epsom-ewell.gov.uk",
|
731
|
+
"wiki_name": "Epsom and Ewell Borough Council",
|
732
|
+
"wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
|
733
|
+
},
|
712
734
|
"ErewashBoroughCouncil": {
|
713
735
|
"skip_get_url": true,
|
714
736
|
"uprn": "10003582028",
|
@@ -1162,6 +1184,14 @@
|
|
1162
1184
|
"wiki_name": "Mid Devon Council",
|
1163
1185
|
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
|
1164
1186
|
},
|
1187
|
+
"MiddlesbroughCouncil": {
|
1188
|
+
"house_number": "12 Constantine Court Park Road North, Middlesbrough",
|
1189
|
+
"skip_get_url": true,
|
1190
|
+
"url": "https://www.midsussex.gov.uk/waste-recycling/bin-collection/",
|
1191
|
+
"web_driver": "http://selenium:4444",
|
1192
|
+
"wiki_name": "Middlesbrough Council",
|
1193
|
+
"wiki_note": "Pass the entire address without postcode as it appears when you type it on the website. This parser requires a Selenium webdriver."
|
1194
|
+
},
|
1165
1195
|
"MidSuffolkDistrictCouncil": {
|
1166
1196
|
"house_number": "Monday",
|
1167
1197
|
"postcode": "Week 2",
|
@@ -1457,6 +1487,14 @@
|
|
1457
1487
|
"wiki_name": "Reading Borough Council",
|
1458
1488
|
"wiki_note": "Replace XXXXXXXX with your property's UPRN."
|
1459
1489
|
},
|
1490
|
+
"RedcarandClevelandCouncil": {
|
1491
|
+
"house_number": "11",
|
1492
|
+
"postcode": "TS10 2RE",
|
1493
|
+
"skip_get_url": true,
|
1494
|
+
"url": "https://www.redcar-cleveland.gov.uk",
|
1495
|
+
"wiki_name": "Redcar and Cleveland Council",
|
1496
|
+
"wiki_note": "Pass the house name/number and postcode in their respective parameters"
|
1497
|
+
},
|
1460
1498
|
"RedditchBoroughCouncil": {
|
1461
1499
|
"uprn": "10094557691",
|
1462
1500
|
"url": "https://redditchbc.gov.uk",
|
@@ -1531,6 +1569,13 @@
|
|
1531
1569
|
"wiki_name": "Rugby Borough Council",
|
1532
1570
|
"wiki_note": "Provide your UPRN and postcode. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search)."
|
1533
1571
|
},
|
1572
|
+
"RunnymedeBoroughCouncil": {
|
1573
|
+
"skip_get_url": true,
|
1574
|
+
"uprn": "100061483636",
|
1575
|
+
"url": "https://www.runnymede.gov.uk/",
|
1576
|
+
"wiki_name": "Runnymede Borough Council",
|
1577
|
+
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
|
1578
|
+
},
|
1534
1579
|
"RushcliffeBoroughCouncil": {
|
1535
1580
|
"postcode": "NG13 8TZ",
|
1536
1581
|
"skip_get_url": true,
|
@@ -0,0 +1,60 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
|
3
|
+
import requests
|
4
|
+
|
5
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
6
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
7
|
+
|
8
|
+
|
9
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
10
|
+
class CouncilClass(AbstractGetBinDataClass):
|
11
|
+
"""
|
12
|
+
Concrete classes have to implement all abstract operations of the
|
13
|
+
base class. They can also override some operations with a default
|
14
|
+
implementation.
|
15
|
+
"""
|
16
|
+
|
17
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
18
|
+
|
19
|
+
user_uprn = kwargs.get("uprn")
|
20
|
+
check_uprn(user_uprn)
|
21
|
+
bindata = {"bins": []}
|
22
|
+
|
23
|
+
WASTE_TYPES_DATE_KEY = {
|
24
|
+
"REFUSE": "refuseNextDate",
|
25
|
+
"RECYCLING": "recyclingNextDate",
|
26
|
+
"GREEN": "greenNextDate",
|
27
|
+
"COMMUNAL REFUSE": "communalRefNextDate",
|
28
|
+
"COMMUNAL RECYCLING": "communalRycNextDate",
|
29
|
+
}
|
30
|
+
|
31
|
+
URI = f"https://info.ambervalley.gov.uk/WebServices/AVBCFeeds/WasteCollectionJSON.asmx/GetCollectionDetailsByUPRN?uprn={user_uprn}"
|
32
|
+
|
33
|
+
# Make the GET request
|
34
|
+
response = requests.get(URI)
|
35
|
+
|
36
|
+
# Parse the JSON response
|
37
|
+
bin_collection = response.json()
|
38
|
+
|
39
|
+
# print(bin_collection)
|
40
|
+
|
41
|
+
for bin, datge_key in WASTE_TYPES_DATE_KEY.items():
|
42
|
+
date_ = datetime.strptime(
|
43
|
+
bin_collection[datge_key], "%Y-%m-%dT%H:%M:%S"
|
44
|
+
).strftime(date_format)
|
45
|
+
if date_ == "01/01/1":
|
46
|
+
continue
|
47
|
+
elif date_ == "01/01/1900":
|
48
|
+
continue
|
49
|
+
|
50
|
+
dict_data = {
|
51
|
+
"type": bin,
|
52
|
+
"collectionDate": date_,
|
53
|
+
}
|
54
|
+
bindata["bins"].append(dict_data)
|
55
|
+
|
56
|
+
bindata["bins"].sort(
|
57
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
|
58
|
+
)
|
59
|
+
|
60
|
+
return bindata
|
@@ -0,0 +1,298 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
import requests
|
4
|
+
|
5
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
6
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
7
|
+
|
8
|
+
|
9
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
10
|
+
class CouncilClass(AbstractGetBinDataClass):
|
11
|
+
"""
|
12
|
+
Concrete classes have to implement all abstract operations of the
|
13
|
+
base class. They can also override some operations with a default
|
14
|
+
implementation.
|
15
|
+
"""
|
16
|
+
|
17
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
18
|
+
|
19
|
+
user_uprn = kwargs.get("uprn")
|
20
|
+
check_uprn(user_uprn)
|
21
|
+
bindata = {"bins": []}
|
22
|
+
|
23
|
+
SESSION_URL = "https://selfservice.bolsover.gov.uk/authapi/isauthenticated?uri=https%253A%252F%252Fselfservice.bolsover.gov.uk%252Fservice%252FCheck_your_Bin_Day&hostname=selfservice.bolsover.gov.uk&withCredentials=true"
|
24
|
+
|
25
|
+
API_URL = "https://selfservice.bolsover.gov.uk/apibroker/runLookup"
|
26
|
+
|
27
|
+
data = {
|
28
|
+
"formValues": {"Bin Collection": {"uprnLoggedIn": {"value": user_uprn}}},
|
29
|
+
}
|
30
|
+
headers = {
|
31
|
+
"Content-Type": "application/json",
|
32
|
+
"Accept": "application/json",
|
33
|
+
"User-Agent": "Mozilla/5.0",
|
34
|
+
"X-Requested-With": "XMLHttpRequest",
|
35
|
+
"Referer": "https://selfservice.bolsover.gov.uk/fillform/?iframe_id=fillform-frame-1&db_id=",
|
36
|
+
}
|
37
|
+
s = requests.session()
|
38
|
+
r = s.get(SESSION_URL)
|
39
|
+
r.raise_for_status()
|
40
|
+
session_data = r.json()
|
41
|
+
sid = session_data["auth-session"]
|
42
|
+
params = {
|
43
|
+
"id": "6023d37e037c3",
|
44
|
+
"repeat_against": "",
|
45
|
+
"noRetry": "true",
|
46
|
+
"getOnlyTokens": "undefined",
|
47
|
+
"log_id": "",
|
48
|
+
"app_name": "AF-Renderer::Self",
|
49
|
+
# unix_timestamp
|
50
|
+
"_": str(int(time.time() * 1000)),
|
51
|
+
"sid": sid,
|
52
|
+
}
|
53
|
+
|
54
|
+
r = s.post(API_URL, json=data, headers=headers, params=params)
|
55
|
+
r.raise_for_status()
|
56
|
+
|
57
|
+
data = r.json()
|
58
|
+
rows_data = data["integration"]["transformed"]["rows_data"]["0"]
|
59
|
+
if not isinstance(rows_data, dict):
|
60
|
+
raise ValueError("Invalid data returned from API")
|
61
|
+
|
62
|
+
# print(rows_data)
|
63
|
+
|
64
|
+
route = rows_data["Route"]
|
65
|
+
|
66
|
+
# print(route)
|
67
|
+
|
68
|
+
def get_route_number(route):
|
69
|
+
if route[:2] == "Mo":
|
70
|
+
return 0
|
71
|
+
elif route[:2] == "Tu":
|
72
|
+
return 1
|
73
|
+
elif route[:2] == "We":
|
74
|
+
return 2
|
75
|
+
elif route[:2] == "Th":
|
76
|
+
return 3
|
77
|
+
elif route[:2] == "Fr":
|
78
|
+
return 4
|
79
|
+
else:
|
80
|
+
return None # Default case if none of the conditions match
|
81
|
+
|
82
|
+
dayOfCollectionAsNumber = get_route_number(route)
|
83
|
+
# print(dayOfCollectionAsNumber)
|
84
|
+
|
85
|
+
def calculate_collection_date(
|
86
|
+
dayOfCollectionAsNumber,
|
87
|
+
currentDayAsNumber,
|
88
|
+
today,
|
89
|
+
dayDiffPlus,
|
90
|
+
dayDiffMinus,
|
91
|
+
):
|
92
|
+
if dayOfCollectionAsNumber == currentDayAsNumber:
|
93
|
+
return today
|
94
|
+
elif dayOfCollectionAsNumber > currentDayAsNumber:
|
95
|
+
return today + timedelta(days=dayDiffPlus)
|
96
|
+
else:
|
97
|
+
return today + timedelta(days=dayDiffMinus)
|
98
|
+
|
99
|
+
# Example usage
|
100
|
+
today = datetime.today() # Current date
|
101
|
+
currentDayAsNumber = today.weekday()
|
102
|
+
dayDiffPlus = dayOfCollectionAsNumber - currentDayAsNumber
|
103
|
+
dayDiffMinus = dayOfCollectionAsNumber - currentDayAsNumber + 7
|
104
|
+
|
105
|
+
week1 = calculate_collection_date(
|
106
|
+
dayOfCollectionAsNumber,
|
107
|
+
currentDayAsNumber,
|
108
|
+
today,
|
109
|
+
dayDiffPlus,
|
110
|
+
dayDiffMinus,
|
111
|
+
)
|
112
|
+
week2 = week1 + timedelta(days=7)
|
113
|
+
week3 = week2 + timedelta(days=7)
|
114
|
+
week4 = week3 + timedelta(days=7)
|
115
|
+
|
116
|
+
# print(week1.strftime(date_format))
|
117
|
+
# print(week2.strftime(date_format))
|
118
|
+
# print(week3.strftime(date_format))
|
119
|
+
# print(week4.strftime(date_format))
|
120
|
+
|
121
|
+
greenSusStart = datetime.strptime("2024-11-08", "%Y-%m-%d")
|
122
|
+
greenSusEnd = datetime.strptime("2025-03-18", "%Y-%m-%d")
|
123
|
+
|
124
|
+
def is_within_green_sus(dtDay0, greenSusStart, greenSusEnd):
|
125
|
+
return "Yes" if greenSusStart <= dtDay0 < greenSusEnd else "No"
|
126
|
+
|
127
|
+
week1InSus = is_within_green_sus(week1, greenSusStart, greenSusEnd)
|
128
|
+
week2InSus = is_within_green_sus(week2, greenSusStart, greenSusEnd)
|
129
|
+
week3InSus = is_within_green_sus(week3, greenSusStart, greenSusEnd)
|
130
|
+
week4InSus = is_within_green_sus(week4, greenSusStart, greenSusEnd)
|
131
|
+
|
132
|
+
# print(week1InSus)
|
133
|
+
# print(week2InSus)
|
134
|
+
# print(week3InSus)
|
135
|
+
# print(week4InSus)
|
136
|
+
|
137
|
+
WeekBlack = rows_data["WeekBlack"]
|
138
|
+
WeekBandG = rows_data["WeekBandG"]
|
139
|
+
|
140
|
+
if WeekBlack == "1":
|
141
|
+
WeekBandG = ""
|
142
|
+
if WeekBandG == "1":
|
143
|
+
WeekBlack = ""
|
144
|
+
|
145
|
+
def determine_bin_collection_week1(
|
146
|
+
txtBlack, txtBurgGreen, dtDay0, today, week1InSus
|
147
|
+
):
|
148
|
+
# Check for empty values
|
149
|
+
if txtBlack == "" and txtBurgGreen == "":
|
150
|
+
return ""
|
151
|
+
|
152
|
+
# Black Bin Collection
|
153
|
+
if txtBlack == "1" and dtDay0 >= today:
|
154
|
+
return "Black Bin"
|
155
|
+
|
156
|
+
# Burgundy Bin Collection
|
157
|
+
if txtBurgGreen == "1" and dtDay0 > today:
|
158
|
+
if week1InSus == "Yes":
|
159
|
+
return "Burgundy Bin"
|
160
|
+
elif week1InSus == "No":
|
161
|
+
return "Burgundy Bin & Green Bin"
|
162
|
+
|
163
|
+
# Default cases based on week1InSus
|
164
|
+
if txtBlack == "" and dtDay0 >= today:
|
165
|
+
if week1InSus == "Yes":
|
166
|
+
return "Burgundy Bin"
|
167
|
+
elif week1InSus == "No":
|
168
|
+
return "Burgundy Bin & Green Bin"
|
169
|
+
|
170
|
+
return "" # Default empty case
|
171
|
+
|
172
|
+
def determine_bin_collection_week2(
|
173
|
+
txtBlack, txtBurgGreen, dtDay7, today, week2InSus
|
174
|
+
):
|
175
|
+
# Check for empty values
|
176
|
+
if txtBlack == "" and txtBurgGreen == "":
|
177
|
+
return ""
|
178
|
+
|
179
|
+
# Black Bin Collection
|
180
|
+
if txtBlack == "" and dtDay7 >= today:
|
181
|
+
return "Black Bin"
|
182
|
+
|
183
|
+
# Burgundy Bin Collection (week2InSus check)
|
184
|
+
if txtBurgGreen == "1" and dtDay7 > today:
|
185
|
+
if week2InSus == "Yes":
|
186
|
+
return "Burgundy Bin"
|
187
|
+
elif week2InSus == "No":
|
188
|
+
return "Burgundy Bin & Green Bin"
|
189
|
+
|
190
|
+
# Burgundy Bin Collection for txtBlack = '1'
|
191
|
+
if txtBlack == "1" and dtDay7 >= today:
|
192
|
+
if week2InSus == "Yes":
|
193
|
+
return "Burgundy Bin"
|
194
|
+
elif week2InSus == "No":
|
195
|
+
return "Burgundy Bin & Green Bin"
|
196
|
+
|
197
|
+
return "" # Default empty case
|
198
|
+
|
199
|
+
def determine_bin_collection_week3(
|
200
|
+
txtBlack, txtBurgGreen, dtDay14, today, week3InSus
|
201
|
+
):
|
202
|
+
# Check for empty values
|
203
|
+
if txtBlack == "" and txtBurgGreen == "":
|
204
|
+
return ""
|
205
|
+
|
206
|
+
# Black Bin Collection
|
207
|
+
if txtBlack == "1" and dtDay14 >= today:
|
208
|
+
return "Black Bin"
|
209
|
+
|
210
|
+
# Burgundy Bin Collection (week3InSus check)
|
211
|
+
if txtBurgGreen == "1" and dtDay14 > today:
|
212
|
+
if week3InSus == "Yes":
|
213
|
+
return "Burgundy Bin"
|
214
|
+
elif week3InSus == "No":
|
215
|
+
return "Burgundy Bin & Green Bin"
|
216
|
+
|
217
|
+
# Burgundy Bin Collection for txtBlack = ''
|
218
|
+
if txtBlack == "" and dtDay14 >= today:
|
219
|
+
if week3InSus == "Yes":
|
220
|
+
return "Burgundy Bin"
|
221
|
+
elif week3InSus == "No":
|
222
|
+
return "Burgundy Bin & Green Bin"
|
223
|
+
|
224
|
+
return "" # Default empty case
|
225
|
+
|
226
|
+
def determine_bin_collection_week4(
|
227
|
+
txtBlack, txtBurgGreen, dtDay21, today, week4InSus
|
228
|
+
):
|
229
|
+
# Check for empty values
|
230
|
+
if txtBlack == "" and txtBurgGreen == "":
|
231
|
+
return ""
|
232
|
+
|
233
|
+
# Black Bin Collection
|
234
|
+
if txtBlack == "" and dtDay21 >= today:
|
235
|
+
return "Black Bin"
|
236
|
+
|
237
|
+
# Burgundy Bin Collection (week4InSus check)
|
238
|
+
if txtBurgGreen == "1" and dtDay21 > today:
|
239
|
+
if week4InSus == "Yes":
|
240
|
+
return "Burgundy Bin"
|
241
|
+
elif week4InSus == "No":
|
242
|
+
return "Burgundy Bin & Green Bin"
|
243
|
+
|
244
|
+
# Burgundy Bin Collection for txtBlack = '1'
|
245
|
+
if txtBlack == "1" and dtDay21 >= today:
|
246
|
+
if week4InSus == "Yes":
|
247
|
+
return "Burgundy Bin"
|
248
|
+
elif week4InSus == "No":
|
249
|
+
return "Burgundy Bin & Green Bin"
|
250
|
+
|
251
|
+
return "" # Default empty case
|
252
|
+
|
253
|
+
week1Text = determine_bin_collection_week1(
|
254
|
+
WeekBlack, WeekBandG, week1, today, week1InSus
|
255
|
+
)
|
256
|
+
week2Text = determine_bin_collection_week2(
|
257
|
+
WeekBlack, WeekBandG, week2, today, week2InSus
|
258
|
+
)
|
259
|
+
week3Text = determine_bin_collection_week3(
|
260
|
+
WeekBlack, WeekBandG, week3, today, week3InSus
|
261
|
+
)
|
262
|
+
week4Text = determine_bin_collection_week4(
|
263
|
+
WeekBlack, WeekBandG, week4, today, week4InSus
|
264
|
+
)
|
265
|
+
|
266
|
+
# print(week1Text)
|
267
|
+
# print(week2Text)
|
268
|
+
# print(week3Text)
|
269
|
+
# print(week4Text)
|
270
|
+
|
271
|
+
week_data = [
|
272
|
+
(week1Text, week1),
|
273
|
+
(week2Text, week2),
|
274
|
+
(week3Text, week3),
|
275
|
+
(week4Text, week4),
|
276
|
+
]
|
277
|
+
|
278
|
+
# print(week_data)
|
279
|
+
|
280
|
+
# Iterate through the array
|
281
|
+
for week_text, week_date in week_data:
|
282
|
+
# Check if '&' exists and split
|
283
|
+
if "&" in week_text:
|
284
|
+
split_texts = [text.strip() for text in week_text.split("&")]
|
285
|
+
for text in split_texts:
|
286
|
+
dict_data = {
|
287
|
+
"type": text,
|
288
|
+
"collectionDate": week_date.strftime(date_format),
|
289
|
+
}
|
290
|
+
bindata["bins"].append(dict_data)
|
291
|
+
else:
|
292
|
+
dict_data = {
|
293
|
+
"type": week_text,
|
294
|
+
"collectionDate": week_date.strftime(date_format),
|
295
|
+
}
|
296
|
+
bindata["bins"].append(dict_data)
|
297
|
+
|
298
|
+
return bindata
|