Can this jinja template be written more efficiently?

I’m asking this question mainly as a jinja / template learning exercise and because I know @petro is a Template Master.

state_attr(‘sensor.security_ingress_json’,‘json_xpath_status’) has this dict value:

{
  "Count": [
    {
      "type": "all",
      "name": "ntotal",
      "status": 9
    },
    {
      "type": "all",
      "name": "nopen",
      "status": 0
    },
    {
      "type": "all",
      "name": "nclosed",
      "status": 9
    }
  ],
  "Home": [
    {
      "type": "Door",
      "name": "Front",
      "status": "closed"
    },
    {
      "type": "Door",
      "name": "Back Patio",
      "status": "closed"
    }
  ],
  "Living_Room": [
    {
      "type": "Window",
      "name": "East",
      "status": "closed"
    },
    {
      "type": "Window",
      "name": "West",
      "status": "closed"
    }
  ],
  "Bedrooms": [
    {
      "type": "Window",
      "name": "SE",
      "status": "closed"
    }
  ],
  "Garage": [
    {
      "type": "Door",
      "name": "Overhead",
      "status": "closed"
    },
    {
      "type": "Door",
      "name": "Side",
      "status": "closed"
    },
    {
      "type": "Window",
      "name": "East",
      "status": "closed"
    }
  ],
  "Basement": [
    {
      "type": "Window",
      "name": "Escape",
      "status": "closed"
    },
    {
      "type": "Window",
      "name": "South",
      "status": "closed"
    }
  ]
}

The goal is to display in the only those doors and windows that are OPEN. Here’s the Jinja code which gets the job done, but I’m wondering if it can be improved:

Ingress Detection

{% set ingress_status = 'open' -%}
{% set ingress_pts = state_attr('sensor.security_ingress_json','json_xpath_status') -%} 
{% set ns = namespace(list='```', strlen=0, hdrlen=0) -%}  
{% for ingress_type in ['Door', 'Window'] -%}
   {% for area in (ingress_pts|list) -%}
      {% if (loop.index == 1) -%}
         {% if ns.list|length() > ns.strlen -%}
            {% set prefix = ns.list ~'\n' -%}
         {% else-%}
            {% set prefix = '```\n' -%}
         {% endif-%} 
         {% set ns.list = prefix ~ ingress_status.title() ~' '~ ingress_type ~'s:' -%}
         {% set ns.strlen = ns.list|length() -%}
         {% set ns.hdrlen = ns.strlen - prefix|length() + 1 %}
      {% endif -%}
      {% set ingress_pt_list = ingress_pts[area]|selectattr('type','==',ingress_type)|list -%}
      {% set ingress_pt_status = ingress_pt_list|selectattr('status','==',ingress_status)|list -%}
      {% for ingress_pt in range(ingress_pt_status|count()) -%}
          {% if ingress_pt_status|length>0 -%}
            {% set ns.list = ns.list ~'\n- '~ ingress_pt_status[ingress_pt]['name'] ~ ' ' ~ area.replace('rooms','room') ~' '~ ingress_type -%}
         {% endif -%}
      {% endfor -%}
   {% endfor -%}
   {% if ns.list|length() == ns.strlen-%}
      {% set ns.list = ns.list[0:ns.strlen - ns.hdrlen] -%}
   {% else -%}
      {% set ns.list = ns.list ~ '\n' -%}
   {% endif-%} 
{% endfor-%} 
{{- ns.list }}

Example Output:

Ingress Detection

Open Windows:
- Escape Basement Window
- South Basement Window

I’m sure there’s other ways to do it

{% set ingress_status = 'open' -%}
{% set ingress_pts = state_attr('sensor.security_ingress_json','json_xpath_status') -%} 
{% set ns = namespace(windows=[], doors=[]) %}
{% for area, items in ingress_pts.items() %}
  {% for item in items if item.type in ['Window', 'Door'] and item.status == ingress_status %}
    {% set name = [item.name, area, item.type] | join(" ") %}
    {% if item.type == 'Window' %}
      {% set ns.windows = ns.windows + [ name ] %}
    {% else %}
      {% set ns.doors = ns.doors + [ name ] %}
    {% endif %}
  {% endfor %}
{% endfor %}

{%- macro announce(what, items) %}
{%- if items %}
{{- what }}
{%- for item in items %}
 - {{ item }}
{%- endfor %}
{%- endif %}
{%- endmacro %}
Ingress Detection

