We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[Bug]: Error when configuring Recycle Coach
### I Have A Problem With:

A specific source

### What's Your Problem

Receiving error when configuring recycle coach for Vaughan Ontarion (unlisted city, but supported by recycle coach)


### Source (if relevant)

recycle coach

### Logs

```Shell
Logger: waste_collection_schedule.source_shell
Source: custom_components/waste_collection_schedule/waste_collection_schedule/source_shell.py:136
Integration: waste_collection_schedule (documentation)
First occurred: 3:10:21 PM (1 occurrences)
Last logged: 3:10:21 PM

fetch failed for source Recycle Coach: Traceback (most recent call last): File "/config/custom_components/waste_collection_schedule/waste_collection_schedule/source_shell.py", line 134, in fetch entries = self._source.fetch() ^^^^^^^^^^^^^^^^^^^^ File "/config/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py", line 159, in fetch self._lookup_zones() File "/config/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py", line 135, in _lookup_zones for zone_res in zone_data["results"]: ~~~~~~~~~^^^^^^^^^^^ KeyError: 'results'
```


### Relevant Configuration

```YAML
waste_collection_schedule:
  sources:
  - name: recyclecoach_com 
    calendar_title: Waste Collection Schedule
    args:
      street: <<street address in lowercase>>
      city: <<city in lowercase>>
      state: <<province in lowercase>>
```


### Checklist Source Error

