Showing posts with label digital. Show all posts
Showing posts with label digital. Show all posts

Visualization at the edge between intuitive and training

I've had a long term interest in visualization -- mostly implementing other people's designs. I don't believe that all visual tools need to be intuitive; many will need training to use effectively. I think this one is on the edge between intuitive and training needed.

This Widget Forces You to Think - YouTube

Preparing "Approval Tests" for refactoring Python

Emily Bache's short example of preparing "Approval Tests" for refactoring Python code is very good. Not only for the approach, but also for how advanced IDEs like PyCharm greatly support developers common editing and refactoring needs.

Reaction: Arjan Codes wrote tests the HARD way - My way is BETTER - YouTube

Don't look up

If you live in RI your home's electric meter was recently replaced to enable remote access. I have wondered how secure is that remote access? While this presentation does not address the electric meter, it does address how much of our satellite infrastructure is unsecured and accessible with cheap hardware. The presentation is about the available downlink data, but note the presenter's off-hand comment that if you fly a drone between the uplink site and the satellite you could likely gather the uplink data.

39C3 - Don’t look up: There are sensitive internal links in the clear on GEO satellites - YouTube

Streamlit and a trivial cities explorer

I spent a little time this morning learning the basics of Streamlit. Earlier this month I had found a US cities dataset¹ and decided to use Streamlit to explore city populations. 

This dashboard shows each state's population along with a state selector to dynamically show a state's city's population along with a map showing its location and relative population size. 

While it took me much of the morning to figure out how to use the Streamlit and Pandas SDKs, along with a side-quest on how to debug a Streamlit application in Visual Studio Code, the final application is hearteningly trivial at barely a dozen lines of Python.

1. Unfortunately, the dataset of not very good based on what I know about RI and NH cities and towns.

Jenson's stuck desktop and Foster's recalibration of the futurism


I suspect many of you have seen by now Scott Jenson's presentation "Are we stuck with the same Desktop UX forever?" But, if you have not then it is worth your time. I went on to read the book he mentions Nick Foster's Could Should Might Don't: How We Think About the Future. Jenson's presentation suggests that Foster's book is a "how to", but it is more a useful survey of, and a call for the recalibration of, the futurism field.

Classic Design Patterns: Where Are They Now

Brandon Rhodes' talk "Classic Design Patterns: Where Are They Now" is a good critique of the "Gang of 4" Design Patterns book that dominated software design thinking for decades. Why should you give your attention to a critique of a book from 1994? In part because the critique gives concrete examples of how successful design solutions become molded into programming languages and how programming languages outgrow the limitations that originally necessitated the design solution.

I don't know of the speaker, but he seems to speak at many Python conferences.

Update: I have since watched several of Brandon Rhodes' presentations. He is a good speaker and his presentations are worth your time.

Streamlit and data visualization

I do like computational notebooks, but find them cumbersome to work within. If like me, you've been looking for a notebook-like experience, coding in python, editing using your preferred editor, auto reloading, primarily local, deployable to a small audience, etc then Kris Jenkins's talk Building Data Visualisations in Python in Minutes about Streamlit will be of interest.

Installing Pi-hole

Having become irritated by the daily deluge of web advertisements that make most sites unusable on my phone, this weekend I took the time to install Pi-hole. I'd prefer that the little Raspberry Pi Zero 2 W used an ethernet connection, but am too cheap frugal to buy yet another dongle. Interestingly, the combinded cost of the Raspberry Pi and a micro SD card is about that of a used thin client (eg an HP T630) which can be configured as a general purpose server running several lightweight services.

These two video tutorials were helpful (even if the process is quite straightforward): Block Ads on ALL DEVICES (Smart TVs) — Simplest Pi-hole Tutorial and Raspberry Pi Set Static IP.

Update: The default blocklist is very aggressive. Many community and non-profit sites use the free or low-cost tier of commercial communication services (eg, Constant Contact). The default blocklist blocks these services due to their tracking activities via image URLs. Unfortunatly, this made the community communications less worthwhile. Until I have time to better consider the blocklist I have disabled Pi-hole. For the short period the Pi-hole was enabled, it was a glorious few days of smooth, swift internet browsing. Reminded me of the early days.

Why Stripe’s API Never Breaks

The short video Why Stripe’s API Never Breaks introduces how Stripe handles API versioning. Stripe's API URLs do not include a (consequential) version number and yet every API release is supported. Instead,

  1. The API client includes in each HTTP requests a header with the client's API release date, eg "X-Stripe-Version: 2024-01-23".
  2. Stripe's API server transforms the dated request into a current release request.
  3. Stripe's API server transforms the current release response into the dated response.

This does mean that each new API release needs transformers for all previous releases. I suspect that there are few breaking changes and most transformers default to the previous release's transformers.

Using a version HTTP header does not seem different than using a version number in the URL path, but there is something elegant about maintaining stable URLs across API versions. It also seems easer in most web frameworks (eg Rails) to introduce the transformers after routing.

5x cost increase

