Bidirectional Shopping List Sync Automation

Thanks for sharing this automation. I have it mostly working, but I want to implement the check to see if both lists are identical so I can run the sync more frequently than 20 minutes. Where exactly does this section of the code go? Should I be inserting it as the first action? Below is the actions section of my automation and it doesn’t currently work for me.

  actions:
    - alias: Check if lists are different
      sequence:
        - alias: Get ALL list entries of both lists
          action: todo.get_items
          metadata: {}
          data:
            status:
              - needs_action
              - completed
          response_variable: all_items
          target:
            entity_id:
              - todo.alexa_shopping_list
              - todo.shopping_list
        - alias: Check If both lists are identical
          if:
            - condition: template
              value_template: >
                {% set shopping_list_items = all_items['todo.shopping_list']['items'] |
                selectattr('status', 'equalto', 'needs_action') | map(attribute='summary') | list | sort %}

                {% set alexa_shopping_list_items = all_items['todo.alexa_shopping_list']['items'] | 
                selectattr('status', 'equalto', 'needs_action') | map(attribute='summary') | list | sort %}

                {{ shopping_list_items == alexa_shopping_list_items }}
          then:
            - stop: Both lists are identical
      enabled: true
    - choose:
        - conditions:
            - condition: template
              value_template: >-
                {{ states.todo.shopping_list.last_changed >
                states.todo.alexa_shopping_list.last_changed }}
              alias: Alexa list is more up-to-date than AnyList
            - condition: template
              value_template: " {{ (now() - states.todo.alexa_shopping_list.last_changed).total_seconds() > 180 }}"
              alias: AnyList list has not been updated for several minutes
          sequence:
            - sequence:
                - alias: Clear the AnyList list
                  sequence:
                    - alias: Retrieve existing entries from AnyList
                      target:
                        entity_id: todo.alexa_shopping_list
                      data:
                        status:
                          - needs_action
                          - completed
                      response_variable: alexa_shopping_list_items
                      action: todo.get_items
                    - alias: Empty the AnyList list
                      repeat:
                        while:
                          - condition: template
                            value_template: "{{ (states('todo.alexa_shopping_list') | int ) > 0 }}"
                        sequence:
                          - alias: Refresh AnyList list after deletion
                            target:
                              entity_id: todo.alexa_shopping_list
                            data:
                              status:
                                - needs_action
                                - completed
                            response_variable: alexa_shopping_list_items
                            action: todo.get_items
                          - delay: "00:00:02"
                          - repeat:
                              for_each: >-
                                {{ alexa_shopping_list_items['todo.alexa_shopping_list']['items'] |
                                map(attribute='summary') | list }}
                              sequence:
                                - target:
                                    entity_id: todo.alexa_shopping_list
                                  data: |
                                    {{ dict(item=repeat.item) }}
                                  action: todo.remove_item
                - alias: Update AnyList list with Alexa items
                  sequence:
                    - alias: Retrieve all entries from Alexa
                      target:
                        entity_id: todo.shopping_list
                      data:
                        status:
                          - needs_action
                          - completed
                      response_variable: shopping_list_items
                      action: todo.get_items
                    - alias: Define variables for unique items
                      variables:
                        unique_needs_action_items: |-
                          {{ shopping_list_items['todo.shopping_list']['items']
                            | selectattr('status', 'eq', 'needs_action')
                            | map(attribute='uid')
                            | list
                            | map('regex_replace', '^', '{\"uid\": \"')
                            | map('regex_replace', '$', '\"}')
                            | map('from_json')
                            | list }}
                        unique_completed_items: |-
                          {{ shopping_list_items['todo.shopping_list']['items']
                            | selectattr('status', 'eq', 'completed')
                            | map(attribute='uid')
                            | list
                            | map('regex_replace', '^', '{\"uid\": \"')
                            | map('regex_replace', '$', '\"}')
                            | map('from_json')
                            | list }}
                    - alias: Copy pending items from Alexa to AnyList
                      repeat:
                        for_each: "{{ unique_needs_action_items }}"
                        sequence:
                          - target:
                              entity_id: todo.alexa_shopping_list
                            data:
                              item: >-
                                {{ shopping_list_items['todo.shopping_list']['items'] |
                                selectattr('uid', 'eq', repeat.item.uid) |
                                map(attribute='summary') | first }}
                            action: todo.add_item
                    - alias: >-
                        Copy completed items from Alexa to AnyList and mark as
                        completed
                      repeat:
                        for_each: "{{ unique_completed_items }}"
                        sequence:
                          - target:
                              entity_id: todo.alexa_shopping_list
                            data:
                              item: >-
                                {{ shopping_list_items['todo.shopping_list']['items'] |
                                selectattr('uid', 'eq', repeat.item.uid) |
                                map(attribute='summary') | first }}
                            action: todo.add_item
                          - target:
                              entity_id: todo.alexa_shopping_list
                            data:
                              item: >-
                                {{ shopping_list_items['todo.shopping_list']['items'] |
                                selectattr('uid', 'eq', repeat.item.uid) |
                                map(attribute='summary') | first }}
                              status: completed
                            action: todo.update_item
              alias: Update AnyList list
          alias: Alexa list is more up-to-date than AnyList list
        - conditions:
            - alias: AnyList list is more up-to-date than Alexa list
              condition: template
              value_template: >-
                {{ states.todo.alexa_shopping_list.last_changed >
                states.todo.shopping_list.last_changed }}
            - alias: Alexa list has not been updated for several minutes
              condition: template
              value_template: " {{ (now() - states.todo.shopping_list.last_changed).total_seconds() > 180 }}"
          sequence:
            - alias: Update Alexa list with AnyList items
              sequence:
                - alias: Clear the Alexa list
                  sequence:
                    - alias: Retrieve existing entries from Alexa
                      target:
                        entity_id: todo.shopping_list
                      data:
                        status:
                          - needs_action
                          - completed
                      response_variable: shopping_list_items
                      action: todo.get_items
                    - alias: Empty the Alexa list
                      repeat:
                        while:
                          - condition: template
                            value_template: "{{ (states('todo.shopping_list') | int ) > 0 }}"
                        sequence:
                          - alias: Refresh Alexa list after deletion
                            target:
                              entity_id: todo.shopping_list
                            data:
                              status:
                                - needs_action
                                - completed
                            response_variable: shopping_list_items
                            action: todo.get_items
                          - delay: "00:00:02"
                          - repeat:
                              for_each: >-
                                {{ shopping_list_items['todo.shopping_list']['items'] |
                                map(attribute='summary') | list }}
                              sequence:
                                - target:
                                    entity_id: todo.shopping_list
                                  data: |
                                    {{ dict(item=repeat.item) }}
                                  action: todo.remove_item
                - alias: Update Alexa list with AnyList items
                  sequence:
                    - alias: Retrieve all entries from AnyList
                      target:
                        entity_id: todo.alexa_shopping_list
                      data:
                        status:
                          - needs_action
                          - completed
                      response_variable: alexa_shopping_list_items
                      action: todo.get_items
                    - alias: Define variables for unique items
                      variables:
                        unique_needs_action_items: |-
                          {{ alexa_shopping_list_items['todo.alexa_shopping_list']['items']
                            | selectattr('status', 'eq', 'needs_action')
                            | map(attribute='uid')
                            | list
                            | map('regex_replace', '^', '{\"uid\": \"')
                            | map('regex_replace', '$', '\"}')
                            | map('from_json')
                            | list }}
                        unique_completed_items: |-
                          {{ alexa_shopping_list_items['todo.alexa_shopping_list']['items']
                            | selectattr('status', 'eq', 'completed')
                            | map(attribute='uid')
                            | list
                            | map('regex_replace', '^', '{\"uid\": \"')
                            | map('regex_replace', '$', '\"}')
                            | map('from_json')
                            | list }}
                    - alias: Copy pending items from AnyList to Alexa
                      repeat:
                        for_each: "{{ unique_needs_action_items }}"
                        sequence:
                          - target:
                              entity_id: todo.shopping_list
                            data:
                              item: >-
                                {{ alexa_shopping_list_items['todo.alexa_shopping_list']['items'] |
                                selectattr('uid', 'eq', repeat.item.uid) |
                                map(attribute='summary') | first }}
                            action: todo.add_item
                    - alias: >-
                        Copy completed items from AnyList to Alexa and mark as
                        completed
                      repeat:
                        for_each: "{{ unique_completed_items }}"
                        sequence:
                          - target:
                              entity_id: todo.shopping_list
                            data:
                              item: >-
                                {{ alexa_shopping_list_items['todo.alexa_shopping_list']['items'] |
                                selectattr('uid', 'eq', repeat.item.uid) |
                                map(attribute='summary') | first }}
                            action: todo.add_item
                          - target:
                              entity_id: todo.shopping_list
                            data:
                              item: >-
                                {{ alexa_shopping_list_items['todo.alexa_shopping_list']['items'] |
                                selectattr('uid', 'eq', repeat.item.uid) |
                                map(attribute='summary') | first }}
                              status: completed
                            action: todo.update_item
          alias: AnyList list is more up-to-date than Alexa list
      enabled: true
  mode: single