{{ announce('Open Doors', ns.doors) }}
{{ announce('Open Windows', ns.windows) }}

That was quick :grinning:
Very good and thanks !! Like I said I’m mainly doing it as a learning exercise and you didn’t disappoint!

Well, I already don’t like that way i did it before

{% set ingress_status = 'open' -%}
{% set ingress_pts = state_attr('sensor.security_ingress_json','json_xpath_status') -%} 
{% set find = 'Window', 'Door' %}
{% set ns = namespace(items=[]) %}
{% for area, items in ingress_pts.items() %}
  {% for item in items if item.type in find and item.status == ingress_status %}
    {% set id = [ingress_status, item.type] | join(" ") | title %}
    {% set name = [item.name, area, item.type] | join(" ") %}
    {% set ns.items = ns.items + [ {'i': id, 'n':name} ] %}
  {% endfor %}
{% endfor %}
Ingress Detection
{% for area, items in ns.items | groupby(attribute='i') %}
{{ area }}
{%- for item in items %}
- {{ item.n }}
{%- endfor %}
{% endfor %}

I like this approach better as well. It’s clear, easy to maintain and compact.

Is it possible to say that it is also the fastest/most efficient from a code execution standpoint? I’m from the days of when memory and processor time were at a premium so old habits …

Thanks again for taking the time.

I would say it is the most optimized out of all 3. It’s unfortunate that a series of for loops is required. If your incoming data was flat, it could be handled with a single generator. Are you creating the data or is it coming from a device?

Yep. The nested for loops were bothering me as well and are, in fact, why I started this thread.

I created the incoming data several years ago - back at a time when I was first self-learning json, jinja, and playing with python within HAC (I used python to save/restore that attribute from a text file after each HAC shutdown/restart). It’s one of those things I know can be improved upon as I learn more and as HAC added features/capabilities.

So if you have a better way to structure that attribute, I’d be interested. And assuming you don’t mind, it’d be great if you could also show me how to restore a sensor’s attribute from the database! It’s been driving me nuts! :joy:

If you reconfigured the data to be…

[
  {
    "area": "Count",
    "type": "all",
    "name": "ntotal",
    "status": 9
  },
  {
    "area": "Count",
    "type": "all",
    "name": "nopen",
    "status": 0
  },
  {
    "area": "Count",
    "type": "all",
    "name": "nclosed",
    "status": 9
  },
  {
    "area": "Home",
    "type": "Door",
    "name": "Front",
    "status": "closed"
  },
  {
    "area": "Home",
    "type": "Door",
    "name": "Back Patio",
    "status": "closed"
  },
  {
    "area": "Living_Room",
    "type": "Window",
    "name": "East",
    "status": "closed"
  },
  {
    "area": "Living_Room",
    "type": "Window",
    "name": "West",
    "status": "closed"
  },
  {
    "area": "Bedrooms",
    "type": "Window",
    "name": "SE",
    "status": "closed"
  },
  {
    "area": "Garage",
    "type": "Door",
    "name": "Overhead",
    "status": "closed"
  },
  {
    "area": "Garage",
    "type": "Door",
    "name": "Side",
    "status": "closed"
  },
  {
    "area": "Garage",
    "type": "Window",
    "name": "East",
    "status": "closed"
  },
  {
    "area": "Basement",
    "type": "Window",
    "name": "Escape",
    "status": "closed"
  },
  {
    "area": "Basement",
    "type": "Window",
    "name": "South",
    "status": "closed"
  }
]

Then the template would be…

{% set ingress_status = 'open' -%}
{% set ingress_find = 'Window', 'Door' -%}
{% set ingress_pts = state_attr('sensor.security_ingress_json','json_xpath_status') -%} 
Ingress Detection
{% for grp, items in ingress_pts | selectattr('status','eq',ingress_status) | selectattr('type', 'in', ingress_find) | groupby('type') %}
{{ ingress_status | title }} {{ grp }}s
  {%- for item in items %}
  - {{ item.name }} {{ item.area | replace('_', ' ') }} {{ item.type }}
  {%- endfor %}
{% endfor %}
1 Like

Man you are quick… Looks good! That would have taken me hours. I need to go back and play / digest / unpack. Thanks yet again. I’ve learned a lot in a very short time.

Implemented and working great. :+1:

1 Like