[BETA-TESTERS] KidsChores – Family Chore Management Integration 🏡

—- Update - It is working fine now, I missed the capitalization in one of the name changes…. Leaving this post in case someone else has the same self induced issue. Thank you!

I finally had a little bit of time to start looking at this, but I think your thought process and the work put in is pretty amazing so far. There is a lot more complexity to managing it than what you would think when you say “Kid chores.” :slight_smile: In getting the integration setup, I have a couple of comments and questions about how the dates work for chores, specifically the recurring chores, but I need to play around with it a little to provide good feedback.

In the meantime, can you double check the yaml you listed for the dashboard? I know there are a couple of custom cards needed, which I added the ones I didn’t have. Additionally, I made those adjustments for faros, at the same time I replaced all of the variation of “kid name, kid_name, etc.”

I tried troubleshooting, but the issue seems to be with the way template is being used in the auto-entities card. Are you sure it is formatted properly? I tried to narrow the card just down to that and it doesn’t seem right.

Thanks for all your work on this!

@ccpk1 Glad you were able to sort it out.

Thank you for the comments.

About the chore setup and the recurring days that’s something that I have to have a deeper look into. My initial setup may need some rework to make it more clean and useful. I will try to get it reset on the day set and if it has a recurring period set, reset to the next day/week or month.

My initial idea was to set chores with a due date, and after I thought about the recurring periods, so some things might need some improvement.

I will try to get it revised for next releases.

Cheers,

As I dug in more to this more, there is a lot of good work and creativity to get this far along. Really good work!

As you saw in my earlier post, I had some trouble with the dashboards, and even when I got them working, they weren’t formatting as nicely as was displayed in your example. I made quite a few adjustments to formatting and the templates to hopefully simplify some things and better handle some errors that were showing up because some of the new entities didn’t have values when I first setup the dashboard. I also broke out two separate cards which helped me with some testing. One of the cards is for the kid, and the other for the parent. Below are some pictures as well as the code:

To use these, find and replace the following words “Points” and “points” with whatever you are using. Then find and replace the kids name by find and replace “PAYTON”, “Payton”, and “payton” being sure to match case.

