How to correctly form Homeassistant API call using python requests

The documentation on the HA website is centered around cURL, which for many purposes is more than enough.  I wanted to create a standalone python implementation of the API call to activate a script. The reason?  WAF (wife acceptance factor).  By having an easy to use desktop shortcut to activate automation, it’s much more usable.  So it goes with UI and jokes; if you have to explain it, it isn’t very good.

This was typical of my frustrations with HA implementation.  98% of HA stuff just works, 1.5% breaks with updates, and 0.5% is nebulous at best.  Pretty impressive breakdown for an open source project evolving as fast as Homeassistant is.

Calls to the API with requests are seemingly straightforward.  There is great documentation for cURL here with a cursory explanation of requests usage.

There is a phenomenal tool that translates from cURL syntax to python requests HERE.

Sadly, I discovered it much too late.

After futzing with a lot of code, including a command that turned on every “switch” in my house linked through HA, I found the following worked.

from requests import post
url = 'http://bananahammock:8123/api/services/script/turn_on?api_password=BANANAHAMMOCK'
headers = {'content-type': 'application/json'}
payload = '{"entity_id":"script.remote_shutdown_banana_hammock"}'
response = post(url, headers=headers, data=payload)
print(response.text)

The key element is to encapsulate the JSON into a string with quotes, otherwise, it will be interpreted as a dict and the API has no idea what you are presenting it with and will reject it as not being JSON and you’ll end up with this staring at you:

Clipboard01

Advertisements

Adapting to a Suboptimal Teleradiology Reading Environment – Part I

This post is the start of chronicle of  improvements that can be made with thoughtful introspection into workflow.  It is in no way intended to disparage the reading environment, but rather takes a look at the barriers to information transfer and how to overcome them on a personal basis.  All of these were my own developments to streamline my reading environment, improve patient care and reduce stress for the radiologist.

The environment uses a RIS that is web based, running on a coldfusion server.  The appropriate link activates a java based application that launches from the link.  The images are stored on one of two servers and an annotation is there that tells the rad which link to click.  Whether clicking the “O” link “B” link is a pseudorandom pattern.  Some studies are preferentially on one server but it is inconsistent.  I don’t know the reasons, nor do they matter.

While this seems like a trivial matter, over the course of a shift, that is around 100 times of finding the text, clicking the link, and then reading the study.

One of the problems is that because of the presentation of text, which link to use can be in a different spot on every single page.

 

generic screencap

This is what a generic patient link looks like before modification.

generic screencap

Annotations showing which link to use and the actual link is above it.

So to make my life a little easier, I inject some color with javascript and greasemonkey in firefox.  The colors do not match the “o” and “b” but that will make sense in a follow up post.

generic screencap

The correct link to use is now highlighted.

This is just the first step in optimizing the display of images.  More to follow.

Analysis of Radiology Report Word Frequency – Part I

The point of this programming exercise is to find outlier words that speech recognition shouldn’t, but does, insert into the dictated reports.  By taking a large sample size for a user, the word frequency can be used to remove inappropriate words from the user dictionary.

As an example, I had the word “heber” escape my proofreading at one point and it made it into the list.  I’ve removed it now, but the ultimate goal is to check each report for outlier words, and when they fall below a frequency threshold, to flag them for review.

You can see that there are some words that just don’t belong.  This frequency analysis allows removal from the lexicon and by doing so, should improve reports AND recognition. There are a few problems with the data that are fixable.

  • Indexing of unimportant elements
    • Numbers
    • Measurements (cm, mm, g …)
    • HTML tags
    • Technique (modalities and technique description)
  • Punctuation filtering
    • gives rise to concatenated words

composite

From the radiologist’s perspective, if you are going to give a diagnosis that you’ve never given before, it should not be taken lightly.  This follows the axiom that an uncommon presentation of a common disease is more common than a rare disease.  If at all possible, try to have another radiologist consult and confirm your suspicions and for heaven’s sake, if there is a differential, please give it.

I have frequently seen radiologists try to hit a “home run” by naming a rare disease, not giving a differential, and flubbing the case because it’s actually a common disease with an unusual presentation.

The point of that aside is that a frequency analysis can prompt the radiologist to ask “do I really mean to use that word” and if you do, it will make you think about whether that is the correct and only differential consideration you want to give.

All too often, technology solutions in medicine increase the burden on the physician rather than facilitate the practice of medicine.  EMRs are a perfect example.  You spend more time clicking than caring.  The eventual goal will be a real time prompt on signing a report that keeps the rad out of trouble and produces a polished professional report.  At the end of the day, the report is the indelible work product.

Parsing Radiology Exam Data with Python Class

I’ve had a really clunky program to track what I do.  It lets me know how much volume I’m reading calculated each 30 min based a logfile.

I’ve decided to get a little more sophisticated with the program because it has grown to the point where it’s painful for me to edit, and if I wrote it, it must be completely abstruse to someone else.

I have never used a class in python but it solves many issues for me:

  • Cleans up the code
  • Moves functions to a module for importing and repurposing
  • Teaches me how to construct a class properly
  • Allows parsing of text with a structure that makes sense
    • Class instance attributes now make inherent sense
  • Eliminates having to bring global var into functions for modification
    • The class instance can access global counters
    • The class output can eliminate the need for global counters
  • Allows me to expand to the class to perform other manipulation
    • calculating age of patient
    • SQL archival
  • Works in python 2 and 3
  • Uses the Regex from hell

 