Crossref, a previous employer, has moved their infrastructure to the cloud. There are some good reasons for this, eg publication metadata depost volume is highly variable and being able to add capacity on short notice is good. But others not so much, eg search does grow but slowly over long periods where normal hardware purchasing can occur. (This may have changed since I was employeed.) What shocked me was the cost:

Moving from a physical data centre to the cloud also has some trade-offs; for instance, the cost will be approximately five times higher than running the system in the data centre; with initial data, it’s not unlikely the annual cost may be up to 2,000,000 USD. We aim to optimise and control this cost going forward.

For a non-profit with a duty to carefully steward their members' money this seems hard to justify.

Board game UX

I don't know if the game Star Wars: Outer Rim is enjoyable, but the game's physical design is amazing. Which reminds me I have What the Tech World Can Learn from Video Game UX lined up to watch.

Update: The video was about using gamification to enhance learning to use a handheld ultrasound scanner. Nothing new.

Inter-company event sourcing

I was in a client's meeting today and they were discussing how one of their batch processes has a dependency on data generated by a supplier's batch process. The problem is that the client does not know when the supplier's process is complete so they can start their process. (The supplier's data is accessed via an API rather than a bulk file delivery.) The discussion went along the lines of 

We know their process starts around 10 PM and takes about an hour to complete. So let's give it a couple of hours to finish, just in case it's slow that day, and we will start our process at, say, midnight.

I scream into the void. This kind of inter-company data processing coordination is quite common. Common enough that I am still surprised there is no standard, or industry-specific, solution we use.

Costs of helpful data flexibility.

I'm having a discussion currently with a young developer who has only ever worked in Ruby and JavaScript. I noticed that the developer had chained "symbolize_keys" to the end of a method call. In Ruby this converts a hash's keys from strings to symbols, ie { "a" => 123 } becomes { :a => 123 }. Their reason for this was to offer flexibility to the called as to how it returned the result. They thought this provided flexility and robustness. I countered that it did the exact opposite.

When a function can be given parameters and return results in multiple formats then robustness is only had when the function handles all formats equally. To do that the function needs to be tested with all formats. This can be done, but in practice, and I've seen across many organizations, it is not. Not only does the function need to be tested with the multiple formats, but the callers and the called need to be tested too. It's a combinatorial explosion of testing.

The other detriment to this flexibility is that since no function is sure of the format every function converts the data to its preferred format even if the data is already in the preferred format. This conversion adds to the function's code size and has a runtime cost (CPU and memory) on every invocation. The cost of a single use might be small, but our applications work in a world with thousands of concurrent sessions each with deep call chains, and expect microsecond responses. Those single uses add up.

My recommendation to the developer was to require one format as part of its contract and add validation that runs at least during testing. (I'd like to just tell them to use a typed language where this wouldn't even be an issue!)

I mentioned that the developer's experience is in Ruby and JavaScript. I have found that is common for such developers to not expect data to be in a specific format or type. I assume some of this comes from never being trained to always validate and convert data coming from the outside before using it inside. (Eg, directly passing around an INPUT element's value or a database's column value.)  Once inside, you can be assured of its correctness. Instead, data is passed around without any function knowing a priori that it is correct.

I am unsure if I will convince this developer to not use "symbolize_keys". I am rowing against the tide.

Update: Not only did I not convince the developer, but the system's architect rejected it also.

SSL terminating tunnel using ghostunnel

From time to time the need for a simple SSL terminating tunnel is wanted. This is used to enable the browser to use an HTTPS connection to an HTTP server. It is common to use a proxy server, but I was curious if there was something simpler. I was able to create an SSL tunnel using ghostunnel

https://github.com/ghostunnel/ghostunnel

To build it for MacOS 14.7 I needed to update the go.mod to use toolchain go1.22.7 (instead of toolchain go1.22.4).

Created the cert and key

openssl req \
  -x509 \
  -newkey rsa:4096 \
  -keyout key.pem \
  -out cert.pem \
  -sha256 \
  -days 3650 \
  -nodes \
  -subj "/C=US/ST=RI/L=Providence/O=MojoTech/OU=Labs/CN=clientsite.com"

Add the client's domain name to /etc/hosts

127.0.0.1 clientsite.com

Run the tunnel

sudo ghostunnel server \
  --listen clientsite.com:443 \
  --target localhost:3000 \
  --cert cert.pem \
  --key key.pem \
  --disable-authentication

Run Python's file directory serving http server

python3 -m http.server 3000

And finally, open https://clientsite.com in the browser or with curl

curl -k https://clientsite.com

I think since this is Go and executables are statically linked, you could share the ghostunnel executable and PEMs with other developers.

Bye little Linode VM

The website https://andrewgilmatin.com/ is no more. I wasn't using the little Linode VM for much of anything anymore. If I were to keep it running I really needed to move it off of the discontinued CentOS 7. I would have to transition content, old code, and figure out security. Much has changed since I last needed to do that. I was not up for that marathon again.

A templating system using the file system for inheritance

Way back in the early days of the web, around 2004, I wrote a templating system that used the file system for inheritance. I think Fred Toth originally conceived of the technique. 

In the directory /A/B/C you place the template M with content

Hello [%include N%]

