Skip to content

Commit 2ab5027

Browse files
authored
Merge pull request #5565 from flyingsubs/fix/philadelphia-recycling-holiday
fix: Philadelphia recycling and holiday logic
2 parents a2b05f3 + 8f2774f commit 2ab5027

1 file changed

Lines changed: 54 additions & 33 deletions

File tree

  • custom_components/waste_collection_schedule/waste_collection_schedule/source

custom_components/waste_collection_schedule/waste_collection_schedule/source/phila_gov.py

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,17 @@
2929
"Recycle": "mdi:recycle",
3030
}
3131

32-
# ### Arguments affecting the configuration GUI ####
33-
34-
HOW_TO_GET_ARGUMENTS_DESCRIPTION = { # Optional dictionary to describe how to get the arguments, will be shown in the GUI configuration form above the input fields, does not need to be translated in all languages
32+
HOW_TO_GET_ARGUMENTS_DESCRIPTION = {
3533
"en": "Search for your collection schedule at [phila.gov](https://www.phila.gov/services/trash-recycling-city-upkeep/find-your-trash-and-recycling-collection-day), use your address as it is displayed on the search results.",
3634
}
3735

38-
PARAM_DESCRIPTIONS = { # Optional dict to describe the arguments, will be shown in the GUI configuration below the respective input field
36+
PARAM_DESCRIPTIONS = {
3937
"en": {
4038
"address": "Street name and house number of the property",
4139
},
4240
}
4341

44-
PARAM_TRANSLATIONS = { # Optional dict to translate the arguments, will be shown in the GUI configuration form as placeholder text
42+
PARAM_TRANSLATIONS = {
4543
"en": {
4644
"address": "Street name and house number of the property",
4745
},
@@ -57,61 +55,84 @@ def create_dates(self, days: list, start: date, end: date):
5755
return dts
5856

5957
def check_holidays(self, hols: list[date], dt: date) -> date:
60-
# collections shifted by 1 day if they fall on a holiday
61-
# if adjusted day is also a holiday, it shifts again
62-
if dt in hols:
63-
x = dt + timedelta(days=1)
64-
x = self.check_holidays(hols, x)
65-
else:
66-
x = dt
67-
return x
58+
"""
59+
Shift a collection date forward for holidays in the same week.
60+
61+
Rules per phila.gov:
62+
- Weekend holidays: already excluded from hols, no effect.
63+
- One holiday in the week: all collections on or after that holiday
64+
are shifted by 1 day.
65+
- Two holidays in the same week: collections shift by 1 or 2 days
66+
depending on how many holidays fall on or before the collection day.
67+
- If the shifted date itself lands on a holiday, shift again.
68+
"""
69+
# find all weekday holidays in the same Mon-Sun week as dt
70+
week_start = dt - timedelta(days=dt.weekday()) # Monday
71+
week_end = week_start + timedelta(days=6) # Sunday
72+
week_hols = sorted(h for h in hols if week_start <= h <= week_end)
73+
74+
# shift by 1 for each holiday that falls on or before the collection day
75+
shift = sum(1 for h in week_hols if h <= dt)
76+
adjusted = dt + timedelta(days=shift)
77+
78+
# if the adjusted date itself is a holiday, shift once more
79+
if adjusted in hols and adjusted != dt:
80+
adjusted = self.check_holidays(hols, adjusted)
81+
82+
return adjusted
6883

6984
def fetch(self) -> list[Collection]:
7085
s = requests.Session()
7186

7287
# get list of observed holidays
88+
# weekend holidays are excluded as they don't affect collections
7389
holidays = []
7490
h_json = s.get(
7591
"https://api.phila.gov/phila/trashday/v1",
7692
headers=HEADERS,
7793
).json()
7894
for item in h_json["holidays"]:
7995
h_date = datetime.strptime(item["start_date"], "%Y-%m-%d").date()
80-
# weekend holidays don't impact collections so remove them
8196
if h_date.weekday() not in [5, 6]:
8297
holidays.append(h_date)
8398

8499
# get property info
85100
p_json = s.get(
86-
f"https://api.phila.gov/ais/v1/addresses/{(self._address)}", headers=HEADERS
101+
f"https://api.phila.gov/ais/v1/addresses/{self._address}",
102+
headers=HEADERS,
87103
).json()
104+
105+
props = p_json["features"][0]["properties"]
106+
88107
# extract collection days
108+
# rubbish_recycle_day = primary day for BOTH trash and recycling
109+
# secondary_rubbish_day = second trash-only day (some addresses)
89110
waste_days = []
90111
recycle_days = []
91-
for item in p_json["features"][0]["properties"]:
92-
if "rubbish_" in item:
93-
try:
94-
waste_days.append(DAYS[p_json["features"][0]["properties"][item]])
95-
except KeyError: # not all keys have values
96-
pass
97-
if "recycle_" in item:
98-
try:
99-
recycle_days.append(DAYS[p_json["features"][0]["properties"][item]])
100-
except KeyError: # not all keys have values
101-
pass
102-
# generate day dates
112+
113+
for key in props:
114+
val = props[key]
115+
if not isinstance(val, str) or val.upper() not in DAYS:
116+
continue
117+
day = DAYS[val.upper()]
118+
if "rubbish" in key.lower():
119+
# rubbish_recycle_day and secondary_rubbish_day both go into trash
120+
waste_days.append(day)
121+
if key.lower() == "rubbish_recycle_day":
122+
# the primary day is also the recycling day
123+
recycle_days.append(day)
124+
125+
# generate day dates for the current year
103126
year = datetime.now().year
104127
start: date = date(year, 1, 1)
105128
end: date = date(year, 12, 31)
106129
trash = self.create_dates(waste_days, start, end)
107130
recycle = self.create_dates(recycle_days, start, end)
131+
108132
# adjust for observed holidays
109-
adjusted_trash = []
110-
adjusted_recycling = []
111-
for item in trash:
112-
adjusted_trash.append(self.check_holidays(holidays, item.date()))
113-
for item in recycle:
114-
adjusted_recycling.append(self.check_holidays(holidays, item.date()))
133+
adjusted_trash = [self.check_holidays(holidays, d.date()) for d in trash]
134+
adjusted_recycling = [self.check_holidays(holidays, d.date()) for d in recycle]
135+
115136
waste_schedule = list(set(adjusted_trash))
116137
recycle_schedule = list(set(adjusted_recycling))
117138

0 commit comments

Comments
 (0)