Is there a more optimal way to automatically sort cards based on input_datetime helpers?

Hi all,

Disclaimer: I am VERY new to coding so please bear with me. I learned what a string was about two months ago.

What I want is a list of chores on my dashboard, sorted automatically based on how overdue they are. I’ve managed to accomplish this using the custom template card from Mushroom on HACS, but the way I thought of is horribly inefficient and bloated. I’m noticing slowdowns because of my enormous 6000 line templates.yaml file. I’m hoping someone here might have a MUCH cleaner way of accomplishing the same thing.

Here’s how I’ve done it:

First, I made an input_datetime helper and template sensor for each chore (totaling over 30 across multiple separate categories). The “type” and “period” attributes exist because there are multiple categories of chores that I want sorted but only within those categories.

- sensor:
    - name: mopping_due
      state: >
        {% if (((as_timestamp(now()) - state_attr('input_datetime.mopping','timestamp')) | int / 86400)  | round(0) -1) >= 7 %}
          due
        {% elif (((as_timestamp(now()) - state_attr('input_datetime.mopping','timestamp')) | int / 86400)  | round(0) -1) >= 5 %}
          almost
        {% else %}
          notdue
        {% endif %}
      attributes:
        type: 'weekly'
        overdue: >
          {{((as_timestamp(now()) - state_attr('input_datetime.mopping','timestamp')) | int / 86400)  | round(0) -8}}
        mdi: "mdi:broom"
        period: 'seven'
        name: 'Mop'
        inputdatetime: 'input_datetime.mopping'

Then, I created an additional template sensor for each chore, this time sorting them.

- sensor: 
    - name: 0th_weekly_cleaning
      state: >
          {% set x = (states.sensor
          | selectattr('state', 'in', ['notdue','almost', 'due'])
          | selectattr("attributes.type", 'eq', 'weekly')
          | sort(attribute="attributes.overdue", reverse=true)) %}
          {% set output = namespace(task=[]) %}
          {% for state in x %}
            {% set s = state_attr(state.entity_id, 'overdue') %}
            {% set output.task = output.task + [s] %}
          {% endfor %}
          {{ output.task[0] }}
      attributes:
        name: >
          {% set x = (states.sensor
          | selectattr('state', 'in', ['notdue','almost', 'due'])
          | selectattr("attributes.type", 'eq', 'weekly')
          | sort(attribute="attributes.overdue", reverse=true)) %}
          {% set output = namespace(task=[]) %}
          {% for state in x %}
            {% set s = state_attr(state.entity_id, 'name') %}
            {% set output.task = output.task + [s] %}
          {% endfor %}
          {{ output.task[0] }}
        mdi: >
          {% set x = (states.sensor
          | selectattr('state', 'in', ['notdue','almost', 'due'])
          | selectattr("attributes.type", 'eq', 'weekly')
          | sort(attribute="attributes.overdue", reverse=true)) %}
          {% set output = namespace(task=[]) %}
          {% for state in x %}
            {% set s = state_attr(state.entity_id, 'mdi') %}
            {% set output.task = output.task + [s] %}
          {% endfor %}
          {{ output.task[0] }}
        inputdatetime: >
          {% set x = (states.sensor
          | selectattr('state', 'in', ['notdue','almost', 'due'])
          | selectattr("attributes.type", 'eq', 'weekly')
          | sort(attribute="attributes.overdue", reverse=true)) %}
          {% set output = namespace(task=[]) %}
          {% for state in x %}
            {% set s = state_attr(state.entity_id, 'inputdatetime') %}
            {% set output.task = output.task + [s] %}
          {% endfor %}
          {{ output.task[0] }}
        due: >
          {% set x = (states.sensor
          | selectattr('state', 'in', ['notdue','almost', 'due'])
          | selectattr("attributes.type", 'eq', 'weekly')
          | sort(attribute="attributes.overdue", reverse=true)) %}
          {% set output = namespace(task=[]) %}
          {% for state in x %}
            {% set s = states(state.entity_id) %}
            {% set output.task = output.task + [s] %}
          {% endfor %}
          {{ output.task[0] }}
        secondary: >
          {% set x = (states.sensor
          | selectattr('state', 'in', ['notdue','almost', 'due'])
          | selectattr("attributes.type", 'eq', 'weekly')
          | sort(attribute="attributes.overdue", reverse=true)) %}
          {% set output = namespace(task=[]) %}
          {% set output2 = namespace(task=[]) %}
          {% for state in x %}
            {% set n = state_attr(state.entity_id, 'overdue') %}
            {% set output2.task = output2.task + [n] %}
            {% set s = state_attr(state.entity_id, 'period') %}
            {% set output.task = output.task + [s] %}
          {% endfor %}
          {% if output2.task[0] == -1 %}
          Every 7 days - Due tomorrow
          {% elif output2.task[0] ==0 %}
          Every 7 days - Due today 
          {% elif output2.task[0] ==1 %}
          Every 7 days - 1 day overdue
          {% elif output2.task[0] >1 %}
          Every 7 days - {{output2.task[0]}} days overdue
          {% elif output2.task[0] <-1 %}
          Every 7 days - {{ output2.task[0] * -1}} days left
          {% endif %}