You then have the templating system expand /A/B/C/M. It would execute the directive [%include N%] to include the template N by looking up the directory tree, in order, /A/B/C/N, /A/B/N, and /A/N, and using the first N it found. You would place common templates (eg headers) and default content (eg company name) in the upper directories and "override" them in the lower directories. It worked really well for the mostly static sites my department was creating.

I have not seen something like this elsewhere. You can, however, achieve the same effect by manipulating your templating system's template search path per output document.

The system came to be called Trampoline and it has a Perl and a partial Java implementation. The implementations are in the Clownbike project at Source Forge. None of the templates Clownbike used made it to Source Forge, unfortunately. Those became the proprietary web sites our customers were paying for. Galley, an internal project, seems to have some.

I have no idea if any of this code still works. I am sure to be embarrassed by the code's quality! Some quiet, rainy day this winter perhaps I will try running it.

Setting a Mac's default email client

I mostly love using Macs, but sometimes the conviences provided are not. I needed to change my default mail client to Microsoft Outlook. You set the default mail client within Apple's Mail app's Settings. However, you can't access Settings unless you first configure an email account! Since I don't want Mail to touch a actual real email account I ran these mail services locally using Docker:

docker run virtuasa/docker-mail-devel
This enabled me to configure Mail to use "debug@example.com" and the local POP server. And now I can access Mail's Settings to set the default mail client to Microsoft Outlook. I really do feel for all those users without there own System Admin.

Using data from external files

I am working with some code that processes CSV files. Each row corresponds to an existing record and the record is updated in response to the column values. This is not an uncommon task. The existing code implements this in an also not uncommon way by intermixing row parsing and record updating. For example, assume we are updating Foos

class Foo
  attr id, :location, :valuation
  # ...
end
A typical intermixing is
row = [...]
raise "unusable foo id" if row[0].blank?
foo = Foo.find(row[0].to_i)
raise "foo not found with id #{row[0]}" unless foo
raise "unusable town location" if row[1].blank?
location = Location.find_by(town: row[1])
raise "location not found with town #{row[1]}" unless location;
foo.location = location
raise "unusable valuation" unless values[2].to_i < 10_000
foo.valulation = values[2].to_i
While this initially seems like a reasonable approach it quickly breaks down as the number of columns increase, column format is non-trival, and there are column (or row) interdependencies. But the more significant problem is that the parsing and the updating can't be tested individually. This makes the test harder to write, understand, and maintain.

It is always better to first parse the raw data, validate it, and then use it. Eg

class Record
  attr :id, :town, :valuation
  attr :foo, :location

  def initialize(values)
    raise "unusable foo id" unless /^\s*(\d+)\s*$/ =~ values[0]
    id = $1.to_i
    raise "unusable town location" unless /^\s*(.+)\s*$/ =~ values[1]
    location = $1
    raise "unusable valuation" unless /^\s*(\d+)\s*$/ =~ values[2]
    valuation = $1.to_i
  end

  def validate
    foo = Foo.find(id)
    raise "foo not found with id #{id}" unless foo
    location = Location.find_by(town:)
    raise "location not found with town #{town}" unless location
    raise "valuation does not match the minimum" unless valuation >= 10_000;
  end
end

# read the raw data
rows = [[...], ...]

# parse and validate the data
records = rows.map do |row|
  record = Record.new(row)
  record.validate
end

# use the data
records.each do |record|
  record.foo.location = record.location
  record.foo.valuation = record.valuation
  record.foo.save
end
This parse, validate, and use approach is approporate for all cases where you are bringing data from the outside into your application, no matter what the outside source.

ps. These small, helper classes are your friends. Prefer them over your language's hash primitive as they provide great control. Most languages have efficient syntax for creating and using them.

Short list of useful Mac utilities

I don't add a lot of customization to my Mac, but these I have found to be very useful:

Shottr for screenshots, text recognition (OCR), and QR code recognition.
https://shottr.cc/ 

Espanso for text expansion.

Pure Paste for clipboard format removal.

Doll to show messaging apps' icon and badge in the menu bar. (I turn off all notification sounds and banners.)

cd to app Finder toolbar extension that opens the current directory in the Terminal.

The recent discussion Taking command of the Context Menu in macOS on Hacker News has good advice on configuring the Finder's context menu with the ContextMenu, Automator, and other tools.

Update 2025-06: I added Stillcolor in an attempt to tame video anomalies.

Remain in control of your runtime

Several years ago I wrote Remain in control of your search. When you directly provide to your users a mechanism that your application depends on for its runtime then you immediately lose control of your ability to evolve the application. The recent speaker on the Stack Overflow podcast spoke about how ScriptRunner was implemented in Groovy, and it directly provided Groovy as the user's scripting language. When the application's evolution required updating Groovy the developers incurred the burden of ensuring that all the user's Groovy scripts would continue to operate correctly as is. Scripting languages designed for embedding usually provide a sandbox mechanism for limiting access to runtime resources, but few (none?) limit the language itself. It would have been better if ScriptRunner's design used a Groovy-like language that they translated to the runtime language. Having done that they would have been free to update Groovy, incurring only the burden of ensuring the translation continued to work.