uk_bin_collection 0.74.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/README.rst +0 -0
- uk_bin_collection/tests/council_feature_input_parity.py +79 -0
- uk_bin_collection/tests/features/environment.py +7 -0
- uk_bin_collection/tests/features/validate_council_outputs.feature +767 -0
- uk_bin_collection/tests/input.json +1077 -0
- uk_bin_collection/tests/output.schema +41 -0
- uk_bin_collection/tests/step_defs/step_helpers/file_handler.py +46 -0
- uk_bin_collection/tests/step_defs/test_validate_council.py +87 -0
- uk_bin_collection/tests/test_collect_data.py +104 -0
- uk_bin_collection/tests/test_common_functions.py +342 -0
- uk_bin_collection/uk_bin_collection/collect_data.py +133 -0
- uk_bin_collection/uk_bin_collection/common.py +292 -0
- uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py +43 -0
- uk_bin_collection/uk_bin_collection/councils/ArunCouncil.py +97 -0
- uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py +69 -0
- uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py +51 -0
- uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py +180 -0
- uk_bin_collection/uk_bin_collection/councils/BarnsleyMBCouncil.py +109 -0
- uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py +72 -0
- uk_bin_collection/uk_bin_collection/councils/BathAndNorthEastSomersetCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/BedfordBoroughCouncil.py +49 -0
- uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py +70 -0
- uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py +147 -0
- uk_bin_collection/uk_bin_collection/councils/BirminghamCityCouncil.py +119 -0
- uk_bin_collection/uk_bin_collection/councils/BlackburnCouncil.py +105 -0
- uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py +104 -0
- uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py +103 -0
- uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py +137 -0
- uk_bin_collection/uk_bin_collection/councils/BristolCityCouncil.py +141 -0
- uk_bin_collection/uk_bin_collection/councils/BromleyBoroughCouncil.py +115 -0
- uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py +107 -0
- uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py +95 -0
- uk_bin_collection/uk_bin_collection/councils/BuryCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/CalderdaleCouncil.py +123 -0
- uk_bin_collection/uk_bin_collection/councils/CannockChaseDistrictCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/CardiffCouncil.py +172 -0
- uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py +96 -0
- uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py +54 -0
- uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py +127 -0
- uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py +32 -0
- uk_bin_collection/uk_bin_collection/councils/CheshireWestAndChesterCouncil.py +125 -0
- uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/ConwyCountyBorough.py +27 -0
- uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py +61 -0
- uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py +291 -0
- uk_bin_collection/uk_bin_collection/councils/DerbyshireDalesDistrictCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/DoncasterCouncil.py +77 -0
- uk_bin_collection/uk_bin_collection/councils/DorsetCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py +41 -0
- uk_bin_collection/uk_bin_collection/councils/DurhamCouncil.py +49 -0
- uk_bin_collection/uk_bin_collection/councils/EastCambridgeshireCouncil.py +44 -0
- uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py +74 -0
- uk_bin_collection/uk_bin_collection/councils/EastLindseyDistrictCouncil.py +108 -0
- uk_bin_collection/uk_bin_collection/councils/EastRidingCouncil.py +142 -0
- uk_bin_collection/uk_bin_collection/councils/EastSuffolkCouncil.py +112 -0
- uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py +70 -0
- uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py +48 -0
- uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py +61 -0
- uk_bin_collection/uk_bin_collection/councils/FenlandDistrictCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/ForestOfDeanDistrictCouncil.py +113 -0
- uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py +118 -0
- uk_bin_collection/uk_bin_collection/councils/GedlingBoroughCouncil.py +1580 -0
- uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +55 -0
- uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py +150 -0
- uk_bin_collection/uk_bin_collection/councils/HaltonBoroughCouncil.py +142 -0
- uk_bin_collection/uk_bin_collection/councils/HaringeyCouncil.py +59 -0
- uk_bin_collection/uk_bin_collection/councils/HarrogateBoroughCouncil.py +63 -0
- uk_bin_collection/uk_bin_collection/councils/HighPeakCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/HullCityCouncil.py +48 -0
- uk_bin_collection/uk_bin_collection/councils/HuntingdonDistrictCouncil.py +44 -0
- uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py +84 -0
- uk_bin_collection/uk_bin_collection/councils/KirkleesCouncil.py +130 -0
- uk_bin_collection/uk_bin_collection/councils/KnowsleyMBCouncil.py +139 -0
- uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py +71 -0
- uk_bin_collection/uk_bin_collection/councils/LeedsCityCouncil.py +137 -0
- uk_bin_collection/uk_bin_collection/councils/LisburnCastlereaghCityCouncil.py +101 -0
- uk_bin_collection/uk_bin_collection/councils/LiverpoolCityCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/LondonBoroughHounslow.py +82 -0
- uk_bin_collection/uk_bin_collection/councils/LondonBoroughRedbridge.py +161 -0
- uk_bin_collection/uk_bin_collection/councils/MaldonDistrictCouncil.py +52 -0
- uk_bin_collection/uk_bin_collection/councils/MalvernHillsDC.py +57 -0
- uk_bin_collection/uk_bin_collection/councils/ManchesterCityCouncil.py +106 -0
- uk_bin_collection/uk_bin_collection/councils/MansfieldDistrictCouncil.py +38 -0
- uk_bin_collection/uk_bin_collection/councils/MertonCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/MidAndEastAntrimBoroughCouncil.py +128 -0
- uk_bin_collection/uk_bin_collection/councils/MidSussexDistrictCouncil.py +80 -0
- uk_bin_collection/uk_bin_collection/councils/MiltonKeynesCityCouncil.py +54 -0
- uk_bin_collection/uk_bin_collection/councils/MoleValleyDistrictCouncil.py +98 -0
- uk_bin_collection/uk_bin_collection/councils/NeathPortTalbotCouncil.py +139 -0
- uk_bin_collection/uk_bin_collection/councils/NewarkAndSherwoodDC.py +52 -0
- uk_bin_collection/uk_bin_collection/councils/NewcastleCityCouncil.py +57 -0
- uk_bin_collection/uk_bin_collection/councils/NewhamCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py +203 -0
- uk_bin_collection/uk_bin_collection/councils/NorthEastDerbyshireDistrictCouncil.py +115 -0
- uk_bin_collection/uk_bin_collection/councils/NorthEastLincs.py +53 -0
- uk_bin_collection/uk_bin_collection/councils/NorthKestevenDistrictCouncil.py +45 -0
- uk_bin_collection/uk_bin_collection/councils/NorthLanarkshireCouncil.py +46 -0
- uk_bin_collection/uk_bin_collection/councils/NorthLincolnshireCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/NorthNorfolkDistrictCouncil.py +108 -0
- uk_bin_collection/uk_bin_collection/councils/NorthNorthamptonshireCouncil.py +72 -0
- uk_bin_collection/uk_bin_collection/councils/NorthSomersetCouncil.py +76 -0
- uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py +220 -0
- uk_bin_collection/uk_bin_collection/councils/NorthWestLeicestershire.py +114 -0
- uk_bin_collection/uk_bin_collection/councils/NorthYorkshire.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +123 -0
- uk_bin_collection/uk_bin_collection/councils/NottinghamCityCouncil.py +36 -0
- uk_bin_collection/uk_bin_collection/councils/OldhamCouncil.py +51 -0
- uk_bin_collection/uk_bin_collection/councils/PortsmouthCityCouncil.py +131 -0
- uk_bin_collection/uk_bin_collection/councils/PrestonCityCouncil.py +97 -0
- uk_bin_collection/uk_bin_collection/councils/ReadingBoroughCouncil.py +30 -0
- uk_bin_collection/uk_bin_collection/councils/ReigateAndBansteadBoroughCouncil.py +81 -0
- uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py +135 -0
- uk_bin_collection/uk_bin_collection/councils/RhonddaCynonTaffCouncil.py +80 -0
- uk_bin_collection/uk_bin_collection/councils/RochdaleCouncil.py +69 -0
- uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py +60 -0
- uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py +93 -0
- uk_bin_collection/uk_bin_collection/councils/RushcliffeBoroughCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py +81 -0
- uk_bin_collection/uk_bin_collection/councils/SalfordCityCouncil.py +70 -0
- uk_bin_collection/uk_bin_collection/councils/SevenoaksDistrictCouncil.py +106 -0
- uk_bin_collection/uk_bin_collection/councils/SheffieldCityCouncil.py +54 -0
- uk_bin_collection/uk_bin_collection/councils/ShropshireCouncil.py +45 -0
- uk_bin_collection/uk_bin_collection/councils/SolihullCouncil.py +48 -0
- uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py +203 -0
- uk_bin_collection/uk_bin_collection/councils/SouthAyrshireCouncil.py +73 -0
- uk_bin_collection/uk_bin_collection/councils/SouthCambridgeshireCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/SouthGloucestershireCouncil.py +74 -0
- uk_bin_collection/uk_bin_collection/councils/SouthLanarkshireCouncil.py +78 -0
- uk_bin_collection/uk_bin_collection/councils/SouthNorfolkCouncil.py +91 -0
- uk_bin_collection/uk_bin_collection/councils/SouthOxfordshireCouncil.py +93 -0
- uk_bin_collection/uk_bin_collection/councils/SouthTynesideCouncil.py +98 -0
- uk_bin_collection/uk_bin_collection/councils/StAlbansCityAndDistrictCouncil.py +43 -0
- uk_bin_collection/uk_bin_collection/councils/StHelensBC.py +56 -0
- uk_bin_collection/uk_bin_collection/councils/StaffordshireMoorlandsDistrictCouncil.py +112 -0
- uk_bin_collection/uk_bin_collection/councils/StockportBoroughCouncil.py +39 -0
- uk_bin_collection/uk_bin_collection/councils/StokeOnTrentCityCouncil.py +79 -0
- uk_bin_collection/uk_bin_collection/councils/StratfordUponAvonCouncil.py +94 -0
- uk_bin_collection/uk_bin_collection/councils/SunderlandCityCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/SwaleBoroughCouncil.py +52 -0
- uk_bin_collection/uk_bin_collection/councils/TamesideMBCouncil.py +62 -0
- uk_bin_collection/uk_bin_collection/councils/TandridgeDistrictCouncil.py +60 -0
- uk_bin_collection/uk_bin_collection/councils/TelfordAndWrekinCouncil.py +50 -0
- uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py +203 -0
- uk_bin_collection/uk_bin_collection/councils/TonbridgeAndMallingBC.py +101 -0
- uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py +51 -0
- uk_bin_collection/uk_bin_collection/councils/TorridgeDistrictCouncil.py +154 -0
- uk_bin_collection/uk_bin_collection/councils/ValeofGlamorganCouncil.py +119 -0
- uk_bin_collection/uk_bin_collection/councils/ValeofWhiteHorseCouncil.py +103 -0
- uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py +89 -0
- uk_bin_collection/uk_bin_collection/councils/WarwickDistrictCouncil.py +34 -0
- uk_bin_collection/uk_bin_collection/councils/WaverleyBoroughCouncil.py +119 -0
- uk_bin_collection/uk_bin_collection/councils/WealdenDistrictCouncil.py +86 -0
- uk_bin_collection/uk_bin_collection/councils/WelhatCouncil.py +73 -0
- uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/WestLindseyDistrictCouncil.py +118 -0
- uk_bin_collection/uk_bin_collection/councils/WestLothianCouncil.py +103 -0
- uk_bin_collection/uk_bin_collection/councils/WestNorthamptonshireCouncil.py +34 -0
- uk_bin_collection/uk_bin_collection/councils/WestSuffolkCouncil.py +64 -0
- uk_bin_collection/uk_bin_collection/councils/WiganBoroughCouncil.py +97 -0
- uk_bin_collection/uk_bin_collection/councils/WiltshireCouncil.py +135 -0
- uk_bin_collection/uk_bin_collection/councils/WindsorAndMaidenheadCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/WokingBoroughCouncil.py +114 -0
- uk_bin_collection/uk_bin_collection/councils/WyreCouncil.py +89 -0
- uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py +45 -0
- uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py +33 -0
- uk_bin_collection/uk_bin_collection/get_bin_data.py +165 -0
- uk_bin_collection-0.74.0.dist-info/LICENSE +21 -0
- uk_bin_collection-0.74.0.dist-info/METADATA +247 -0
- uk_bin_collection-0.74.0.dist-info/RECORD +171 -0
- uk_bin_collection-0.74.0.dist-info/WHEEL +4 -0
- uk_bin_collection-0.74.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
from datetime import timedelta
|
2
|
+
|
3
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
4
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
5
|
+
|
6
|
+
|
7
|
+
def format_bin_data(key: str, date: datetime):
|
8
|
+
formatted_date = date.strftime(date_format)
|
9
|
+
|
10
|
+
if re.match(r"^R\d+$", key) is not None:
|
11
|
+
# RX matches both general waste and recycling
|
12
|
+
return [
|
13
|
+
("General Waste (Black Bin)", formatted_date),
|
14
|
+
("Recycling & Food Waste", formatted_date),
|
15
|
+
]
|
16
|
+
elif re.match(r"^G\d+$", key) is not None:
|
17
|
+
return [("Garden Waste (Green Bin)", formatted_date)]
|
18
|
+
elif re.match(r"^C\d+$", key) is not None:
|
19
|
+
return [("Recycling & Food Waste", formatted_date)]
|
20
|
+
else:
|
21
|
+
return None
|
22
|
+
|
23
|
+
|
24
|
+
class CouncilClass(AbstractGetBinDataClass):
|
25
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
26
|
+
uprn = kwargs.get("uprn")
|
27
|
+
check_uprn(uprn)
|
28
|
+
|
29
|
+
api_url = (
|
30
|
+
f"https://webapps.southglos.gov.uk/Webservices/SGC.RefuseCollectionService/RefuseCollectionService"
|
31
|
+
f".svc/getCollections/{uprn}"
|
32
|
+
)
|
33
|
+
|
34
|
+
headers = {"content-type": "application/json"}
|
35
|
+
|
36
|
+
response = requests.get(api_url, headers=headers)
|
37
|
+
|
38
|
+
json_response = json.loads(response.content)
|
39
|
+
if not json_response:
|
40
|
+
raise ValueError("No collection data found for provided UPRN.")
|
41
|
+
|
42
|
+
collection_data = json_response[0]
|
43
|
+
|
44
|
+
today = datetime.today()
|
45
|
+
eight_weeks = datetime.today() + timedelta(days=8 * 7)
|
46
|
+
data = {"bins": []}
|
47
|
+
collection_tuple = []
|
48
|
+
|
49
|
+
for key in collection_data:
|
50
|
+
if key == "CalendarName":
|
51
|
+
continue
|
52
|
+
|
53
|
+
item = collection_data[key]
|
54
|
+
|
55
|
+
if item == "":
|
56
|
+
continue
|
57
|
+
|
58
|
+
collection_date = datetime.strptime(item, date_format)
|
59
|
+
if today.date() <= collection_date.date() <= eight_weeks.date():
|
60
|
+
bin_data = format_bin_data(key, collection_date)
|
61
|
+
if bin_data is not None:
|
62
|
+
for bin_date in bin_data:
|
63
|
+
collection_tuple.append(bin_date)
|
64
|
+
|
65
|
+
ordered_data = sorted(collection_tuple, key=lambda x: x[1])
|
66
|
+
|
67
|
+
for item in ordered_data:
|
68
|
+
dict_data = {
|
69
|
+
"type": item[0],
|
70
|
+
"collectionDate": item[1],
|
71
|
+
}
|
72
|
+
data["bins"].append(dict_data)
|
73
|
+
|
74
|
+
return data
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import time
|
2
|
+
from datetime import timedelta
|
3
|
+
|
4
|
+
from bs4 import BeautifulSoup
|
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
|
+
data = {"bins": []}
|
19
|
+
collection_types = [
|
20
|
+
"non recyclable waste",
|
21
|
+
"food and garden",
|
22
|
+
"paper and card",
|
23
|
+
"glass, cans and plastics",
|
24
|
+
]
|
25
|
+
|
26
|
+
# Make a BS4 object
|
27
|
+
soup = BeautifulSoup(page.text, features="html.parser")
|
28
|
+
soup.prettify()
|
29
|
+
|
30
|
+
week_details = soup.find("div", {"class": "bin-dir-snip"})
|
31
|
+
week_dates = week_details.find("div", {"class": "clearfix"}).find("p")
|
32
|
+
week_collections = week_details.find_all_next("h4")
|
33
|
+
|
34
|
+
results = re.search(
|
35
|
+
"([A-Za-z0-9 ]+) to ([A-Za-z0-9 ]+)", week_dates.get_text().strip()
|
36
|
+
)
|
37
|
+
if results:
|
38
|
+
week_start = datetime.strptime(results.groups()[0], "%A %d %B %Y")
|
39
|
+
week_end = datetime.strptime(results.groups()[1], "%A %d %B %Y")
|
40
|
+
week_days = (
|
41
|
+
week_start + timedelta(days=i)
|
42
|
+
for i in range((week_end - week_start).days + 1)
|
43
|
+
)
|
44
|
+
|
45
|
+
week_collection_types = []
|
46
|
+
for week_collection in week_collections:
|
47
|
+
week_collection = (
|
48
|
+
week_collection.get_text().strip().lower().replace("-", " ")
|
49
|
+
)
|
50
|
+
for collection_type in collection_types:
|
51
|
+
if collection_type in week_collection:
|
52
|
+
week_collection_types.append(collection_type)
|
53
|
+
|
54
|
+
collection_schedule = (
|
55
|
+
soup.find("div", {"class": "serviceDetails"})
|
56
|
+
.find("table")
|
57
|
+
.find_all_next("tr")
|
58
|
+
)
|
59
|
+
|
60
|
+
for day in week_days:
|
61
|
+
for row in collection_schedule:
|
62
|
+
schedule_type = row.find("th").get_text().strip()
|
63
|
+
results2 = re.search("([^(]+)", row.find("td").get_text().strip())
|
64
|
+
if results2:
|
65
|
+
schedule_day = results2[1].strip()
|
66
|
+
for collection_type in week_collection_types:
|
67
|
+
if collection_type in schedule_type.lower():
|
68
|
+
if (
|
69
|
+
day.weekday()
|
70
|
+
== time.strptime(schedule_day, "%A").tm_wday
|
71
|
+
):
|
72
|
+
dict_data = {
|
73
|
+
"type": schedule_type,
|
74
|
+
"collectionDate": day.strftime(date_format),
|
75
|
+
}
|
76
|
+
data["bins"].append(dict_data)
|
77
|
+
|
78
|
+
return data
|
@@ -0,0 +1,91 @@
|
|
1
|
+
from xml.etree import ElementTree
|
2
|
+
|
3
|
+
from bs4 import BeautifulSoup
|
4
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
5
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
6
|
+
|
7
|
+
|
8
|
+
class CouncilClass(AbstractGetBinDataClass):
|
9
|
+
"""
|
10
|
+
Concrete classes have to implement all abstract operations of the
|
11
|
+
baseclass. They can also override some
|
12
|
+
operations with a default implementation.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
16
|
+
uprn = kwargs.get("uprn")
|
17
|
+
check_uprn(uprn)
|
18
|
+
council = "SNO"
|
19
|
+
|
20
|
+
# Make SOAP request
|
21
|
+
headers = {
|
22
|
+
"Accept": "*/*",
|
23
|
+
"Accept-Encoding": "gzip, deflate, br",
|
24
|
+
"Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
|
25
|
+
"Connection": "keep-alive",
|
26
|
+
"Content-Type": "text/xml; charset=UTF-8",
|
27
|
+
"Host": "collections-southnorfolk.azurewebsites.net",
|
28
|
+
"Origin": "https://collections-southnorfolk.azurewebsites.net",
|
29
|
+
"Referer": "https://collections-southnorfolk.azurewebsites.net/calendar.html",
|
30
|
+
"sec-ch-ua": '"Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"',
|
31
|
+
"sec-ch-ua-mobile": "?0",
|
32
|
+
"sec-ch-ua-platform": '"Windows"',
|
33
|
+
"Sec-Fetch-Dest": "empty",
|
34
|
+
"Sec-Fetch-Mode": "cors",
|
35
|
+
"Sec-Fetch-Site": "same-origin",
|
36
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
|
37
|
+
}
|
38
|
+
requests.packages.urllib3.disable_warnings()
|
39
|
+
post_data = (
|
40
|
+
'<?xml version="1.0" encoding="utf-8"?>'
|
41
|
+
'<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">'
|
42
|
+
'<soap:Body><getRoundCalendarForUPRN xmlns="http://webaspx-collections.azurewebsites.net/">'
|
43
|
+
"<council>" + council + "</council><UPRN>" + uprn + "</UPRN>"
|
44
|
+
"<from>Chtml</from></getRoundCalendarForUPRN></soap:Body></soap:Envelope>"
|
45
|
+
)
|
46
|
+
response = requests.post(
|
47
|
+
"https://collections-southnorfolk.azurewebsites.net/WSCollExternal.asmx",
|
48
|
+
headers=headers,
|
49
|
+
data=post_data,
|
50
|
+
)
|
51
|
+
if response.status_code != 200:
|
52
|
+
raise ValueError("No bin data found for provided UPRN.")
|
53
|
+
|
54
|
+
# Get HTML from SOAP response
|
55
|
+
xmltree = ElementTree.fromstring(response.text)
|
56
|
+
html = xmltree.find(
|
57
|
+
".//{http://webaspx-collections.azurewebsites.net/}getRoundCalendarForUPRNResult"
|
58
|
+
).text
|
59
|
+
# Parse with BS4
|
60
|
+
soup = BeautifulSoup(html, features="html.parser")
|
61
|
+
soup.prettify()
|
62
|
+
|
63
|
+
data = {"bins": []}
|
64
|
+
for bin_type in ["RefuseBin", "RecycleBin", "GardenBin"]:
|
65
|
+
bin_el = soup.find("b", string=bin_type)
|
66
|
+
if bin_el:
|
67
|
+
bin_info = bin_el.next_sibling.split(": ")[1]
|
68
|
+
collection_date = ""
|
69
|
+
results = re.search("([A-Za-z]+ \\d\\d? [A-Za-z]+) then", bin_info)
|
70
|
+
if results:
|
71
|
+
date = get_next_occurrence_from_day_month(
|
72
|
+
datetime.strptime(
|
73
|
+
results[1] + " " + datetime.now().strftime("%Y"),
|
74
|
+
"%a %d %b %Y",
|
75
|
+
)
|
76
|
+
)
|
77
|
+
if date:
|
78
|
+
collection_date = date.strftime(date_format)
|
79
|
+
else:
|
80
|
+
results2 = re.search("([A-Za-z]+) then", bin_info)
|
81
|
+
if results2:
|
82
|
+
collection_date = results2[1]
|
83
|
+
|
84
|
+
if collection_date != "":
|
85
|
+
dict_data = {
|
86
|
+
"type": bin_type,
|
87
|
+
"collectionDate": collection_date,
|
88
|
+
}
|
89
|
+
data["bins"].append(dict_data)
|
90
|
+
|
91
|
+
return data
|
@@ -0,0 +1,93 @@
|
|
1
|
+
import requests
|
2
|
+
from bs4 import BeautifulSoup
|
3
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
4
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
5
|
+
|
6
|
+
|
7
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
8
|
+
class CouncilClass(AbstractGetBinDataClass):
|
9
|
+
"""
|
10
|
+
Concrete classes have to implement all abstract operations of the
|
11
|
+
base class. They can also override some operations with a default
|
12
|
+
implementation.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
16
|
+
user_uprn = kwargs.get("uprn")
|
17
|
+
check_uprn(user_uprn)
|
18
|
+
|
19
|
+
# UPRN is passed in via a cookie. Set cookies/params and GET the page
|
20
|
+
cookies = {
|
21
|
+
# 'JSESSIONID': '96F2A15C14569B2ED2BBEB140FE86532',
|
22
|
+
"SVBINZONE": f"SOUTH%3AUPRN%40{user_uprn}",
|
23
|
+
}
|
24
|
+
headers = {
|
25
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
26
|
+
"Accept-Language": "en-GB,en;q=0.7",
|
27
|
+
"Cache-Control": "max-age=0",
|
28
|
+
"Connection": "keep-alive",
|
29
|
+
"Referer": "https://eform.southoxon.gov.uk/ebase/BINZONE_DESKTOP.eb?SOVA_TAG=SOUTH&ebd=0&ebz=1_1668467255368",
|
30
|
+
"Sec-Fetch-Dest": "document",
|
31
|
+
"Sec-Fetch-Mode": "navigate",
|
32
|
+
"Sec-Fetch-Site": "same-origin",
|
33
|
+
"Sec-Fetch-User": "?1",
|
34
|
+
"Sec-GPC": "1",
|
35
|
+
"Upgrade-Insecure-Requests": "1",
|
36
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
|
37
|
+
}
|
38
|
+
params = {
|
39
|
+
"SOVA_TAG": "SOUTH",
|
40
|
+
"ebd": "0",
|
41
|
+
# 'ebz': '1_1668467255368',
|
42
|
+
}
|
43
|
+
requests.packages.urllib3.disable_warnings()
|
44
|
+
response = requests.get(
|
45
|
+
"https://eform.southoxon.gov.uk/ebase/BINZONE_DESKTOP.eb",
|
46
|
+
params=params,
|
47
|
+
headers=headers,
|
48
|
+
cookies=cookies,
|
49
|
+
)
|
50
|
+
|
51
|
+
# Parse response text for super speedy finding
|
52
|
+
soup = BeautifulSoup(response.text, features="html.parser")
|
53
|
+
soup.prettify()
|
54
|
+
|
55
|
+
data = {"bins": []}
|
56
|
+
|
57
|
+
# Page has slider info side by side, which are two instances of this class
|
58
|
+
for bin in soup.find_all("div", {"class": "binextra"}):
|
59
|
+
bin_info = list(bin.stripped_strings)
|
60
|
+
try:
|
61
|
+
# On standard collection schedule, date will be contained in the first stripped string
|
62
|
+
if contains_date(bin_info[0]):
|
63
|
+
bin_date = get_next_occurrence_from_day_month(
|
64
|
+
datetime.strptime(
|
65
|
+
bin_info[0] + " " + datetime.today().strftime("%Y"),
|
66
|
+
"%A %d %B - %Y",
|
67
|
+
)
|
68
|
+
).strftime(date_format)
|
69
|
+
bin_type = str.capitalize(bin_info[1])
|
70
|
+
# On exceptional collection schedule (e.g. around English Bank Holidays), date will be contained in the second stripped string
|
71
|
+
else:
|
72
|
+
bin_date = get_next_occurrence_from_day_month(
|
73
|
+
datetime.strptime(
|
74
|
+
bin_info[1] + " " + datetime.today().strftime("%Y"),
|
75
|
+
"%A %d %B - %Y",
|
76
|
+
)
|
77
|
+
).strftime(date_format)
|
78
|
+
bin_type = str.capitalize(bin_info[2])
|
79
|
+
except Exception as ex:
|
80
|
+
raise ValueError(f"Error parsing bin data: {ex}")
|
81
|
+
|
82
|
+
# Build data dict for each entry
|
83
|
+
dict_data = {
|
84
|
+
"type": bin_type,
|
85
|
+
"collectionDate": bin_date,
|
86
|
+
}
|
87
|
+
data["bins"].append(dict_data)
|
88
|
+
|
89
|
+
data["bins"].sort(
|
90
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
91
|
+
)
|
92
|
+
|
93
|
+
return data
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import json
|
2
|
+
from datetime import datetime
|
3
|
+
|
4
|
+
import requests
|
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
|
+
def get_address_uprn(postcode: str, paon: str, api_url: str) -> str:
|
10
|
+
"""
|
11
|
+
Gets the UPRN and address in desired format
|
12
|
+
:rtype: str
|
13
|
+
:param postcode: Postcode to use
|
14
|
+
:param paon: House number to use
|
15
|
+
:param api_url: API to POST
|
16
|
+
:return: UPRN and postcode in str format
|
17
|
+
"""
|
18
|
+
addr = ""
|
19
|
+
payload = json.dumps(
|
20
|
+
{
|
21
|
+
"jsonrpc": "2.0",
|
22
|
+
"id": "1689431267990",
|
23
|
+
"method": "stc.common.snippets.getAddressList",
|
24
|
+
"params": {"postcode": f"{postcode.replace(' ', '')}", "localonly": "true"},
|
25
|
+
}
|
26
|
+
)
|
27
|
+
headers = {"Content-Type": "application/json"}
|
28
|
+
response = requests.post(api_url, data=payload, headers=headers)
|
29
|
+
|
30
|
+
json_response = json.loads(response.content)
|
31
|
+
results = json_response["result"]["ReturnedList"]
|
32
|
+
|
33
|
+
for item in results:
|
34
|
+
if item["Address"].split()[0] == paon.strip():
|
35
|
+
addr = item["UPRN"] + "|" + item["Address"]
|
36
|
+
break
|
37
|
+
|
38
|
+
return addr
|
39
|
+
|
40
|
+
|
41
|
+
class CouncilClass(AbstractGetBinDataClass):
|
42
|
+
"""
|
43
|
+
Concrete classes have to implement all abstract operations of the
|
44
|
+
base class. They can also override some operations with a default
|
45
|
+
implementation.
|
46
|
+
"""
|
47
|
+
|
48
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
49
|
+
requests.packages.urllib3.disable_warnings()
|
50
|
+
api_url = "https://www.southtyneside.gov.uk/apiserver/ajaxlibrary/"
|
51
|
+
user_postcode = kwargs.get("postcode")
|
52
|
+
user_paon = kwargs.get("paon")
|
53
|
+
data = {"bins": []}
|
54
|
+
|
55
|
+
check_postcode(user_postcode)
|
56
|
+
check_paon(user_paon)
|
57
|
+
|
58
|
+
try:
|
59
|
+
if user_paon is None:
|
60
|
+
raise ValueError("Invalid house number")
|
61
|
+
except Exception as ex:
|
62
|
+
print(f"Exception encountered: {ex}")
|
63
|
+
print(
|
64
|
+
"Please check the provided house number. If this error continues, please first trying setting the "
|
65
|
+
"house number manually on line 25 before raising an issue."
|
66
|
+
)
|
67
|
+
exit(1)
|
68
|
+
|
69
|
+
# Get the "UPRN" (actually the UPRN + address)
|
70
|
+
uprn = get_address_uprn(user_postcode, user_paon, api_url)
|
71
|
+
|
72
|
+
# Set up payload and headers, then post to API to get schedule
|
73
|
+
payload = json.dumps(
|
74
|
+
{
|
75
|
+
"jsonrpc": "2.0",
|
76
|
+
"id": "1689431609779",
|
77
|
+
"method": "stc.waste.collections.getDates",
|
78
|
+
"params": {"addresscode": uprn},
|
79
|
+
}
|
80
|
+
)
|
81
|
+
headers = {"Content-Type": "application/json"}
|
82
|
+
response = requests.request("POST", api_url, headers=headers, data=payload)
|
83
|
+
|
84
|
+
# Break down the resulting JSON and load into dictionary
|
85
|
+
json_result = json.loads(response.text)["result"]
|
86
|
+
months = json_result["SortedCollections"]
|
87
|
+
for month in months:
|
88
|
+
collections_in_month = month["Collections"]
|
89
|
+
for item in collections_in_month:
|
90
|
+
dict_data = {
|
91
|
+
"type": item["Type"],
|
92
|
+
"collectionDate": datetime.strptime(
|
93
|
+
item["DateString"], "%d %B %Y"
|
94
|
+
).strftime(date_format),
|
95
|
+
}
|
96
|
+
data["bins"].append(dict_data)
|
97
|
+
|
98
|
+
return data
|
@@ -0,0 +1,43 @@
|
|
1
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
2
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
3
|
+
from dateutil import parser
|
4
|
+
|
5
|
+
|
6
|
+
class CouncilClass(AbstractGetBinDataClass):
|
7
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
8
|
+
user_uprn = kwargs.get("uprn")
|
9
|
+
check_uprn(user_uprn)
|
10
|
+
|
11
|
+
data = {"bins": []}
|
12
|
+
|
13
|
+
headers = {
|
14
|
+
"Content-Type": "application/json; charset=UTF-8",
|
15
|
+
}
|
16
|
+
|
17
|
+
req_data = {"uprn": user_uprn, "noticeBoard": "default"}
|
18
|
+
|
19
|
+
url = "https://gis.stalbans.gov.uk/NoticeBoard9/VeoliaProxy.NoticeBoard.asmx/GetServicesByUprnAndNoticeBoard"
|
20
|
+
|
21
|
+
response = requests.post(url, json=req_data, headers=headers)
|
22
|
+
|
23
|
+
collections_response = response.json()
|
24
|
+
|
25
|
+
collections = []
|
26
|
+
|
27
|
+
for collection in collections_response["d"]:
|
28
|
+
collection_data = collection["ServiceHeaders"][0]
|
29
|
+
bin_type = collection_data["TaskType"]
|
30
|
+
collection_date = collection_data["Next"]
|
31
|
+
next_collection = parser.isoparser().isoparse(collection_date)
|
32
|
+
collections.append((bin_type, next_collection))
|
33
|
+
|
34
|
+
ordered_data = sorted(collections, key=lambda x: x[1])
|
35
|
+
|
36
|
+
for item in ordered_data:
|
37
|
+
dict_data = {
|
38
|
+
"type": item[0],
|
39
|
+
"collectionDate": item[1].strftime(date_format),
|
40
|
+
}
|
41
|
+
data["bins"].append(dict_data)
|
42
|
+
|
43
|
+
return data
|
@@ -0,0 +1,56 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
3
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
4
|
+
|
5
|
+
|
6
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
7
|
+
class CouncilClass(AbstractGetBinDataClass):
|
8
|
+
"""
|
9
|
+
Concrete classes have to implement all abstract operations of the
|
10
|
+
baseclass. They can also override some
|
11
|
+
operations with a default implementation.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
15
|
+
uprn = kwargs.get("uprn")
|
16
|
+
# Check the UPRN is valid
|
17
|
+
check_uprn(uprn)
|
18
|
+
|
19
|
+
# Request URL
|
20
|
+
url = f"https://secure.sthelens.net/website/CollectionDates.nsf/servlet.xsp/NextCollections?source=1&refid={uprn}"
|
21
|
+
|
22
|
+
# Make Request
|
23
|
+
requests.packages.urllib3.disable_warnings()
|
24
|
+
s = requests.session()
|
25
|
+
page = s.get(url)
|
26
|
+
|
27
|
+
# Make a BS4 object
|
28
|
+
soup = BeautifulSoup(
|
29
|
+
re.sub("<div([^>]+)>", "", page.text).replace("</div>", ""),
|
30
|
+
features="html.parser",
|
31
|
+
)
|
32
|
+
soup.prettify()
|
33
|
+
|
34
|
+
data = {"bins": []}
|
35
|
+
collection_rows = (
|
36
|
+
soup.find("table", {"class": "multitable"}).find("tbody").find_all("tr")
|
37
|
+
)
|
38
|
+
|
39
|
+
for collection_row in collection_rows:
|
40
|
+
# Get bin collection type
|
41
|
+
bin_type = collection_row.find("th")
|
42
|
+
if bin_type:
|
43
|
+
bin_type = bin_type.get_text(strip=True)
|
44
|
+
# Get bin collection dates
|
45
|
+
for bin_date in collection_row.find_all("td"):
|
46
|
+
if bin_date.get_text(strip=True) != "Dates not allocated":
|
47
|
+
collection_date = datetime.strptime(
|
48
|
+
bin_date.get_text(strip=True), "%a %d %b %Y"
|
49
|
+
)
|
50
|
+
dict_data = {
|
51
|
+
"type": bin_type,
|
52
|
+
"collectionDate": collection_date.strftime(date_format),
|
53
|
+
}
|
54
|
+
data["bins"].append(dict_data)
|
55
|
+
|
56
|
+
return data
|
@@ -0,0 +1,112 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
from selenium.webdriver.common.by import By
|
3
|
+
from selenium.webdriver.support import expected_conditions as EC
|
4
|
+
from selenium.webdriver.support.ui import Select
|
5
|
+
from selenium.webdriver.support.wait import WebDriverWait
|
6
|
+
|
7
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
8
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
9
|
+
|
10
|
+
|
11
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
12
|
+
class CouncilClass(AbstractGetBinDataClass):
|
13
|
+
"""
|
14
|
+
Concrete classes have to implement all abstract operations of the
|
15
|
+
base class. They can also override some operations with a default
|
16
|
+
implementation.
|
17
|
+
"""
|
18
|
+
|
19
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
20
|
+
data = {"bins": []}
|
21
|
+
user_uprn = kwargs.get("uprn")
|
22
|
+
user_postcode = kwargs.get("postcode")
|
23
|
+
web_driver = kwargs.get("web_driver")
|
24
|
+
headless = kwargs.get("headless")
|
25
|
+
check_uprn(user_uprn)
|
26
|
+
check_postcode(user_postcode)
|
27
|
+
|
28
|
+
# Create Selenium webdriver
|
29
|
+
driver = create_webdriver(web_driver, headless)
|
30
|
+
driver.get("https://www.staffsmoorlands.gov.uk/findyourbinday")
|
31
|
+
|
32
|
+
# Close cookies banner
|
33
|
+
cookieAccept = WebDriverWait(driver, 10).until(
|
34
|
+
EC.presence_of_element_located(
|
35
|
+
(By.CSS_SELECTOR, ".cookiemessage__link--close")
|
36
|
+
)
|
37
|
+
)
|
38
|
+
cookieAccept.click()
|
39
|
+
|
40
|
+
# Wait for the postcode field to appear then populate it
|
41
|
+
inputElement_postcode = WebDriverWait(driver, 30).until(
|
42
|
+
EC.presence_of_element_located(
|
43
|
+
(By.ID, "FINDBINDAYSSTAFFORDSHIREMOORLANDS_POSTCODESELECT_POSTCODE")
|
44
|
+
)
|
45
|
+
)
|
46
|
+
inputElement_postcode.send_keys(user_postcode)
|
47
|
+
|
48
|
+
# Click search button
|
49
|
+
findAddress = WebDriverWait(driver, 10).until(
|
50
|
+
EC.presence_of_element_located(
|
51
|
+
(
|
52
|
+
By.ID,
|
53
|
+
"FINDBINDAYSSTAFFORDSHIREMOORLANDS_POSTCODESELECT_PAGE1NEXT_NEXT",
|
54
|
+
)
|
55
|
+
)
|
56
|
+
)
|
57
|
+
findAddress.click()
|
58
|
+
|
59
|
+
# Wait for the 'Select address' dropdown to appear and select option matching UPRN
|
60
|
+
dropdown = WebDriverWait(driver, 30).until(
|
61
|
+
EC.presence_of_element_located(
|
62
|
+
(By.ID, "FINDBINDAYSSTAFFORDSHIREMOORLANDS_ADDRESSSELECT_ADDRESS")
|
63
|
+
)
|
64
|
+
)
|
65
|
+
# Create a 'Select' for it, then select the matching URPN option
|
66
|
+
dropdownSelect = Select(dropdown)
|
67
|
+
dropdownSelect.select_by_value(user_uprn)
|
68
|
+
|
69
|
+
# Wait for the submit button to appear, then click it to get the collection dates
|
70
|
+
submit = WebDriverWait(driver, 10).until(
|
71
|
+
EC.presence_of_element_located(
|
72
|
+
(
|
73
|
+
By.ID,
|
74
|
+
"FINDBINDAYSSTAFFORDSHIREMOORLANDS_ADDRESSSELECT_ADDRESSSELECTNEXTBTN_NEXT",
|
75
|
+
)
|
76
|
+
)
|
77
|
+
)
|
78
|
+
submit.click()
|
79
|
+
|
80
|
+
soup = BeautifulSoup(driver.page_source, features="html.parser")
|
81
|
+
|
82
|
+
# Quit Selenium webdriver to release session
|
83
|
+
driver.quit()
|
84
|
+
|
85
|
+
# Get months
|
86
|
+
for month_wrapper in soup.find_all("div", {"class": "bin-collection__month"}):
|
87
|
+
if month_wrapper:
|
88
|
+
month_year = month_wrapper.find(
|
89
|
+
"h3", {"class": "bin-collection__title"}
|
90
|
+
).get_text(strip=True)
|
91
|
+
# Get collections
|
92
|
+
for collection in month_wrapper.find_all(
|
93
|
+
"li", {"class": "bin-collection__item"}
|
94
|
+
):
|
95
|
+
day = collection.find(
|
96
|
+
"span", {"class": "bin-collection__number"}
|
97
|
+
).get_text(strip=True)
|
98
|
+
if month_year and day:
|
99
|
+
bin_date = datetime.strptime(day + " " + month_year, "%d %B %Y")
|
100
|
+
dict_data = {
|
101
|
+
"type": collection.find(
|
102
|
+
"span", {"class": "bin-collection__type"}
|
103
|
+
).get_text(strip=True),
|
104
|
+
"collectionDate": bin_date.strftime(date_format),
|
105
|
+
}
|
106
|
+
data["bins"].append(dict_data)
|
107
|
+
|
108
|
+
data["bins"].sort(
|
109
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
|
110
|
+
)
|
111
|
+
|
112
|
+
return data
|