Skip to content

fix: accept weekday names in chore applicable_days - #258

Merged
ccpk1 merged 1 commit into
ccpk1:mainfrom
tmmueller:fix/257-coerce-applicable-days
Aug 28, 2026
Merged

fix: accept weekday names in chore applicable_days#258
ccpk1 merged 1 commit into
ccpk1:mainfrom
tmmueller:fix/257-coerce-applicable-days

Conversation

@tmmueller

@tmmueller tmmueller commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Summary

applicable_days is a select whose options are weekday names ("mon", "wed"), but create_chore stored those strings unchanged and update_chore later cast them with int(). Any update to such a chore raised:

invalid literal for int() with base 10: 'wed'

Storing the names was silently wrong on its own, independent of the crash: consumers compare against weekday integers — calendar.py does if current.weekday() in applicable_days: — so a chore created through the service never matched a day.

Every other path in the codebase already converts or tolerates names:

Path Handling
helpers/flow_helpers.py:1558 maps via WEEKDAY_NAME_TO_INT
options_flow.py:1179, :1517 maps via WEEKDAY_NAME_TO_INT
engines/chore_engine.py:1463 WEEKDAY_NAME_TO_INT.get(d.lower())
engines/schedule_engine.py:1209 sniffs for str and maps
migrations/pre_v50.py:2547 rewrites stored names to integers
services.py missing — bare int(d)

So this is the one place that never got the conversion.

The change:

  1. Normalize at the service boundary (create_chore and update_chore) so stored data is canonical integers, matching what the config flow already writes.
  2. Coerce on read in _ensure_per_assignee_due_dates, so chores already stored with names by an earlier version stop failing without needing a migration.
  3. Derive _DAY_OF_WEEK_VALUES from WEEKDAY_NAME_TO_INT rather than repeating the day literals, so the values the schema accepts and the values the conversion understands cannot drift apart. (This replaces the # using raw values since there are no individual DAY_* constants comment — the mapping constant already existed.)

Unrecognized or out-of-range values are dropped with a warning rather than raising, consistent with how flow_helpers and chore_engine already filter.

I did not touch flow_helpers or options_flow, which already convert correctly — happy to consolidate the four conversion sites into the shared helper as a follow-up refactor if you'd prefer that.

Linked issue

Main merge automation checks

  • PR body uses a closing keyword when applicable (Closes #...)
  • Correct release-note label is applied for .github/release.yml categorization — I don't have permission to set labels on this repo. Suggested: bug + area: automation/services.
  • Excluded triage or status labels have been removed before merge (none were set)
  • PR title is suitable for generated release notes

Change type

  • Bug fix
  • Enhancement
  • Refactor
  • Documentation

Scope

  • Integration logic/state
  • Dashboard/template behavior
  • Services/automations
  • Documentation/wiki

Dashboard source boundary

  • This PR does not introduce manual source edits under custom_components/choreops/dashboards/
  • If dashboard source changes are needed, a corresponding PR is opened in ccpk1/ChoreOps-Dashboards

Validation

  • ./utils/quick_lint.sh --fix — ruff check, ruff format, mypy --config-file mypy_quick.ini, and utils/check_boundaries.py all clean
  • python -m pytest tests/ -v --tb=line

Tests were run on Linux CI rather than locally: the suite cannot run on Windows, because homeassistant/runner.py imports fcntl unconditionally and pytest-homeassistant-custom-component imports that runner. Results on ubuntu-latest, Python 3.13:

passed skipped errors
Before this change 2015 4 1
After this change 2022 4 1

The 7 added tests are the new ones below. The 1 error is pre-existing on an unmodified main and unrelated to this change — tests/test_workflow_gamification_pending_queue.py::test_achievement_selected_unassigned_chore_not_awarded fails in teardown with ValueError: Assignee non-existent-assignee does not exist (may have been deleted). Happy to open a separate issue for it.

Tests added

Added TestApplicableDaysCoercion to tests/test_chore_crud_services.py, following tests/SERVICE_TESTING_PATTERNS.md (calls go through hass.services.async_call so the real schema validates):

  • test_create_stores_weekday_names_as_integers — day names are stored as [0, 2]
  • test_update_after_create_with_weekday_names — the create_chore accepts applicable_days as day-name strings, but the code casts them with int() — update_chore then 500s #257 repro
  • test_update_tolerates_legacy_string_days_in_storage — a chore already holding ["wed", "fri"] still updates. Uses an INDEPENDENT chore deliberately: _ensure_per_assignee_due_dates is only reached for those, so a shared chore here passes without the fix and pins nothing.
  • test_update_rewrites_stored_days_to_integersupdate_chore also normalizes
  • test_coerce_accepts_names_integers_and_mixtures
  • test_coerce_drops_unusable_values
  • test_schema_day_values_match_the_coercion_mapping — pins the constant the schema and conversion now share

I also ran these seven against unmodified main to confirm they actually catch the bug rather than passing vacuously: all 7 fail there, two of them with the reported ValueError: invalid literal for int() with base 10: 'wed'.

Documentation impact

  • No documentation updates needed
  • Documentation updated (README / wiki / inline docs)

services.yaml already documents the field as names (example: "['mon', 'wed', 'fri']"), which is now the behavior.

Release notes

  • No release notes needed

  • Release notes needed (summarize user-visible changes below)

  • Release note summary: Fixed create_chore / update_chore failing with invalid literal for int() with base 10 when applicable_days was set. Weekday names from the selector are now converted to weekday integers, and chores already saved with day names are repaired on the next update.

Breaking changes

  • No breaking changes
  • Breaking change (describe migration or compatibility impact below)

Both conventions are accepted, so existing automations and stored chores keep working either way.

The applicable_days service field is a select whose options are weekday
names ("mon", "wed"), but create_chore stored those strings unchanged and
update_chore later cast them with int(), so any update to such a chore
raised "invalid literal for int() with base 10: 'wed'".

Storing the raw strings was also silently wrong on its own: consumers
compare against weekday integers (calendar.py does `current.weekday() in
applicable_days`), so a chore created this way never matched a day.

Every other path already converts or tolerates names - flow_helpers and
options_flow map through WEEKDAY_NAME_TO_INT, chore_engine lowercases and
looks up, and the pre-v50 migration rewrites stored names to integers.
Only the service path was missing the conversion.

Normalize at the service boundary so stored data is canonical integers,
and coerce on read in _ensure_per_assignee_due_dates so chores already
stored with names by an earlier version stop failing rather than needing
a migration.

Also derive _DAY_OF_WEEK_VALUES from WEEKDAY_NAME_TO_INT instead of
repeating the day literals, so the values the schema accepts and the
values the conversion understands cannot drift apart.

Closes ccpk1#257
@tmmueller
tmmueller requested a review from ccpk1 as a code owner August 23, 2026 22:26
@ccpk1 ccpk1 added bug Something is not working area: integration Integration logic and state labels Aug 25, 2026
@ccpk1
ccpk1 merged commit 61e0e95 into ccpk1:main Aug 28, 2026
5 checks passed
@ccpk1

ccpk1 commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Thank you for submitting the issue and the fix it was well built. There is a minor update to the shared read path I mentioned in the issue comments, but I'm going to go ahead and handle that one. Thank you!

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

Labels

area: integration Integration logic and state bug Something is not working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

create_chore accepts applicable_days as day-name strings, but the code casts them with int() — update_chore then 500s

2 participants