type: grid
square: false
columns: 1
cards:
  - type: heading
    heading: PAYTON
    heading_style: title
  - type: grid
    square: false
    columns: 2
    cards:
      - type: entity
        entity: sensor.payton_points
        unit: Points
        name: PAYTON
      - type: custom:mushroom-entity-card
        entity: sensor.payton_highest_badge
        layout: vertical
        primary_info: state
        secondary_info: none
        fill_container: true
      - type: tile
        entity: sensor.payton_total_chores_completed
        name: Total Chores Completed
      - type: tile
        entity: sensor.payton_chores_completed_today
        name: Chores Completed Today
  - type: custom:mini-graph-card
    name: PAYTON
    entities:
      - entity: sensor.payton_points
    hours_to_show: 168
    group_by: date
    smoothing: false
    show:
      state: false
      name: false
      icon: false
  - type: heading
    icon: ""
    heading: Claim Chores (Hold to Claim)
    heading_style: title
  - type: custom:auto-entities
    card:
      square: false
      type: grid
      columns: 2
    card_param: cards
    filter:
      template: |-
        {%- set Kid_name = 'Payton' -%}  {# Define the kid's name dynamically #}
        {% for state in states.button -%}
          {%- if state.entity_id.startswith('button.' + Kid_name | lower + '_claim_chore_') -%}
            {%- set sensor_suffix = state.entity_id.split('button.' + Kid_name | lower + '_claim_chore_')[1] -%}
            {%- set sensor_id = 'sensor.' + Kid_name | lower + '_status_' + sensor_suffix -%}
            {%- set secondary = 'Unknown' if state.state in ['unknown', 'unavailable', 'error'] else '' -%}
            {%- if not secondary and state.state | float(default=0) > 0 -%}
              {%- set delta = now().timestamp() - as_timestamp(state.state) -%}
              {%- set secondary = 
                '%.0f minutes ago' % (delta / 60) if delta < 3600 else 
                '%.0f hours ago' % (delta / 3600) if delta < 86400 else 
                '%.0f days ago' % (delta / 86400)
              -%}
            {%- endif -%}
            {{
              {
                'type': 'custom:mushroom-template-card',
                'entity': state.entity_id,
                'primary': state.attributes.friendly_name.split(Kid_name + ' - Claim Chore: ')[1],
                'secondary': secondary,
                'icon': state.attributes.icon,
                'icon_color': (
                  'green' if states(sensor_id) == 'approved' else
                  'yellow' if states(sensor_id) == 'partial' else
                  'orange' if states(sensor_id) == 'claimed' else
                  'red' if states(sensor_id) == 'overdue' else
                  'grey'
                ),
                'tap_action': {
                  'action': 'more-info'
                },
                'hold_action': {
                  'action': 'toggle'
                }
              }
            }},
          {%- endif -%}
        {%- endfor %}
    sort:
      method: friendly_name
  - type: heading
    icon: ""
    heading: Redeem Rewards (Hold to Redeem)
    heading_style: title
  - type: custom:auto-entities
    card:
      square: false
      type: grid
      columns: 2
    card_param: cards
    filter:
      template: >-
        {%- set Kid_name = 'Payton' -%}  {# Define the kid's name dynamically
        #}       

        {% for state in states.button -%}
          {%- if state.entity_id.startswith('button.' + Kid_name | lower + '_redeem_reward_') -%}
            {%- set sensor_suffix = state.entity_id.split('button.' + Kid_name | lower + '_redeem_reward_')[1] -%}
            {%- set sensor_id = 'sensor.' + Kid_name | lower + '_reward_status_' + sensor_suffix -%}
            {%- set cost = state_attr(sensor_id, 'cost') -%}
            {%- set claim_sensor_id = 'sensor.' + Kid_name | lower + '_' + sensor_suffix + '_claims' -%}
            {%- set claims = states(claim_sensor_id) | string if states(claim_sensor_id) != 'unknown' else 'N/A' -%}
            {%- set approvals_sensor_id = 'sensor.' + Kid_name | lower + '_' + sensor_suffix + '_approvals' -%}
            {%- set approvals = states(approvals_sensor_id) | string if states(approvals_sensor_id) != 'unknown' else 'N/A' -%}
            {%- set secondary = 'Cost: ' + (cost | string if cost else 'N/A') + '\n' + 'Claims: ' + claims + '\n' + 'Approved: ' + approvals-%}
            {{
              {
                'type': 'custom:mushroom-template-card',
                'entity': state.entity_id,
                'primary': state.attributes.friendly_name.split(Kid_name + ' - Redeem Reward: ')[1],
                'secondary': secondary,
                'multiline_secondary': 'false',
                'icon': state.attributes.icon,
                'icon_color': 'blue',
                'tap_action': {
                  'action': 'more-info'
                },
                'hold_action': {
                  'action': 'toggle'
                }
              }
            }},
          {%- endif -%}
        {% endfor %}
    sort:
      method: friendly_name

type: grid
square: false
columns: 1
cards:
  - type: heading
    heading: PAYTON PARENT
    heading_style: title
  - type: grid
    square: false
    columns: 2
    cards:
      - type: entity
        entity: sensor.payton_points
        unit: Points
        name: PAYTON
      - type: custom:mushroom-entity-card
        entity: sensor.payton_highest_badge
        layout: vertical
        primary_info: state
        secondary_info: none
        fill_container: true
      - type: tile
        entity: sensor.payton_total_chores_completed
        name: Total Chores Completed
      - type: tile
        entity: sensor.payton_chores_completed_today
        name: Chores Completed Today
  - type: custom:mini-graph-card
    name: PAYTON
    entities:
      - entity: sensor.payton_points
    hours_to_show: 168
    group_by: date
    smoothing: false
    show:
      state: false
      name: false
      icon: false
  - type: heading
    heading: Chore Status
    heading_style: title
  - type: custom:auto-entities
    card:
      square: false
      type: grid
      columns: 2
    card_param: cards
    filter:
      template: |-
        {%- set Kid_name = 'Payton' -%}  {# Define the name dynamically #}   
        {% for state in states.sensor -%}
          {%- if state.entity_id.startswith('sensor.' + Kid_name | lower + '_status_') -%}
            {{
              {
                'type': 'custom:mushroom-template-card',
                'entity': state.entity_id,
                'primary': state.attributes.friendly_name.split(Kid_name + ' - Status - ')[1],
                'secondary': (
                  'Approved' if state.state == 'approved' else
                  'Claimed' if state.state == 'claimed' else
                  'Pending' if state.state == 'pending' else
                  'Partial' if state.state == 'partial' else
                  'Due' if state.state == 'overdue' else
                  'Pending'
                ),
                'icon': state.attributes.icon,
                'icon_color': (
                  'green' if state.state == 'approved' else
                  'orange' if state.state == 'claimed' else
                  'grey' if state.state == 'pending' else
                  'yellow' if state.state == 'partial' else
                  'red' if state.state == 'overdue' else
                  'grey'
                ),
                'tap_action': {
                  'action': 'more-info'
                }
              }
            }},
          {%- endif -%}
        {%- endfor %}
      exclude: []
    sort:
      method: friendly_name
  - type: heading
    icon: ""
    heading: Approve Chores (Hold to Approve)
    heading_style: title
  - type: custom:auto-entities
    card:
      square: false
      type: grid
      columns: 2
    card_param: cards
    filter:
      template: >-
        {%- set Kid_name = 'Payton' -%}  {# Define the kid's name dynamically
        #}     

        {% for state in states.button -%}
          {%- if state.entity_id.startswith('button.' + Kid_name | lower + '_approve_chore_') -%}
            {%- set sensor_suffix = state.entity_id.split('button.' + Kid_name | lower + '_approve_chore_')[1] -%}
            {%- set sensor_id = 'sensor.' + Kid_name | lower + '_status_' + sensor_suffix -%}
            {%- set secondary = 'Unknown' if state.state in ['unknown', 'unavailable', 'error'] else '' -%}
            {%- if not secondary and state.state | float(default=0) > 0 -%}
              {%- set delta = now().timestamp() - as_timestamp(state.state) -%}
              {%- set secondary = 
                '%.0f minutes ago' % (delta / 60) if delta < 3600 else 
                '%.0f hours ago' % (delta / 3600) if delta < 86400 else 
                '%.0f days ago' % (delta / 86400)
              -%}
            {%- endif -%}
            {{
              {
                'type': 'custom:mushroom-template-card',
                'entity': state.entity_id,
                'primary': state.attributes.friendly_name.split(Kid_name + ' - Approve Chore: ')[1],
                'secondary': secondary,
                'icon': state.attributes.icon,
                'icon_color': (
                  'green' if states(sensor_id) == 'approved' else
                  'yellow' if states(sensor_id) == 'partial' else
                  'orange' if states(sensor_id) == 'claimed' else
                  'red' if states(sensor_id) == 'overdue' else
                  'grey'
                ),
                'tap_action': {
                  'action': 'more-info'
                },
                'hold_action': {
                  'action': 'toggle'
                }
              }
            }},
          {%- endif -%}
        {%- endfor %}
    sort:
      method: friendly_name
  - type: heading
    icon: ""
    heading: Disapprove Chores (Hold to Disapprove)
    heading_style: title
  - type: custom:auto-entities
    card:
      square: false
      type: grid
      columns: 2
    card_param: cards
    filter:
      template: |-
        {%- set Kid_name = 'Payton' -%}  {# Define the kid's name dynamically #}
        {% for state in states.button -%}
          {%- if state.entity_id.startswith('button.' + Kid_name | lower + '_disapprove_chore_') -%}
            {%- set sensor_suffix = state.entity_id.split('button.' + Kid_name | lower + '_disapprove_chore_')[1] -%}
            {%- set sensor_id = 'sensor.' + Kid_name | lower + '_status_' + sensor_suffix -%}
            {%- set secondary = 'Unknown' if state.state in ['unknown', 'unavailable', 'error'] else '' -%}
            {%- if not secondary and state.state | float(default=0) > 0 -%}
              {%- set delta = now().timestamp() - as_timestamp(state.state) -%}
              {%- set secondary = 
                '%.0f minutes ago' % (delta / 60) if delta < 3600 else 
                '%.0f hours ago' % (delta / 3600) if delta < 86400 else 
                '%.0f days ago' % (delta / 86400)
              -%}
            {%- endif -%}
            {{
              {
                'type': 'custom:mushroom-template-card',
                'entity': state.entity_id,
                'primary': state.attributes.friendly_name.split(Kid_name + ' - Disapprove Chore: ')[1],
                'secondary': secondary,
                'icon': state.attributes.icon,
                'icon_color': (
                  'green' if states(sensor_id) == 'approved' else
                  'yellow' if states(sensor_id) == 'partial' else
                  'orange' if states(sensor_id) == 'claimed' else
                  'red' if states(sensor_id) == 'overdue' else
                  'grey'
                ),
                'tap_action': {
                  'action': 'more-info'
                },
                'hold_action': {
                  'action': 'toggle'
                }
              }
            }},
          {%- endif -%}
        {%- endfor %}
    sort:
      method: friendly_name
  - type: heading
    icon: ""
    heading: Approve Rewards (Hold to Approve)
    heading_style: title
  - type: custom:auto-entities
    card:
      square: false
      type: grid
      columns: 2
    card_param: cards
    filter:
      template: |-
        {%- set Kid_name = 'Payton' -%}  {# Define the kid's name dynamically #}
        {% for state in states.button -%}
          {%- if state.entity_id.startswith('button.' + Kid_name | lower + '_approve_reward_') -%}
            {%- set sensor_suffix = state.entity_id.split('button.' + Kid_name | lower + '_approve_reward_')[1] -%}
            {%- set sensor_id = 'sensor.' + Kid_name | lower + '_reward_status_' + sensor_suffix -%}
            {%- set cost = state_attr(sensor_id, 'cost') -%}
            {%- set secondary = 'Cost: ' + (cost | string if cost else 'N/A') -%}
            {{
              {
                'type': 'custom:mushroom-template-card',
                'entity': state.entity_id,
                'primary': state.attributes.friendly_name.split(Kid_name + ' - Approve Reward: ')[1],
                'secondary': secondary,
                'icon': state.attributes.icon,
                'icon_color': (
                  'green' if states(sensor_id) == 'approved' else
                  'yellow' if states(sensor_id) == 'partial' else
                  'orange' if states(sensor_id) == 'claimed' else
                  'red' if states(sensor_id) == 'overdue' else
                  'grey'
                ),
                'tap_action': {
                  'action': 'more-info'
                },
                'hold_action': {
                  'action': 'toggle'
                }
              }
            }},
          {%- endif -%}
        {%- endfor %}
    sort:
      method: friendly_name
  - type: heading
    icon: ""
    heading: Disapprove Rewards (Hold to Disapprove)
    heading_style: title
  - type: custom:auto-entities
    card:
      square: false
      type: grid
      columns: 2
    card_param: cards
    filter:
      template: >-
        {%- set Kid_name = 'Payton' -%}  {# Define the kid's name dynamically
        #} 

        {% for state in states.button -%}
          {%- if state.entity_id.startswith('button.' + Kid_name | lower + '_disapprove_reward_') -%}
            {%- set sensor_suffix = state.entity_id.split('button.' + Kid_name | lower + '_disapprove_reward_')[1] -%}
            {%- set sensor_id = 'sensor.' + Kid_name | lower + '_reward_status_' + sensor_suffix -%}
            {%- set cost = state_attr(sensor_id, 'cost') -%}
            {%- set secondary = 'Cost: ' + (cost | string if cost else 'N/A') -%}
            {{
              {
                'type': 'custom:mushroom-template-card',
                'entity': state.entity_id,
                'primary': state.attributes.friendly_name.split(Kid_name + ' - Disapprove Reward: ')[1],
                'secondary': secondary,
                'icon': state.attributes.icon,
                'icon_color': (
                  'green' if states(sensor_id) == 'approved' else
                  'yellow' if states(sensor_id) == 'partial' else
                  'orange' if states(sensor_id) == 'claimed' else
                  'red' if states(sensor_id) == 'overdue' else
                  'grey'
                ),
                'tap_action': {
                  'action': 'more-info'
                },
                'hold_action': {
                  'action': 'toggle'
                }
              }
            }},
          {%- endif -%}
        {%- endfor %}
    sort:
      method: friendly_name
  - type: heading
    icon: ""
    heading: Penalties
    heading_style: title
  - type: custom:auto-entities
    card:
      square: false
      type: grid
      columns: 2
    card_param: cards
    filter:
      template: |-
        {%- set Kid_name = 'Payton' -%}  {# Define the kid's name dynamically #}
        {% for state in states.button -%}
          {%- if state.entity_id.startswith('button.' + Kid_name | lower + '_apply_penalty_') -%}
            {{
              {
                'type': 'custom:mushroom-template-card',
                'entity': state.entity_id,
                'primary': state.attributes.friendly_name.split(Kid_name + ' - Apply Penalty: ')[1],
                'icon': state.attributes.icon,
                'icon_color': 'blue',
                'tap_action': {
                  'action': 'toggle'
                },
                'hold_action': {
                  'action': 'more-info'
                }
              }
            }},
          {%- endif -%}
        {%- endfor %}
    sort:
      method: friendly_name
  - type: heading
    icon: ""
    heading: Manual Points
    heading_style: title
  - type: custom:auto-entities
    card:
      square: false
      type: grid
      columns: 2
    card_param: cards
    filter:
      template: |-
        {%- set Kid_name = 'Payton' -%}  {# Define the kid's name dynamically #}
        {% for state in states.button -%}
          {%- if state.entity_id | regex_match("^button\\." + Kid_name | lower + "_\\d+_points(?:_\\d+)?$", ignorecase=False) -%}
            {{
              {
                'type': 'custom:mushroom-template-card',
                'entity': state.entity_id,
                'primary': state.attributes.friendly_name.split(Kid_name + ' ')[1],
                'icon': state.attributes.icon,
                'icon_color': 'blue',
                'tap_action': {
                  'action': 'toggle'
                },
                'hold_action': {
                  'action': 'more-info'
                }
              }
            }},
          {%- endif -%}
        {%- endfor %}
    sort:
      method: domain
      numeric: true
      ignore_case: false
      reverse: false
grid_options:
  columns: full
  rows: 8

I’m guessing your time is primarily focused on the backend work at the moment, so my goal was just to make some iterative improvements that hopefully help others more easily jump in to try it out. I think it is well designed; it just takes a little while to understand concepts of how you’re handling everything. I can definitely tell there is a lot of potential for improving.

I also made some notes while I was trying it out. I will put them in this post for now, but if you are at a point you want to start logging them on github, happy to do that:

Issues

  1. Chore Deletion
  • Problem: When a chore is deleted, the entities are not automatically removed.
  • Workaround: Manually delete the entities to prevent them from appearing on dashboards.
  1. Date Field Requirement
  • Problem: When managing chores, the date field must be clicked and selected every time. Not selecting it prevents submission, but the error is subtle and only noticeable after repeated submission attempts.
  • Workaround: Ensure you select the date field during creation or updates. Consider adding a note or warning to remind users.
  1. Claim Chore Button
  • Problem: When claiming a chore, the sensor status for that chore does not update to “claimed.” Instead, it remains as “overdue.” However, it is still possible to approve the claim. (This seems to work correctly for rewards.)
  1. Disapproving Chores/Rewards
  • Problem: When disapproving a chore or reward, the claim count does not decrement.
  • Note: This behavior might be by design and requires clarification.
  1. Manual Points Tracking
  • Problem: Manual points are displayed correctly in the max_points_ever sensor, but they do not appear in the points_earned sensors.
  • Note: This might also be by design and requires clarification.
  1. Overdue Chore Timing
  • Problem: Chores are marked as “overdue” even when the due time is later in the day (e.g., 9 PM).

Comments

  • Clarity on Dates and Recurring Activities: The process for managing dates and recurring activities needs more explanation or documentation.
  • Understanding kid_state vs global_state: There needs to be clearer guidance on how these states are used or differentiated.
  • Shared Chores:
    • Observation: When setting up a chore, at least one kid must be selected. Initially, I assumed leaving it blank would create a “shared chore” for all kids.
    • Suggestion: No system changes needed; however, an FAQ or description update in the form could clarify this behavior.
  • Observation: I’m not sure if there is value in supporting fractional points.
    • Suggestion: I notice that points in the dashboard are showing with a decimal. Unless there is a specific need to support partial points, I’d recommend limiting it to whole numbers for simplicity.

Future Feature Requests

  1. Ad-Hoc Chores
  • Request: Allow a type of chore that doesn’t have a specific due date. Parents could assign a date later for tasks that aren’t time-sensitive (e.g., “Get your room ready for guests”).
  1. Auto-Approved Chores
  • Request: Add an option to automatically approve certain chores. For example, chores like “Brush your teeth” could be trusted without requiring manual approval.
  1. Default Dashboard YAML
  • Request: Considering some of the required templating is a little more complex in these dashboards, it would be worth adding a “Default” or “Initial” dashboard configuration file to you this github project so there is a common place to maintain and update it.
  1. Time Sensitive or Critical Chores
  • Request: Mark key chores, such as feed the cat, with an attribute to identify it as a time sensitive chore that you could easily filter for and build separate alerting or notifications if it is overdue.

Hi @ccpk1
Thanks for all the working you’ve put on this and all the comments. I was not able to test everything, cause I was focused on getting this out asap, so we would have something to play around and having more eyes finding issues. :slight_smile:

About the issues you’ve found and comments, let me try to clarify some of those. See my comments below.

That’s something I noticed, but I did not find a way yet to delete the entities. I am still learning also some programming and the inner works of HA. Let mee see if I find a way to handle this.

That’s also an issue that I found with my latest version. The problem was that before the date field was not populating from ConfigEntry, so every time you would go in it would be empty. As I mentioned on the GitHub discussions, that’s something that I need to rework almost entirely, also to give some meaning to the recurring frequency and dates.

That only happens with overdue chores I understand. Is that it?

So I though about that, but decided for leaving the Claim count intact, cause it would give an idea of how many times a chore has been claimed but not approved. If claim == approve, there was not so much meaning. So if claims > approved, you know the number of times a chore was claimed but not approved.

At least this made sense to me and my use case.

Well, that was by design, but makes sense to sum to the points_earned. Let me revisit that.

That should not happen. In my tests that was working properly and time dependent. I will need to recheck that.


  1. That’s something already on my roadmap to review.

  2. Kid State vs Global State. This one is a bit trickier. The main idea is that s shared chore may be Globally approved, because one kid successfully accomplished the chore, but another kid may not participate on that task (or may not perform it well). This means that the chore is approved, the chore on kid 1 will be shown as approved, but for Kid 2 it will be shown as pending. Hope this clarifies the question.

  3. Shared chores. Noted to include this on a FAQ. But in summary, yes, each chore shall be assigned to the kids you want. If not assigned the kids cannot claim the chores.


Let me try to break this down:

  1. You don’t need to set a due date for each chore, actually you can leave the due date empty and it will not affect. The same for recurring frequency, which may also be set as None. I will expand the “mandatory” fields check during setup (which some are already set), but as I see it, right now, you can do this without an issue.

  2. I believe that this would change a bit the whole concept. Maybe you can add some automation for this and use the “approve_chore” service to automatically approve those chores at the time you may specify. That’s the best solution I can think of without complicating to much the whole mechanic of the integration.

  3. Integrations cannot incorporate Lovelace cards. In the future I may considering developing a separate integration only with lovelace cards, but by the moment I will stick to the base of the integration and maybe move from there in the future once this is stable.

Hope this all helps. I will get a copy of your post and get it on the Github to ease my follow-up.

Cheers

Thank you for the detailed response. There are a couple of things I’m seeing work differently than you describe, so for the issues, I’ll log an issue for each one I’m still seeing so I can provide some more detail and specifics. I’ll do the same for Feature request, but of course those are lower priority.

Thanks again!