Bin Vader Python Script Documentation

Purpose

bin_vader.py is the deterministic rota engine for Bin Vader.

It calculates:

  • whether today is bin night
  • the next collection date
  • which bins need to go out
  • which flat is responsible
  • whether a UK bank holiday shifts collection from Friday to Saturday
  • one-off swaps
  • exchange swaps
  • cancelled swaps
  • manual overrides
  • schedule listings for up to 12 weeks

The script is designed so that rota decisions and reminders do not depend on language-model judgement. The agent may explain the result, but the script should decide the result.


bin-vader/
  scripts/
    bin_vader.py
    __init__.py
  state/
    bin_rota.json
    bank_holidays_cache.json
  tests/
    __init__.py
    test_bin_vader.py
  docs/
    bin_vader_script_documentation.md

Runtime requirements

Python 3.11 or later is recommended.

The main script uses only the Python standard library:

  • argparse
  • json
  • urllib.request
  • dataclasses
  • datetime
  • pathlib
  • typing
  • zoneinfo

No external package is required to run the script.

pytest is only needed for the test harness.


State file

By default, the script reads state from:

state/bin_rota.json

Minimal state file:

{
  "timezone": "Europe/London",
  "bank_holiday_region": "england-and-wales",
  "anchor_collection_date": "2026-01-02",
  "anchor_rota_index": 0,
  "anchor_bin_pattern": "black_green",
  "swaps": [],
  "manual_overrides": []
}

timezone

The timezone used when the script needs to know today's date.

Recommended value:

"Europe/London"

bank_holiday_region

The GOV.UK bank holiday region.

For England and Wales:

"england-and-wales"

Other GOV.UK region keys are:

scotland
northern-ireland

anchor_collection_date

A known real collection date.

This must be a date where you know:

  • collection actually happened
  • which flat was responsible
  • which bin type was collected

Example:

"anchor_collection_date": "2026-01-02"

anchor_rota_index

The index in the rota sequence for the anchor collection date.

The rota sequence is:

index:  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
flat:   1 2 3 4 5 6 7 8 8 7  6  5  4  3  2  1

Example:

"anchor_rota_index": 0

means Flat 1 was responsible on the anchor date.

anchor_bin_pattern

The bin type on the anchor collection date.

Allowed values:

black_green
green_only

Example:

"anchor_bin_pattern": "black_green"

Core rota rules

Normal collection:

Friday morning

Normal bin night:

Thursday night

Bank-holiday-shifted collection:

Saturday morning

Bank-holiday-shifted bin night:

Friday night

The current bank holiday rule is:

If there is a UK bank holiday from Monday to Friday in the same collection week,
the usual Friday collection shifts to Saturday.

This rule is implemented in:

affected_by_bank_holiday()

If the council behaves differently, change that function.


Bin pattern

Collections alternate between:

black and green bins
green bins only

If the anchor collection is black_green, then:

collection 0: black and green bins
collection 1: green bins only
collection 2: black and green bins
collection 3: green bins only

If the anchor collection is green_only, the pattern is reversed.


Flat rota

The rota sequence is:

1,2,3,4,5,6,7,8,8,7,6,5,4,3,2,1

The sequence then repeats.

The rota advances once per actual collection. It does not advance by calendar week independently of collection dates.


Bank holiday data

The script fetches bank holidays from GOV.UK:

https://www.gov.uk/bank-holidays.json

The result is cached in:

state/bank_holidays_cache.json

The cache is reused for up to 14 days. If the cache is missing, stale, or invalid, the script fetches fresh data.


Command reference

All examples assume the command is run from the project root.

Check whether today is bin night

scripts/bin_vader.py today

Example output on a bin night:

Tonight is bin night. Collection is tomorrow morning, 2026-06-05. Bins: green bins only. Responsible flat: 4.

Example output when it is not bin night:

No. Tonight is not bin night. Next bin night is 2026-06-11. Collection is 2026-06-12. Bins: black and green bins. Responsible flat: 5.

Test with a fixed date:

scripts/bin_vader.py today --date 2026-05-29

Emit a reminder only on bin night

scripts/bin_vader.py remind

This prints a reminder only if today is bin night. If today is not bin night, it prints nothing.

This makes it suitable for cron.

Test with a fixed date:

scripts/bin_vader.py remind --date 2026-05-29

Show the next collection

scripts/bin_vader.py next

Example output:

Collection: 2026-06-05
Bin night: 2026-06-04
Bins: green bins only
Responsible flat: 4
Base rota flat: 4
Rota index: 3

Start from a fixed date:

scripts/bin_vader.py next --date 2026-06-01

Show a specific collection date

scripts/bin_vader.py date 2026-06-05

The date must be a real collection date.

A normal week collection date will usually be Friday. A bank-holiday-shifted collection date will be Saturday.


List a schedule

scripts/bin_vader.py schedule 4

This lists the next 4 weeks.

The maximum allowed value is:

12

Requests above 12 weeks are rejected.

Example output:

| Bin night | Collection | Bins | Flat | Notes |
|---|---|---:|---:|---|
| Thursday 2026-06-04 | Friday 2026-06-05 | black and green bins | 3 | |
| Thursday 2026-06-11 | Friday 2026-06-12 | green bins only | 4 | |

Start from a fixed date:

scripts/bin_vader.py schedule 6 --date 2026-06-01

JSON output:

scripts/bin_vader.py schedule 12 --format json

Swaps

One-off swaps

A one-off swap means another flat covers a single collection.

Command:

scripts/bin_vader.py add-swap COLLECTION_DATE ORIGINAL_FLAT REPLACEMENT_FLAT

Example:

