This file is written for AI coding assistants (Claude Code, Cursor, codex, Aider, etc.) helping contributors add or fix sources in this repository. GitHub Copilot reads .github/copilot-instructions.md instead — that file mirrors this one.
For the full human-facing guide, see CONTRIBUTING.md.
Claude Code users: Claude Code also reads
CLAUDE.md(a role-aware project guide) and exposes specialised slash commands. Use/new-sourceto be walked through investigating a provider, generating the source module, and submitting a PR. See.claude/agents/for the full list of available agents.
Two nested packages:
custom_components/waste_collection_schedule/— Home Assistant integration layer (config flow, sensors, calendar, services).custom_components/waste_collection_schedule/waste_collection_schedule/— Core library, importable aswaste_collection_schedule. No HA dependency.
Source modules live at custom_components/waste_collection_schedule/waste_collection_schedule/source/<provider>.py. Shared platforms (used by multiple sources) live at .../service/<Platform>.py.
New Python sources must target release/3.0.0 of mampfes/hacs_waste_collection_schedule. Base the feature branch on release/3.0.0; changing only the PR target does not convert a legacy implementation. Bug fixes for the current stable release still target master, and fixes to existing legacy sources do not require migration.
New sources must use the BaseSource pipeline. New legacy-style sources with a hand-written fetch() are not accepted. Follow the release branch contribution guide, source skeleton, and PR template.
- Declare metadata and typed
PARAMSonSource(BaseSource); use canonical waste types rather than a per-source icon map. - Compose shared retrievers, parsers, preprocessors, and transformers. Extend shared components under
service/when needed rather than adding source-local retrieval/parsing steps or HTTP callbacks. - Record real
TEST_CASESwithpython tests/record_fixtures.py <module>and commit the resultingtests/fixtures/<module>/*.json. Explain any genuine recording blocker in the PR summary. - Pipeline source documentation in
doc/source/is generated from class metadata; do not hand-write or commit it.
The legacy-specific contracts, icon rules, language rules, and examples below apply to existing sources on master. For new sources, use the release branch guide above.
Every legacy file under source/ must define:
TITLE: str— display name shown in the README and config flow.DESCRIPTION: str— one-line summary.URL: str— provider website.TEST_CASES: dict[str, dict]— at least one working test case (constructor kwargs).- A
Sourceclass with__init__(**kwargs)andfetch() -> list[Collection].
Optional but recommended:
COUNTRY: str(ISO-3166-1 alpha-2, lower-case).EXTRA_INFO: list[dict]for sources that cover multiple municipalities.PARAM_DESCRIPTIONS,PARAM_TRANSLATIONS,HOW_TO_GET_ARGUMENTS_DESCRIPTIONkeyed by language code. Onlyen,de,it,frare supported — other codes (e.g.lt,pl,nl) will failtest_source_has_necessary_parameters. See the HOW_TO_GET_ARGUMENTS_DESCRIPTION rules section below.
Collection(date, t, icon=None, picture=None) is the data model. Return one per collection event. For icon, use a member of the canonical Icons enum (from waste_collection_schedule import Icons — full catalogue at custom_components/waste_collection_schedule/waste_collection_schedule/icons.py) rather than a raw "mdi:..." string.
The following are regenerated automatically by CI after every merge to master. Do not edit them in your PR branch — your changes will be overwritten:
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}.jsondoc/ics/*.md(generated fromdoc/ics/yaml/*.yaml)
doc/source/*.md is hand-maintained for legacy sources on master. For pipeline sources on release/3.0.0, it is generated from class metadata and must not be committed.
The integration ships with a canonical icon catalogue (Icons enum at custom_components/waste_collection_schedule/waste_collection_schedule/icons.py). Use it for all ICON_MAP values:
from waste_collection_schedule import Collection, Icons
ICON_MAP = {
"Glass": Icons.GLASS,
"Paper": Icons.PAPER,
}Adding a new catalogue member is gated. Only propose a new Icons.* member when (a) your provider returns a category that doesn't fit any existing member, AND (b) the category is general enough to expect other sources to use it. Open an issue first — propose name + MDI icon + example providers — and wait for maintainer agreement. Then a small separate PR adds the member, after which your source PR can use it. Do not add raw "mdi:..." strings to ICON_MAP to skip this conversation.
Personal taste is not a catalogue concern. If a user just prefers a different MDI icon for a waste category, they override it locally via customize.icon in their YAML (or via the GUI's icon picker). No source change needed:
waste_collection_schedule:
sources:
- name: my_source
args: { ... }
customize:
- type: "Glass"
icon: mdi:bottle-wineSee doc/contributing_source.md for the full version of these rules.
If the provider exposes an ICS feed, prefer adding a single YAML at doc/ics/yaml/<name>.yaml over writing a new source file. The generic ics source consumes it. Use {%Y} as a year placeholder if the URL embeds the current year. CI will regenerate the corresponding doc/ics/<name>.md automatically after merge — do not run update_docu_links.py in your PR branch.
If you add a new Python source, consider declaring yourself as an owner by adding SOURCE_CODEOWNERS to the module:
SOURCE_CODEOWNERS = ["@your-github-handle"]For a new ICS YAML provider, add the codeowners key:
codeowners:
- "@your-github-handle"Owners are automatically pinged and assigned when a bug report is filed for their source. This is the primary way maintainers know who to contact when a source breaks. Every handle must start with @.
Things the maintainers consistently bounce in code review — avoid these from the start:
- Hardcoded dates or weekday rules. Sources must fetch from a real, dated upstream (API, ICS, or scrapeable HTML). A rule like "Recycling is every other Monday" is not acceptable.
- Generic
Exception. UseSourceArgumentNotFoundorSourceArgumentNotFoundWithSuggestionsfromwaste_collection_schedule.exceptionsfor missing/invalid user input. Bubble uprequests.HTTPErrorfor upstream failures. if __name__ == "__main__"blocks. Sources are libraries; no standalone entry points.- Dummy parameters added solely to satisfy the config GUI. If a parameter has no use, drop it.
- Unsupported language codes in
PARAM_DESCRIPTIONS,PARAM_TRANSLATIONS, orHOW_TO_GET_ARGUMENTS_DESCRIPTION. Onlyen,de,it,frare valid keys. Including any other code (e.g.lt,nl,pl) failstest_source_has_necessary_parameters. If a contributor wrote theirHOW_TO_GET_ARGUMENTS_DESCRIPTIONin only one language (e.g.{"en": "..."}) that is fine — do not add translations for the other languages. See the HOW_TO_GET_ARGUMENTS_DESCRIPTION rules section. - Editing files in the "must not hand-edit" list above — they get overwritten by CI after merge.
- Suppressing failures silently. Returning
[]on an HTTP error masks problems as "no upcoming collections". - Unformatted code. Run
ruff check --fixandruff formatbefore committing (see Commands below). - Raw
mdi:*strings inICON_MAP. Use the canonicalIconsenum (from waste_collection_schedule import Icons) so icons stay consistent across sources for the same logical waste category. Seecustom_components/waste_collection_schedule/waste_collection_schedule/icons.py. - A dedicated
tests/test_<source>.pyunit-test file.TEST_CASESplus a live run oftest_sources.py -s <name> -lis the normal, expected coverage for a source — don't add a standalone test file just because it seems thorough. Default to not adding one; add it only when absolutely necessary, i.e. logic so non-trivial (a tricky date/regex parser, say) thatTEST_CASESgenuinely cannot exercise it, and even then mock any network calls — don't hit the live API from the test. With ~600 sources, one test file per source doesn't scale, and rootpytest.ini'spython_filesallowlist covers onlytest_source_components.pyandtest_fetch_retry.py— anything else intests/is never collected bypytest tests/or CI.
HOW_TO_GET_ARGUMENTS_DESCRIPTION is an optional dict shown in the Home Assistant config flow above the input fields. It explains to users how to find the required parameter values (e.g. "Visit https://example.com and note the ID from the URL").
Valid language keys: only "en", "de", "it", "fr" — the same set as PARAM_TRANSLATIONS and PARAM_DESCRIPTIONS. Any other key (e.g. "pl", "nl", "lt", "cs") will cause test_source_has_necessary_parameters to fail.
It does not need to be provided in every language. A contributor who only writes English or only writes their native language is perfectly valid:
# Valid — only one language
HOW_TO_GET_ARGUMENTS_DESCRIPTION = {
"en": "Visit https://example.com and select your street.",
}
# Also valid — contributor provided two languages
HOW_TO_GET_ARGUMENTS_DESCRIPTION = {
"en": "Visit https://example.com and select your street.",
"de": "Besuchen Sie https://example.com und wählen Sie Ihre Straße.",
}
# Invalid — "pl" is not a supported language code
HOW_TO_GET_ARGUMENTS_DESCRIPTION = {
"pl": "Odwiedź https://example.com i wybierz swoją ulicę.",
}For reviewers and AI agents fixing up contributor PRs: if a contributor has used an unsupported language code (e.g. "pl"), remove that key. Do not replace it by adding "en" unless you can write an accurate description of how to obtain the arguments — that knowledge belongs to the contributor. If you cannot write an accurate description, either remove the entire HOW_TO_GET_ARGUMENTS_DESCRIPTION dict or leave a comment for the contributor to provide one. Do not add language keys that the contributor did not originally include.
# Run the full automated test suite
python -m pytest tests/
# Test a specific source against its TEST_CASES
cd custom_components/waste_collection_schedule/waste_collection_schedule/test
python test_sources.py -s <source_name> -l
# Format + lint before every commit — REQUIRED on every file you change
# ruff replaces black, flake8 and isort
ruff check --fix <path>
ruff format <path>
# Activate the git pre-commit hook (run once after cloning — enforces formatting automatically)
pre-commit installpre-commit run --all-files runs the full lint suite (ruff lint + format, mypy, codespell, bandit, pyupgrade, yamlfmt).
AI agents: Always run
ruff check --fix <path> && ruff format <path>on every changed file before callinggit commit. The pre-commit hook enforces this for human contributors; agents must do it explicitly.
Note: Do not run
update_docu_links.pyin a PR branch. TheUpdate DocumentationCI workflow runs it automatically on every push tomaster(post-merge) and commits the generated output.
- HTML scraping (BeautifulSoup):
source/birmingham_gov_uk.py - ICS-based:
source/stadtreinigung_hamburg.py - JSON/REST API:
source/toronto_ca.py
- Source module passes
test_sources.py -s <name>against real TEST_CASES. - Pipeline source documentation is generated from class metadata; do not commit
doc/source/<name>.md. Keep legacy documentation accurate when fixing an existing stable source. python -m pytest tests/passes locally.- Changed files are formatted and linted with
ruff formatandruff check --fix. - New-source PRs target
release/3.0.0ofmampfes/hacs_waste_collection_scheduleand useBaseSourcewith recorded fixtures. Stable bug fixes targetmaster; existing legacy sources need not be migrated for a fix. - Do not commit changes to
README.md,info.md,sources.json,source_metadata.json,translations/*.json, ordoc/ics/*.md— CI regenerates these after merge.