Any suggestions would be really appreciated.

I ended up implementing shpongledsummer’s original script/automation solution with some changes. I’ve been attempting to sync Alexa with AnyList using the two integrations and the bi-directional sync solution wasn’t ideal because the integrations update at different intervals and it was causing duplicates and all sorts of other problems. So what I did was create one script to update Anylist when an Alexa item was added and a second script to mark an Alexa item as complete when I check off an AnyList item. Because I only use AnyList for my shopping, I don’t really care if AnyList adds new items I create within the app to Alexa. But I do want Alexa updated when I mark an item completed within AnyList for obvious reasons. So I only need one-way sync for these tasks to accomplish what I need. Here’s my final solution, which is working well so far:

Scripts
Update AnyList when a new item is added to Alexa:

update_anylist:
  alias: Sync New Alexa Items with AnyList
  icon: mdi:food-fork-drink
  sequence:
    - alias: Update list | AnyList
      sequence:
        - alias: Retrieve existing entries from Alexa Shopping List
          target:
            entity_id: todo.shopping_list
          data:
            status:
              - needs_action
          response_variable: source_items
          action: todo.get_items
        - alias: Retrieve existing entries from AnyList
          target:
            entity_id: todo.anylist_shopping_list
          data:
            status:
              - needs_action
          response_variable: existing_items
          action: todo.get_items
        - alias: Add new entries to AnyList list
          repeat:
            for_each: |-
              {{
                source_items['todo.shopping_list']['items']
                | selectattr('status', 'eq', 'needs_action')
                | rejectattr('summary', 'in', existing_items['todo.anylist_shopping_list']['items'] | map(attribute='summary') | list)
                | list
              }}
            sequence:
              - target:
                  entity_id: todo.anylist_shopping_list
                data:
                  item: "{{ repeat.item.summary }}"
                action: todo.add_item
        - alias: Reset re-added items
          repeat:
            for_each: |-
              {{
                source_items['todo.shopping_list']['items']
                | selectattr('needs_action')
                | selectattr('summary', 'in', existing_items['todo.anylist_shopping_list']['items'] | selectattr('status', 'eq', 'completed') | map(attribute='summary') | list)
                | list
              }}
            sequence:
              - target:
                  entity_id: todo.anylist_shopping_list
                data:
                  item: "{{ repeat.item.summary }}"
                  status: needs_action
                action: todo.update_item