parser_fx.py
import re

class Parser:
    """
    A class to parse data strings into components
    USE:
    from parser_fx import *
    result = Parser()     ## Instantiate class
    result.parse(study)   ## Pass the instance a study string
    result.XXXXXXX        ## Attributes of result now available
    """

    def __init__(self):
        """ Simply establishes the regex expression"""
        self.regex = re.compile(r'([\'A-Z\s-]+)(\[.+||\s])\s*'
                                '(CURRENT STUDY:)([A-Z\s/\[\]]+)'
                                '([\d-]+)\s([\d:]+)\s'
                                '(\[DOB:[\s0-9/]+])\s+(\[ID:.+])')

    def parse(self, study_string=('SIMPSON HOMER J[Prelim  report]CURRENT STUDY:'
        'CT HEAD 2016-01-01 23:59:59 [DOB: 1111/1/11 ] [ID: 1234567]')):
        """Parser with failure options"""
        self.study_string = study_string
        try:
            self.matchobj = self.regex.match(self.study_string)
            ## Attributes: name, prelim/final, study, date, time dob, ID 
            self.name = self.matchobj.group(1)
            self.type = self.matchobj.group(2)[1:7].rstrip()
            self.study_name = self.matchobj.group(4)
            self.date = self.matchobj.group(5)
            self.time = self.matchobj.group(6)
            self.DOB = self.matchobj.group(7)
            self.ID = self.matchobj.group(8)[1:-1]
        except:
            print("REGEX Match Failed")
            

    """  Leaving here as an example to follow 
    def ID(self):
        ''' returns study  '''
        self.ID = self.matchobj.group(8)
        return self.ID   
    """


"""
study= ('SIMPSON HOMER J[Prelim  report]CURRENT STUDY:'
        'CT HEAD 2016-01-01 23:59:59 [DOB: 1111/1/11 ] [ID: 1234567]')
result = Parser()
result.parse(study)
print("%s | %s | %s | %s | %s | %s" %(result.type, result.name,
        result.study_name, result.date, result.time, result.ID))

"""

After testing for a week, I’ll report back on success or failure.

Python 3.6 Install on DietPi

Debian repos don’t have python 3.6 ready to go at this time but thanks to the awesome dudes at HA forums, the guide to replacing your venv with 3.6 works to install also.

Step 1.

sudo apt-get install build-essential tk-dev libncurses5-dev libncursesw5-dev libreadline6-dev libdb5.3-dev libgdbm-dev libsqlite3-dev libssl-dev libbz2-dev libexpat1-dev liblzma-dev zlib1g-dev

Step2.

wget https://www.python.org/ftp/python/3.6.0/Python-3.6.0.tgz tar
xzvf Python-3.6.0.tgz cd Python-3.6.0/
./configure
make
sudo make install

DietPi HomeAssistant Aliases

THIS DEV UNIT IS RUNNING AS ROOT.  DON’T DO THIS FOR YOUR REAL DEPLOYMENT.  IT’S BAD SECURITY PRACTICE.

(I’m just too lazy to type sudo every time).

This is an update to the list of aliases included in the main post. I have set up a dev server on RPi and my instructions did were not previously complete.

alias config='nano ~/.homeassistant/configuration.yaml'
alias checkconfig='venv && hass --script check_config && cd ~'
alias venv='cd ~/homeassistant && source bin/activate'
alias hasslog='nano ~/.homeassistant/home-assistant.log'
alias customize='nano ~/.homeassistant/customize.yaml'
alias restart='sudo systemctl restart home-assistant@root'
alias status='sudo systemctl status home-assistant@root'
alias stop='sudo systemctl stop home-assistant@root'
alias start='sudo systemctl start home-assistant@root'
alias secrets='nano ~/.homeassistant/secrets.yaml'
alias ms='mosquitto_sub -h "127.0.0.1" -t "#" -v'

The most used are:

  • restart
  • config
  • checkconfig
  • status

Using Python GSpread to Fill Spreadsheet Values

import gspread
from oauth2client.service_account import ServiceAccountCredentials

 

#Gspread authorization
scope = ['https://spreadsheets.google.com/feeds']
credentials = ServiceAccountCredentials.from_json_keyfile_name('creds.json', scope)

gc = gspread.authorize(credentials)

 

## Critical to share spreadsheet with service account
## 
## If you don't you'll just end up with a not found error.

 

# Open a worksheet from spreadsheet with one shot
wks = gc.open_by_key("<<>>").sheet1

# Generate list with alphabet
alphabet = map(chr, range(65, 91)) ## Gives capital letters A-Z
print(alphabet)  ## Just printing to make sure it's okay 
for letter in alphabet:
    for x in range(1,10):
        wks.update_acell((letter + str(x)), ("Cell ") + letter +str(x))

 

Excellent help from here: https://terrameijar.wordpress.com/2017/02/03/python-how-to-generate-a-list-of-letters-in-the-alphabet/