- [X] Use the example parameters for your source (often available in the documentation) (don't forget to restart Home Assistant after changing the configuration)
- [X] Checked that the website of your service provider is still working
- [X] Tested my attributes on the service provider website (if possible)
- [X] I have tested with the latest version of the integration (master) (for HACS in the 3 dot menu of the integration click on "Redownload" and choose master as version)

### Checklist Sensor Error

- [ ] Checked in the Home Assistant Calendar tab if the event names match the types names (if types argument is used)

### Required

- [X] I have searched past (closed AND opened) issues to see if this bug has already been reported, and it hasn't been.
- [X] I understand that people give their precious time for free, and thus I've done my very best to make this problem as easy as possible to investigate.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py`
Content:
```
1 import json
2 from datetime import datetime
3 
4 import requests
5 from waste_collection_schedule import Collection
6 
7 TITLE = "Recycle Coach"
8 DESCRIPTION = "Source loader for recyclecoach.com"
9 URL = "https://recyclecoach.com"
10 COUNTRY = "us"
11 
12 ICON_MAP = {
13     "Garbage": "mdi:trash-can",
14     "Recycling": "mdi:recycle",
15     "Yard Waste": "mdi:leaf",
16 }
17 
18 EXTRA_INFO = [
19     {
20         "title": "Albuquerque, New Mexico, USA",
21         "url": "https://recyclecoach.com/cities/usa-nm-city-of-albuquerque/",
22     },
23     {
24         "title": "Tucson, Arizona, USA",
25         "url": "https://recyclecoach.com/cities/usa-az-city-of-tucson/",
26     },
27     {
28         "title": "Olympia, Washington, USA",
29         "url": "https://recyclecoach.com/cities/usa-wa-city-of-olympia/",
30     },
31     {
32         "title": "Newark, Delaware, USA",
33         "url": "https://recyclecoach.com/cities/usa-de-city-of-newark/",
34     },
35     {
36         "title": "Louisville, Kentucky, USA",
37         "url": "https://recyclecoach.com/cities/usa-ky-city-of-louisville/",
38     },
39     {"title": "London (ON)", "url": "https://london.ca/", "country": "ca"},
40     {"title": "Aurora (ON)", "url": "https://www.aurora.ca/", "country": "ca"},
41 ]
42 
43 TEST_CASES = {
44     "Default": {"street": "2242 grinstead drive", "city": "louisville", "state": "KY"},
45     "Problematic City Lookup": {
46         "street": "2202 E Florence Dr",
47         "city": "Tucson",
48         "state": "AZ",
49         "district_id": "TUC",
50         "project_id": "532",
51     },
52     "olympia": {
53         "street": "1003 Lybarger St NE",
54         "city": "Olympia",
55         "state": "Washington",
56     },
57     "newark": {"street": "24 Townsend Rd", "city": "Newark", "state": "Delaware"},
58     "albuquerque": {
59         "street": "1505 Silver Ave SE",
60         "city": "Albuquerque",
61         "state": "New Mexico",
62     },
63     "london ontario": {
64         "street": "1065 Sunningdale Rd E",
65         "city": "London",
66         "state": "Ontario",
67     },
68     "london ontario with districtID": {
69         "street": "1065 Sunningdale Rd E",
70         "city": "London",
71         "state": "Ontario",
72         "project_id": "528",
73         "district_id": "CityofLondon",
74         "zone_id": "zone-z547",
75     },
76     "aurora ontario": {
77         "street": "123 Cranberry Lane",
78         "city": "Aurora",
79         "state": "Ontario",
80     },
81 }
82 
83 
84 class Source:
85     def __init__(
86         self, street, city, state, project_id=None, district_id=None, zone_id=None
87     ):  # argX correspond to the args dict in the source configuration
88         self.street = self._format_key(street)
89         self.city = self._format_key(city)
90         self.state = self._format_key(state)
91         self.project_id = self._format_key(project_id) if project_id else None
92         self.district_id = district_id.strip() if district_id else None
93 
94         self.zone_id = zone_id  # uses lowercase z's, not sure if matters
95         self.stage = 0
96 
97     def _format_key(self, param):
98         """Get rid of ambiguity in caps/spacing."""
99         return param.upper().strip()
100 
101     def _lookup_city(self):
102         city_finder = f"https://recyclecoach.com/wp-json/rec/v1/cities?find={self.city}, {self.state}"
103         res = requests.get(city_finder)
104         city_data = res.json()
105 
106         if len(city_data["cities"]) == 1:
107             self.project_id = city_data["cities"][0]["project_id"]
108             self.district_id = city_data["cities"][0]["district_id"]
109             self.stage = float(city_data["cities"][0]["stage"])
110 
111             if self.stage < 3:
112                 raise Exception(
113                     "Found your city, but it is not yet supported fully by recycle coach."
114                 )
115 
116         elif len(city_data["cities"]) > 1:
117 
118             for city in city_data["cities"]:
119                 if city["city_nm"].upper() == self.city.upper():
120                     self.project_id = city["project_id"]
121                     self.district_id = city["district_id"]
122                     self.stage = float(city["stage"])
123                     return True
124 
125             # not sure what to do with ambiguity here
126             # print(json.dumps(city_data['cities'], indent=4))
127             raise Exception(
128                 "Could not determine district or project, Debug here to find your discrict and project_id"
129             )
130 
131     def _lookup_zones(self):
132         zone_finder = f"https://api-city.recyclecoach.com/zone-setup/address?sku={self.project_id}&district={self.district_id}&prompt=undefined&term={self.street}"
133         res = requests.get(zone_finder)
134         zone_data = res.json()
135         for zone_res in zone_data["results"]:
136             streetpart = self._format_key(zone_res["address"]).split(",")[0]
137 
138             if streetpart in self.street:
139                 self.zone_id = self._build_zone_string(zone_res["zones"])
140                 return self.zone_id
141 
142         raise Exception("Unable to find zone")
143 
144     def _build_zone_string(self, z_match):
145         """Take matching json and build a format zone-z12312-z1894323-z8461."""
146         zone_str = "zone"
147 
148         for zonekey in z_match:
149             zone_str += f"-{z_match[zonekey]}"
150 
151         return zone_str
152 
153     def fetch(self):
154         """Build the date fetching request through looking up address on separate endpoints, skip these requests if you can provide the district_id, project_id and/or zone_id."""
155         if not self.project_id or not self.district_id:
156             self._lookup_city()
157 
158         if not self.zone_id:
159             self._lookup_zones()
160 
161         collection_def_url = f"https://reg.my-waste.mobi/collections?project_id={self.project_id}&district_id={self.district_id}&zone_id={self.zone_id}&lang_cd=en_US"
162         schedule_url = f"https://pkg.my-waste.mobi/app_data_zone_schedules?project_id={self.project_id}&district_id={self.district_id}&zone_id={self.zone_id}"
163 
164         collection_def = None
165         schedule_def = None
166         collection_types = None
167 
168         response = requests.get(collection_def_url)
169         collection_def = json.loads(response.text)
170 
171         response = requests.get(schedule_url)
172         schedule_def = json.loads(response.text)
173 
174         collection_types = collection_def["collection"]["types"]
175 
176         entries = []
177         date_format = "%Y-%m-%d"
178 
179         for year in schedule_def["DATA"]:
180             for month in year["months"]:
181                 for event in month["events"]:
182                     for collection in event["collections"]:
183                         if collection["status"] == "is_none":
184                             continue
185                         ct = collection_types["collection-" + str(collection["id"])]
186                         c = Collection(
187                             datetime.strptime(event["date"], date_format).date(),
188                             ct["title"],
189                             ICON_MAP.get(ct["title"]),
190                         )
191                         entries.append(c)
192         return entries
193
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py
--- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py
+++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py
@@ -2,7 +2,7 @@
 from datetime import datetime
 
 import requests
-from waste_collection_schedule import Collection
+from waste_collection_schedule import Collection  # type: ignore[attr-defined]
 
 TITLE = "Recycle Coach"
 DESCRIPTION = "Source loader for recyclecoach.com"
@@ -38,6 +38,7 @@
     },
     {"title": "London (ON)", "url": "https://london.ca/", "country": "ca"},
     {"title": "Aurora (ON)", "url": "https://www.aurora.ca/", "country": "ca"},
+    {"title": "Vaughan (ON)", "url": "https://www.vaughan.ca/", "country": "ca"},
 ]
 
 TEST_CASES = {
@@ -78,6 +79,11 @@
         "city": "Aurora",
         "state": "Ontario",
     },
+    "Vaughan, Ontario, Canada": {  # https://app.my-waste.mobi/widget/576-Vaughan/home.php
+        "street": "Main St",
+        "city": "Vaughan",
+        "state": "Ontario",
+    },
 }
 
 
@@ -89,14 +95,14 @@
         self.city = self._format_key(city)
         self.state = self._format_key(state)
         self.project_id = self._format_key(project_id) if project_id else None
-        self.district_id = district_id.strip() if district_id else None
+        self.district_id = str(district_id).strip() if district_id else None
 
         self.zone_id = zone_id  # uses lowercase z's, not sure if matters
         self.stage = 0
 
     def _format_key(self, param):
         """Get rid of ambiguity in caps/spacing."""
-        return param.upper().strip()
+        return str(param).upper().strip()
 
     def _lookup_city(self):
         city_finder = f"https://recyclecoach.com/wp-json/rec/v1/cities?find={self.city}, {self.state}"
@@ -114,7 +120,6 @@
                 )
 
         elif len(city_data["cities"]) > 1:
-
             for city in city_data["cities"]:
                 if city["city_nm"].upper() == self.city.upper():
                     self.project_id = city["project_id"]
@@ -128,10 +133,35 @@
                 "Could not determine district or project, Debug here to find your discrict and project_id"
             )
 
+    def _lookup_zones_with_geo(self):
+        pos_finder = f"https://api-city.recyclecoach.com/geo/address?address={self.street}&uuid=ecdb86fe-e42d-4a9d-94d6-7057777ef283&project_id={self.project_id}&district_id={self.district_id}"
+        res = requests.get(pos_finder)
+        lat = None
+        pos_data = res.json()
+        for pos_res in pos_data:
+            streetpart = self._format_key(pos_res["address"]).split(",")[0]
+
+            if streetpart in self.street:
+                lat = pos_res["lat"]
+                lng = pos_res["lng"]
+                break
+
+        if not lat:
+            raise Exception("Unable to find zone")
+
+        zone_finder = f"https://pkg.my-waste.mobi/get_zones?project_id={self.project_id}&district_id={self.district_id}&lat={lat}&lng={lng}"
+        res = requests.get(zone_finder)
+        zone_data = {z["prompt_id"]: "z" + z["zone_id"] for z in res.json()}
+        self.zone_id = self._build_zone_string(zone_data)
+
+        return self.zone_id
+
     def _lookup_zones(self):
         zone_finder = f"https://api-city.recyclecoach.com/zone-setup/address?sku={self.project_id}&district={self.district_id}&prompt=undefined&term={self.street}"
         res = requests.get(zone_finder)
         zone_data = res.json()
+        if "results" not in zone_data:
+            return self._lookup_zones_with_geo()
         for zone_res in zone_data["results"]:
             streetpart = self._format_key(zone_res["address"]).split(",")[0]

{"golden_diff": "diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py\n--- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py\n+++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py\n@@ -2,7 +2,7 @@\n from datetime import datetime\n \n import requests\n-from waste_collection_schedule import Collection\n+from waste_collection_schedule import Collection  # type: ignore[attr-defined]\n \n TITLE = \"Recycle Coach\"\n DESCRIPTION = \"Source loader for recyclecoach.com\"\n@@ -38,6 +38,7 @@\n     },\n     {\"title\": \"London (ON)\", \"url\": \"https://london.ca/\", \"country\": \"ca\"},\n     {\"title\": \"Aurora (ON)\", \"url\": \"https://www.aurora.ca/\", \"country\": \"ca\"},\n+    {\"title\": \"Vaughan (ON)\", \"url\": \"https://www.vaughan.ca/\", \"country\": \"ca\"},\n ]\n \n TEST_CASES = {\n@@ -78,6 +79,11 @@\n         \"city\": \"Aurora\",\n         \"state\": \"Ontario\",\n     },\n+    \"Vaughan, Ontario, Canada\": {  # https://app.my-waste.mobi/widget/576-Vaughan/home.php\n+        \"street\": \"Main St\",\n+        \"city\": \"Vaughan\",\n+        \"state\": \"Ontario\",\n+    },\n }\n \n \n@@ -89,14 +95,14 @@\n         self.city = self._format_key(city)\n         self.state = self._format_key(state)\n         self.project_id = self._format_key(project_id) if project_id else None\n-        self.district_id = district_id.strip() if district_id else None\n+        self.district_id = str(district_id).strip() if district_id else None\n \n         self.zone_id = zone_id  # uses lowercase z's, not sure if matters\n         self.stage = 0\n \n     def _format_key(self, param):\n         \"\"\"Get rid of ambiguity in caps/spacing.\"\"\"\n-        return param.upper().strip()\n+        return str(param).upper().strip()\n \n     def _lookup_city(self):\n         city_finder = f\"https://recyclecoach.com/wp-json/rec/v1/cities?find={self.city}, {self.state}\"\n@@ -114,7 +120,6 @@\n                 )\n \n         elif len(city_data[\"cities\"]) > 1:\n-\n             for city in city_data[\"cities\"]:\n                 if city[\"city_nm\"].upper() == self.city.upper():\n                     self.project_id = city[\"project_id\"]\n@@ -128,10 +133,35 @@\n                 \"Could not determine district or project, Debug here to find your discrict and project_id\"\n             )\n \n+    def _lookup_zones_with_geo(self):\n+        pos_finder = f\"https://api-city.recyclecoach.com/geo/address?address={self.street}&uuid=ecdb86fe-e42d-4a9d-94d6-7057777ef283&project_id={self.project_id}&district_id={self.district_id}\"\n+        res = requests.get(pos_finder)\n+        lat = None\n+        pos_data = res.json()\n+        for pos_res in pos_data:\n+            streetpart = self._format_key(pos_res[\"address\"]).split(\",\")[0]\n+\n+            if streetpart in self.street:\n+                lat = pos_res[\"lat\"]\n+                lng = pos_res[\"lng\"]\n+                break\n+\n+        if not lat:\n+            raise Exception(\"Unable to find zone\")\n+\n+        zone_finder = f\"https://pkg.my-waste.mobi/get_zones?project_id={self.project_id}&district_id={self.district_id}&lat={lat}&lng={lng}\"\n+        res = requests.get(zone_finder)\n+        zone_data = {z[\"prompt_id\"]: \"z\" + z[\"zone_id\"] for z in res.json()}\n+        self.zone_id = self._build_zone_string(zone_data)\n+\n+        return self.zone_id\n+\n     def _lookup_zones(self):\n         zone_finder = f\"https://api-city.recyclecoach.com/zone-setup/address?sku={self.project_id}&district={self.district_id}&prompt=undefined&term={self.street}\"\n         res = requests.get(zone_finder)\n         zone_data = res.json()\n+        if \"results\" not in zone_data:\n+            return self._lookup_zones_with_geo()\n         for zone_res in zone_data[\"results\"]:\n             streetpart = self._format_key(zone_res[\"address\"]).split(\",\")[0]\n", "issue": "[Bug]: Error when configuring Recycle Coach\n### I Have A Problem With:\n\nA specific source\n\n### What's Your Problem\n\nReceiving error when configuring recycle coach for Vaughan Ontarion (unlisted city, but supported by recycle coach)\r\n\n\n### Source (if relevant)\n\nrecycle coach\n\n### Logs\n\n```Shell\nLogger: waste_collection_schedule.source_shell\r\nSource: custom_components/waste_collection_schedule/waste_collection_schedule/source_shell.py:136\r\nIntegration: waste_collection_schedule (documentation)\r\nFirst occurred: 3:10:21 PM (1 occurrences)\r\nLast logged: 3:10:21 PM\r\n\r\nfetch failed for source Recycle Coach: Traceback (most recent call last): File \"/config/custom_components/waste_collection_schedule/waste_collection_schedule/source_shell.py\", line 134, in fetch entries = self._source.fetch() ^^^^^^^^^^^^^^^^^^^^ File \"/config/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py\", line 159, in fetch self._lookup_zones() File \"/config/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py\", line 135, in _lookup_zones for zone_res in zone_data[\"results\"]: ~~~~~~~~~^^^^^^^^^^^ KeyError: 'results'\n```\n\n\n### Relevant Configuration\n\n```YAML\nwaste_collection_schedule:\r\n  sources:\r\n  - name: recyclecoach_com \r\n    calendar_title: Waste Collection Schedule\r\n    args:\r\n      street: <<street address in lowercase>>\r\n      city: <<city in lowercase>>\r\n      state: <<province in lowercase>>\n```\n\n\n### Checklist Source Error\n\n- [X] Use the example parameters for your source (often available in the documentation) (don't forget to restart Home Assistant after changing the configuration)\n- [X] Checked that the website of your service provider is still working\n- [X] Tested my attributes on the service provider website (if possible)\n- [X] I have tested with the latest version of the integration (master) (for HACS in the 3 dot menu of the integration click on \"Redownload\" and choose master as version)\n\n### Checklist Sensor Error\n\n- [ ] Checked in the Home Assistant Calendar tab if the event names match the types names (if types argument is used)\n\n### Required\n\n- [X] I have searched past (closed AND opened) issues to see if this bug has already been reported, and it hasn't been.\n- [X] I understand that people give their precious time for free, and thus I've done my very best to make this problem as easy as possible to investigate.\n", "before_files": [{"content": "import json\nfrom datetime import datetime\n\nimport requests\nfrom waste_collection_schedule import Collection\n\nTITLE = \"Recycle Coach\"\nDESCRIPTION = \"Source loader for recyclecoach.com\"\nURL = \"https://recyclecoach.com\"\nCOUNTRY = \"us\"\n\nICON_MAP = {\n    \"Garbage\": \"mdi:trash-can\",\n    \"Recycling\": \"mdi:recycle\",\n    \"Yard Waste\": \"mdi:leaf\",\n}\n\nEXTRA_INFO = [\n    {\n        \"title\": \"Albuquerque, New Mexico, USA\",\n        \"url\": \"https://recyclecoach.com/cities/usa-nm-city-of-albuquerque/\",\n    },\n    {\n        \"title\": \"Tucson, Arizona, USA\",\n        \"url\": \"https://recyclecoach.com/cities/usa-az-city-of-tucson/\",\n    },\n    {\n        \"title\": \"Olympia, Washington, USA\",\n        \"url\": \"https://recyclecoach.com/cities/usa-wa-city-of-olympia/\",\n    },\n    {\n        \"title\": \"Newark, Delaware, USA\",\n        \"url\": \"https://recyclecoach.com/cities/usa-de-city-of-newark/\",\n    },\n    {\n        \"title\": \"Louisville, Kentucky, USA\",\n        \"url\": \"https://recyclecoach.com/cities/usa-ky-city-of-louisville/\",\n    },\n    {\"title\": \"London (ON)\", \"url\": \"https://london.ca/\", \"country\": \"ca\"},\n    {\"title\": \"Aurora (ON)\", \"url\": \"https://www.aurora.ca/\", \"country\": \"ca\"},\n]\n\nTEST_CASES = {\n    \"Default\": {\"street\": \"2242 grinstead drive\", \"city\": \"louisville\", \"state\": \"KY\"},\n    \"Problematic City Lookup\": {\n        \"street\": \"2202 E Florence Dr\",\n        \"city\": \"Tucson\",\n        \"state\": \"AZ\",\n        \"district_id\": \"TUC\",\n        \"project_id\": \"532\",\n    },\n    \"olympia\": {\n        \"street\": \"1003 Lybarger St NE\",\n        \"city\": \"Olympia\",\n        \"state\": \"Washington\",\n    },\n    \"newark\": {\"street\": \"24 Townsend Rd\", \"city\": \"Newark\", \"state\": \"Delaware\"},\n    \"albuquerque\": {\n        \"street\": \"1505 Silver Ave SE\",\n        \"city\": \"Albuquerque\",\n        \"state\": \"New Mexico\",\n    },\n    \"london ontario\": {\n        \"street\": \"1065 Sunningdale Rd E\",\n        \"city\": \"London\",\n        \"state\": \"Ontario\",\n    },\n    \"london ontario with districtID\": {\n        \"street\": \"1065 Sunningdale Rd E\",\n        \"city\": \"London\",\n        \"state\": \"Ontario\",\n        \"project_id\": \"528\",\n        \"district_id\": \"CityofLondon\",\n        \"zone_id\": \"zone-z547\",\n    },\n    \"aurora ontario\": {\n        \"street\": \"123 Cranberry Lane\",\n        \"city\": \"Aurora\",\n        \"state\": \"Ontario\",\n    },\n}\n\n\nclass Source:\n    def __init__(\n        self, street, city, state, project_id=None, district_id=None, zone_id=None\n    ):  # argX correspond to the args dict in the source configuration\n        self.street = self._format_key(street)\n        self.city = self._format_key(city)\n        self.state = self._format_key(state)\n        self.project_id = self._format_key(project_id) if project_id else None\n        self.district_id = district_id.strip() if district_id else None\n\n        self.zone_id = zone_id  # uses lowercase z's, not sure if matters\n        self.stage = 0\n\n    def _format_key(self, param):\n        \"\"\"Get rid of ambiguity in caps/spacing.\"\"\"\n        return param.upper().strip()\n\n    def _lookup_city(self):\n        city_finder = f\"https://recyclecoach.com/wp-json/rec/v1/cities?find={self.city}, {self.state}\"\n        res = requests.get(city_finder)\n        city_data = res.json()\n\n        if len(city_data[\"cities\"]) == 1:\n            self.project_id = city_data[\"cities\"][0][\"project_id\"]\n            self.district_id = city_data[\"cities\"][0][\"district_id\"]\n            self.stage = float(city_data[\"cities\"][0][\"stage\"])\n\n            if self.stage < 3:\n                raise Exception(\n                    \"Found your city, but it is not yet supported fully by recycle coach.\"\n                )\n\n        elif len(city_data[\"cities\"]) > 1:\n\n            for city in city_data[\"cities\"]:\n                if city[\"city_nm\"].upper() == self.city.upper():\n                    self.project_id = city[\"project_id\"]\n                    self.district_id = city[\"district_id\"]\n                    self.stage = float(city[\"stage\"])\n                    return True\n\n            # not sure what to do with ambiguity here\n            # print(json.dumps(city_data['cities'], indent=4))\n            raise Exception(\n                \"Could not determine district or project, Debug here to find your discrict and project_id\"\n            )\n\n    def _lookup_zones(self):\n        zone_finder = f\"https://api-city.recyclecoach.com/zone-setup/address?sku={self.project_id}&district={self.district_id}&prompt=undefined&term={self.street}\"\n        res = requests.get(zone_finder)\n        zone_data = res.json()\n        for zone_res in zone_data[\"results\"]:\n            streetpart = self._format_key(zone_res[\"address\"]).split(\",\")[0]\n\n            if streetpart in self.street:\n                self.zone_id = self._build_zone_string(zone_res[\"zones\"])\n                return self.zone_id\n\n        raise Exception(\"Unable to find zone\")\n\n    def _build_zone_string(self, z_match):\n        \"\"\"Take matching json and build a format zone-z12312-z1894323-z8461.\"\"\"\n        zone_str = \"zone\"\n\n        for zonekey in z_match:\n            zone_str += f\"-{z_match[zonekey]}\"\n\n        return zone_str\n\n    def fetch(self):\n        \"\"\"Build the date fetching request through looking up address on separate endpoints, skip these requests if you can provide the district_id, project_id and/or zone_id.\"\"\"\n        if not self.project_id or not self.district_id:\n            self._lookup_city()\n\n        if not self.zone_id:\n            self._lookup_zones()\n\n        collection_def_url = f\"https://reg.my-waste.mobi/collections?project_id={self.project_id}&district_id={self.district_id}&zone_id={self.zone_id}&lang_cd=en_US\"\n        schedule_url = f\"https://pkg.my-waste.mobi/app_data_zone_schedules?project_id={self.project_id}&district_id={self.district_id}&zone_id={self.zone_id}\"\n\n        collection_def = None\n        schedule_def = None\n        collection_types = None\n\n        response = requests.get(collection_def_url)\n        collection_def = json.loads(response.text)\n\n        response = requests.get(schedule_url)\n        schedule_def = json.loads(response.text)\n\n        collection_types = collection_def[\"collection\"][\"types\"]\n\n        entries = []\n        date_format = \"%Y-%m-%d\"\n\n        for year in schedule_def[\"DATA\"]:\n            for month in year[\"months\"]:\n                for event in month[\"events\"]:\n                    for collection in event[\"collections\"]:\n                        if collection[\"status\"] == \"is_none\":\n                            continue\n                        ct = collection_types[\"collection-\" + str(collection[\"id\"])]\n                        c = Collection(\n                            datetime.strptime(event[\"date\"], date_format).date(),\n                            ct[\"title\"],\n                            ICON_MAP.get(ct[\"title\"]),\n                        )\n                        entries.append(c)\n        return entries\n", "path": "custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py"}], "after_files": [{"content": "import json\nfrom datetime import datetime\n\nimport requests\nfrom waste_collection_schedule import Collection  # type: ignore[attr-defined]\n\nTITLE = \"Recycle Coach\"\nDESCRIPTION = \"Source loader for recyclecoach.com\"\nURL = \"https://recyclecoach.com\"\nCOUNTRY = \"us\"\n\nICON_MAP = {\n    \"Garbage\": \"mdi:trash-can\",\n    \"Recycling\": \"mdi:recycle\",\n    \"Yard Waste\": \"mdi:leaf\",\n}\n\nEXTRA_INFO = [\n    {\n        \"title\": \"Albuquerque, New Mexico, USA\",\n        \"url\": \"https://recyclecoach.com/cities/usa-nm-city-of-albuquerque/\",\n    },\n    {\n        \"title\": \"Tucson, Arizona, USA\",\n        \"url\": \"https://recyclecoach.com/cities/usa-az-city-of-tucson/\",\n    },\n    {\n        \"title\": \"Olympia, Washington, USA\",\n        \"url\": \"https://recyclecoach.com/cities/usa-wa-city-of-olympia/\",\n    },\n    {\n        \"title\": \"Newark, Delaware, USA\",\n        \"url\": \"https://recyclecoach.com/cities/usa-de-city-of-newark/\",\n    },\n    {\n        \"title\": \"Louisville, Kentucky, USA\",\n        \"url\": \"https://recyclecoach.com/cities/usa-ky-city-of-louisville/\",\n    },\n    {\"title\": \"London (ON)\", \"url\": \"https://london.ca/\", \"country\": \"ca\"},\n    {\"title\": \"Aurora (ON)\", \"url\": \"https://www.aurora.ca/\", \"country\": \"ca\"},\n    {\"title\": \"Vaughan (ON)\", \"url\": \"https://www.vaughan.ca/\", \"country\": \"ca\"},\n]\n\nTEST_CASES = {\n    \"Default\": {\"street\": \"2242 grinstead drive\", \"city\": \"louisville\", \"state\": \"KY\"},\n    \"Problematic City Lookup\": {\n        \"street\": \"2202 E Florence Dr\",\n        \"city\": \"Tucson\",\n        \"state\": \"AZ\",\n        \"district_id\": \"TUC\",\n        \"project_id\": \"532\",\n    },\n    \"olympia\": {\n        \"street\": \"1003 Lybarger St NE\",\n        \"city\": \"Olympia\",\n        \"state\": \"Washington\",\n    },\n    \"newark\": {\"street\": \"24 Townsend Rd\", \"city\": \"Newark\", \"state\": \"Delaware\"},\n    \"albuquerque\": {\n        \"street\": \"1505 Silver Ave SE\",\n        \"city\": \"Albuquerque\",\n        \"state\": \"New Mexico\",\n    },\n    \"london ontario\": {\n        \"street\": \"1065 Sunningdale Rd E\",\n        \"city\": \"London\",\n        \"state\": \"Ontario\",\n    },\n    \"london ontario with districtID\": {\n        \"street\": \"1065 Sunningdale Rd E\",\n        \"city\": \"London\",\n        \"state\": \"Ontario\",\n        \"project_id\": \"528\",\n        \"district_id\": \"CityofLondon\",\n        \"zone_id\": \"zone-z547\",\n    },\n    \"aurora ontario\": {\n        \"street\": \"123 Cranberry Lane\",\n        \"city\": \"Aurora\",\n        \"state\": \"Ontario\",\n    },\n    \"Vaughan, Ontario, Canada\": {  # https://app.my-waste.mobi/widget/576-Vaughan/home.php\n        \"street\": \"Main St\",\n        \"city\": \"Vaughan\",\n        \"state\": \"Ontario\",\n    },\n}\n\n\nclass Source:\n    def __init__(\n        self, street, city, state, project_id=None, district_id=None, zone_id=None\n    ):  # argX correspond to the args dict in the source configuration\n        self.street = self._format_key(street)\n        self.city = self._format_key(city)\n        self.state = self._format_key(state)\n        self.project_id = self._format_key(project_id) if project_id else None\n        self.district_id = str(district_id).strip() if district_id else None\n\n        self.zone_id = zone_id  # uses lowercase z's, not sure if matters\n        self.stage = 0\n\n    def _format_key(self, param):\n        \"\"\"Get rid of ambiguity in caps/spacing.\"\"\"\n        return str(param).upper().strip()\n\n    def _lookup_city(self):\n        city_finder = f\"https://recyclecoach.com/wp-json/rec/v1/cities?find={self.city}, {self.state}\"\n        res = requests.get(city_finder)\n        city_data = res.json()\n\n        if len(city_data[\"cities\"]) == 1:\n            self.project_id = city_data[\"cities\"][0][\"project_id\"]\n            self.district_id = city_data[\"cities\"][0][\"district_id\"]\n            self.stage = float(city_data[\"cities\"][0][\"stage\"])\n\n            if self.stage < 3:\n                raise Exception(\n                    \"Found your city, but it is not yet supported fully by recycle coach.\"\n                )\n\n        elif len(city_data[\"cities\"]) > 1:\n            for city in city_data[\"cities\"]:\n                if city[\"city_nm\"].upper() == self.city.upper():\n                    self.project_id = city[\"project_id\"]\n                    self.district_id = city[\"district_id\"]\n                    self.stage = float(city[\"stage\"])\n                    return True\n\n            # not sure what to do with ambiguity here\n            # print(json.dumps(city_data['cities'], indent=4))\n            raise Exception(\n                \"Could not determine district or project, Debug here to find your discrict and project_id\"\n            )\n\n    def _lookup_zones_with_geo(self):\n        pos_finder = f\"https://api-city.recyclecoach.com/geo/address?address={self.street}&uuid=ecdb86fe-e42d-4a9d-94d6-7057777ef283&project_id={self.project_id}&district_id={self.district_id}\"\n        res = requests.get(pos_finder)\n        lat = None\n        pos_data = res.json()\n        for pos_res in pos_data:\n            streetpart = self._format_key(pos_res[\"address\"]).split(\",\")[0]\n\n            if streetpart in self.street:\n                lat = pos_res[\"lat\"]\n                lng = pos_res[\"lng\"]\n                break\n\n        if not lat:\n            raise Exception(\"Unable to find zone\")\n\n        zone_finder = f\"https://pkg.my-waste.mobi/get_zones?project_id={self.project_id}&district_id={self.district_id}&lat={lat}&lng={lng}\"\n        res = requests.get(zone_finder)\n        zone_data = {z[\"prompt_id\"]: \"z\" + z[\"zone_id\"] for z in res.json()}\n        self.zone_id = self._build_zone_string(zone_data)\n\n        return self.zone_id\n\n    def _lookup_zones(self):\n        zone_finder = f\"https://api-city.recyclecoach.com/zone-setup/address?sku={self.project_id}&district={self.district_id}&prompt=undefined&term={self.street}\"\n        res = requests.get(zone_finder)\n        zone_data = res.json()\n        if \"results\" not in zone_data:\n            return self._lookup_zones_with_geo()\n        for zone_res in zone_data[\"results\"]:\n            streetpart = self._format_key(zone_res[\"address\"]).split(\",\")[0]\n\n            if streetpart in self.street:\n                self.zone_id = self._build_zone_string(zone_res[\"zones\"])\n                return self.zone_id\n\n        raise Exception(\"Unable to find zone\")\n\n    def _build_zone_string(self, z_match):\n        \"\"\"Take matching json and build a format zone-z12312-z1894323-z8461.\"\"\"\n        zone_str = \"zone\"\n\n        for zonekey in z_match:\n            zone_str += f\"-{z_match[zonekey]}\"\n\n        return zone_str\n\n    def fetch(self):\n        \"\"\"Build the date fetching request through looking up address on separate endpoints, skip these requests if you can provide the district_id, project_id and/or zone_id.\"\"\"\n        if not self.project_id or not self.district_id:\n            self._lookup_city()\n\n        if not self.zone_id:\n            self._lookup_zones()\n\n        collection_def_url = f\"https://reg.my-waste.mobi/collections?project_id={self.project_id}&district_id={self.district_id}&zone_id={self.zone_id}&lang_cd=en_US\"\n        schedule_url = f\"https://pkg.my-waste.mobi/app_data_zone_schedules?project_id={self.project_id}&district_id={self.district_id}&zone_id={self.zone_id}\"\n\n        collection_def = None\n        schedule_def = None\n        collection_types = None\n\n        response = requests.get(collection_def_url)\n        collection_def = json.loads(response.text)\n\n        response = requests.get(schedule_url)\n        schedule_def = json.loads(response.text)\n\n        collection_types = collection_def[\"collection\"][\"types\"]\n\n        entries = []\n        date_format = \"%Y-%m-%d\"\n\n        for year in schedule_def[\"DATA\"]:\n            for month in year[\"months\"]:\n                for event in month[\"events\"]:\n                    for collection in event[\"collections\"]:\n                        if collection[\"status\"] == \"is_none\":\n                            continue\n                        ct = collection_types[\"collection-\" + str(collection[\"id\"])]\n                        c = Collection(\n                            datetime.strptime(event[\"date\"], date_format).date(),\n                            ct[\"title\"],\n                            ICON_MAP.get(ct[\"title\"]),\n                        )\n                        entries.append(c)\n        return entries\n", "path": "custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclecoach_com.py"}]}