The Shopping List That Sorts Itself
People ask why I bother with a smart home. I skip the lights and show them this.
the moment
I pull into the Walmart parking lot. Before I’m out of the car, my phone buzzes. It’s my shopping list, the same one my family and I have been dictating to the house all week (“add tortillas to the store list”), rewritten. Grouped by section, in the order you walk the store:
🥬 PRODUCE — bananas, spinach, limes
🥩 MEAT — chicken thighs, ground beef
🧀 DAIRY — shredded cheddar, greek yogurt
🧊 FROZEN — waffles, peas
Only the categories I need that day. No empty headers. I walk the loop once and check things off.
You typed nothing new. No one prepared the list before leaving. The automation reorganizes it the moment you arrive, because arrival is the only time aisle order is worth anything.
how it works
Four pieces, all stock Home Assistant.
A zone trigger fires when my phone enters the store’s geofence. I keep one zone per store I shop at.
todo.get_items reads the current list. My family adds to it all week by voice, which is the normal HA todo integration doing its job.
ai_task.generate_data does the fun part. HA’s AI tasks support structured output, so you hand an LLM a job and get typed data back instead of prose. The prompt is one paragraph: group these items by store section, in walking order for a typical supercenter, return JSON, only include sections with items, put an emoji header on each. Cloud or local models both handle a task this small.
The automation then wipes and rewrites the list in the sorted order and fires a notification. Ten seconds after the geofence trips, the sorted list is on my phone.
why rewrite the list instead of notifying
My first version sent a notification with the sorted text. A notification disappears, and in a store your thumb lives in the list app. Rewriting the todo items themselves, headers included, puts the checkable list in aisle order. The notification exists as a tap-target to open it.
the prompt matters less than you think
I budgeted evenings for prompt tuning. In practice, “group by section in the order you’d walk a supercenter” gets it right nearly every time, because LLMs have read a lot about what stores look like. The output shape is the part worth pinning down. Structured output means the model can’t ramble and my automation skips parsing prose.
where it breaks
Two failure modes so far. An ambiguous item (“bars”: granola or soap?) gets filed somewhere silly once in a while; you notice, grab it anyway, move on. And if you enter the geofence without shopping, curbside pickup say, the list re-sorts for nothing. The operation is idempotent so nothing is lost, but the notification is noise. Adding a “more than 3 items on the list” condition trimmed most of that.
the config
Sanitized version of the real automation below. Entity names are generic stand-ins. Swap in your own store zone, list, and notify target. The AI task entity is whichever provider you set up; this works the same against a cloud model or a local one.
- id: shopping_list_aisle_sort
alias: "Shopping list — sort itself on store arrival"
trigger:
- platform: zone
entity_id: person.me
zone: zone.grocery_store
event: enter
condition:
- condition: template
value_template: >
{{ state_attr('todo.store_list', 'items') is not none }}
action:
- service: todo.get_items
target:
entity_id: todo.store_list
data:
status: needs_action
response_variable: current
- variables:
item_names: >
{{ current['todo.store_list']['items']
| map(attribute='summary') | list }}
- condition: template
value_template: "{{ item_names | count > 3 }}"
- service: ai_task.generate_data
data:
task_name: aisle_sort
instructions: >
Group these shopping items by store section, ordered the way
you'd walk a typical supercenter (produce first, frozen near
the end, household last). Only include sections that have
items. Each section gets an emoji + ALL-CAPS header.
Items: {{ item_names | join(', ') }}
structure:
sections:
selector:
object:
response_variable: sorted
# wipe the list…
- repeat:
for_each: "{{ item_names }}"
sequence:
- service: todo.remove_item
target:
entity_id: todo.store_list
data:
item: "{{ repeat.item }}"
# …and rewrite it in aisle order, headers included
- repeat:
for_each: "{{ sorted.data.sections }}"
sequence:
- service: todo.add_item
target:
entity_id: todo.store_list
data:
item: "{{ repeat.item.header }}"
- repeat:
for_each: "{{ repeat.item.items }}"
sequence:
- service: todo.add_item
target:
entity_id: todo.store_list
data:
item: "{{ repeat.item }}"
- service: notify.my_phone
data:
title: "🛒 List sorted for the store"
message: "{{ item_names | count }} items, in aisle order. Tap to open."
data:
clickAction: "app://todo"
mode: single
One YAML block, no custom components. The hardest part is retraining yourself to say “add it to the store list” instead of writing on the whiteboard.
If you build a version of this with a different store, model, or list app, tell me how it goes. The pattern (event, read your own data, AI reshapes it, write it back, notify) has turned out to be the most reusable thing in my whole config.


Post Comment