Skip to content

fix(LondonBoroughCamdenCouncil): rewrite for new waste portal API#2052

Open
InertiaUK wants to merge 1 commit into
robbrad:masterfrom
InertiaUK:fix/camden-rewrite-nextjs-api
Open

fix(LondonBoroughCamdenCouncil): rewrite for new waste portal API#2052
InertiaUK wants to merge 1 commit into
robbrad:masterfrom
InertiaUK:fix/camden-rewrite-nextjs-api

Conversation

@InertiaUK
Copy link
Copy Markdown
Contributor

@InertiaUK InertiaUK commented May 12, 2026

Summary

  • Camden migrated from environmentservices.camden.gov.uk (server-rendered HTML) to recyclingandrubbishcollections.camden.gov.uk (Next.js SPA with JSON API)
  • Old scraper parsed HTML service-wrapper divs which no longer exist
  • Rewrote as pure requests hitting the new JSON API:
    1. POST /api/getPropertySearch with postcode to resolve property ID from UPRN
    2. POST /api/getCollectionDays with pointId/pointType/councilId to get schedules
  • No Selenium needed, no reCAPTCHA token required
  • Verified returning Rubbish, Food, and Recycling collection dates

Summary by CodeRabbit

  • Chores
    • Updated Camden Council bin collection data retrieval to use official API endpoints for improved reliability and accuracy.

Review Change Stack

Camden migrated from environmentservices.camden.gov.uk (server-rendered
HTML tables) to recyclingandrubbishcollections.camden.gov.uk (Next.js
SPA with JSON API).

Rewrote as pure requests: POST /api/getPropertySearch (postcode lookup
to resolve property ID from UPRN) then POST /api/getCollectionDays
(pointId + pointType PointAddress, councilId 27).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

Walkthrough

Camden Council's bin collection module transitions from HTML page scraping using BeautifulSoup to calling the council's recycling API. It now defines API credentials, searches for properties by postcode with optional UPRN matching, fetches active service schedules, and returns upcoming collection dates sorted chronologically.

Changes

Camden Council API Integration

Layer / File(s) Summary
API-based property and collection retrieval
uk_bin_collection/uk_bin_collection/councils/LondonBoroughCamdenCouncil.py
Imports simplified by removing BeautifulSoup; module defines BASE_URL and COUNCIL_ID constants. Core function now uses JSON POST requests to resolve property IDs by postcode with UPRN matching (defaulting to first result), fetches active service schedules, collects all dates on/after today, formats dates, and sorts results by collection date.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Suggested reviewers

  • dp247

Poem

🐰 A Camden bin collector hops with glee,
No more parsing HTML in a spree,
APIs guide the way so clean,
Dates sorted bright, collection serene,
The postcode whispers, UPRN agrees!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately reflects the main change: rewriting the LondonBoroughCamdenCouncil module to use a new waste portal API instead of the previous HTML scraping approach.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.67%. Comparing base (8ecf878) to head (c6a6a8c).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #2052   +/-   ##
=======================================
  Coverage   86.67%   86.67%           
=======================================
  Files           9        9           
  Lines        1141     1141           
