Guidance for Claude Code when working in this repository. Other AI coding assistants are welcome to read this file too.
At the start of a session, determine who you are helping. The two roles see this codebase very differently:
- Contributor: implementing a new source, fixing a bug, or otherwise preparing changes for a Pull Request. No write access to the upstream repo (
mampfes/hacs_waste_collection_schedule). Works in a fork. - Maintainer: has write access to the upstream repo. Reviews and merges PRs, triages issues, prepares releases. Can push to contributor branches when "allow maintainer edits" is enabled.
If it isn't already clear from the user's first message or from a CLAUDE.local.md file, ask once at the start of the session:
"Are you a contributor working on a Pull Request, or a maintainer with write access to the upstream repo?"
Use the answer to choose which slash commands and agents apply:
| Role | Primary commands | Primary agents |
|---|---|---|
| Contributor | /new-source |
source-investigator, source-implementer |
| Maintainer | /review-pr, /review-issue, /cleanup |
pr-reviewer, pr-executor, issue-triager, issue-executor, repo-cleanup |
Maintainers may keep personal overrides (permission allowlists, hooks, draft templates) in .claude/settings.local.json and CLAUDE.local.md, both gitignored. Load those when present.
hacs_waste_collection_schedule is a Home Assistant (HACS) custom component that retrieves waste/bin collection schedules from ~600 service providers worldwide. It supports both YAML and UI-based configuration.
custom_components/waste_collection_schedule/— Home Assistant integration layer (config flow, sensors, calendar, services).custom_components/waste_collection_schedule/waste_collection_schedule/— Core library (standalone, no HA dependency). Importable aswaste_collection_schedule. Contains the data-fetching logic.
- Sources (
waste_collection_schedule/source/): ~600 provider-specific modules. Each is a single Python file exportingTITLE,DESCRIPTION,URL,COUNTRY,TEST_CASES, and aSourceclass with afetch()method that returnslist[Collection]. - Services (
waste_collection_schedule/service/): ~17 shared service modules used by multiple sources (e.g.ICS,AbfallIO,AppAbfallplusDe). - SourceShell (
source_shell.py): wraps source modules; handles customisation (aliases, icons, filtering) and the fetch lifecycle. - Wizard (
waste_collection_schedule/wizard/): multi-step config flow helpers for sources with cascading API lookups. - Config flow (
config_flow.py): UI-based setup using HA's config entries. Dynamically loads source modules and their parameters.
Collection(date, t, icon=None, picture=None) — a single collection event with a date and waste-type string t.
# Automated test suite (pytest)
python -m pytest tests/
# Single test file
python -m pytest tests/test_source_components.py
# Specific test by name
python -m pytest tests/test_source_components.py -k "test_name"
# Test one source manually against its TEST_CASES
cd custom_components/waste_collection_schedule/waste_collection_schedule/test
python test_sources.py -s <source_name> -l
# All pre-commit hooks (ruff lint + format, mypy, codespell, bandit, pyupgrade, yamlfmt)
pre-commit run --all-files
# Install dependencies
pip install -r requirements.txttests/test_source_components.py validates structure (TITLE, URL, COUNTRY, TEST_CASES, EXTRA_INFO format, etc.) — it runs in CI for every PR. waste_collection_schedule/test/test_sources.py is a CLI tool for live-testing a source against its TEST_CASES; it is excluded from pytest.
- ruff (replaces black, flake8 and isort): line-length 88, lint select
E,F,W,I(pycodestyle + pyflakes + isort, profile=black), ignoreE203,E501,E721.ruff formatmirrors black;ruff checkmirrors flake8 + isort. Config lives in.pre-commit-config.yaml/pyproject.toml. - mypy:
--ignore-missing-imports --explicit-package-bases - bandit: config at
tests/bandit.yaml - pyupgrade: targets Python 3.7+
- yamlfmt: mapping=2, sequence=4, width=150, offset=2
For a single source file edit:
ruff check --fix <file>
ruff format <file>Every source file in custom_components/waste_collection_schedule/waste_collection_schedule/source/ must define:
| Symbol | Type | Notes |
|---|---|---|
TITLE |
str |
Display name. |
DESCRIPTION |
str |
One-line description. |
URL |
str |
Provider's website. |
COUNTRY |
str |
Lowercase code from update_docu_links.py's COUNTRYCODES list. UK = "uk" (NOT "gb"); Canada = "ca" (lowercase). An invalid value silently orphans the source out of README/info/sources.json. |
TEST_CASES |
dict |
Maps test-case name → constructor kwargs. Must not be empty. |
Source class |
__init__(**kwargs) and fetch() -> list[Collection]. |
Optional:
EXTRA_INFO— list of dicts (title,url,country,default_params) for sources that cover multiple municipalities under one module.TITLE_LANG/EXTRA_INFO_LANG— non-English titles.HOW_TO_GET_ARGUMENTS_DESCRIPTION— per-language guidance shown in the config wizard.PARAM_TRANSLATIONS/PARAM_DESCRIPTIONS— per-language argument labels and descriptions. Currently still required on master (read byupdate_docu_links.pyto generatetranslations/en.json). A future i18n YAML migration will replace these but is not yet merged — keep them.SOURCE_CODEOWNERS—list[str]of GitHub handles (e.g.["@your-handle"]) who maintain this source. Each entry must start with@.update_docu_links.pywrites these into.github/source_owners.json; a GitHub Action pings+assigns the listed owners when a bug report names this source. Strongly encouraged for all new sources. ICS YAML providers use the equivalentcodeowners:key in their.yamlfile.
tests/test_source_components.py runs in CI on every PR and enforces:
- Language allowlist:
PARAM_TRANSLATIONSandPARAM_DESCRIPTIONSkeys MUST be in{"en", "de", "it", "fr"}. Default behaviour when a contributor / agent wants to use any other language (e.g.fi,es,nl,pl): strip the unsupported-language block from the source's translation dicts in the current PR, and open a separate issue titledAdd <lang> (xx) language support to PARAM_TRANSLATIONS allowlistlinked back to the original PR/issue, asking for contributors to help with the full translation pipeline (allowlist +update_docu_links.py+translations/<xx>.json). Never silently include an unsupported language — CI will reject the PR. - Icons enum:
ICON_MAPvalues MUST be members of theIconsenum (from waste_collection_schedule import Icons). Raw"mdi:..."strings fail thetest_icon_map_uses_canonical_iconscheck. The canonical catalogue is atcustom_components/waste_collection_schedule/waste_collection_schedule/icons.py— pick the nearest sensible member; do not extend the enum in a source PR. - COUNTRY allowlist as already noted above.
Run python -m pytest tests/test_source_components.py -q locally after any source-module change and before committing; do not rely on CI to catch these.
Use SourceArgumentNotFound / SourceArgumentNotFoundWithSuggestions from waste_collection_schedule.exceptions, not generic Exception. The HA UI surfaces these to the user with helpful context.
Always try curl_cffi first:
from curl_cffi import requests
session = requests.Session(impersonate="chrome")If a site returns 403 with regular requests, switch to curl_cffi — it bypasses Cloudflare in most cases. See east_renfrewshire_gov_uk.py and south_ayrshire_gov_uk.py for examples.
- ❌ Hardcoded dates or schedules. Sources must fetch from a live API, ICS feed, or webpage.
- ❌
if __name__ == "__main__":blocks or standalone-script boilerplate. - ❌ Dummy parameters (e.g.
_) just to satisfy the config GUI. - ❌ Login-required sources. The project only supports publicly accessible endpoints.
- ❌ Sources for providers already covered by a shared platform — check
recollect.yaml,mein_abfallkalender_online.yaml,recyclecoach_com.py'sEXTRA_INFOlist,c_trace_de,service/OpenCities.py(OpenCities/MyArea council CMS widget —api/v1/myarea/search+ocapi/Public/myarea/wasteservicesendpoints), and the other shared platforms first. - ❌ A dedicated
tests/test_<source>.pyunit-test file.TEST_CASESplus a live run oftest_sources.py -s <name> -lis the normal, expected coverage — that's what reviewers actually check. Do not add a standalone test file unless it is absolutely necessary: logic so non-trivial (a tricky date/regex parser, say) thatTEST_CASESgenuinely cannot exercise it, and even then mock any network calls rather than hitting the live API. Default to not adding one. With ~600 sources, a test file per source doesn't scale, and rootpytest.ini'spython_filesallowlist covers onlytest_source_components.pyandtest_fetch_retry.py— files outside that allowlist are never collected bypytest tests/or CI. Treat that as reason for restraint, not as an invitation to "fix" it unprompted in an unrelated PR.
These files are produced by update_docu_links.py, which runs automatically via the Update Documentation CI workflow on every push to master (post-merge). Never run update_docu_links.py yourself in a PR branch, and never commit changes to these files:
README.md,info.mdcustom_components/waste_collection_schedule/sources.jsoncustom_components/waste_collection_schedule/source_metadata.jsoncustom_components/waste_collection_schedule/translations/{en,de,it,fr}.json(config-flowargs_*sections only — theoptions.step.initsection IS hand-maintained)custom_components/waste_collection_schedule/waste_collection_schedule/translations/*.jsondoc/ics/*.md(one perdoc/ics/yaml/*.yaml)
If a PR diff touches any of the above, revert with git checkout upstream/master -- <file> before pushing.
custom_components/waste_collection_schedule/waste_collection_schedule/source/*.py— source modules.doc/source/<id>.md— must be created manually for each new source. The update script reads but does not create these.doc/source/ics.md,doc/source/static.md— manually maintained (blacklisted from generation, except for an auto-patched service-table section in each).doc/ics/yaml/*.yaml— ICS provider definitions. Each file generates the matchingdoc/ics/<name>.md. When adding a provider, update BOTH theextra_infolist (for the README/sources.json listing) AND the table inside thehowto.enstring (for the generated.md).CHANGELOG.md,manifest.json— manually maintained (release-time only).
These are the issues that come up most often in PR review. Avoid them and your PR will sail through.
COUNTRYmismatch. Must be a lowercase code fromupdate_docu_links.py'sCOUNTRYCODESlist. UK ="uk", Canada ="ca". An invalid value silently orphans the source — CI did not catch this in the past.- Generated files in the diff. See list above. Revert before pushing.
- Missing
doc/source/<id>.md. Required for every new source; create it manually. - Hardcoded data. Fetch live; do not paste a schedule.
- Provider already covered by a shared platform (Recollect, RecycleCoach, ICS YAML, Publidata, IntraMaps, OpenCities/MyArea, etc.). Check first.
- Generic
Exception. UseSourceArgumentNotFound/SourceArgumentNotFoundWithSuggestions. - 403 from a Cloudflare site. Switch to
curl_cffi. - Login-required. Not supported — the project only consumes public endpoints.
- Running
update_docu_links.pyin a PR branch. Don't — CI handles it post-merge. - Editing translations directly. The
config.step.args_*sections are generated. Hand-edit onlyoptions.step.init(in the outertranslations/*.json). - Raw
mdi:*strings inICON_MAP. Use theIconsenum fromwaste_collection_schedule(catalogue atwaste_collection_schedule/icons.py). This keeps icons consistent across sources for the same logical waste category — see #2813. - Adding a
tests/test_<source>.pyfile. Not standard coverage — see "What NOT to do" above. Only justified when absolutely necessary;TEST_CASES+ a livetest_sources.py -s <name> -lrun is what reviewers expect otherwise.
If you're a contributor: see .claude/commands/new-source.md or run /new-source. The agents under .claude/agents/source-* walk you through investigating a provider, implementing the source, and submitting a PR.
Branch and PR target: always open PRs against mampfes/hacs_waste_collection_schedule:master from a feature branch on your fork. Never push to your fork's master.
If you're a maintainer: see .claude/commands/{review-pr,review-issue,cleanup}.md. Each spawns a planner agent that produces a Phase 1 report; on approval, an executor agent runs the plan in an isolated worktree.
Key workflow rules:
- Always present drafts before posting on GitHub. Reviews, comments, label changes, closes — show the user the exact text first.
- Fix minor issues yourself; escalate substantive ones. Style, whitespace, missing doc file, lint failures → push the fix to the contributor's branch and approve. Hardcoded data, missing API integration, security issues → request changes.
- Never commit directly to
master— local, origin, or upstream. Every change goes through a feature branch and PR to upstream. - Never run
update_docu_links.pymanually. CI runs it post-merge. - After merge, clean up.
/cleanupsyncsmasterwith upstream, deletes merged branches locally and on origin, removes any contributor fork remotes. - Never ask users for their address or identifying information. Use existing TEST_CASES or ask for known-good public examples.
- Upstream: https://github.com/mampfes/hacs_waste_collection_schedule
- HACS integration: install via HACS by adding this repository as a custom repository
- Contributor docs:
CONTRIBUTING.md