Finally, I made cards for the sorted template sensors.

- type: custom:mushroom-template-card
  primary: '{{ state_attr(''sensor.0th_weekly_cleaning'',''name'')}}'
  secondary: '{{ state_attr(''sensor.0th_weekly_cleaning'',''secondary'')}}'
  icon: '{{ state_attr(''sensor.0th_weekly_cleaning'',''mdi'')}}'
  icon_color: |-
    {% if is_state_attr('sensor.0th_weekly_cleaning','due', 'due') %}
    red
    {% elif is_state_attr('sensor.0th_weekly_cleaning','due', 'almost') %}
    #DF7853
    {% else %}
    green
    {% endif %}
  badge_icon: |-
    {% if is_state_attr('sensor.0th_weekly_cleaning','due', 'due') %}
    mdi:alert-circle
    {% else %}
    {% endif %}
  badge_color: |-
    {% if is_state_attr('sensor.0th_weekly_cleaning','due', 'due') %}
    red
    {% elif is_state_attr('sensor.0th_weekly_cleaning','due', 'almost') %}
    #DF7853
    {% else %}
    green
    {% endif %}
  tap_action:
    action: none
  hold_action:
    action: none
  double_tap_action:
    action: call-service
    service: script.resetweekly0

This is the script for the above card

resetweeklychore0:
  alias: Resetweeklychore0
  sequence:
  - service: input_datetime.set_datetime
    data_template:
      date: '{{ now().strftime(''%Y-%m-%d'') }}'
    target:
      entity_id: '{{ state_attr(''sensor.0th_weekly_chore'', ''inputdatetime'') }}'
  mode: single

One way I thought might help is if I could somehow store the value of the following block and then reference that in my templates instead of repeating it a million times, but it seems that the state of a sensor can’t be above 255 characters. I have Node-RED maybe I could do something there?

{% set x = (states.sensor
| selectattr('state', 'in', ['notdue','almost', 'due'])
| selectattr("attributes.type", 'eq', 'weekly')
| sort(attribute="attributes.overdue", reverse=true)) %}

I’m aware of the Lovelace Auto Entities card , but I want the customizability of the template card, including the ability to assign separate tap actions to each chore.

If there’s no better way, do you think a faster CPU might fix things? I’m running HAOS on a vm on my Synology NAS (CPU is a Celeron J4125). Maybe if I upgraded to something like an i5-12400 and ran HAOS on bare metal it might help.

If you’re not married to this method, I would highly recommend the Grocy addon. Thought it’s primary purpose is for keeping an inventory of grocery items, menu plans, and shopping lists it also handles chores and tasks. There is a custom component to pull your chores and tasks into HA as sensors and a custom card to display chores that are “due soon”, “due today”, or “overdue”.

2 Likes

Thanks for the recommendation! I just checked it out and it seems like there’s similar functionality, but I’m just not loving it visually. Maybe I can mess around with it a little more. I do like the other components like grocery items and menu plans though.

I figured out a way to accomplish the same thing through Node-RED. It’s noticeably faster, but I’d still like a better way to do it. In case anyone is interested, here’s a sample flow.

