LLM bullshit engine’s are bad at HA, especially templates. Unless you use very detailed, specific prompts that provide clear limits, grounding, and context; you will almost always get answers that include plausible-sounding but not fit for purpose actions, defunct configuration keys, and some utter nonsense.
States are always strings. The is_number test/filter will return True if the input can be parsed by Python’s float function and the parsed input is not inf or nan, in all other cases returns False. But that doesn’t mean the value is a float or integer.
You must convert the state to a float or integer before comparing it to threshold. One way to do that with what you have already designed is to add an inline if to the for loop’s definition:
{% set threshold = 25 %}
{% set zwave_entries = integration_entities('zwave_js') %}
{% for s in states.sensor
| selectattr('entity_id', 'in', zwave_entries)
| selectattr('attributes.device_class', 'eq', 'battery')
| selectattr('state', 'is_number')
| list if s.state|int(0) < threshold
%}
- {{ s.name }} ({{ s.entity_id }}) → {{ s.state }}%
{% endfor %}