scripts/bin_vader.py add-swap 2026-06-05 3 6 --recorded-by "Nigel"

Meaning:

Flat 6 covers Flat 3 on 2026-06-05.

Example output:

Recorded swap-0001. For collection on 2026-06-05, Flat 6 is covering Flat 3.

The swap is persisted in the state file.


Exchange swaps

An exchange means two flats swap turns across two dates.

Command:

scripts/bin_vader.py add-exchange FLAT_A FLAT_B COLLECTION_DATE_A COLLECTION_DATE_B

Example:

scripts/bin_vader.py add-exchange 3 6 2026-06-05 2026-07-31 --recorded-by "Nigel"

Meaning:

Flat 6 covers Flat 3 on 2026-06-05.
Flat 3 covers Flat 6 on 2026-07-31.

Both dates are annotated in schedule output.

Example output:

Recorded swap-0001. Flat 3 and Flat 6 have exchanged 2026-06-05 and 2026-07-31.

List swaps

scripts/bin_vader.py list-swaps

Example output:

swap-0001 [active] exchange: Flat 6 covers Flat 3 on 2026-06-05; Flat 3 covers Flat 6 on 2026-07-31

Cancelled swaps remain visible:

swap-0001 [cancelled] exchange: Flat 6 covers Flat 3 on 2026-06-05; Flat 3 covers Flat 6 on 2026-07-31

Cancel swaps

Swaps are cancelled by ID.

Command:

scripts/bin_vader.py cancel-swap swap-0001 --cancelled-by "Nigel"

Optional reason:

scripts/bin_vader.py cancel-swap swap-0001 --cancelled-by "Nigel" --reason "Changed back"

Example output:

Cancelled swap-0001.

Cancellation does not delete the swap. Instead, the swap record is marked with cancellation fields:

{
  "cancelled_at": "2026-06-01T18:30:00+01:00",
  "cancelled_by": "Nigel",
  "cancellation_reason": "Changed back"
}

Cancelled swaps do not affect:

  • today checks
  • reminders
  • next collection
  • schedule listings
  • date lookups

Manual overrides

A manual override is a human-confirmed correction. Use this if the deterministic rota needs to be overridden for a specific collection.

Command:

scripts/bin_vader.py add-override COLLECTION_DATE RESPONSIBLE_FLAT BINS

Allowed bin values:

black_green
green_only

Example:

scripts/bin_vader.py add-override 2026-06-06 5 green_only --source "confirmed by council notice"

Manual overrides are applied before swaps.

This means:

  1. the override changes the base responsibility or bin type
  2. any relevant swap is then applied

Reminder cron

Use remind for scheduled reminders.

Example crontab:

TZ=Europe/London

0 18 * * * cd /opt/bin-vader && ./scripts/bin_vader.py remind | /opt/bin-vader/scripts/post_to_group.sh

Because remind prints nothing when it is not bin night, the posting script should send only when input is non-empty.

Example safe posting wrapper:

#!/usr/bin/env bash
set -euo pipefail

message="$(cat)"

if [ -z "$message" ]; then
  exit 0
fi

# Replace this with the WhatsApp sending command.
echo "$message"

WhatsApp integration

The WhatsApp bridge should enforce the activation phrase before passing messages to Bin Vader.

Recommended activation phrase:

Hey Bin Vader

Example gate:

import re

ACTIVATION_RE = re.compile(r"^\s*hey\s*,?\s*bin\s+vader\b[:,]?\s*(.*)$", re.I)

def extract_bin_vader_command(message: str) -> str | None:
    match = ACTIVATION_RE.match(message)
    if not match:
        return None

    command = match.group(1).strip()
    return command or "help"

Messages without the activation phrase should be ignored silently by the bridge.

Scheduled reminders do not need the activation phrase.


Testing

Install pytest:

python3 -m pip install pytest

Run tests:

python3 -m pytest -q

Expected result for the supplied harness:

15 passed

The tests use fixed bank holiday data and do not call GOV.UK.


Behaviours covered by tests

The test harness checks:

  • rota sequence
  • forward and backward flat order
  • alternating bin pattern
  • bank holiday shift from Friday to Saturday
  • Friday bin night before a shifted Saturday collection
  • one-off swaps
  • cancelled one-off swaps
  • exchange swaps
  • annotations on both exchange dates
  • cancelled exchanges
  • manual override priority
  • swap ID allocation
  • cancelling swaps
  • schedule maximum of 12 weeks
  • table and JSON schedule output

Troubleshooting

State file not found

The script could not find:

state/bin_rota.json

Create the state file or pass a custom path:

scripts/bin_vader.py --state /path/to/bin_rota.json today

anchor_collection_date does not look like a valid generated collection date

The anchor date does not match the generated collection calendar.

Likely causes:

  • the anchor date is wrong
  • the anchor date was a Saturday but the script did not identify that week as bank-holiday-shifted
  • the bank holiday shift rule needs changing
  • the bank holiday region is wrong

Target date is not a collection date

The date command was given a date that is not an actual collection date.

Use Friday for normal weeks.

Use Saturday for bank-holiday-shifted weeks.


weeks must be no more than 12

The schedule command was asked for more than 12 weeks.

Use:

scripts/bin_vader.py schedule 12

No swap found with id

The swap ID does not exist.

List swaps:

scripts/bin_vader.py list-swaps

Design principle

The script is the source of truth.

The language model may:

  • explain results
  • format messages
  • interpret activated requests
  • pass commands to the script

The language model must not:

  • invent rota state
  • guess bank holiday shifts
  • silently create swaps
  • remember swaps only in conversation
  • override the deterministic script without a recorded manual override