=======================================
  Hits          989      989           
  Misses        152      152           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@uk_bin_collection/uk_bin_collection/councils/LondonBoroughCamdenCouncil.py`:
- Around line 64-70: The loop over collection_data currently tolerates
missing/changed payloads by using permissive defaults; instead validate and fail
loudly: check that collection_data contains "activeServices" and that it's a
list, raise a ValueError if not; inside the loop assert each service has a
non-empty "serviceName" (don't default to "Unknown") and that "serviceSchedules"
is present and is a list, and for each schedule assert "currentScheduledDate"
exists and is a parseable date (raise an exception if missing/invalid). Update
the code around collection_data, activeServices, serviceName, serviceSchedules
and currentScheduledDate to perform these explicit validations and raise
descriptive exceptions rather than silently continuing.
- Around line 43-45: The code currently falls back to properties[0]["id"] when
point_id is falsy, which silently returns another address's data if an uprn was
supplied but not matched; instead, in the method where point_id, properties and
uprn are handled (LondonBoroughCamdenCouncil.py, symbols: point_id, properties,
uprn), remove the silent fallback and add an explicit failure: if an uprn was
provided and no matching property was found, raise a clear exception (e.g.,
ValueError) containing the uprn and a descriptive message; keep the existing
behavior of selecting properties[0]["id"] only when no uprn was supplied (if
that is intended), otherwise eliminate the fallback entirely per the review.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 97604f43-d3e4-4bb2-a608-2c808042f937

📥 Commits

Reviewing files that changed from the base of the PR and between 8ecf878 and c6a6a8c.

📒 Files selected for processing (1)
  • uk_bin_collection/uk_bin_collection/councils/LondonBoroughCamdenCouncil.py

Comment on lines +43 to +45
# Fallback: first property if UPRN not matched
if not point_id:
point_id = properties[0]["id"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail on UPRN mismatch instead of falling back to the first property.

At Line 44, defaulting to properties[0]["id"] can return another address’s collection data when uprn is provided but not found.

Suggested fix
-        # Fallback: first property if UPRN not matched
-        if not point_id:
-            point_id = properties[0]["id"]
+        if not point_id:
+            raise ValueError(
+                f"UPRN {user_uprn} not found for postcode: {user_postcode}"
+            )

Based on learnings: “prefer explicit failures (raise exceptions on unexpected formats) over silent defaults or swallowed errors.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@uk_bin_collection/uk_bin_collection/councils/LondonBoroughCamdenCouncil.py`
around lines 43 - 45, The code currently falls back to properties[0]["id"] when
point_id is falsy, which silently returns another address's data if an uprn was
supplied but not matched; instead, in the method where point_id, properties and
uprn are handled (LondonBoroughCamdenCouncil.py, symbols: point_id, properties,
uprn), remove the silent fallback and add an explicit failure: if an uprn was
provided and no matching property was found, raise a clear exception (e.g.,
ValueError) containing the uprn and a descriptive message; keep the existing
behavior of selecting properties[0]["id"] only when no uprn was supplied (if
that is intended), otherwise eliminate the fallback entirely per the review.

Comment on lines +64 to +70
for service in collection_data.get("activeServices", []):
bin_type = service.get("serviceName", "Unknown")

for schedule in service.get("serviceSchedules", []):
date_str = schedule.get("currentScheduledDate")
if not date_str:
continue
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid silent defaults when parsing activeServices payload fields.

Using permissive defaults (activeServices -> [], serviceName -> "Unknown", silently skipping missing dates) can mask upstream API changes and produce misleading empty/partial output.

Suggested fix
-        for service in collection_data.get("activeServices", []):
-            bin_type = service.get("serviceName", "Unknown")
+        active_services = collection_data.get("activeServices")
+        if not isinstance(active_services, list):
+            raise ValueError("Unexpected collection response: missing/invalid activeServices")
+
+        for service in active_services:
+            if "serviceName" not in service:
+                raise ValueError("Unexpected collection response: missing serviceName")
+            bin_type = service["serviceName"]

Based on learnings: “prefer explicit failures (raise exceptions on unexpected formats) over silent defaults or swallowed errors.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for service in collection_data.get("activeServices", []):
bin_type = service.get("serviceName", "Unknown")
for schedule in service.get("serviceSchedules", []):
date_str = schedule.get("currentScheduledDate")
if not date_str:
continue
active_services = collection_data.get("activeServices")
if not isinstance(active_services, list):
raise ValueError("Unexpected collection response: missing/invalid activeServices")
for service in active_services:
if "serviceName" not in service:
raise ValueError("Unexpected collection response: missing serviceName")
bin_type = service["serviceName"]
for schedule in service.get("serviceSchedules", []):
date_str = schedule.get("currentScheduledDate")
if not date_str:
continue
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@uk_bin_collection/uk_bin_collection/councils/LondonBoroughCamdenCouncil.py`
around lines 64 - 70, The loop over collection_data currently tolerates
missing/changed payloads by using permissive defaults; instead validate and fail
loudly: check that collection_data contains "activeServices" and that it's a
list, raise a ValueError if not; inside the loop assert each service has a
non-empty "serviceName" (don't default to "Unknown") and that "serviceSchedules"
is present and is a list, and for each schedule assert "currentScheduledDate"
exists and is a parseable date (raise an exception if missing/invalid). Update
the code around collection_data, activeServices, serviceName, serviceSchedules
and currentScheduledDate to perform these explicit validations and raise
descriptive exceptions rather than silently continuing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant