Any idea why this automation to remove duplicates from a todo list isn't working?

I have a simple todo list, like this, and am trying to write an automation which will remove duplicates in the completed items whenever it is fired:

This is what I have, but I cannot figure out why it isn’t working:

alias: Remove duplicate items
description: ""
triggers: []
conditions: []
actions:
  - action: todo.get_items
    target:
      entity_id: todo.test_list
    data:
      status:
        - completed
    response_variable: completed_items
  - variables:
      items: "{{ completed_items['todo.test_list']['items'] }}"
      item_summaries: "{{ items | map(attribute='summary') | list }}"
      seen: []
  - repeat:
      count: "{{ items | length }}"
      sequence:
        - variables:
            item: "{{ items[repeat.index] }}"
            item_summary: "{{ item_summaries[repeat.index] }}"
        - choose:
            - conditions:
                - condition: template
                  value_template: "{{ item_summary not in seen }}"
              sequence:
                - variables:
                    seen: "{{ seen + [item_summary] }}"
            - conditions:
                - condition: template
                  value_template: "{{ item_summary in seen }}"
              sequence:
                - action: todo.remove_item
                  target:
                    entity_id: todo.test_list
                  data:
                    item: "{{ item }}"
mode: single

Basically, I am looping over every item in the list and trying to create a variable (seen) which keeps track of whether or not we have already encountered a particular item. If not, we append it to this seen list. If it has been seen before, then the item should be removed using todo.remove_item.

Can anyone help?

Currently, it doesn’t work because of scoping rules. The variable seen you initially define is independent of the variable by the same name you define within choose. The scope of the second instance of seen is local to where it’s defined (in conditions within choose).

In the future (next month’s release) it will work because scoping rules will change.

Reference

Scope of variables

1 Like

I see. Thank you, makes sense now.

Can you suggest a way to adjust it to achieve what I want (without waiting till next month :slightly_smiling_face:)?

I suggest you simply wait until next Wednesday (April release date).

The alternative is to find the duplicates using templating as opposed to a repeat.

1 Like

This was the way I originally tried to do it. I assume you mean with a “for” loop inside the “data” section of the action. But I dont think we can apply the “todo.remove_item” inside a templated “for loop”. Do you agree?

That was the reason for switching to a repeat block.

You would use the template to produce a list of duplicated items. Use repeat for_each to iterate through the list and delete each item using todo.remove_item.

Here’s a very basic example for producing a list of duplicates. Copy-paste it into the Template Editor and observe what it reports.

{% set x = ['cat', 'hat', 'bat', 'cat', 'rat', 'hat'] %}
{% set ns = namespace(uni=[], dup=[]) %}
{% for y in x %}
  {% if y in ns.uni %}
    {% set ns.dup = ns.dup + [y] %}
  {% else %}
    {% set ns.uni = ns.uni + [y] %}
  {% endif %}
{% endfor %}
{{ ns.dup }}

You’ll need to adapt it to work with completed_items and assign it to a variable (for example, such as duplicates). Then you would use this:

repeat:
  for_each: "{{ duplicates }}"
  sequence:
    - action: todo.remove_item
      data:
        item: "{{ repeat.item }}"

Or wait until next Wednesday.

@teeeeee

So here’s how it can be done using what I had suggested.

alias: Remove duplicate items
description: ""
triggers: []
conditions: []
actions:
  - action: todo.get_items
    target:
      entity_id: todo.test_list
    data:
      status:
        - completed
    response_variable: completed_items
  - variables:
      duplicates: >
        {% set x = completed_items['todo.test_list']['items'] | map(attribute='summary') | list %}
        {% set ns = namespace(uni=[], dup=[]) %}
        {% for y in x %}
          {% if y in ns.uni %}
            {% set ns.dup = ns.dup + [y] %}
          {% else %}
            {% set ns.uni = ns.uni + [y] %}
          {% endif %}
        {% endfor %}
        {{ ns.dup }}
  - repeat:
      for_each: "{{ duplicates }}"
      sequence:
        - action: todo.remove_item
          target:
            entity_id: todo.test_list
          data:
            item: "{{ repeat.item }}"
mode: single
2 Likes

Excellent, this works. Thanks for helping me understand the problem.

Is the use of the namespace type critical here? I read in the jinja documentation here that the namespace class is related to the scope of variables. Do you use it for this reason?

1 Like

Oh actually I see what you have done. ns.uni essentially represents the seen variable that I had before.