Update Alexa when an item is marked as complete in AnyList:

update_alexa_list:
  alias: Sync Completed Anylist Items with Alexa
  icon: mdi:food-fork-drink
  sequence:
    - alias: Update list | Alexa
      sequence:
        - alias: Retrieve existing entries from AnyList
          target:
            entity_id: todo.anylist_shopping_list
          data:
            status:
              - completed
          response_variable: source_items
          action: todo.get_items
        - alias: Retrieve existing entries from Alexa
          target:
            entity_id: todo.shopping_list
          data:
            status:
              - needs_action
          response_variable: existing_items
          action: todo.get_items
        - alias: Mark completed items as done in AnyList
          repeat:
            for_each: |-
              {{
                source_items['todo.anylist_shopping_list']['items']
                | selectattr('status', 'eq', 'completed')
                | selectattr('summary', 'in', existing_items['todo.shopping_list']['items'] | selectattr('status', 'ne', 'completed') | map(attribute='summary') | list)
                | list
              }}
            sequence:
              - target:
                  entity_id: todo.shopping_list
                data:
                  item: "{{ repeat.item.summary }}"
                  status: completed
                action: todo.update_item

And here’s the automation:

- id: dishwasher_complete_notification
  alias: Notify when dishwasher starts
  triggers:
    trigger: state
    entity_id: sensor.dishwasher_operation_state
    to: "Ready"
  actions:
    action: notify.mobile_app_chris_iphone
    data:
      message: Dishwasher Has Completed The Cycle
      data:
        push:
          badge: 0
