Value template to test if strings almost match

I’m creating an automation to process license plate recognition via MQTT.
My template works:
{{trigger.payload_json.plate | upper() | replace(" ", "")=="plate_number_here"}}

The LPR isn’t perfect, so I remove any spaces and set to upper case. However, the LPR often get’s 1 character wrong on the plate. I would like a template to return true if 6 of the 7 characters match. In python I could just do the following:
sum(1 for char1 in trigger.payload_json.plate.replace(" ", "").upper() if char1 in "plate_number_here") >= 6

How would I write this in HA?

It’s a bit spaghetti but I came up with a working template:

{% set lpr_plate = trigger.payload_json.plate | upper() | replace(" ", "") %}
{% set target_plate = "plate_number_here" %}
{% set ns = namespace(matches=0) %}
{% for i in range(7) %}
  {% if lpr_plate[i] == target_plate[i] %}
    {% set ns.matches = ns.matches + 1 %}
  {% endif %}
{% endfor %}
{{ ns.matches >= 6 }}

I’d be interested if there’s a more elegant solution.

This has me intrigued and I’ll be thinking on it.

But, my first thought is you are removing spaces from the lpr plate. Am I missing something? Doesn’t that shift the lpr plate letters/numbers? Meaning that if the early characters come in as a space, you won’t be able to match six positions?

For example:
target plate = 1234567
lpr plate = 1b34567 (b is a space).

So, you are now comparing 1234567 to 134567. Only the first position matches.

I am sure I missed something. What is it?

I’m not great with python but doesn’t your python code ignore the order of the letters entirely? Additionally, what would happen in your python code if the LPR plate is AAA1111 and you are trying to match it to ABC1234? Isn’t it going to result in a match?

If you are ok with those flaws, you can use this code:

{% set ns = namespace(total=0) %}
{% for char in 'AAA1111' %}
  {% set ns.total = ns.total + ( 1 if char in 'ABC1234') %}
{% endfor %}
{{ ns.total >=6 }}

If you’re looking for some other logic (or your python code already does something better) then if you could explain what it’s doing I’m sure we could come up with something.

@mekaneck You’re right about the bug. It should be:
sum(1 for char1, char2 in zip(plate.replace(" ", "").upper(), TARGET) if char1 == char2) >= 6
Here are the results to match 7ABC123:

7ABC123: True
7ABd123: True
7ABC 123: True
7ABC321: False
6ABC123: True
7AB 123: False
7abc123: True
7LBC124: False
AAA1111: False

The order does matter. Here’s the Jinga2 code I came up with although I have not tested it yet in HA:

{% set target_cleaned = TARGET_PLATE_HERE.upper() %}
{% set plate_cleaned = trigger.payload_json.plate.replace(" ", "").upper() %}
{% set matched_chars = [] %}
{% for i in range(plate_cleaned|length) %}
  {% if plate_cleaned[i] == target_cleaned[i] %}
    {% set _ = matched_chars.append(1) %}
  {% endif %}
{% endfor %}
{{ matched_chars|length >= 6 }}

@jeffcrum The plates I’m matching should not have any spaces. Sometimes CPAI adds non-existent spaces. I have seen CPAI return 7ABC 123 instead of 7ABC123. If CPAI replaced a letter with a space then you are correct that it would cause a problem as shown above. I’ll cross that bridge if I see it. I suppose I could only remove the spaces if the length is greater than 7.

Got it. I was thinking a space of it could not match a character.

You’ll have to use a namespace to access variables created or modified inside a for loop.

Try this out in developer tools → template:

{% set target_cleaned = '7ABC123'.upper() %}
{% set plate_cleaned = '7ab c23'.replace(" ", "").upper() %}
{% set ns = namespace(matched_chars = "") %}
{% for i in range(0, min(target_cleaned|length, plate_cleaned|length)) %}
  {% if plate_cleaned[i] == target_cleaned[i] %}
    {% set ns.matched_chars = ns.matched_chars ~ plate_cleaned[i] %}
  {% endif %}
{% endfor %}
{{ ns.matched_chars | length >=6 }}
1 Like

@mekaneck Your code works, thanks!

You can iterate through a string like this:

{% set target_cleaned = '7ABC123'.upper() %}
{% set plate_cleaned = '7ABC321'.replace(" ", "").upper() %}
{% set ns = namespace(matched_chars = []) %}
{% for i in plate_cleaned %}
  {% if i == target_cleaned[loop.index-1] %}
    {% set ns.matched_chars = ns.matched_chars + [i] %}
  {% endif %}
{% endfor %}
{{ ns.matched_chars | length >= 6 }}

For future reference, the append method is disallowed in Home Assistant’s implentation of Jinja.