[{"id":"80291e161d188da2","type":"ha-get-entities","z":"52b0e34b0520b7c7","name":"Grab Array","server":"2c76f9b4.5da3a6","version":0,"rules":[{"property":"attributes.type","logic":"is","value":"weekly","valueType":"str"}],"output_type":"array","output_empty_results":false,"output_location_type":"msg","output_location":"payload","output_results_count":1,"x":490,"y":480,"wires":[["deb05740e00bd9c7"]]},{"id":"deb05740e00bd9c7","type":"sort","z":"52b0e34b0520b7c7","name":"","order":"descending","as_num":false,"target":"payload","targetType":"msg","msgKey":"attributes.overdue","msgKeyType":"jsonata","seqKey":"payload","seqKeyType":"msg","x":630,"y":480,"wires":[["2709a6de85f318a6"]]},{"id":"d73bf7996bce0c2c","type":"switch","z":"52b0e34b0520b7c7","name":"","property":"parts.index","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"str"},{"t":"eq","v":"1","vt":"str"},{"t":"eq","v":"2","vt":"str"},{"t":"eq","v":"3","vt":"str"},{"t":"eq","v":"4","vt":"str"},{"t":"eq","v":"5","vt":"str"},{"t":"eq","v":"6","vt":"str"},{"t":"eq","v":"7","vt":"str"},{"t":"eq","v":"8","vt":"str"},{"t":"eq","v":"9","vt":"str"},{"t":"eq","v":"10","vt":"str"},{"t":"eq","v":"11","vt":"str"},{"t":"eq","v":"12","vt":"str"}],"checkall":"false","repair":false,"outputs":13,"x":1130,"y":480,"wires":[["a0d689e4a8c0561d"],["833f5d23eb0f2740"],["333ac7483ce9c984"],["e4c475265dc6180a"],["f86e9c6d5e648dfe"],["f05f9b58d4f301ed"],["7de4ab84ca0e2768"],["4b1854d935963290"],["bbaa6dac47f89ce9"],["af3afff29e929be8"],["6dab261bf938c6e8"],["42d76f2e275e2178"],["26fbf9a3600edb8d"]]},{"id":"2709a6de85f318a6","type":"split","z":"52b0e34b0520b7c7","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":750,"y":480,"wires":[["71a65d87db416bd2"]]},{"id":"71a65d87db416bd2","type":"change","z":"52b0e34b0520b7c7","name":"filter","rules":[{"t":"move","p":"payload.state","pt":"msg","to":"payload.attributes.due","tot":"msg"},{"t":"move","p":"payload.attributes.overdue","pt":"msg","to":"payload.state","tot":"msg"},{"t":"delete","p":"payload.attributes.type","pt":"msg"},{"t":"delete","p":"payload.attributes.subtype","pt":"msg"},{"t":"delete","p":"payload.context","pt":"msg"},{"t":"delete","p":"payload.last_changed","pt":"msg"},{"t":"delete","p":"payload.last_updated","pt":"msg"},{"t":"delete","p":"payload.timeSinceChangedMs","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":870,"y":480,"wires":[["bd18da9dff5ee73b"]]},{"id":"a0d689e4a8c0561d","type":"ha-entity","z":"52b0e34b0520b7c7","name":"0th_weekly_cleaning","server":"2c76f9b4.5da3a6","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"0th_weekly_cleaning"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1340,"y":360,"wires":[[]]},{"id":"833f5d23eb0f2740","type":"ha-entity","z":"52b0e34b0520b7c7","name":"1st_weekly_cleaning","server":"2c76f9b4.5da3a6","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"1st_weekly_cleaning"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1340,"y":380,"wires":[[]]},{"id":"333ac7483ce9c984","type":"ha-entity","z":"52b0e34b0520b7c7","name":"2nd_weekly_cleaning","server":"2c76f9b4.5da3a6","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"2nd_weekly_cleaning"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1340,"y":400,"wires":[[]]},{"id":"e4c475265dc6180a","type":"ha-entity","z":"52b0e34b0520b7c7","name":"3rd_weekly_cleaning","server":"2c76f9b4.5da3a6","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"3rd_weekly_cleaning"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1340,"y":420,"wires":[[]]},{"id":"f86e9c6d5e648dfe","type":"ha-entity","z":"52b0e34b0520b7c7","name":"4th_weekly_cleaning","server":"2c76f9b4.5da3a6","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"4th_weekly_cleaning"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1340,"y":440,"wires":[[]]},{"id":"f05f9b58d4f301ed","type":"ha-entity","z":"52b0e34b0520b7c7","name":"5th_weekly_cleaning","server":"2c76f9b4.5da3a6","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"5th_weekly_cleaning"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1340,"y":460,"wires":[[]]},{"id":"7de4ab84ca0e2768","type":"ha-entity","z":"52b0e34b0520b7c7","name":"6th_weekly_cleaning","server":"2c76f9b4.5da3a6","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"6th_weekly_cleaning"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1340,"y":480,"wires":[[]]},{"id":"4b1854d935963290","type":"ha-entity","z":"52b0e34b0520b7c7","name":"7th_weekly_cleaning","server":"2c76f9b4.5da3a6","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"7th_weekly_cleaning"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1340,"y":500,"wires":[[]]},{"id":"bbaa6dac47f89ce9","type":"ha-entity","z":"52b0e34b0520b7c7","name":"8th_weekly_cleaning","server":"2c76f9b4.5da3a6","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"8th_weekly_cleaning"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1340,"y":520,"wires":[[]]},{"id":"af3afff29e929be8","type":"ha-entity","z":"52b0e34b0520b7c7","name":"9th_weekly_cleaning","server":"2c76f9b4.5da3a6","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"9th_weekly_cleaning"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1340,"y":540,"wires":[[]]},{"id":"6dab261bf938c6e8","type":"ha-entity","z":"52b0e34b0520b7c7","name":"10th_weekly_cleaning","server":"2c76f9b4.5da3a6","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"10th_weekly_cleaning"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1340,"y":560,"wires":[[]]},{"id":"42d76f2e275e2178","type":"ha-entity","z":"52b0e34b0520b7c7","name":"11th_weekly_cleaning","server":"2c76f9b4.5da3a6","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"11th_weekly_cleaning"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1340,"y":580,"wires":[[]]},{"id":"26fbf9a3600edb8d","type":"ha-entity","z":"52b0e34b0520b7c7","name":"12th_weekly_cleaning","server":"2c76f9b4.5da3a6","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"12th_weekly_cleaning"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1340,"y":600,"wires":[[]]},{"id":"f1d5660e8fd8d78d","type":"inject","z":"52b0e34b0520b7c7","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":500,"y":440,"wires":[["80291e161d188da2"]]},{"id":"1e0a987c993353a1","type":"trigger-state","z":"52b0e34b0520b7c7","name":"Weekly Cleaning","server":"2c76f9b4.5da3a6","version":2,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityid":["sensor.cleanbaseboards_due","sensor.cleandishrack_due","sensor.cleankitchen_due","sensor.cleanlivingroomtable_due","sensor.cleanmirrorbathroom_due","sensor.cleanstove_due","sensor.cleantoothbrushes_due","sensor.cleanwindowsills_due","sensor.dusting_due","sensor.emptyclean_vacuum_due","sensor.laundry_due","sensor.mopping_due","sensor.vacuuming_due"],"entityidfiltertype":"list","debugenabled":false,"constraints":[{"targetType":"this_entity","targetValue":"","propertyType":"current_state","propertyValue":"new_state.state","comparatorType":"is","comparatorValueDatatype":"str","comparatorValue":""}],"inputs":0,"outputs":2,"customoutputs":[],"outputinitially":false,"state_type":"str","enableInput":false,"x":100,"y":480,"wires":[["80291e161d188da2"],["80291e161d188da2"]]},{"id":"bd18da9dff5ee73b","type":"function","z":"52b0e34b0520b7c7","name":"Template","func":"var state = parseInt(msg.payload.state)\nif (state == -1) {\n    msg.payload.attributes.secondary = 'Every 7 days - Due tomorrow';\n}\nelse if (state == 0) {\n    msg.payload.attributes.secondary = 'Every 7 days - Due today';\n}\nelse if (state == 1) {\n    msg.payload.attributes.secondary = 'Every 7 days - 1 day overdue';\n}\nelse if (state > 1) {\n    msg.payload.attributes.secondary = 'Every 7 days - ' + (state) + ' days overdue';\n}\nelse if (state < -1) {\n    msg.payload.attributes.secondary = 'Every 7 days - ' + (state * -1) + ' days left';\n}\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1000,"y":480,"wires":[["d73bf7996bce0c2c"]]},{"id":"2c76f9b4.5da3a6","type":"server","name":"Home Assistant","version":4,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30,"areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m"}]