- id: update_shopping_lists
  alias: Sync Lists On Update
  description: Sync Alexa and Anylist when updates are made
  mode: single
  triggers:
    - entity_id: todo.shopping_list
      trigger: state
      alias: Alexa list was updated
      id: Alexa list was updated
    - entity_id: todo.anylist_shopping_list
      trigger: state
      alias: Anylist list was updated
      id: Anylist list was updated
    - alias: 15-minute trigger
      trigger: time_pattern
      id: 15-minute trigger
      minutes: /15
  actions:
    - delay:
        hours: 0
        minutes: 0
        seconds: 5
        milliseconds: 0
    - choose:
        - conditions:
            - condition: trigger
              id:
                - Alexa list was updated
          sequence:
            - action: script.update_anylist
              metadata: {}
              data: {}
              alias: Update Anylist list
          alias: Alexa list was updated (update Anylist)
        - conditions:
            - condition: trigger
              id:
                - Anylist list was updated
          sequence:
            - action: script.update_alexa_list
              metadata: {}
              data: {}
              alias: Update Alexa list
          alias: Anylist list was updated (update Alexa)
        - conditions:
            - condition: trigger
              id:
                - 15-minute trigger
          sequence:
            - action: script.update_anylist
              metadata: {}
              data: {}
              alias: Update Anylist list
          alias: Alexa list was updated (update Anylist based on time)

