Skip to content

Commit

Permalink
feat: Add support for West Lothian Council
Browse files Browse the repository at this point in the history
  • Loading branch information
OliverCullimore committed Oct 20, 2023
1 parent 2d6ee41 commit 780f6b4
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 0 deletions.
39 changes: 39 additions & 0 deletions uk_bin_collection/tests/council_schemas/WestLothianCouncil.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$ref": "#/definitions/Welcome7",
"definitions": {
"Welcome7": {
"type": "object",
"additionalProperties": false,
"properties": {
"bins": {
"type": "array",
"items": {
"$ref": "#/definitions/Bin"
}
}
},
"required": [
"bins"
],
"title": "Welcome7"
},
"Bin": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": {
"type": "string"
},
"collectionDate": {
"type": "string"
}
},
"required": [
"collectionDate",
"type"
],
"title": "Bin"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Feature: Test each council output matches expected results in /outputs
| WaverleyBoroughCouncil |
| WealdenDistrictCouncil |
| WelhatCouncil |
| WestLothianCouncil |
| WiganBoroughCouncil |
| WiltshireCouncil |
| WindsorAndMaidenheadCouncil |
Expand Down
8 changes: 8 additions & 0 deletions uk_bin_collection/tests/input.json
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,14 @@
"url": "https://www.welhat.gov.uk/xfp/form/214",
"wiki_name": "Welhat Council"
},
"WestLothianCouncil": {
"SKIP_GET_URL": "SKIP_GET_URL",
"postcode": "EH52 5JE",
"house_number": "1 GOSCHEN PLACE",
"url": "https://www.westlothian.gov.uk/",
"wiki_name": "West Lothian Council",
"wiki_note": "Pass the house name/number in the house number parameter, wrapped in double quotes"
},
"WiganBoroughCouncil": {
"SKIP_GET_URL": "SKIP_GET_URL",
"postcode": "WN24UQ",
Expand Down
20 changes: 20 additions & 0 deletions uk_bin_collection/tests/outputs/WestLothianCouncil.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"bins": [
{
"type": "Green Bin",
"collectionDate": "20/10/2023"
},
{
"type": "Grey Bin",
"collectionDate": "27/10/2023"
},
{
"type": "Brown Bin (Now for Garden and Food Waste)",
"collectionDate": "30/10/2023"
},
{
"type": "Blue Bin",
"collectionDate": "03/11/2023"
}
]
}
88 changes: 88 additions & 0 deletions uk_bin_collection/uk_bin_collection/councils/WestLothianCouncil.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait

from uk_bin_collection.uk_bin_collection.common import *
from uk_bin_collection.uk_bin_collection.get_bin_data import \
AbstractGetBinDataClass


# import the wonderful Beautiful Soup and the URL grabber
class CouncilClass(AbstractGetBinDataClass):
"""
Concrete classes have to implement all abstract operations of the
base class. They can also override some operations with a default
implementation.
"""

def parse_data(self, page: str, **kwargs) -> dict:
data = {"bins": []}
user_paon = kwargs.get("paon")
user_postcode = kwargs.get("postcode")
check_paon(user_paon)
check_postcode(user_postcode)

# Set up Selenium to run 'headless'
options = webdriver.ChromeOptions()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--disable-gpu")
options.add_argument("--disable-dev-shm-usage")
options.add_experimental_option("excludeSwitches", ["enable-logging"])

# Create Selenium webdriver
driver = webdriver.Chrome(options=options)
driver.get("https://www.westlothian.gov.uk/article/31528/Bin-Collection-Calendar-Dates")

# Close feedback banner
feedbackBanner = WebDriverWait(driver, 10).until(
EC.presence_of_element_located(
(By.CSS_SELECTOR, ".feedback__link--no"))
)
feedbackBanner.click()

# Wait for the postcode field to appear then populate it
inputElement_postcode = WebDriverWait(driver, 30).until(
EC.presence_of_element_located(
(By.ID, "WLBINCOLLECTION_PAGE1_ADDRESSLOOKUPPOSTCODE"))
)
inputElement_postcode.send_keys(user_postcode)

# Click search button
findAddress = WebDriverWait(driver, 10).until(
EC.presence_of_element_located(
(By.ID, "WLBINCOLLECTION_PAGE1_ADDRESSLOOKUPSEARCH"))
)
findAddress.click()

# Wait for the 'Select address' dropdown to appear and select option matching the house name/number
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((
By.XPATH,
"//select[@id='WLBINCOLLECTION_PAGE1_ADDRESSLOOKUPADDRESS']//option[contains(., '" + user_paon + "')]"
))).click()

# Wait for the collections table to appear
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, ".bin-collections")))

soup = BeautifulSoup(driver.page_source, features="html.parser")

# Get collections
for collection in soup.find_all("div", {"class": "bin-collect"}):
dict_data = {
"type": collection.find("h3").get_text(strip=True),
"collectionDate": datetime.strptime(
remove_ordinal_indicator_from_date_string(
collection.find("span", {"class": "bin-collect__date"}).get_text(strip=True)
),
"%A, %B %d %Y"
).strftime(date_format)
}
data["bins"].append(dict_data)

data["bins"].sort(
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
)

return data

0 comments on commit 780f6b4

Please sign in to comment.