This thread inspired me to try and sync my Mealie and Bring lists as well. Apart from the issues and struggles pointed out above, one specific thing that I wanted to fix was that in the Bring items should start with the ingredient, and quantities (+ units) and notes should go in the description field (so ‘65 gr pesto (green)’ in Mealie should translate to ‘pesto’ in Bring!, with ‘65 gr (green)’ in the description.

With a lot of experimenting, and much help from Claude.ai I managed to get at the following setup where I tried to minimize the number of locations where the list names need to be defined:

  • a simple automation that triggers the update of the local copy of the Mealie list (as Mealie does not send a trigger itself)
  • an automation that is triggered by either an update of the Bring! list, the Mealie list, or a periodic time trigger; this automation contains the names of the two lists to be synchronized
  • a generic script that deals with the synchronization, in either direction, or both. The script receives the names of the lists as a parameter (so if you have multiple pairs of lists, you only need a single copy of this sync script).
    In the code below the names of the Bring! and Mealie list are todo.i_norge and todo.mealie_i_norge, respectively.

In order to get this working, I had to adopt the procedure to demarcate the text in the notes to an ingredient (I used parentheses).

The trigger script for Mealie updates (the name of the list needs to be modified in one location)

alias: trigger update Mealie I Norge
description: ""
triggers:
  - trigger: time_pattern
    minutes: /3
conditions: []
actions:
  - action: homeassistant.update_entity
    data:
      entity_id:
        - todo.mealie_i_norge
mode: single

The automation that calls the sync script. Each of the list names have to modified in two places: in the trigger, and in the variable definition.

alias: Mealie & Bring | Sync (I Norge)
description: Synchronizes the shopping lists between Mealie and Bring! bidirectionally.
triggers:
  - alias: Mealie list was updated
    entity_id:
      - todo.mealie_i_norge
    id: mealie_updated
    trigger: state
  - alias: Bring! list was updated
    entity_id:
      - todo.i_norge
    id: bring_updated
    trigger: state
  - alias: Periodic sync every 5 minutes
    id: periodic_sync
    minutes: /5
    trigger: time_pattern
conditions: []
actions:
  - variables:
      mealie_list: todo.mealie_i_norge
      bring_list: todo.i_norge
  - delay:
      seconds: 10
  - choose:
      - conditions:
          - condition: trigger
            id:
              - mealie_updated
        sequence:
          - data:
              mealie_list: "{{ mealie_list }}"
              bring_list: "{{ bring_list }}"
              direction: mealie_to_bring
            action: script.shopping_list_sync_bidirectional_generic
        alias: Mealie updated → sync to Bring
      - conditions:
          - condition: trigger
            id:
              - bring_updated
        sequence:
          - data:
              mealie_list: "{{ mealie_list }}"
              bring_list: "{{ bring_list }}"
              direction: bring_to_mealie
            action: script.shopping_list_sync_bidirectional_generic
        alias: Bring updated → sync to Mealie
      - conditions:
          - condition: trigger
            id:
              - periodic_sync
        sequence:
          - data:
              mealie_list: "{{ mealie_list }}"
              bring_list: "{{ bring_list }}"
              direction: mealie_to_bring
            action: script.shopping_list_sync_bidirectional_generic
          - data:
              mealie_list: "{{ mealie_list }}"
              bring_list: "{{ bring_list }}"
              direction: bring_to_mealie
            action: script.shopping_list_sync_bidirectional_generic
        alias: Periodic sync (both directions)
mode: single

Finally the generic synchronization script (works for any pair of lists). In order to be able to parse the list items from Mealie so that we can separate quantity, units, ingredient and notes, the script needs to know about the units you use. That list is contained in the variable supported_units. I extracted that information from Mealie via Settings → Data management → select ‘Units’ in drop down list → Download. Next, you need to bring in the format as used in this script (a list with items separated by a ‘|’ ).

alias: Shopping List Sync | Bidirectional (Generic)
description: >-
  Bidirectional synchronization between Mealie and Bring! shopping lists.
  Supports multiple list pairs through input parameters. Developed with
  assistance from Claude.ai
fields:
  mealie_list:
    description: The Mealie todo list entity ID
    example: todo.mealie_thuis
    required: true
    selector:
      entity:
        domain: todo
  bring_list:
    description: The Bring! todo list entity ID
    example: todo.test_lijst
    required: true
    selector:
      entity:
        domain: todo
  direction:
    description: Sync direction (mealie_to_bring or bring_to_mealie)
    example: mealie_to_bring
    required: true
    selector:
      select:
        options:
          - mealie_to_bring
          - bring_to_mealie
  supported_units:
    description: Supported units for parsing (can be left as default)
    example: g|gr|gram|kg|l|ml
    required: false
    default: >-
      bakje|blaadje|blaadjes|blik|blikken|bosje|bundel|bundels|c|cm|cup|eetlepel|eetlepels|el|fl_oz|fluid_ounce|g|gal|gallon|gr|gram|hoofd|hoofden|kg|kilogram|kilogrammen|kopjes|l|lb|liter|liters|mespuntje|mespuntjes|mg|milligram|milligrammen|milliliter|milliliters|ml|ounce|oz|pak|pakken|pint|pond_US|pond|ponden|portie|porties|pot|potten|pt|qt|quart|scheutje|snufje|snuif|st|stengel|stengels|stuk|stuks|takje|teen|teentjes|theelepel|theelepels|tl
mode: queued
max: 5
max_exceeded: silent
sequence:
  - variables:
      supported_units: |-
        {% if supported_units is defined %}
          {{ supported_units }}
        {% else %}
          bakje|blaadje|blaadjes|blik|blikken|bosje|bundel|bundels|c|cm|cup|eetlepel|eetlepels|el|fl_oz|fluid_ounce|g|gal|gallon|gr|gram|hoofd|hoofden|kg|kilogram|kilogrammen|kopjes|l|lb|liter|liters|mespuntje|mespuntjes|mg|milligram|milligrammen|milliliter|milliliters|ml|ounce|oz|pak|pakken|pint|pond_US|pond|ponden|portie|porties|pot|potten|pt|qt|quart|scheutje|snufje|snuif|st|stengel|stengels|stuk|stuks|takje|teen|teentjes|theelepel|theelepels|tl
        {% endif %}
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ direction == 'mealie_to_bring' }}"
        sequence:
          - alias: Retrieve existing entries from Mealie list
            action: todo.get_items
            target:
              entity_id: "{{ mealie_list }}"
            data:
              status:
                - needs_action
                - completed
            response_variable: source_items
          - alias: Retrieve existing entries from Bring! list
            action: todo.get_items
            target:
              entity_id: "{{ bring_list }}"
            data:
              status:
                - needs_action
                - completed
            response_variable: existing_items
          - alias: Process each Mealie item
            repeat:
              for_each: "{{ source_items[mealie_list]['items'] }}"
              sequence:
                - variables:
                    original_summary: "{{ repeat.item.summary | trim }}"
                    parsed_item: >-
                      {%- set original = repeat.item.summary | trim -%}

                      {%- set units = supported_units -%}

                      {%- set ns = namespace(quantity='', unit='',
                      ingredient='', note='') -%}


                      {# Extract note if present #}

                      {%- set note_match = original |
                      regex_findall('(.+?)\\s*\\(([^)]+)\\)\\s*$') -%}

                      {%- if note_match | length > 0 -%}
                        {%- set without_note = note_match[0][0] | trim -%}
                        {%- set ns.note = note_match[0][1] | trim -%}
                      {%- else -%}
                        {%- set without_note = original -%}
                      {%- endif -%}


                      {# Pattern 1: quantity + unit + ingredient #}

                      {%- set pattern1_regex = '^(\\d+(?:\\.\\d+)?)\\s*(' +
                      units + ')\\s+(.+)$' -%}

                      {%- set pattern1_match = without_note |
                      regex_findall(pattern1_regex, ignorecase=true) -%}

                      {%- if pattern1_match | length > 0 -%}
                        {%- set ns.quantity = pattern1_match[0][0] -%}
                        {%- set ns.unit = pattern1_match[0][1] -%}
                        {%- set ns.ingredient = pattern1_match[0][2] | trim -%}
                      {%- else -%}
                        {# Pattern 2: quantity + ingredient #}
                        {%- set pattern2_regex = '^(\\d+(?:\\.\\d+)?)\\s+(.+)$' -%}
                        {%- set pattern2_match = without_note | regex_findall(pattern2_regex, ignorecase=true) -%}
                        {%- if pattern2_match | length > 0 -%}
                          {%- set ns.quantity = pattern2_match[0][0] -%}
                          {%- set ns.ingredient = pattern2_match[0][1] | trim -%}
                        {%- else -%}
                          {%- set ns.ingredient = without_note | trim -%}
                        {%- endif -%}
                      {%- endif -%}


                      {# Build description #}

                      {%- if ns.quantity and ns.unit and ns.note -%}
                        {%- set description = ns.quantity ~ ' ' ~ ns.unit ~ ' (' ~ ns.note ~ ')' -%}
                      {%- elif ns.quantity and ns.unit -%}
                        {%- set description = ns.quantity ~ ' ' ~ ns.unit -%}
                      {%- elif ns.quantity and ns.note -%}
                        {%- set description = ns.quantity ~ ' (' ~ ns.note ~ ')' -%}
                      {%- elif ns.quantity -%}
                        {%- set description = ns.quantity -%}
                      {%- elif ns.note -%}
                        {%- set description = '(' ~ ns.note ~ ')' -%}
                      {%- else -%}
                        {%- set description = '' -%}
                      {%- endif -%}

                      {{- ns.ingredient ~ '|||' ~ description -}}
                    ingredient_name: "{{ parsed_item.split('|||')[0] | trim }}"
                    ingredient_description: >-
                      {{ parsed_item.split('|||')[1] | trim if
                      parsed_item.split('|||') | length > 1 else '' }}
                - alias: Handle item based on its current status
                  choose:
                    - alias: Process active items (needs_action)
                      conditions:
                        - condition: template
                          value_template: "{{ repeat.item.status == 'needs_action' }}"
                      sequence:
                        - alias: Add item if it doesn't exist in target list
                          if:
                            - condition: template
                              value_template: |-
                                {{
                                  ingredient_name not in existing_items[bring_list]['items'] | map(attribute='summary') | list
                                }}
                          then:
                            - action: todo.add_item
                              target:
                                entity_id: "{{ bring_list }}"
                              data:
                                item: "{{ ingredient_name }}"
                                description: "{{ ingredient_description }}"
                        - alias: Update item if description changed
                          if:
                            - condition: template
                              value_template: >-
                                {% set existing =
                                existing_items[bring_list]['items'] |
                                selectattr('summary', 'eq', ingredient_name) |
                                list %}

                                {{
                                  existing | length > 0 and
                                  (existing[0].description | default('') | trim) != ingredient_description
                                }}
                          then:
                            - action: todo.update_item
                              target:
                                entity_id: "{{ bring_list }}"
                              data:
                                item: "{{ ingredient_name }}"
                                description: "{{ ingredient_description }}"
                                status: needs_action
                        - alias: Reactivate completed item
                          if:
                            - condition: template
                              value_template: |-
                                {{
                                  ingredient_name in existing_items[bring_list]['items'] 
                                  | selectattr('status', 'eq', 'completed') 
                                  | map(attribute='summary') 
                                  | list
                                }}
                          then:
                            - action: todo.update_item
                              target:
                                entity_id: "{{ bring_list }}"
                              data:
                                item: "{{ ingredient_name }}"
                                description: "{{ ingredient_description }}"
                                status: needs_action
                    - alias: Process completed items
                      conditions:
                        - condition: template
                          value_template: "{{ repeat.item.status == 'completed' }}"
                      sequence:
                        - alias: Mark item as completed
                          if:
                            - condition: template
                              value_template: |-
                                {{
                                  ingredient_name in existing_items[bring_list]['items'] 
                                  | selectattr('status', 'ne', 'completed') 
                                  | map(attribute='summary') 
                                  | list
                                }}
                          then:
                            - action: todo.update_item
                              target:
                                entity_id: "{{ bring_list }}"
                              data:
                                item: "{{ ingredient_name }}"
                                status: completed
      - conditions:
          - condition: template
            value_template: "{{ direction == 'bring_to_mealie' }}"
        sequence:
          - alias: Retrieve existing entries from Bring! list
            action: todo.get_items
            target:
              entity_id: "{{ bring_list }}"
            data:
              status:
                - needs_action
                - completed
            response_variable: source_items
          - alias: Retrieve existing entries from Mealie list
            action: todo.get_items
            target:
              entity_id: "{{ mealie_list }}"
            data:
              status:
                - needs_action
                - completed
            response_variable: existing_items
          - alias: Process each Bring! item
            repeat:
              for_each: "{{ source_items[bring_list]['items'] }}"
              sequence:
                - variables:
                    ingredient_name: "{{ repeat.item.summary | trim }}"
                    ingredient_description: "{{ repeat.item.description | default('') | trim }}"
                    transformed_summary: >-
                      {%- set ingredient = repeat.item.summary | trim -%}

                      {%- set description = repeat.item.description |
                      default('') | trim -%}


                      {%- if not description -%}
                        {{ ingredient }}
                      {%- else -%}
                        {%- set ns = namespace(quantity='', unit='', note='', remainder='') -%}
                        
                        {# Extract note #}
                        {%- set note_match = description | regex_findall('^(.+?)\\s*\\(([^)]+)\\)\\s*$') -%}
                        {%- if note_match | length > 0 -%}
                          {%- set without_note = note_match[0][0] | trim -%}
                          {%- set ns.note = note_match[0][1] | trim -%}
                        {%- else -%}
                          {%- set note_match2 = description | regex_findall('^\\(([^)]+)\\)\\s*$') -%}
                          {%- if note_match2 | length > 0 -%}
                            {%- set without_note = '' -%}
                            {%- set ns.note = note_match2[0] | trim -%}
                          {%- else -%}
                            {%- set without_note = description -%}
                          {%- endif -%}
                        {%- endif -%}
                        
                        {# Parse quantity and unit #}
                        {%- if without_note -%}
                          {%- set qty_unit_match = without_note | regex_findall('^(\\d+(?:\\.\\d+)?)\\s+(.+)$') -%}
                          {%- if qty_unit_match | length > 0 -%}
                            {%- set ns.quantity = qty_unit_match[0][0] -%}
                            {%- set ns.unit = qty_unit_match[0][1] | trim -%}
                          {%- else -%}
                            {%- set qty_match = without_note | regex_findall('^(\\d+(?:\\.\\d+)?)$') -%}
                            {%- if qty_match | length > 0 -%}
                              {%- set ns.quantity = qty_match[0] -%}
                            {%- else -%}
                              {%- set ns.remainder = without_note -%}
                            {%- endif -%}
                          {%- endif -%}
                        {%- endif -%}
                        
                        {# Build Mealie format #}
                        {%- if ns.quantity and ns.unit and ns.note -%}
                          {{ ns.quantity ~ ' ' ~ ns.unit ~ ' ' ~ ingredient ~ ' (' ~ ns.note ~ ')' }}
                        {%- elif ns.quantity and ns.unit -%}
                          {{ ns.quantity ~ ' ' ~ ns.unit ~ ' ' ~ ingredient }}
                        {%- elif ns.quantity and ns.note -%}
                          {{ ns.quantity ~ ' ' ~ ingredient ~ ' (' ~ ns.note ~ ')' }}
                        {%- elif ns.quantity -%}
                          {{ ns.quantity ~ ' ' ~ ingredient }}
                        {%- elif ns.note -%}
                          {{ ingredient ~ ' (' ~ ns.note ~ ')' }}
                        {%- elif ns.remainder -%}
                          {{ ns.remainder ~ ' ' ~ ingredient }}
                        {%- else -%}
                          {{ ingredient }}
                        {%- endif -%}
                      {%- endif -%}
                - alias: Handle item based on its current status
                  choose:
                    - alias: Process active items (needs_action)
                      conditions:
                        - condition: template
                          value_template: "{{ repeat.item.status == 'needs_action' }}"
                      sequence:
                        - alias: >-
                            Add item if it doesn't exist (DISABLED - only sync
                            existing items)
                          if:
                            - condition: template
                              value_template: |-
                                {{
                                  transformed_summary not in existing_items[mealie_list]['items'] | map(attribute='summary') | list
                                }}
                          then:
                            - action: todo.add_item
                              target:
                                entity_id: "{{ mealie_list }}"
                              data:
                                item: "{{ transformed_summary }}"
                          enabled: false
                        - alias: Reactivate completed item
                          if:
                            - condition: template
                              value_template: |-
                                {{
                                  transformed_summary in existing_items[mealie_list]['items'] 
                                  | selectattr('status', 'eq', 'completed') 
                                  | map(attribute='summary') 
                                  | list
                                }}
                          then:
                            - action: todo.update_item
                              target:
                                entity_id: "{{ mealie_list }}"
                              data:
                                item: "{{ transformed_summary }}"
                                status: needs_action
                    - alias: Process completed items
                      conditions:
                        - condition: template
                          value_template: "{{ repeat.item.status == 'completed' }}"
                      sequence:
                        - alias: Mark item as completed
                          if:
                            - condition: template
                              value_template: |-
                                {{
                                  transformed_summary in existing_items[mealie_list]['items'] 
                                  | selectattr('status', 'ne', 'completed') 
                                  | map(attribute='summary') 
                                  | list
                                }}
                          then:
                            - action: todo.update_item
                              target:
                                entity_id: "{{ mealie_list }}"
                              data:
                                item: "{{ transformed_summary }}"
                                status: completed
1 Like