<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>PlaidSheep</title>
    <subtitle>Most business data is already structured. The problem is interpretation. Build systems that surface what is already there — patterns in behavior, pressure in markets, and structure in noise.</subtitle>
    <link href="feed.xml" rel="self" />
    <link href="http://plaidsheep.ca/feed.xml" />
    <updated>2024-01-18T00:00:00Z</updated>
    <id>http://plaidsheep.ca/feed.xml</id>
    <author>
        <name>11ty LibDoc</name>
    </author>
        <entry>
            <title>The Evils of Sequential IDs</title>
            <link href="http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/" />
            <updated>2024-01-18T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/0*4vlvGWNBZGxPabCs.jpeg&quot; alt=&quot;The Evils of Sequential IDs&quot;&gt;
                &lt;ul&gt;
&lt;li&gt;Sequential IDs have several re-occurring risks that are known&lt;/li&gt;
&lt;li&gt;We will discuss 3 risks associated with Sequential IDs as Primary Keys&lt;/li&gt;
&lt;li&gt;Some valid cases for maintaining Sequential numbers are discussed&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;iframe src=&quot;https://www.youtube.com/embed/M7AAwIWxIpM?start=23&amp;amp;feature=oembed&amp;amp;start=23&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;Why do we find sequential numbers comforting?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;False sense of utility&lt;/h2&gt;
&lt;p&gt;One argument that is often raised in favour of sequential IDs is the “familiarity” of numbers that are in order. This is expressed as&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We have a sense of how many records there are&lt;/li&gt;
&lt;li&gt;It gives me a way to orient myself&lt;/li&gt;
&lt;li&gt;Numbers are easier to read&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What people are expressing is an emotional state: we are pattern-seeking monkeys and seeing ordinal growth gives us a sense of comfort. As hard as it is, the data is not there to satisfy your psychological comfort; rather we are experts who deal with the uncomfortable realities of data.&lt;/p&gt;
&lt;p&gt;This puts us in a tough place. Humans are really good at post-hoc rationalisations. Any time we experience discomfort, we will reach for the comfortable, and justify it to ourselves in any way we can. Post-Hoc rationalisation is a problem we must each individually struggle with. It&#39;s been a while since I&#39;ve read &lt;a href=&quot;https://amzn.to/3uWByrl&quot;&gt;The Righteous Mind&lt;/a&gt;, but &lt;a href=&quot;https://skepticink.com/tippling/2013/11/14/post-hoc-rationalisation-reasoning-our-intuition-and-changing-our-minds/&quot;&gt;Haidt always nails this point&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We cannot allow ourselves to use what is comfortable, but rather to become comfortable with the most appropriate techniques. To do that we need to take time to consider the benefits.&lt;/p&gt;
&lt;p&gt;If we are using a sequence of numbers as a primary key, we are using arbitrary keys. These are not for human consumption, and therefore they should not make your reading easier, they should make the computer&#39;s reading easier, and hopefully our maintenance.&lt;/p&gt;
&lt;p&gt;A key touch-point in Software development, expressed mostly from the perspective
of &lt;a href=&quot;https://en.wikipedia.org/wiki/Functional_programming&quot;&gt;Functional Programming&lt;/a&gt;,
is to avoid side effects. Each command, each function, should do one thing,
and should not be used to affect the system beyond what was specified.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In computer science, an operation or expression is said to have a &lt;strong&gt;side effect&lt;/strong&gt;
if it has any observable effect other than its primary effect of reading
the value of its arguments and returning a value to the invoker of the
operation.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Side_effect_%28computer_science%29&quot;&gt;Wikipedia: Side Effect&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When searching for the above link, Google gave me a sidebar that stated&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Side effects are any changes in the state of the program or the environment
that are not reflected in the function&#39;s output. Side effects can make
the program unpredictable, hard to test, and difficult to debug.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&#39;ll add to that, that we don&#39;t want to become dependent on side effects because, by definition, there is more than one effect coming from a single action. We now cannot change the action to accommodate one effect, because that would impact the other effect.&lt;/p&gt;
&lt;p&gt;Using the Primary Key as a sequence? Now you can&#39;t change its data type to something else if you need to because that would make it non-sequential. Need to change the order? Not possible, it has foreign key references.&lt;/p&gt;
&lt;p&gt;Our job, as experts, is not to define data in ways that make us comfortable; rather it is our job to define data as it is.&lt;/p&gt;
&lt;blockquote&gt;
define whatever it is we perceive — to trace its outline — so we can see what it really is: its substance. Stripped bare. As a whole. Unmodified. And to call it by its name — the thing itself and its components … Nothing is so conducive to spiritual growth as this capacity for logical and accurate analysis of everything that happens to us.
&lt;/blockquote&gt;
&lt;blockquote&gt;
— &lt;a href=&quot;https://amzn.to/41bygNc&quot; target=&quot;_blank&quot;&gt;Meditations 3.11&lt;/a&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Risks&lt;/h3&gt;
&lt;h4&gt;Security&lt;/h4&gt;
&lt;p&gt;The two main examples from my personal experience draw on the security risk associated with sequential numbers. Numbers in sequence give us a pattern which we can use to predict the next, or previous, value.&lt;/p&gt;
&lt;p&gt;Whether we meant to or not, by using sequential IDs, we have introduced information into our dataset.&lt;/p&gt;
&lt;p&gt;In exposed applications that control information, this has been known to hint to people that there is more information available.&lt;/p&gt;
&lt;p&gt;In Nova Scotia, in 2018, an &lt;a href=&quot;https://www.cbc.ca/news/canada/nova-scotia/concerns-teen-being-railroaded-in-privacy-breach-to-cover-government-slip-1.4616972&quot; target=&quot;_blank&quot;&gt;individual using a government data transfer system&lt;/a&gt; noticed that when retrieving FOIP information releases online, the numbers were relatively small. He was able to surmise that the numbers were probably sequential and simply tried to type the next number into his browser. He was surprised when he got a FOIP record that did not belong to him. Had non-sequential values been used, his guess would likely have resulted in a miss. The large spaces with non-valid values would have been missed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Clicking on the item&#39;s link brings the user to the item&#39;s page and puts the URL for that item, including its unique identification number, in the browser&#39;s address bar. Changing just the number in the address bar takes the user to a different item directly&lt;/p&gt;
&lt;p&gt;— &lt;a href=&quot;https://oipc.novascotia.ca/sites/default/files/publications/OIPC%20Investigation%20Report%20IR19-01%20%2815%20Jan%202019%29.pdf&quot;&gt;OIPC Investigation Report&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While this type of risk is well known it remains common, but at least it is obvious. Unfortunately, there are far more insidious ways of data leaks occurring.&lt;/p&gt;
&lt;p&gt;Sequential IDs are famously capable of being exploited statistically to gain estimates of total counts. This is known as The German Tank problem.&lt;/p&gt;
&lt;p&gt;During WWII, it was necessary to identify how many tanks Germany was producing. Using the serial numbers off the wheels of destroyed tanks, analysts were able to estimate the total number of wheels (and therefore tanks) that were in operation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;wheels, which were observed to be sequentially numbered … Analysis of wheels from two tanks … yielded an estimate of 270 tanks produced in February 1944 … German records after the war showed production for the month of February 1944 was 276&lt;/p&gt;
&lt;p&gt;— &lt;a href=&quot;https://en.wikipedia.org/wiki/German_tank_problem#Historical_example_of_the_problem&quot; target=&quot;_blank&quot;&gt;German Tank Problem&lt;/a&gt;, Wikipedia&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Randomly selecting a sample and then looking at their sequence numbers gives us an idea of how dense the population is. Using one of &lt;a href=&quot;https://en.wikipedia.org/wiki/German_tank_problem#Example&quot; target=&quot;_blank&quot;&gt;several functions&lt;/a&gt;, we can estimate population (and also sub-population) size from a sample.&lt;/p&gt;
&lt;p&gt;When we share large datasets for analysis, we often exclude records deemed to be outside the Data Scientist&#39;s domain of interest (often for privacy). Unfortunately, if we are sharing underlying sequential values we are exposing more information about the larger set than we intended. The customer can derive information about the unshared portion of the data.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/0-cef-q79qobzs-nuw-4D14_oF3qR-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/0-cef-q79qobzs-nuw-4D14_oF3qR-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;303&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;The goal is to get data onto the distributed processors with as little fuss as possible.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;While the OSF came up with a very interesting solution to this problem, random numbers and cryptographic hashes have become more popular in the intervening decades. Using extremely large random numbers, we can be reasonably assured that the number generated will be unique to the population; meaning there is no need to coordinate between systems. Each processor can independently assign identifiers without concerning itself with the work of other processors in the system. The results can later be merged as one step.&lt;/p&gt;
&lt;p&gt;Hashes serve a similar purpose but have the benefit of being deterministic. It is possible to do processing on a unit and identify which unit it is without having to do expensive scanning on the larger set. If some feature of the data can be used as a unique identifier, &lt;a href=&quot;https://medium.com/javascript-in-plain-english/de-duplicating-data-storage-i-3ed467afc528&quot; target=&quot;_blank&quot;&gt;hashes can be used to convert that feature into a reasonably sized and well-distributed integer&lt;/a&gt;, suitable for use as a key. Like random numbers, we can be reasonably assured that there will not be a conflicting number, but we also have the benefit of being able to integrate overlapping entities if they are then determined to represent the same thing.&lt;/p&gt;
&lt;h4&gt;Imbalanced Indexes&lt;/h4&gt;
&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Rule_of_three_%28writing%29&quot; target=&quot;_blank&quot;&gt;law of threes&lt;/a&gt; dictates that I make three subpoints, and so I had to come up with a third reason not to use Sequential IDs; but this one is a little more of a stretch.&lt;/p&gt;
&lt;p&gt;There is an argument that during partitioning for storage or processing, or for indexing of datasets, sequential IDs do not balance well in the binary tree. This first came up during a (now lost) lecture I watched by Jan (can&#39;t remember her last name) of IBM. She discussed why random keys were so important to database performance and asked audience members if their keys became imbalanced. This highlighted issues I remember from Project Gutenberg&#39;s data store in which they partition based on the first digit of the sequential number of the book submission.&lt;/p&gt;
&lt;p&gt;Under these conditions, &lt;a href=&quot;https://en.wikipedia.org/wiki/Benford%27s_law&quot; target=&quot;_blank&quot;&gt;Benford&#39;s Law&lt;/a&gt; is going to burn us.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/0-jrl-c2iu-djezqa-_MfVnl07nE-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/0-jrl-c2iu-djezqa-_MfVnl07nE-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;584&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Benford&#39;s Law indicates that the first digit of numbers will tend to be smaller values (&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Benford-physical.svg&quot; target=&quot;_blank&quot;&gt;Wikicommons&lt;/a&gt; CC0)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Benford&#39;s Law states that often, the first digit of a number is low. In a sequential set, it is obvious why this would be true: every time you change scales (say 1&#39;s to 10&#39;s) it takes ten times longer to change the first digit (in other cases this remains a mystery to me).&lt;/p&gt;
&lt;p&gt;For partitioning, this means that partition 1 is going to get 30% of the work, while partition 9 is only going to get 5% of the work; creating massive bottlenecks while one grinds away, and another sits idle.&lt;/p&gt;
&lt;p&gt;While I have seen real-world examples of this, and do recognize the problem as a problem, I am generally dismissive of it. It is relatively simple to implement a fast, balanced hashing algorithm that resolves this problem.&lt;/p&gt;
&lt;p&gt;Using the example from above, Benford&#39;s Law can be circumvented by simply reversing the numbers (effectively a Mod10 hash). So instead of the sequence &lt;code class=&quot;markup--code markup--p-code&quot;&gt;[101, 102, 103, 104, 105]&lt;/code&gt; being sent to the same partition, we can instead use &lt;code class=&quot;markup--code markup--p-code&quot;&gt;[101, 201, 301, 401, 501]&lt;/code&gt; to distribute the values more equitably.&lt;/p&gt;
&lt;p&gt;Having come across articles on the hashing algorithms used in indexing by some of the big database engines, I would expect the balancing of data during partitioning to be a well-understood and handled problem in any platform I choose to adopt. If you have to balance the values yourself, these may be considerations you need to take, but looking at your chosen tools may be in order.&lt;/p&gt;
&lt;h3&gt;Valid Uses&lt;/h3&gt;
&lt;p&gt;As with any hard and fast rule … well … there is no such thing. There are exceptions and valid reasons for why you might want a sequential identifier.&lt;/p&gt;
&lt;h4&gt;Audits&lt;/h4&gt;
&lt;p&gt;Going back to my very early days in my career, when my colleagues and I were just trying to figure out how to turn a dollar, a few of us took to teaching “How to use Office” night classes at local community centres, job retraining centres, or (mine) crime exit programs; all of us had a module “Use Excel to Make an Invoice”.&lt;/p&gt;
&lt;p&gt;One weekend, I was at one of these friends&#39; houses, we were having a few beers and working on a hobby problem together when he mentioned a lesson he got from one of his students.&lt;/p&gt;
&lt;p&gt;As part of his invoice class, he taught how to make invoice numbers automatically (&lt;code class=&quot;markup--code markup--p-code&quot;&gt;last inv number + 1&lt;/code&gt;), but he told the class, “If you want to make your business look more impressive to your customers, change that formula to &lt;code class=&quot;markup--code markup--p-code&quot;&gt;last invoice number + 10&lt;/code&gt;. Even if you only have one customer, it won&#39;t look that way.”&lt;/p&gt;
&lt;p&gt;A hand, near the back slowly went up.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I am an accountant, and have to recommend people not do that. When you undergo a tax audit, they will identify the gaps as &lt;strong class=&quot;markup--strong markup--blockquote-strong&quot;&gt;&lt;em&gt;missing invoices&lt;/em&gt;&lt;/strong&gt; and assume you have done 10x as much business as you have reported. They will estimate what those invoices are worth, and send you a bill for the apparently missing nine invoices&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That&#39;s not something you want to have happen, but the ability to match patterns and find gaps, which is what poses the original risk, also has its benefits. The main reason I will reach for a sequential ID is for audit purposes: a sequential ID can be used to verify missing data by creating gaps in the sequence when data is removed.&lt;/p&gt;
&lt;p&gt;Non-technical individuals often don&#39;t understand how &lt;a href=&quot;https://archive.org/web/&quot; target=&quot;_blank&quot;&gt;malleable information&lt;/a&gt; is in a &lt;a href=&quot;https://medium.com/dev-genius/paper-as-a-digital-storage-medium-4096c5062da&quot; target=&quot;_blank&quot;&gt;digital world&lt;/a&gt;, it is easier for them to comprehend that the number has incremented than it is to understand a &lt;a href=&quot;https://en.wikipedia.org/wiki/Merkle_tree&quot; target=&quot;_blank&quot;&gt;chain of hashes&lt;/a&gt;, so adding a sequence ID for visiting auditors can be a courtesy.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/0-inbpd163encovjny-KxrAJwRBB8-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/0-inbpd163encovjny-KxrAJwRBB8-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;464&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Merkle Tree (&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Hash_Tree.svg&quot; target=&quot;_blank&quot;&gt;Wikicommons&lt;/a&gt; — CC0)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4&gt;Replication Checkpoints&lt;/h4&gt;
&lt;p&gt;One of the databases I like to use a lot is &lt;a href=&quot;https://www.couchbase.com/&quot; target=&quot;_blank&quot;&gt;CouchDB&lt;/a&gt;, and one of its super-powers is its ability to replicate the data across multiple peers.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/1-4zrs-3wnghhwxoh6hnueag-w-Ke9Lpu5r-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/1-4zrs-3wnghhwxoh6hnueag-w-Ke9Lpu5r-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;555&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;To achieve this, there are a few things that have to take place to address conflict resolution, change histories, and &lt;a href=&quot;https://guide.couchdb.org/editions/1/en/conflicts.html&quot; target=&quot;_blank&quot;&gt;split brains&lt;/a&gt;; but one of the key elements is the simplest: every change to the database gets a &lt;a href=&quot;https://guide.couchdb.org/editions/1/en/replication.html#magic&quot; target=&quot;_blank&quot;&gt;sequence number&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This number is used during replication to act as a bookmark, allowing one database to request every change since a point in time.&lt;/p&gt;
&lt;p&gt;This is an important tool to be used during replication, being able to state: “give me everything since checkpoint &lt;code class=&quot;markup--code markup--p-code&quot;&gt;X&lt;/code&gt;”.&lt;/p&gt;
&lt;p&gt;A similar construct is also available in MS SQL Server, called a &lt;code class=&quot;markup--code markup--p-code&quot;&gt;rowversion&lt;/code&gt; (it was a &lt;code class=&quot;markup--code markup--p-code&quot;&gt;timestamp&lt;/code&gt; &lt;a href=&quot;https://medium.com/mlearning-ai/how-fast-is-fast-enough-2af77c61007d#9773&quot; target=&quot;_blank&quot;&gt;last I used it&lt;/a&gt;); and I&#39;m sure most other DBs have the same concept: a globally incrementing sequence.&lt;/p&gt;
&lt;h2&gt;Solutions&lt;/h2&gt;
&lt;p&gt;Given all of this, what should we do when constructing identifiers?&lt;/p&gt;
&lt;h3&gt;Big Integers&lt;/h3&gt;
&lt;p&gt;No matter what style of identifier you use, make sure you use really big integers.&lt;/p&gt;
&lt;p&gt;In those early days of early web applications, the databases we used had auto-incrementing indexes that could have “random” specified as the sequence. However, they were constrained to a 32-bit integer: that&#39;s only room for about 4 billion records.&lt;/p&gt;
&lt;p&gt;Not a lot by modern standards.&lt;/p&gt;
&lt;p&gt;If you are using random numbers, you only derive the benefit of the gaps if they are reasonably large gaps. If you are using sequential values, you don&#39;t want to run out.&lt;/p&gt;
&lt;h3&gt;Random values, Hashes, and UUIDs&lt;/h3&gt;
&lt;p&gt;Generally, I suggest using (at least) a 128-bit integer for storage. I don&#39;t choose this number arbitrarily; yes it&#39;s large, but it is also the size of a &lt;a href=&quot;https://en.wikipedia.org/wiki/Universally_unique_identifier&quot; target=&quot;_blank&quot;&gt;UUID&lt;/a&gt;. This has the convenience of having a ready-to-use storage type on most systems as well as standardised functions for generating random (or near random) numbers of that size.&lt;/p&gt;
&lt;p&gt;I say “near random” because while UUIDv4 is a random number, other versions of UUID are not. For example, version 1 was dependent on the computer&#39;s MAC address to form part of its uniqueness. Further, some implementations of UUID generators are not &lt;a href=&quot;https://en.wikipedia.org/wiki/Universally_unique_identifier#Variants&quot; target=&quot;_blank&quot;&gt;truly standards conformant&lt;/a&gt;, instead including timestamps in the number for convenience.&lt;/p&gt;
&lt;p&gt;Hashes are another interesting option, while they are deterministic, they are effectively random. In the case that a dataset has a natural key that we do not necessarily want to share, but makes an obvious primary key, hashes can make a convenient way to mask the natural key. It generates a large, (effectively) random, integer. While an &lt;code class=&quot;markup--code markup--p-code&quot;&gt;md5&lt;/code&gt; will fit into a 128-bit integer, most modern hashes require more space.&lt;/p&gt;
&lt;p&gt;One interesting benefit of using a hash occurs when you require scrambling of your keys. Through the use of a &lt;a href=&quot;https://en.wikipedia.org/wiki/Salt_%28cryptography%29&quot; target=&quot;_blank&quot;&gt;salt value&lt;/a&gt; (perhaps a salt value per customer), we can allow for simple regeneration of different random-like keys if we ever require them to change.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/0-57sca0mozfqct92f-o2PoaLnO15-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/0-57sca0mozfqct92f-o2PoaLnO15-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;730&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;&lt;strong class=&quot;markup--strong markup--figure-strong&quot;&gt;Generated with AI&lt;/strong&gt; ∙ 17 January 2024 at 7:52 pm&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Primary Keys are never to be sequential&lt;/h3&gt;
&lt;p&gt;There are reasons to use sequential numbers, but making a primary lookup value creates the temptation to expose it to the client, and the moment you have something that looks tempting, it will happen. Let me say that again:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Your primary key will get exposed to the customer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are all kinds of reasons we promise it won&#39;t, but at some point, somebody is going to make a mistake and expose that number. After all, it is the value we look at individual records by. If for no other reason than an analyst is going to want to do a join against two tables, that primary key is going to get exposed.&lt;/p&gt;
&lt;p&gt;By using random-like values for PKs, there is less &lt;em&gt;temptation&lt;/em&gt; to use them for inappropriate things. We can&#39;t derive information from them directly and therefore don&#39;t try to use them for secondary purposes. If we need to add sequential values, putting them in a field that explicitly defines why they exist, making their purpose (and non-purpose) clear.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/1-d6noen8oyce35rhbtjdhtg-3qaQ9ALBGQ-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/1-d6noen8oyce35rhbtjdhtg-3qaQ9ALBGQ-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;730&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;&lt;strong class=&quot;markup--strong markup--figure-strong&quot;&gt;Generated with AI&lt;/strong&gt; ∙ 17 January 2024 at 7:46 pm&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Non-primary, Unique Identifiers&lt;/h3&gt;
&lt;p&gt;Just keep an eye out for them.&lt;/p&gt;
&lt;p&gt;Do you have a field called &lt;code class=&quot;markup--code markup--p-code&quot;&gt;created_time&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;If your data has a created timestamp on it, it has something that may be very close to a unique identifier. While this may not be sensitive information in itself, users may be able to use this to join data against other datasets.&lt;/p&gt;
&lt;p&gt;While useful for diagnostic purposes, these &lt;a href=&quot;https://medium.com/@jefferey-cave/how-to-quantifying-privacy-protection-in-shared-datasets-9e52c6635814&quot; target=&quot;_blank&quot;&gt;low entropy values pose a risk when exposed to the wider customer group&lt;/a&gt;. By not having them as the primary key, we can simply remove these values from data shares. By having these unique values not acting as primary keys, we are not bound by their side effects and we are less tempted to share them inappropriately.&lt;/p&gt;
&lt;p&gt;If these values are required for diagnostics, then we must maintain them. Our only defence is to be cognizant of the risks and to keep the values unbound from each other.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;While there are valid reasons for maintaining sequential values in data, using sequential values as the primary key poses significant risks to data. Given the nature of primary identifiers, the cost of the risks versus the perceived benefits do not justify their use.&lt;/p&gt;
&lt;p&gt;Separating valid sequential use cases from the primary identifier allows us to separate the concerns of the purpose of the data, allowing us to move parts independently of side effects. Very large integers have been widely available for over 30 years, and allow us to create large gaps of negative space in our data to evade malicious detection. Modern random number generation and cryptographic hashes offer a secure way to populate that large space.&lt;/p&gt;
&lt;p&gt;Given these simple and well-established solutions, the risks to both security and performance posed by using sequential primary keys do not outweigh their perceived benefits.&lt;/p&gt;
&lt;p&gt;As engineers and architects, we need to consider not the way we have always perceived data, but rather to look at the historic and scientific context that surrounds the protection of that data. We may find that the secure mechanisms of the past, no longer suit the computational realities of the present.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/0-u-k75uxwbbvomd0v-9HiP3f5h61-__641__.avif 641w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2024/3-reasons-not-to-use-Sequential-IDs-as-Primary-Keys/0-u-k75uxwbbvomd0v-9HiP3f5h61-__641__.webp&quot; alt=&quot;&quot; width=&quot;641&quot; height=&quot;406&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Scherbius-1928-patent.png&quot; target=&quot;_blank&quot;&gt;1928 US patent 1,657,411: Enigma Cypher Machine&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
</content>
        </entry>
        <entry>
            <title>How To Quantify Privacy in Datasets</title>
            <link href="http://plaidsheep.ca/posts/2023/how-to-quantify-privacy-in-datasets/" />
            <updated>2023-12-27T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2023/how-to-quantify-privacy-in-datasets/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                &lt;p&gt;We will discuss tools for estimating and automating the enforcement of privacy in datasets.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Objective Data Privacy Evaluation&lt;/strong&gt;: Discuss a straightforward approach using objective measures to gauge and improve data privacy in shared datasets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Estimating Data Privacy Levels&lt;/strong&gt;: Learn through real-world examples how estimating data privacy levels can be a powerful tool, minimizing bias and enhancing informed decision-making&lt;/li&gt;
&lt;li&gt;&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;Automated Checks for Enhanced Workflow&lt;/strong&gt;: Explore the application of automated checks in the approval workflow to simplify processes and boost productivity in safeguarding sensitive information&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Head over to &lt;a href=&quot;https://observablehq.com/d/26ff6fe1cb2b857e&quot; target=&quot;_blank&quot;&gt;ObservableHQ&lt;/a&gt; to see an interactive version of this.&lt;/p&gt;
&lt;div name=&quot;97cd&quot; id=&quot;97cd&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p&quot;&gt;&lt;a href=&quot;https://observablehq.com/d/26ff6fe1cb2b857e&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;Measuring Privacy Exposure&lt;/strong&gt;&lt;br&gt;&lt;em&gt;Jeff Cave `2023-12-15` to `2023-12-20` In my previous article, I highlighted the nuanced task of preserving privacy…&lt;/em&gt;observablehq.com&lt;/a&gt;&lt;a href=&quot;https://observablehq.com/d/26ff6fe1cb2b857e&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;In a &lt;a href=&quot;https://medium.com/@jefferey-cave/tracing-privacy-in-data-shares-f9864bc5da1a&quot; target=&quot;_blank&quot;&gt;previous article&lt;/a&gt;, I delved into the intricate task of preserving privacy, underscoring the critical nature of cautious information sharing. This principle extends beyond safeguarding personal identities to shielding covert subjects like structures, military units, and other sensitive entities. The guiding rule is clear: the less revealed, the better.&lt;/p&gt;
&lt;p&gt;Implementing this principle poses a formidable challenge. Over-sharing not only risks exposing individuals but also jeopardizes the confidentiality of various subjects. Assessing the “privacy” of shared datasets demands meticulous effort. &lt;a href=&quot;https://utrechtuniversity.github.io/dataprivacyhandbook/gdpr.html&quot; target=&quot;_blank&quot;&gt;Privacy analysts invest time scrutinizing datasets&lt;/a&gt;, applying rules, and leveraging professional judgment to ensure the concealment of both personal and classified subjects.&lt;/p&gt;
&lt;p&gt;However, relying solely on subjective analysis has its inherent risks. Analysts, being human, are susceptible to biases, fatigue, and external pressures, occasionally leading to lapses in judgment. Despite these challenges, there&#39;s a growing need to share data for diverse benefits.&lt;/p&gt;
&lt;p&gt;The predicament endures: how do we establish a threshold for sharing information without compromising the privacy of individuals or the secrecy of sensitive subjects? How can we alleviate the burden of the privacy evaluators while simultaneously ensuring that shared data doesn&#39;t pose excessive risks?&lt;/p&gt;
&lt;p&gt;Enter Claude Shannon&#39;s Information Entropy concept, originating from “The Mathematical Theory of Communication” in 1949. Shannon&#39;s concept of the smallest piece of indivisible information provides a quantifiable measure for data. This measure is not only applicable to personal privacy but also to the confidentiality of secret subjects. It furnishes an objective metric to estimate the privacy risk associated with exposing a dataset, serving as a valuable tool for analysts and automated systems alike in assessing risks related to both personal and classified information.&lt;/p&gt;
&lt;p&gt;These concepts present a practical tool for striking a balance between safeguarding the privacy of subjects and the imperative to share data for analysis.&lt;/p&gt;
&lt;h2&gt;Definitions&lt;/h2&gt;
&lt;p&gt;To ensure precision and clarity, we have chosen to substitute the term “individual” with the more inclusive term “subject.” While my main focus revolves around protecting people&#39;s privacy, it&#39;s crucial to recognize that certain fields manage data of a covert nature that extends beyond human entities. Examples include “&lt;a href=&quot;https://glossary.slb.com/en/Terms/t/tight_hole.aspx&quot; target=&quot;_blank&quot;&gt;Tight Holes&lt;/a&gt;” in the Oil and Gas sector, clandestine police and military locations, or &lt;a href=&quot;https://nationalpost.com/news/toronto/new-details-toronto-pearson-airport-gold-heist&quot; target=&quot;_blank&quot;&gt;discreetly insured and transported tangible objects&lt;/a&gt;. Remarkably, the strategies for privacy protection can be universally applied, regardless of the nature of the subject.&lt;/p&gt;
&lt;p&gt;Central to our exploration is the concept of “privacy,” which we define as the resistance to deducing the individual&#39;s identity. Think of it like the classic game of “&lt;a href=&quot;https://amzn.to/47dfGWc&quot; target=&quot;_blank&quot;&gt;Guess Who&lt;/a&gt;?” where your opponent uses provided data to guess the subject&#39;s identity and then uses a broader dataset to gather more information. Decreased privacy erodes when data boosts our confidence in identifying individuals, while increased privacy comes from a lower confidence level in distinguishing one individual from another.&lt;/p&gt;
&lt;p&gt;It&#39;s crucial to emphasize that privacy isn&#39;t only preserved by obscuring the name or ID of a person. Even with fabricated labels for the subject, comprehensive knowledge about them can still lead to privacy breaches. As an analogy, personal privacy can be compromised even if you know everything about a subject but refer to them by a pseudonym, much like my limited knowledge about my neighbour, whom I simply recognize as “that lady next door.”&lt;/p&gt;
&lt;p&gt;There are three significant roles in any informational message transfer: the sender, the receiver, and a potential interceptor. In the case of a dataset being shared, we find these three actors present. Privacy Analysts sit between our source data and filter it, acting like a sender, sending a sanitized message out. The intended recipient is a Data Analyst, whether an internal colleague or a member of the public. Lastly, the Data Analyst could be a malicious actor, either gaining access to the data nefariously or, more pertinently, using a permitted dataset in a nefarious way.&lt;/p&gt;
&lt;p&gt;While “entropy” is often linked with the predictability of physical systems, it can be better understood as a measure of chaos. &lt;em&gt;Information Entropy&lt;/em&gt;, similar to the concept of chaos, quantifies the level of “surprise” within a system. In a stable or predictable state, a system exhibits high entropy. Consequently, possessing complete knowledge about a subject leaves minimal room for surprise; conversely, knowing nothing allows for unexpected discoveries.&lt;/p&gt;
&lt;p&gt;While Claude Shannon is renowned for the bit, there are several names and related measures that apply to the same concept of information entropy. The Shannon measure represents the smallest, indivisible unit of information, a binary state (true or false); it is synonymous with a bit (or “binary digit”). This concept builds on previous work defining the Hartley (Ralph Hartley, 1929), which uses a decimal base and can also be referred to as the dit, among other terms used by Turing, Good, and others.&lt;/p&gt;
&lt;h2&gt;An Example&lt;/h2&gt;
&lt;p&gt;To work through an explanation of enforcing privacy through an automated mechanism, an example is likely useful. Samples often help us orient ourselves to the task at hand. Naturally, in a discussion about privacy, we will want a dataset with individuals whose privacy we will want to protect. Still, we also want to make sure these individuals are not representative of anyone real. To achieve this, we have downloaded a sample customer dataset from &lt;a href=&quot;https://www.slingacademy.com/article/customers-sample-data-csv-json-xml-and-xlsx/&quot; target=&quot;_blank&quot;&gt;Sling Academy&lt;/a&gt;, which gives us a list of individuals and some personal information about them.&lt;/p&gt;
&lt;p&gt;I&#39;ve also gone through a process of “cleaning” the data to make it a little more suitable for our purposes. Mostly by adding some randomized identifiers, but also by introducing some nulls and extraneous character removal.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/how-to-quantify-privacy-in-datasets/1-zazumazz7awvrv9mam-igq-cwuZxE0udZ-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/how-to-quantify-privacy-in-datasets/1-zazumazz7awvrv9mam-igq-cwuZxE0udZ-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;180&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;The probability of guessing an individual by their gender depends on which gender is exposed (&lt;a href=&quot;https://observablehq.com/d/26ff6fe1cb2b857e#cell-381&quot; target=&quot;_blank&quot;&gt;ObservableHQ&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This ability to measure predictability brings us close to Claude Shannon&#39;s definition of “entropy”, or surprise. Given the probability of guessing the person, we can also calculate the amount of surprise. By calculating the probability of randomly selecting an individual from within each category we can get an idea of how private the field is.&lt;/p&gt;
&lt;p&gt;In our case, the selected field, &lt;code class=&quot;markup--code markup--p-code&quot;&gt;gender&lt;/code&gt;, has 3 categories. By taking an average of their probabilities (&lt;code class=&quot;markup--code markup--p-code&quot;&gt;0.0430&lt;/code&gt;), we get a general sense of its level of privacy.&lt;/p&gt;
&lt;p&gt;We can repeat this for all fields, giving us a privacy profile for the dataset.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/how-to-quantify-privacy-in-datasets/1-k0qygo2yr9ratu7dgqtlpg-CvtK2QJkZK-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/how-to-quantify-privacy-in-datasets/1-k0qygo2yr9ratu7dgqtlpg-CvtK2QJkZK-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;1314&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Measures of privacy for each field as a probability, Shannon, or Hart (&lt;a href=&quot;https://observablehq.com/d/26ff6fe1cb2b857e#cell-707&quot; target=&quot;_blank&quot;&gt;ObservableHQ&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The privacy factor calculated for each field matches our intuition: perfectly unique IDs carry a very low privacy factor, and items like&lt;code class=&quot;markup--code markup--p-code&quot;&gt;first_name&lt;/code&gt; are relatively anonymous. Having observed this, we can observe some non-intuitive findings such as the very low privacy associated with &lt;code class=&quot;markup--code markup--p-code&quot;&gt;municipality&lt;/code&gt;. We also have a cautionary reminder as well: emails and phone numbers are very unique to people.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// Calculate the privacy profile of all the fields&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;hljs-title class_&quot;&gt;PrivacyProfile&lt;/span&gt; = (&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;){&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; {&lt;br&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;hashed&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;,&lt;br&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;table&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;entries&lt;/span&gt;(fields1).&lt;span class=&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;d&lt;/span&gt;=&amp;gt;&lt;/span&gt;{&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; key = d[&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;];&lt;br&gt;    d = d[&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;];&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; total = d.&lt;span class=&quot;hljs-property&quot;&gt;total&lt;/span&gt;;&lt;br&gt;  &lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; categories = &lt;span class=&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;values&lt;/span&gt;(d.&lt;span class=&quot;hljs-property&quot;&gt;values&lt;/span&gt;).&lt;span class=&quot;hljs-property&quot;&gt;length&lt;/span&gt;;&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; privFactor = &lt;span class=&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;values&lt;/span&gt;(d.&lt;span class=&quot;hljs-property&quot;&gt;values&lt;/span&gt;)&lt;br&gt;      .&lt;span class=&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;freq&lt;/span&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;-(&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;/freq))&lt;br&gt;      .&lt;span class=&quot;hljs-title function_&quot;&gt;reduce&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;a,d&lt;/span&gt;)=&amp;gt;&lt;/span&gt;a+d,&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;)&lt;br&gt;      /categories&lt;br&gt;      ;&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; shannon = &lt;span class=&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;values&lt;/span&gt;(d.&lt;span class=&quot;hljs-property&quot;&gt;values&lt;/span&gt;)&lt;br&gt;      .&lt;span class=&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;freq&lt;/span&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(freq)/&lt;span class=&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;LN2&lt;/span&gt;)&lt;br&gt;      .&lt;span class=&quot;hljs-title function_&quot;&gt;reduce&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;a,d&lt;/span&gt;)=&amp;gt;&lt;/span&gt;a+d,&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;)&lt;br&gt;      /categories&lt;br&gt;      ;&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; hart = &lt;span class=&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;values&lt;/span&gt;(d.&lt;span class=&quot;hljs-property&quot;&gt;values&lt;/span&gt;)&lt;br&gt;      .&lt;span class=&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;freq&lt;/span&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(freq)/&lt;span class=&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;LN10&lt;/span&gt;)&lt;br&gt;      .&lt;span class=&quot;hljs-title function_&quot;&gt;reduce&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;a,d&lt;/span&gt;)=&amp;gt;&lt;/span&gt;a+d,&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;)&lt;br&gt;      /categories&lt;br&gt;      ;&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; {&lt;br&gt;      &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Field&amp;quot;&lt;/span&gt;: key,&lt;br&gt;      &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;PrivacyFactor&amp;quot;&lt;/span&gt;: privFactor,&lt;br&gt;      &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Shannons&amp;quot;&lt;/span&gt;: shannon,&lt;br&gt;      &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Harts&amp;quot;&lt;/span&gt;: hart,&lt;br&gt;      &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Probability&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;-privFactor,&lt;br&gt;      &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Values&amp;quot;&lt;/span&gt;: total,&lt;br&gt;      &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Categories&amp;quot;&lt;/span&gt; : categories,&lt;br&gt;    };&lt;br&gt;  })&lt;br&gt;  }&lt;br&gt;})();&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Applying the Measure to whole datasets&lt;/h3&gt;
&lt;p&gt;If our goal is to retain privacy while sharing data, in a large-scale environment, we can use this as a tool. Rather than relying purely on the subjective judgement of people, we can &lt;em&gt;aid&lt;/em&gt; their judgement with an objective measure. When a data analyst comes to the Privacy Team, looking for access to sensitive data, we can take a measure of the privacy level of their data request.&lt;/p&gt;
&lt;p&gt;We can calculate the net privacy of a request by taking a cumulative product of the privacy factors of the individual fields being requested.&lt;/p&gt;
&lt;p&gt;Intuitively, we can immediately see that the &lt;em&gt;entire&lt;/em&gt; dataset offers little to no privacy; however, this can be more formally stated by taking the product of all the fields.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-title class_&quot;&gt;PrivacyProfile&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;table&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;reduce&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;a,d&lt;/span&gt;)=&amp;gt;&lt;/span&gt;{&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; a * d.&lt;span class=&quot;hljs-property&quot;&gt;PrivacyFactor&lt;/span&gt;&lt;br&gt;},&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;);&lt;/span&gt;&lt;/p&gt;&lt;p name=&quot;4585&quot; id=&quot;4585&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;&lt;/p&gt;
&lt;p&gt;With items like the SSN, some items are &lt;code class=&quot;markup--code markup--p-code&quot;&gt;0&lt;/code&gt; privacy, and including these in the product results in a Net Privacy Factor of &amp;quot;no privacy&amp;quot; (hard zero: &lt;code class=&quot;markup--code markup--p-code&quot;&gt;0&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;We should expect a Data Request to &lt;a href=&quot;https://utrechtuniversity.github.io/dataprivacyhandbook/data-sharing-collaboration.html&quot; target=&quot;_blank&quot;&gt;be only for the data required for the analysis at hand&lt;/a&gt;. Therefore the request should include a reduced set. To perform this calculation on demand, we can create a generic function&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;GenerateReqest&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;req = &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;&lt;/span&gt;){&lt;br&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;// if nothing specific was requested, make this a randomized selection&lt;/span&gt;&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt;(!req){&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; fldprob = &lt;span class=&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;ceil&lt;/span&gt;(&lt;span class=&quot;hljs-title class_&quot;&gt;PrivacyProfile&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;table&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;length&lt;/span&gt; * &lt;span class=&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;random&lt;/span&gt;()) / &lt;span class=&quot;hljs-title class_&quot;&gt;PrivacyProfile&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;length&lt;/span&gt;;&lt;br&gt;    req = &lt;span class=&quot;hljs-title class_&quot;&gt;PrivacyProfile&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;table&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;filter&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;d&lt;/span&gt;=&amp;gt;&lt;/span&gt;(&lt;span class=&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;random&lt;/span&gt;()&amp;lt;fldprob)).&lt;span class=&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;d&lt;/span&gt;=&amp;gt;&lt;/span&gt;d.&lt;span class=&quot;hljs-property&quot;&gt;Field&lt;/span&gt;);&lt;br&gt;  }&lt;br&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;// select only the items that actually exist&lt;/span&gt;&lt;br&gt;  req = &lt;span class=&quot;hljs-title class_&quot;&gt;PrivacyProfile&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;table&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;filter&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;d&lt;/span&gt;=&amp;gt;&lt;/span&gt;req.&lt;span class=&quot;hljs-title function_&quot;&gt;includes&lt;/span&gt;(d.&lt;span class=&quot;hljs-property&quot;&gt;Field&lt;/span&gt;)).&lt;span class=&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;d&lt;/span&gt;=&amp;gt;&lt;/span&gt;d.&lt;span class=&quot;hljs-property&quot;&gt;Field&lt;/span&gt;);&lt;br&gt;&lt;br&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;// prepare the return&lt;/span&gt;&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; rtn = {&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;req&lt;/span&gt;: req&lt;br&gt;  };&lt;br&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;// calculate the privacy factor&lt;/span&gt;&lt;br&gt;  rtn.&lt;span class=&quot;hljs-property&quot;&gt;score&lt;/span&gt; = rtn.&lt;span class=&quot;hljs-property&quot;&gt;req&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;reduce&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;a,d&lt;/span&gt;)=&amp;gt;&lt;/span&gt;a*&lt;span class=&quot;hljs-title class_&quot;&gt;PrivacyProfile&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;hashed&lt;/span&gt;[d][&lt;span class=&quot;hljs-string&quot;&gt;&#39;PrivacyFactor&#39;&lt;/span&gt;],&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;);&lt;br&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;// if the factor is so small as to require a scientific notation, let&#39;s just call it zero&lt;/span&gt;&lt;br&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;// This convention is only in place to make reading the results easier&lt;/span&gt;&lt;br&gt;  rtn.&lt;span class=&quot;hljs-property&quot;&gt;score&lt;/span&gt; = rtn.&lt;span class=&quot;hljs-property&quot;&gt;score&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;toString&lt;/span&gt;().&lt;span class=&quot;hljs-title function_&quot;&gt;includes&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;e&#39;&lt;/span&gt;) ? &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt; : rtn.&lt;span class=&quot;hljs-property&quot;&gt;score&lt;/span&gt;;&lt;br&gt;  &lt;br&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;// get the filtered dataset&lt;/span&gt;&lt;br&gt;  rtn.&lt;span class=&quot;hljs-property&quot;&gt;data&lt;/span&gt; = basedata.&lt;span class=&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;d&lt;/span&gt;=&amp;gt;&lt;/span&gt;{&lt;br&gt;    d = rtn.&lt;span class=&quot;hljs-property&quot;&gt;req&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;reduce&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;a,f&lt;/span&gt;)=&amp;gt;&lt;/span&gt;{&lt;br&gt;      a[f] = d[f];&lt;br&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; a;&lt;br&gt;    },{});&lt;br&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// generate a random id for each record&lt;/span&gt;&lt;br&gt;    d = &lt;span class=&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;assign&lt;/span&gt;({&lt;br&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;floor&lt;/span&gt;(&lt;span class=&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;random&lt;/span&gt;()*&lt;span class=&quot;hljs-title class_&quot;&gt;Number&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;MAX_SAFE_INTEGER&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;toString&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;32&lt;/span&gt;)&lt;br&gt;      },d);&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; d;&lt;br&gt;  });&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; rtn;&lt;br&gt;}&lt;/span&gt;&lt;/p&gt;&lt;p name=&quot;5e96&quot; id=&quot;5e96&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;&lt;/p&gt;
&lt;p&gt;This generic function takes a list of variables that the analyst wants access to, calculates the cumulative score, and produces the requested dataset. This can then be used to make a request (&lt;code class=&quot;markup--code markup--p-code&quot;&gt;R00001&lt;/code&gt;) for items we know are more generic fields.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-variable constant_&quot;&gt;R00001&lt;/span&gt; = &lt;span class=&quot;hljs-title class_&quot;&gt;GenerateReqest&lt;/span&gt;([&lt;span class=&quot;hljs-string&quot;&gt;&#39;gender&#39;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&#39;age&#39;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&#39;hobbies&#39;&lt;/span&gt;]);&lt;br&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// {req: [&amp;quot;gender&amp;quot;, &amp;quot;age&amp;quot;, &amp;quot;hobbies&amp;quot;], score: 0.8534004245932628}&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p name=&quot;d8fe&quot; id=&quot;d8fe&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;&lt;/p&gt;
&lt;p&gt;Again, the results match our expectations. We know that &lt;code class=&quot;markup--code markup--p-code&quot;&gt;gender&lt;/code&gt;, &lt;code class=&quot;markup--code markup--p-code&quot;&gt;age&lt;/code&gt; and &lt;code class=&quot;markup--code markup--p-code&quot;&gt;hobbies&lt;/code&gt; have a much higher Privacy Factor and should therefore be much safer. However, now that we have a clear measure, we can be more explicit and state that the cumulative product of the request has a Privacy Factor of &lt;code class=&quot;markup--code markup--p-code&quot;&gt;0.8534&lt;/code&gt; .&lt;/p&gt;
&lt;p&gt;Another Data Analyst may make an innocent request for data that surprises us.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-variable constant_&quot;&gt;R00002&lt;/span&gt; = &lt;span class=&quot;hljs-title class_&quot;&gt;GenerateReqest&lt;/span&gt;([&lt;span class=&quot;hljs-string&quot;&gt;&#39;state&#39;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&#39;municipality&#39;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&#39;registered&#39;&lt;/span&gt;]);&lt;br&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// {req: [&#39;state&#39;,&#39;municipality&#39;,&#39;registered&#39;], score: 0.006712926793301226}&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Their intent appears to be a longitudinal study of where the organisation&#39;s successes and failures are. Intuitively, a city should be a reasonably anonymous item. Surprisingly — likely due to our small data size — this request shows a significantly lower Privacy Factor (&lt;code class=&quot;markup--code markup--p-code&quot;&gt;0.0067&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This request should be looked at much more closely by the Privacy Team to determine if there is a problem, and if there are any further transforms that can be applied to the dataset that would reduce the risk profile of the supplied dataset.&lt;/p&gt;
&lt;h3&gt;Using the Thresholds in Automated Checks&lt;/h3&gt;
&lt;p&gt;There is no point in having this tool if we can&#39;t use it in our systems to make our lives easier. We have already discussed using it as a tool to assist in evaluation, but it is also possible to apply this as an automated check. By setting a predefined threshold, it is possible to set an automated check on our datasets to ensure that any requests are immediately rejected if they are below a predetermined value.&lt;/p&gt;
&lt;p&gt;While there is no one threshold suitable to all cases, organizations can inspect sample requests to identify a suitable point at which automatic approval, automatic rejection, or calls for further inspection occur. Further, these thresholds can be applied at different points in the approval chain.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creating the Request&lt;/li&gt;
&lt;li&gt;Privacy Approval&lt;/li&gt;
&lt;li&gt;Data generation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When the Data Analyst begins to put their request together, we can offer them an estimate of what the Privacy Factor of their request will be. As an inexpensive calculation, this can be done live, during the request process, through a web, or application, interface. This allows the customer to get early feedback about what they are requesting and offers them the opportunity to plan their justifications, or mitigating transforms that may reduce the risk profile. Assuming the Privacy Threshold is above a predetermined safe threshold, the request can be delivered immediately, with no further review.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/how-to-quantify-privacy-in-datasets/0-4j2pbhdb43wzxnk7-mItNrjSxAE-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/how-to-quantify-privacy-in-datasets/0-4j2pbhdb43wzxnk7-mItNrjSxAE-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;957&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;Upon submission of a request requiring further review, Privacy Evaluators can have the privacy estimate available to them as a holistic estimate of the risks involved. This offers a tool to formulate their thoughts, it gives them a metric that allows them to focus on problem areas. This also has the benefit of reducing evaluator bias where one evaluator may personally have a different risk tolerance than another evaluator. This metric can form the basis for discussion among evaluators, and with the customer, to focus discussion on real (rather than perceived) issues.&lt;/p&gt;
&lt;p&gt;Lastly, we can continue this process of evaluating results even as the customer performs their transforms. It is possible a dataset were to be approved for use, with the understanding that the trusted analyst will perform aggregation that will increase the Privacy Factor of the dataset, but how do we know that they succeeded? If the calculation is performed on a platform we control, we can use these same measures to evaluate the produced dataset. This evaluation can even go further, using a more complete evaluation of the privacy, rather than the estimate we used to get this far.&lt;/p&gt;
&lt;p&gt;If their privacy measures are unsuccessful, and &lt;a href=&quot;https://utrechtuniversity.github.io/dataprivacyhandbook/secure-computation.html&quot; target=&quot;_blank&quot;&gt;we control the platform&lt;/a&gt;, we can issue an error message indicating that the privacy threshold was not met.&lt;/p&gt;
&lt;p&gt;Judging the appropriate threshold for any given system will be contextual. Privacy Analysts will need to evaluate where the cut-off should lie based on the openness of the shares, and on the nature of the data itself. Secure data managed in secure facilities will require fewer checks than data being published openly on the web.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The ability to measure the anonymity of data before any requests for access offers several advantages to us in managing Data Repositories. Claude Shannon&#39;s concept of Information Entropy gives us just such a mechanism, by measuring the amount of entropy in a potential channel (shared dataset).&lt;/p&gt;
&lt;p&gt;Using this estimate, combined with an evaluation of appropriate thresholds, it is possible to partially automate the approval mechanism for data shares. While complete automation is not possible, we are still able to reduce the burden on Privacy experts, as well as use these objective measures to reduce the impact of any personal biases the experts may have.&lt;/p&gt;
&lt;p&gt;We all recognize the importance of maintaining the privacy of people, and secure assets, in shared datasets. These processes and techniques are useful in helping to reduce costs through objective techniques.&lt;/p&gt;
&lt;h3&gt;Going a Little Further&lt;/h3&gt;
&lt;p&gt;While this was an interesting idea to pursue, there are so many more layers that weren&#39;t covered.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;Privacy Weights&lt;/strong&gt;: every field should have a manually set weight associated with it. Immediately, this can be used to include values that look unique but which will be made available with other protections. For example, a Unique ID should be transformed for every request, so the privacy factor is likely known beforehand but is not represented by the actual data. Having some manually set adjustments can compensate for that.&lt;/li&gt;
&lt;li&gt;&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;Anonymity Functions&lt;/strong&gt;: Several very simple and automatically applicable functions could be included in the initial request. Rounding all dollar figures to the nearest thousandth, or including only the first three letters of a name will drastically change the privacy factor.&lt;/li&gt;
&lt;li&gt;&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;The Thing I Forgot&lt;/strong&gt;: I&#39;m sure there are a million little inclusions you could make to a Data Request that would allow for more refined automation and control. Feel free to drop them in the comments.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Further Reading&lt;/h3&gt;
&lt;p&gt;Some general articles that showed up in Google searches while I was writing this looked interesting. I will be reading them later.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://machinelearningmastery.com/what-is-information-entropy/&quot; target=&quot;_blank&quot;&gt;A Gentle Introduction to Information Entropy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://desfontain.es/privacy/differential-privacy-in-practice.html&quot; target=&quot;_blank&quot;&gt;On privacy, research, and privacy research.&lt;/a&gt; A blog discussing various aspects of data privacy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While we discussed measuring entropy to ensure it was sufficient, there are several things you can do to increase entropy when it is not sufficient artificially.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://utrechtuniversity.github.io/dataprivacyhandbook/statistical-privacy.html&quot; target=&quot;_blank&quot;&gt;Statistical approaches to de-identification&lt;/a&gt;, Utrecht University: Discusses various methods for de-identifying data: K-anonymity, L-diversity, T-closeness, and Differential privacy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I really found Utrecht&#39;s book on the matter interesting. As I was looking for references to help make points I kept finding myself referring back to this book.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/how-to-quantify-privacy-in-datasets/1-d17sbmawnyd-ar-g6hggra-KKCClBypya-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/how-to-quantify-privacy-in-datasets/1-d17sbmawnyd-ar-g6hggra-KKCClBypya-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;730&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
</content>
        </entry>
        <entry>
            <title>Maintaining Privacy in Data Shares</title>
            <link href="http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/" />
            <updated>2023-12-18T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/2560/0*XEGAXpP5FHAykNbp.jpg&quot; alt=&quot;Maintaining Privacy in Data Shares&quot;&gt;
                &lt;ul&gt;
&lt;li&gt;Balancing the power of modern computational capabilities with individual privacy is a formidable challenge in data sharing.&lt;/li&gt;
&lt;li&gt;Benevolent actors, in their quest for insights, may inadvertently breach privacy by applying multiple dimensions to data, exposing individuals.&lt;/li&gt;
&lt;li&gt;Introducing a privacy metric to mechanically measure the risk associated with shared datasets, aiding in decision-making for responsible data sharing.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/0-xegaxpp5fhayknbp-nauIKsIFvM-__730__.avif 730w, http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/0-xegaxpp5fhayknbp-nauIKsIFvM-__1400__.avif 1400w, http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/0-xegaxpp5fhayknbp-nauIKsIFvM-__2560__.avif 2560w&quot; sizes=&quot;auto&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/0-xegaxpp5fhayknbp-nauIKsIFvM-__730__.webp&quot; alt=&quot;&quot; width=&quot;2560&quot; height=&quot;1707&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/0-xegaxpp5fhayknbp-nauIKsIFvM-__730__.webp 730w, http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/0-xegaxpp5fhayknbp-nauIKsIFvM-__1400__.webp 1400w, http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/0-xegaxpp5fhayknbp-nauIKsIFvM-__2560__.webp 2560w&quot; sizes=&quot;auto&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;[&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Sand_Grains.jpg&quot; target=&quot;_blank&quot;&gt;Wikicommons&lt;/a&gt; — CCSA]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When we delve into the intricacies of data sharing, one paramount consideration arises — privacy. The very essence of privacy hinges on how informative the shared data is, necessitating a quantifiable approach.&lt;/p&gt;
&lt;p&gt;In today&#39;s landscape of information gathering, the sheer capacity often leaves one in awe. Privacy and data ethics, perennially debated topics, trace their roots back 250 years to the US Constitution&#39;s Fourth Amendment, asserting the right to be secure in personal papers against unreasonable searches and seizures. This is just one significant example of recognition of the perils of exposing personal information to authorities.&lt;/p&gt;
&lt;p&gt;Long suspected, a revelation in 2013 confirmed governmental organizations, such as the NSA&#39;s, information capabilities to the forefront. The revelation that the NSA accumulates every phone call from every individual was not just mind-boggling; it raised significant questions about privacy and data ethics. The sheer scale of data collection raised concerns about the potential misuse and the safeguards in place.&lt;/p&gt;
&lt;p&gt;We&#39;re not talking about datasets reminiscent of the Access Databases of the 1990s for mailing lists; these are colossal databases maintained by governments and mega-corporations. They harbour the potential to craft exact profiles of individuals. The sheer volume of data generated by individuals, when amalgamated, provides a &lt;a href=&quot;https://fingerprintjs.github.io/fingerprintjs/&quot;&gt;unique perspective on the individual&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;ASIDE&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;K68mPpQOmdxPfzQtgikKaw==&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;My unique computer identifier on the internet.&lt;/p&gt;
&lt;p&gt;This value is generated from my browser settings and allows my online activity to be uniquely tracked across multiple websites, and without the assistance of things like cookies.&lt;/p&gt;
&lt;p&gt;What&#39;s your &lt;a href=&quot;https://fingerprintjs.github.io/fingerprintjs/&quot;&gt;fingerprint&lt;/a&gt;?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;How do we balance the power of modern computational capabilities, with the privacy of the individuals whose data we collect?&lt;/p&gt;
&lt;h2&gt;The Road to Hell is Paved with Good Intentions&lt;/h2&gt;
&lt;p&gt;There are many layers to security, but often the first that should be applied is to guard against misuse: legitimate users, using the data for inappropriate purposes.&lt;/p&gt;
&lt;p&gt;In movies, a private investigator will be seen buying information from an informant; super-spies are seen breaking into vaults to steal information about an individual. These are not complete fabrications; over the years, I have been involved in several investigations regarding leaks of personal information. I have been involved in at least two cases of data theft (both as a Nurse and as a Data Professional), in both cases private investigator hired an insider to look up information in the system. In both cases, gathering evidence to trace the activity was as simple as looking up data access logs that did not align with business duties, but did align with the suspected misuse. The mechanics of the system were sufficient to restrict, identify, and enforce access to data.&lt;/p&gt;
&lt;p&gt;Data Warehouses and modern analytics add a wrinkle to this problem.&lt;/p&gt;
&lt;p&gt;Data Warehouses can be a very powerful information resource. Depending on your business line, they will contain all the joined data of all personal information of customers and employees across multiple business lines. For governmental organizations like the NSA, the customer data points are the citizenry and visitors to the country. This is a very powerful tool for analysis, however, it carries with it risks for misuse.&lt;/p&gt;
&lt;p&gt;Mechanically, the nature of the bulk data that is being shared makes it vulnerable to misuse by benign, malicious, and (most importantly) benevolent actors.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keeping malicious actors out is obvious: do security background checks to find honest people, hire honest people, and create guidelines for use that honest people can follow.&lt;/li&gt;
&lt;li&gt;Benign actors are … well … benign. They are the honest people you hire and are happy to follow corporate policies like: “You must not look up yourself, your friends, or your family”.&lt;/li&gt;
&lt;li&gt;Benevolent actors are more complicated.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we have screened for honest people, we have probably biased our search for helpful people. Combine that with large datasets, and the fact that Data Analysts are a curious lot, and we have a recipe for disaster.&lt;/p&gt;
&lt;p&gt;To derive value from our large datasets, we have to perform analysis on large data sets. The benefit is derived from performing aggregate analysis on many detailed values. If an organisation has sensitive data, but an honest analyst who can offer some significant insight by inspecting the data, they will make the data available to the analyst. This generally takes the form of the analyst proposing their study, describing their data needs, and then sending them the data that aligns with their request.&lt;/p&gt;
&lt;p&gt;… and this is where it starts to fall apart …&lt;/p&gt;
&lt;p&gt;A perfectly reasonable request for data may exist within the &lt;a href=&quot;https://www.census.gov/library/stories/2022/03/what-is-the-gender-wage-gap-in-your-state.html&quot;&gt;United States Census Bureau&lt;/a&gt; to create a report that identifies a wage gap between the genders.&lt;/p&gt;
&lt;div name=&quot;ef27&quot; id=&quot;ef27&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p&quot;&gt;&lt;a href=&quot;https://public.tableau.com/views/GenderPayGapMap_16461609104250/RPP_Dashboard?:embed=y&amp;amp;:display_count=n&amp;amp;:origin=viz_share_link&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;&lt;em&gt;Gender Pay Gap Map #CensusDataViz #CensusData&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;Gender Pay Gap Map #CensusDataViz #CensusDatapublic.tableau.com&lt;/a&gt;&lt;a href=&quot;https://public.tableau.com/views/GenderPayGapMap_16461609104250/RPP_Dashboard?:embed=y&amp;amp;:display_count=n&amp;amp;:origin=viz_share_link&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;To support the request, the analyst is given access to a dataset containing all tax filings for the past dozen years. The analyst then loads the data into the analysis tool of their choice, does a simple sum by state and sees their results.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select
  year,
  state,
  sum(case when gender = &#39;M&#39; then income else 0 end)/sum(case when gender = &#39;M&#39; then 1 else 0 end) as avgM,
  sum(case when gender = &#39;W&#39; then income else 0 end)/sum(case when gender = &#39;W&#39; then 1 else 0 end) as avgW
  sum(income)/count(*) as avg
from
  dataset
group by
  state, year
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Their manager approves hitting the publish button, and the whole team heads out for lunch together.&lt;/p&gt;
&lt;p&gt;Over lunch, the original author is discussing their findings with their colleagues, when someone asks a simple question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I wonder if age has anything to do with that?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That&#39;s an interesting question and may be a useful thing to add to future reports. So the analyst goes back to their favourite tool, the data is still in the analysis tool, and all they have to do is adjust the parameters on the query…. and this is where informational security starts to break down. This is not what the data was authorised to be used for.&lt;/p&gt;
&lt;p&gt;While this is a benign example, each dimension of data brings us a little closer to revealing the individual. In their rush to discover meaningful insights, the analyst has applied two dimensions to the individual in question. While this may not be a big deal at the state level, imagine applying filters like this to the town of &lt;a href=&quot;https://en.wikipedia.org/wiki/Albertville,_Saskatchewan&quot;&gt;Albertville&lt;/a&gt; which has a population of 86. Suddenly using age may be unique enough to distinguish some individuals, and publishing their wages to their neighbours could cause some bad blood in town.&lt;/p&gt;
&lt;p&gt;Security (privacy) of customer data is of paramount importance and requires a means of measuring the information about the individual we are sharing. This concept of revealing the individual effectively that of information entropy. Entropy is a measure of the amount of surprise, and in this case, it is the amount of surprise we have when we discover the actual person.&lt;/p&gt;
&lt;p&gt;Aside from purely mechanical safeguards like network access controls, encryption, and permission, we need to consider the ability for shared data to be used inappropriately. The ultimate security tool is to simply not share data we do not want people to have access to, or (more importantly) to control the context in which the data is interpreted. This allows us to maintain a high level of entropy around the individual.&lt;/p&gt;
&lt;p&gt;Every time someone asks for access, we need to evaluate whether we are exposing enough information to expose the individual.&lt;/p&gt;
&lt;p&gt;This requires human thought and analysis, and humans make mistakes. Overworked, over-tired, or pressured by the office bully, a human may give permission to expose more data than is appropriate, allowing researchers to dox an individual accidently. Some mechanism for mechanically, automatically, and unbiasedly measuring the privacy risk of a proposed dataset is necessary because…&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/The_road_to_hell_is_paved_with_good_intentions#Studies&quot;&gt;Hell truly is paved with good intentions&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/0-zuf1hjlj3041ojqr-lD6axdsTkp-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/0-zuf1hjlj3041ojqr-lD6axdsTkp-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;486&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;Hell&#39;s Gate (Darvas Crater) was created when someone threw a match in to stop a gas leak Darvas Crater [&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Darvaza_Gas_Crater_%2842435082502%29.jpg&quot; target=&quot;_blank&quot;&gt;Wikicommons&lt;/a&gt;, CCA]
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;To best understand the problem, let us consider a simplified example&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/1-sbpr2sjgdapyjrkvqnqcjg-J9YMS6iQTj-__657__.avif 657w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/1-sbpr2sjgdapyjrkvqnqcjg-J9YMS6iQTj-__657__.webp&quot; alt=&quot; &quot; width=&quot;657&quot; height=&quot;116&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Studying this dataset we can see it is a basic set of income data for US citizens. It includes some demographic information like their name and gender, as well as some contact information. While it does have a random identifier, it also contains their Social Security Number, something we do not want to just hand out to the first Private Investigator who asks for it.&lt;/p&gt;
&lt;p&gt;Our goal is to give information to customers (analysts) who request it, but also to ensure we do not give so much that we end up exposing the individual. We can define this as&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;the information given to the analyst must not be sufficient to identify a single individual&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The very first step is to remove the account identifiers, we don&#39;t want those being handed out, but the rest of the data is not so clear. What data does the Analyst need to satisfy their research needs?&lt;/p&gt;
&lt;p&gt;Our customer is studying income, so we &lt;em&gt;must&lt;/em&gt; include that, but the concern would be that it gets exposed. While the individual might recognise their income, assuming they&#39;ve kept that private, it should stay private and not be associated with them.&lt;/p&gt;
&lt;p&gt;Let&#39;s start by considering the email address. Email addresses are designed to be unique to the individual if we give the dataset to our customer with nothing but an email address and an income we have effectively tied the income to that person. On the other hand, if we expose the country, it does not tell us anything unique about the individual (everyone in our dataset is from the USA). So if we are going to share data, we want to share data we want to share data with minimal impact.&lt;/p&gt;
&lt;p&gt;Our analyst studying gender inequality (Alice) is doing pretty well, having only asked for Gender and income.&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/1-jt2od94npskkjyrnx4i3ua-xncee-2Pop-__655__.avif 655w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/1-jt2od94npskkjyrnx4i3ua-xncee-2Pop-__655__.webp&quot; alt=&quot; &quot; width=&quot;655&quot; height=&quot;119&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Another analyst, a couple of desks over (Bob), has been working on an algorithm for a while and thinks he has a way to use Last Name as a proxy for race. He would like to do a study using names and incomes. The Privacy analyst, on the ball, notices that names are perfectly unique so offers a list containing only last names.&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/1-9ofbwzp-jwxok3os1un3cw-AFXByTHNM--__656__.avif 656w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/1-9ofbwzp-jwxok3os1un3cw-AFXByTHNM--__656__.webp&quot; alt=&quot; &quot; width=&quot;656&quot; height=&quot;118&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Alice and Bob are discussing their findings over lunch one day when they are overheard by their co-worker Eve. Eve&#39;s ears perk up because she is also a Private investigator and is always looking for exciting datasets and surreptitiously “acquires” the two datasets.&lt;/p&gt;
&lt;p&gt;Eve knows she has acquired something useless in itself: the two datasets have been vetted to ensure that the individuals involved cannot be uniquely identified. However, with closer inspection, she notes that the incomes are unique. Using this insight, she joins the two tables.&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/1-uvjq9ephojrjmbfsgkxy-a-XMGJIqHoho-__659__.avif 659w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/1-uvjq9ephojrjmbfsgkxy-a-XMGJIqHoho-__659__.webp&quot; alt=&quot; &quot; width=&quot;659&quot; height=&quot;114&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Eve has achieved an interesting effect, she has not only joined the two original datasets to get a more complete picture, she has constructed likely salutations that are new data that exceed the scope of either of the original datasets. While it is not a complete picture, she has started to build a profile on individuals. These profiles can be used for purposes that exceed the original permitted use of the data.&lt;/p&gt;
&lt;h2&gt;A Real Risk&lt;/h2&gt;
&lt;p&gt;While this story is obviously made up, this is not an unrealistic story. If one looks at the way we package modern reports we can see an element of these risks being present all around us.&lt;/p&gt;
&lt;p&gt;Modern data presentations demand some level of interactivity. The ability to filter, change, and compare the data on the fly is a very powerful and compelling tool. But there is a risk. To share these visualisations and make them dynamic, we have to ensure that there is sufficient detail embedded in the data we are sharing.&lt;/p&gt;
&lt;p&gt;That data is in the file, and just because you don&#39;t know how to extract it, doesn&#39;t mean nobody does. For the tool to be useful, &lt;a href=&quot;https://stackoverflow.com/questions/48634674/how-to-read-in-tableau-twbx-file-into-python&quot;&gt;some people do know how&lt;/a&gt; … that&#39;s how they make the visualisations work.&lt;/p&gt;
&lt;p&gt;Like Alice and Bob, we act with the best of intentions but easily become Eve sharing data inappropriately if we aren&#39;t careful. We end up sharing data that carries too little entropy and simply hide it behind a mask.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/1-wzpohzmwa6-sbrg3tgi07a-naop1fHeNf-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/1-wzpohzmwa6-sbrg3tgi07a-naop1fHeNf-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;334&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Screenshot of a dynamic visualization with embedded data [&lt;a href=&quot;https://public.tableau.com/app/profile/european.environment.agency/viz/test_20190116Urban_vulnerability_ideasFR_0/mainpage&quot; target=&quot;_blank&quot;&gt;Tableau Public&lt;/a&gt;]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;What Can We Do?&lt;/h2&gt;
&lt;p&gt;For people concerned with individual privacy, this is bad. So the question becomes, as custodians, how can we prevent it?&lt;/p&gt;
&lt;p&gt;While human thought and analysis will always be necessary, it is subject to bias, inconsistency, and mistakes. Automating decision-making, or offering automated decision-making aids to humans is always a good idea. In light of this need, can we develop a metric that can be used to measure the level of privacy in our shared datasets? Can this metric be used to aid in the decision-making process around approvals of data shares?&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Increase the Entropy&lt;/h3&gt;
&lt;p&gt;There are a few normal practices we can take to increase the entropy of the data. In our example, the two innocent datasets were joined through the use of &lt;code&gt;income&lt;/code&gt; which was unique.&lt;/p&gt;
&lt;p&gt;Originally, &lt;code&gt;income&lt;/code&gt; was allowed because it was necessary, but that is what created the vulnerability. We can increase the entropy of the field by simply rounding it to some level.&lt;/p&gt;
&lt;p&gt;Do we need it to be accurate to the dollar? What about the thousandth of a dollar?&lt;/p&gt;
&lt;p&gt;In doing so we increase the number of people that will match the value and protect their anonymity.&lt;/p&gt;
&lt;h3&gt;Measure the Entropy&lt;/h3&gt;
&lt;p&gt;If we can measure the entropy, we should. Rather than leaving it to custodians to use their best judgment, we can offer them a way to objectively measure the state.&lt;/p&gt;
&lt;p&gt;This metric can be made visible to both requesters and approvers to help them decide the appropriateness of the request. We can estimate the entropy of the request before it is even approved allowing us to keep safety at the forefront of our mind. Later we can measure the entropy of the request to ensure it is sufficiently anonymized before releasing it to the public.&lt;/p&gt;
&lt;h2&gt;Create Versatile Environments&lt;/h2&gt;
&lt;p&gt;Don&#39;t dictate, cooperate.&lt;/p&gt;
&lt;p&gt;One of the risks mentioned early in our example was the step of the Analysts holding on to the data. We produce the “safe” dataset and then make it available to the Analyst for loading into their tool of choice.&lt;/p&gt;
&lt;p&gt;This is a common story: we have powerful computers, we have tools we&#39;ve trained on. These tools have power, but they carry the risk of moving data off of the controlled environments.&lt;/p&gt;
&lt;p&gt;By creating powerful and versatile environments we allow the customers to do their analysis in a controlled environment. We should accommodate the needs of experts and create environments that are capable of accommodating those expert&#39;s diverse needs.&lt;/p&gt;
&lt;p&gt;This passively discourages requests to take the data off-system by giving them access to the tools they want.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;C.E. Shannon, &lt;a href=&quot;https://people.math.harvard.edu/~ctm/home/text/others/shannon/entropy/entropy.pdf&quot;&gt;Mathematical Theory of Communication&lt;/a&gt;, 1948. Shannon created the idea of informational entropy and developed a way to measure it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/0-8lalvcncrqyhlxnj-Vu_qKQtnfB-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/maintaining-privacy-in-data-shares/0-8lalvcncrqyhlxnj-Vu_qKQtnfB-__730__.webp&quot; alt=&quot; &quot; width=&quot;730&quot; height=&quot;955&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
</content>
        </entry>
        <entry>
            <title>Dangers of the Habit of Ignoring Developer Error Messages</title>
            <link href="http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/" />
            <updated>2023-07-11T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/0*2bEU3crGdd_PvpHc.jpeg&quot; alt=&quot;Dangers of the Habit of Ignoring Developer Error Messages&quot;&gt;
                &lt;p&gt;I am going to give multiple examples from my career, demonstrating the dangers of error message fatigue and habituation leading to ignoring vital signals. Further, they will show how easy it is for humans to fall prey to habituation. Finally, I will conclude with specific techniques and modern tools that can be used to reduce the frequency of its occurrence.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/1-7p6ybggbs4cctmp14nmhrq-I3q3QVi7vW-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/1-7p6ybggbs4cctmp14nmhrq-I3q3QVi7vW-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;323&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;44 thousand messages. I wonder if one is relevant (Original Content)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;While I don&#39;t remember the exact defect, what I do remember was following the defect into the Web Server&#39;s logs (Windows Event Viewer). Previously, my focus had been on the system&#39;s external user behaviour, but here in the logs were thousands upon thousands of lines of warnings … every minute. Warnings about uninitialised variables, unsafe typecasts, and … well a little bit of everything. I had never really given much thought to it because it was so overwhelming as to be meaningless, but when I managed to isolate an interaction with the form in question and filter the logs down to just that time frame, I was able to &lt;em&gt;consistently see one warning that seemed to relate.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&#39;d found my needle in the haystack, but it wasn&#39;t an error it was just a warning amounts thousands of warnings I had been ignoring. As I dug through the logs, I could see this message appeared regularly when that form was accessed (not always but regularly) and it went back to the very founding of the system (before I had even started my programming education, I was still a nurse). This error had been getting reported for almost a decade, and nobody had seen it for two reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it was a warning, not an error&lt;/li&gt;
&lt;li&gt;the signal had been lost in a sea of noise&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I learned a valuable lesson that day. “No Warnings, No Errors” became my mantra.&lt;/p&gt;
&lt;p&gt;Naturally, I first fixed the issue that had been spotted, but this defect had been signalled by a warning in the system logs that if someone had addressed we would have fixed it almost a decade before. So I started to address &lt;strong&gt;all&lt;/strong&gt; the warnings in the logs.&lt;/p&gt;
&lt;p&gt;Most were relatively benign, identifying (perhaps) that a variable had not been explicitly initialised before use, but since null was treated as a zero or empty string, it didn&#39;t impact the behaviour of the system. But as I made minor corrections, and the log volume got reduced, some of the warnings started to take on more ominous tones. More system defects were identified (and corrected), and most significantly to me, an actual error started to present regularly … One that had been missed in the excessive volume in the logs.&lt;/p&gt;
&lt;p&gt;So what was the core of the lesson?&lt;/p&gt;
&lt;p&gt;Seeing a large volume of errors can make us insensitive to them. When we ignore significant messages, we train ourselves to not pay attention, and that&#39;s when bad things happen.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-beb0shiev3y-lga-HBrcTP46-F-__450__.avif 450w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-beb0shiev3y-lga-HBrcTP46-F-__450__.webp&quot; alt=&quot;&quot; width=&quot;450&quot; height=&quot;600&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;(Wikimedia, &lt;a href=&quot;https://en.wikipedia.org/wiki/File:Romanian_hay.jpg&quot; target=&quot;_blank&quot;&gt;CC&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The term “habituation” is used in several related contexts in the medical, social, and psychological contexts, but the general summarizing description would be the loss of recognition of negative stimuli due to repeated (habitual) exposure to the stimuli [&lt;a href=&quot;https://en.wikipedia.org/wiki/Habituation&quot; target=&quot;_blank&quot;&gt;Wikipedia&lt;/a&gt;].&lt;/p&gt;
&lt;p&gt;We see this all around us and in our day-to-day lives. People get habituated to getting yelled at by a peer, becoming numb to the exposure. Physically, a carpenter may become desensitized to getting slivers, simply pulling them out at the end of the day rather than immediately flinching. I like a hot shower, but it usually takes a moment for my skin to get used to the hot water. It has even been documented in plants; the Mimosa Pudica is known to “flinch” when touched but will &lt;a href=&quot;https://en.wikipedia.org/wiki/Habituation#Biological_mechanisms&quot;&gt;stop flinching with repeated touching&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
&lt;iframe src=&quot;https://www.youtube.com/embed/XTi6eDeKkQI?feature=oembed&quot; width=&quot;700&quot; height=&quot;393&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
&lt;/figure&gt;
&lt;p&gt;This helps us get on with life.&lt;/p&gt;
&lt;p&gt;Flinching is an important reflexive reaction to protect us from bad things happening. Stubbing your toe &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;should&lt;/strong&gt; produce an immediate “protect your toe” response, getting an unexpected cut on your hand is &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;dangerous&lt;/strong&gt;, and I &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;should&lt;/strong&gt; jerk my hand back from scalding water; but sometimes the cut is minor and expected (slivers) and for the most part just part of the job … life has to go on. A hot shower is a big temperature change, but it isn&#39;t harmful and is rather pleasant, once I get used to it. The process of habituation allows us to maintain our high-alert state, while at the same time learning to moderate it under various conditions.&lt;/p&gt;
&lt;p&gt;Humans are biologically queued to become habituated. It is part of our survival strategy as a species. It&#39;s built into you.&lt;/p&gt;
&lt;p&gt;Therefore you cannot ignore the risk habituation poses to our systems.&lt;/p&gt;
&lt;p&gt;Receiving an error signal (error messages, warnings, failing tests) regularly, evaluating it as being “safe to ignore”, and not taking action, psychologically prepares you to ignore the signal at a later date. It begins to habituate you to the error signal, perhaps placing it on a pile of things we can ignore.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-bt-8xibkkzukzhph-6xQRl3i3Ew-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-bt-8xibkkzukzhph-6xQRl3i3Ew-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;547&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Ignore at your peril (Wikimedia, &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Ignore_this_notice_at_your_peril_-_geograph.org.uk_-_2096912.jpg&quot; target=&quot;_blank&quot;&gt;CC&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Common Examples of Programmer Error Habituation&lt;/h3&gt;
&lt;p&gt;At every organisation I have ever worked at, in every role I have filled, I have found examples of error habituation. They do not always present the same way, but they are pervasive throughout the industry, even presenting themselves as “Best Practices” to the untrained eye.&lt;/p&gt;
&lt;h4&gt;Errors and Warnings&lt;/h4&gt;
&lt;p&gt;This is the most obvious since it&#39;s right in the name, but that makes it a good place to start.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Compiler warnings
&lt;/li&gt;
&lt;li&gt;
System Log Warnings
&lt;/li&gt;
&lt;li&gt;
Pager notifications
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was my first exposure to this. We learn through practice at school that compiler errors prevent us from submitting our assignments, but warnings do not. With the short intensity of student life, ignoring warnings becomes a habitual survival strategy. As we become mature professionals, we learn that each of these messages was put in place to convey meaning to us, and offer us protection.&lt;/p&gt;
&lt;h4&gt;Known Software Defects&lt;/h4&gt;
&lt;p&gt;Defects in software are discovered, and discovering and correcting them is the art form. In the words of Robert Glass:&lt;/p&gt;
&lt;blockquote&gt;
43. Maintenance is a solution, not a problem
&lt;/blockquote&gt;
&lt;blockquote&gt;
— &lt;a href=&quot;https://amzn.to/402Vfs4&quot; target=&quot;_blank&quot;&gt;Facts and Fallacies of Software Engineering&lt;/a&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is important to realise that defects are to be expected and must be worked on. At the same time, there is always more work than time so some form of prioritisation is necessary. This means we must ignore them for a while (even if it is just the time it takes to fix them).&lt;/p&gt;
&lt;p&gt;The problem is that the more defects we acknowledge are present, the more we tend to ignore them as irrelevant. The more we defer fixing bugs, the more we get into the habit of deferring bug fixes.&lt;/p&gt;
&lt;h4&gt;TODO and Change Comments&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;TODO&lt;/code&gt; comments within code were a way to identify an item that needs to be addressed; sort of a “come back to this later”.&lt;/p&gt;
&lt;p&gt;There is a strong likelihood that we are ignoring the problem because we are busy with something else. Certainly, it is not possible to split ourselves into two to address both problems simultaneously, so making a note of the secondary problem while we address the primary one just makes sense.&lt;/p&gt;
&lt;p&gt;The problem arises when we don&#39;t come back to it.&lt;/p&gt;
&lt;p&gt;Accumulating &lt;code&gt;TODO&lt;/code&gt; notes through code can become excessive noise to the point we start to ignore the message. Further, as these are usually listed along with the warnings and errors, they represent noise that drowns out more important signals.&lt;/p&gt;
&lt;p&gt;Do not become habituated to seeing useless comments.&lt;/p&gt;
&lt;p&gt;A header in an individual file containing a list of every change ever made to the file is a common pattern that has become an anti-pattern. The purpose of these comments is to offer a log of changes that have been made to the code in their contextual place.&lt;/p&gt;
&lt;p&gt;Unfortunately, this (good) habit was started decades ago with coding styles that were different. The pattern is based on the assumption that a single file is self-contained to all its changes, and that it does not interact with other entities (since &lt;a href=&quot;https://en.wikipedia.org/wiki/Atomic_commit#Revision_control&quot; target=&quot;_blank&quot;&gt;recognised as a faulty assumption&lt;/a&gt;). There is also the problem that decades of messages accumulating at the start of the file means there is an impenetrable &lt;code&gt;&amp;lt;a href=&amp;quot;https://jefferey-cave.medium.com/the-complexity-of-a-simple-chart-82ac4254e76d&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;WALL-OF-TEXT&amp;lt;/a&amp;gt;&lt;/code&gt; that must be scrolled past before anything meaningful can begin. This immediate “scroll past” habituates us to perceive large blocks of comments as meaningless, when in fact we should consider large explanations in the code something that is important and meaningful.&lt;/p&gt;
&lt;p&gt;What started as a good idea on small files, over short time-frames, has evolved into a bad idea with better alternatives.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-ilukzh9w-kqgrreu-_ixeak5hRC-__640__.avif 640w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-ilukzh9w-kqgrreu-_ixeak5hRC-__640__.webp&quot; alt=&quot;&quot; width=&quot;640&quot; height=&quot;451&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Mimosa Pudica (&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:EB1911_-_Leguminosae_-_Fig._3.%E2%80%94Branch_with_two_leaves_of_the_Sensitive_Plant_%28Mimosa_pudica%29.jpg&quot; target=&quot;_blank&quot;&gt;Wikimedia, Public Domain&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Relearning the Lesson (Twice)&lt;/h3&gt;
&lt;p&gt;A decade later I found myself on contract with a major corporation that had terminated their previous contracting company due to poor quality performance. My team had been hired to not only deliver, but to do it with an eye to quality.&lt;/p&gt;
&lt;p&gt;On my first day reading opening the regression test suite, I naturally glanced at the warning list to see how many warnings were listed in the code. I immediately found myself staring at a list of hundreds of warnings, but also thousands of &lt;code&gt;TODO&lt;/code&gt; messages. Naturally, I tried to ignore them … they were things that needed to be done in the future, not immediately … but as I cleared the significant backlog of “warnings” I started to come across the actual locations that the &lt;code&gt;TODO&lt;/code&gt;s were in.&lt;/p&gt;
&lt;p&gt;It was horrifying.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;@Test&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;ReallyImportantThing&lt;/span&gt;&lt;span class=&quot;hljs-params&quot;&gt;()&lt;/span&gt; {&lt;br&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;//&lt;span class=&quot;hljs-doctag&quot;&gt;TODO:&lt;/span&gt; implement this&lt;/span&gt;&lt;br&gt;  Assert.assertTrue(&lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;);&lt;br&gt;} &lt;/span&gt;&lt;/p&gt;&lt;p name=&quot;9d78&quot; id=&quot;9d78&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;In many (most) of the cases, the note suggested a person should implement the test for real. (There is a similar story, I thought told by Spolsky, of a notorious function implementation in MS Office … the same thing)&lt;/p&gt;
&lt;p&gt;In my case, I suspect the previous team, under pressure to perform and deliver, had been masking gaps for a long time. Many of the regression tests were simple stubs that returned a success no matter what. This allowed them to claim the job was done, while promising themselves they would fix it … later … when they had time. That time never came.&lt;/p&gt;
&lt;p&gt;It was a hard conversation with the client to explain I was taking a week to re-evaluate how much testing was actually being performed. When I reduced their test count by more than half, I needed to remind them they had hired us specifically because they knew there had been a problem and identifying those problems and giving honest assessments is where our value came from.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TODO&lt;/code&gt;s got added to my list of things that were not permitted in code bases I was involved in.&lt;/p&gt;
&lt;blockquote&gt;
No Warnings, No Errors
&lt;/blockquote&gt;
&lt;p&gt;Another decade has passed, and recently (weeks), I had to catch myself again.&lt;/p&gt;
&lt;p&gt;On a new system we are working on, I implemented a basic continuous monitoring alert system. It periodically scans the system for invalid states and immediately notifies the team of the bad state (OK, it notifies me and one other, and we notify the larger team … baby steps). The idea is that if an alert is issued we must act to save the system.&lt;/p&gt;
&lt;p&gt;&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;I ignored a message.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this case, the alert was to notify us that we had stopped receiving signals from a remote source, and I had ignored it. As a batch process, it is not uncommon for the source process to take longer than anticipated, this isn&#39;t a big deal since usually, it delivers shortly after we check and we just pick it up on the next pass.&lt;/p&gt;
&lt;p&gt;Except it is a big deal because I ignored it.&lt;/p&gt;
&lt;p&gt;My colleague, just returning from vacation, called me and asked if I had noticed that the system was erroring, she didn&#39;t see a ticket and wondered if I was dealing with it. I told her it was “no big deal, that one fails regularly” … and as the words came out of my mouth I heard what I had just said.&lt;/p&gt;
&lt;p&gt;Sure enough, we looked closer, and the failure had occurred for three cycles; the source was not transmitting data and I, through habituation, had ignored the failure.&lt;/p&gt;
&lt;p&gt;I hope I remember to come back and share my error habituation story in 2033.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-xdaydr9-vyyd1jfh-S7SHCcfWy9-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-xdaydr9-vyyd1jfh-S7SHCcfWy9-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;486&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;See you in 2033 (&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:2011-365-127_Even_When_Unevenly_Distributed_The_Future_May_-_%285702592254%29.jpg&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;, CC-0)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Preventing Habituation&lt;/h3&gt;
&lt;p&gt;There is really only one solution to preventing error habituation: address every error or warning or notice and treat it immediately, and with the highest priority.&lt;/p&gt;
&lt;p&gt;While I say this there are some subtleties to how we achieve this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Always fix defects before implementing new features&lt;/li&gt;
&lt;li&gt;Never ignore a defect message&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are various tools at our disposal to help us address this and various mentalities.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-31nmw8deq5vrcj4c-2DOhs02bSv-__622__.avif 622w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-31nmw8deq5vrcj4c-2DOhs02bSv-__622__.webp&quot; alt=&quot;&quot; width=&quot;622&quot; height=&quot;387&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;(Wikimedia, &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Vieux_panneau_danger_de_mort.jpg&quot; target=&quot;_blank&quot;&gt;Public Domain&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4&gt;Errors that can be ignored&lt;/h4&gt;
&lt;p&gt;There is no such thing.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the system is in an invalid state and needs to be fixed immediately, or&lt;/li&gt;
&lt;li&gt;the error notification system is flawed and needs to be fixed immediately&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Incorrect notifications could be a log monitor that puts an alert out when an invalid state is encountered. Upon inspection, the state is determined to be “undesirable” (not “invalid”) … we&#39;ll ignore the error, it will correct itself later.&lt;/p&gt;
&lt;p&gt;NO! Change the log monitor to take into account the new information. Maybe it needs to be run less frequently, maybe it needs to count how long the error state exists (waiting before alerting), but whatever gave you a reason to think it can be ignored, needs to be incorporated into the official rules for alerting.&lt;/p&gt;
&lt;h4&gt;Failing Tests&lt;/h4&gt;
&lt;p&gt;As previously mentioned, you can&#39;t be in two places working on two problems at the same time. One problem must be set aside while you focus on the other. Unfortunately, this leads to ignoring errors which becomes habitual.&lt;/p&gt;
&lt;p&gt;To avoid this, the first step is to immediately create a task in your backlog, this immediately gives us a record that the issue exists. Secondly, we should immediately generate an automated test that can give us a way to reproduce the error. The problem here is that the test will be in a failing state, constantly reporting an error to us. This is a failure signal that we want to ignore (probably using a &lt;code&gt;SKIP&lt;/code&gt;) until we get the defect fixed, we immediately mark it as skip, an ignore status.&lt;/p&gt;
&lt;p&gt;This is a problem.&lt;/p&gt;
&lt;p&gt;We can resolve this by having a team rule that all &lt;code&gt;SKIP&lt;/code&gt; tests &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc2119.html&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;MUST&lt;/strong&gt;&lt;/a&gt; have a ticket number associated with them, and also addressing &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;every&lt;/strong&gt; skip during &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;every&lt;/strong&gt; planning meeting. For me, this often takes the form of reporting skips without ticket numbers as fails, and fails must be addressed immediately. Skips with a ticket link directly to their ticket in their reporting.&lt;/p&gt;
&lt;p&gt;Lastly, there &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc2119.html&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;MAY&lt;/strong&gt;&lt;/a&gt; be a &lt;a href=&quot;https://www.cmc-consultants.com/blog/zero-defect-mentality-implementations-and-history&quot; target=&quot;_blank&quot;&gt;zero defects policy&lt;/a&gt; within the team. This is an agreement with the business that defects will be fixed before &lt;a href=&quot;https://www.agiledevelopment.org/agile-talk/134-zero-defects-in-software-development&quot; target=&quot;_blank&quot;&gt;new features are implemented&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🗒 NOTE&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have never been satisfied with the reports generated by testing systems, and have always written custom visualisations to track defects. This has had the side effect of my introducing concepts like adding extra states to TestNG&#39;s default reporting (&lt;code&gt;known&lt;/code&gt;, &lt;code&gt;manual&lt;/code&gt;, &lt;code&gt;feature&lt;/code&gt;), with active links to the repository and issue tracking software. I should really write an article showing my collection of testing reports … or show how to use various Project Management software reports (ADO, GitLab, GitHub).&lt;/p&gt;
&lt;p&gt;A note to my boss … don&#39;t worry, these always get worked on in my own time ;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Comments Calling for Action&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;TODO&lt;/code&gt; comments were a classic way to express something you need to come back to and finish something off, and they still have their place, but fundamentally, they are a call to ignore the problem (but only “for now”).&lt;/p&gt;
&lt;p&gt;The problem here is the same as with all the others, we need a way to prevent “for now” from becoming “forever”.&lt;/p&gt;
&lt;p&gt;One simple way of handling this is to put a Version Control hook in your repository that prevents check-ins of &lt;code&gt;TODO&lt;/code&gt; comments. Generally, you should only put this on protected branches. This allows you to put them in your code to allow you to continue working but prevents you from submitting it to the official branch by accident. Forcing you to finish the job you planned on doing. If you can&#39;t get to a &lt;code&gt;TODO&lt;/code&gt; for some reason, don&#39;t leave it in the code, register it in the backlog as something that still needs doing. This leaves the alert list available for things like warnings and errors so they don&#39;t get hidden.&lt;/p&gt;
&lt;p&gt;Those massive headers at the beginning of the code only work to mask issues. They get in the way of text searches and require a lot of visual space to scroll past. All that is for something that is based on an old paradigm: changes are constrained to a single file.&lt;/p&gt;
&lt;p&gt;Modern VCS tools assume that a change may require the context of several locations in code to be meaningful, and have logging built into them. Keep your changes in the Change System database, reducing the visual noise by placing the changes in a contextual list that is hidden until you need it. When you need it, the list is optimally indexed for what it is.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-qgytll-nogscmalj-lcinhEK1bd-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-qgytll-nogscmalj-lcinhEK1bd-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;547&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;(Wikimedia, &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Danger_sign.jpg&quot; target=&quot;_blank&quot;&gt;Public Domain&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;As humans we make mistakes. Each decision we make is a totally new decision that we must make injecting the opportunity for error, this opportunity for error can be compounded by biases introduced from our experience. Habituation of errors represents a biasing of our behaviour that we are biologically predisposed toward, and can be dangerous to our work.&lt;/p&gt;
&lt;p&gt;It is important, as professionals, that we work to overcome these dangerous biases through constant diligence and self-appraisal.&lt;/p&gt;
&lt;p&gt;As software developers, our work captures decision-making in advance of the stimulus and action, and defects can have catastrophic effects. Teaching ourselves to ignore benign errors can mask more catastrophic issues that have significant effects on people&#39;s lives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Aeroplanes &lt;a href=&quot;https://www.businessinsider.com/boeing-outsourced-737-max-report-2019-6&quot; target=&quot;_blank&quot;&gt;fall out of the sky&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
Small business owners get &lt;a href=&quot;https://www.bbc.com/news/business-57173296&quot; target=&quot;_blank&quot;&gt;falsely accused and imprisoned&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
Retail organisations are physically &lt;a href=&quot;https://jefferey-cave.medium.com/why-wont-staples-take-my-money-2e1d5ae8e135&quot; target=&quot;_blank&quot;&gt;unable to sell their products&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Further Reading&lt;/h4&gt;
&lt;p&gt;I hope I&#39;ve made the case that it is easy to teach ourselves to ignore errors because, at the end of the day, we are humans and humans are fallible. Addressing this is hard, but not new&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://amzn.to/3yuk4RU&quot; target=&quot;_blank&quot;&gt;Facts and Fallacies of Software Engineering&lt;/a&gt; (Robert L. Glass) is a great read that opened my eyes to how common these issues are and how we all want things to be true, even when they aren&#39;t
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.netflix.com/title/81272421&quot; target=&quot;_blank&quot;&gt;Downfall: The Case against Boeing&lt;/a&gt; (Netflix) discusses an important event in computing history. Remember that in 1969 Software saved an aircraft with a bad attitude sensor while in 2018 Software killed 318 people due to a bad attitude sensor.
&lt;/li&gt;
&lt;li&gt;
Any video on &lt;a href=&quot;https://www.youtube.com/@MaydayAirDisaster&quot; target=&quot;_blank&quot;&gt;Aircraft crash investigations&lt;/a&gt; (my wife is a fan). Observe how it requires multiple people to ignore warning signs for a long period of time for a problem to occur. Note how easy it is for dangerous behaviour to become habituated.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Consider reading the manuals of your favourite tool suite to get a better understanding of why the software was developed, how it is meant to help you, and how it can replace some practices you may have thought were a good idea&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://docs.nunit.org/articles/nunit/intro.html&quot; target=&quot;_blank&quot;&gt;Test Suites&lt;/a&gt; can help you identify errors methodically
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://docs.gitlab.com/ee/user/project/issues/&quot; target=&quot;_blank&quot;&gt;Project Management&lt;/a&gt; tools can help prioritise and track outstanding issues
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://git-scm.com/book/en/v2&quot; target=&quot;_blank&quot;&gt;Version Control Systems&lt;/a&gt; can help to understand why historic changes were made, offering a significant amount of context when you need it. I actually recommend reading &lt;a href=&quot;https://svnbook.red-bean.com/en/1.7/svn.basic.version-control-basics.html#svn.basic.vsn-models&quot; target=&quot;_blank&quot;&gt;SVN&#39;s manual&lt;/a&gt; as it brought a significant change in paradigm, at the time it was introduced, that needed to be explained (Use Git, read about SVN)
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;… and always pay attention to your own emotions and biases… your own mistakes are always available for you to learn from.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-3rb9n7o2nft5kq2v-AaSQC587E8-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/Dangers-of-the-Habit-of-Ignoring-Developer-Error-Messages/0-3rb9n7o2nft5kq2v-AaSQC587E8-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;435&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;(&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Pavlov_experiments_with_dog_Wellcome_M0014738.jpg&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;, &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Pavlov_experiments_with_dog_Wellcome_M0014738.jpg&quot; target=&quot;_blank&quot;&gt;CC-BY&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
</content>
        </entry>
        <entry>
            <title>The Complexity of a Simple Chart</title>
            <link href="http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/" />
            <updated>2023-02-13T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/0*MhSArI3gPHeLeDAW.png&quot; alt=&quot;The Complexity of a Simple Chart&quot;&gt;
                &lt;p&gt;I was recently involved in an effort to create an internal web presence
for the internal service my team is working on. We want to take the time
to advertise to our colleagues what work we do, what specific services
we offer, and how they can take advantage of our services.&lt;/p&gt;
&lt;p&gt;Most people see a lot of activity as a lot of work. Most people don&#39;t
see what goes into keeping a design simple. I wanted to share all the
work that goes into keeping it simple and informative.&lt;/p&gt;
&lt;p&gt;For just a moment, I wanted to share just how deep the rabbit holes can
go.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 TIP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Throughout you will see screenshots of the chart as it evolves, they
are all links to a JSFiddle that shows the underlying code changes&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://jsfiddle.net/JeffereyCave/c4bLdox3/243/&quot;&gt;https://jsfiddle.net/JeffereyCave/c4bLdox3/243/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;In the introductory ITIL Foundations class taught to many organisations,
one of the major things that is repetitively highlighted is the need for
clear, transparent, communications with customers. In particular, the
need for an &amp;quot;&lt;a href=&quot;https://www.isitdownrightnow.com/&quot;&gt;is it up&lt;/a&gt;&amp;quot; dashboard
for your services always stood out for me.&lt;/p&gt;
&lt;p&gt;The reasoning and benefits seem obvious to me:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reduce labour by preemptively notifying customers:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The most important thing a person can be doing in the event of a service
failure is repairing the service. Having customers, or managers, continuously
asking if the service is available reduces the time spent understanding
the problem. By notifying customers from a known, predetermined board,
we are able to have them self-serve their questions, leaving technical
experts to focus on solving the problem; by auto-generating it, we
reduce the burden even further.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Don&#39;t disrupt people that don&#39;t care:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Signal-to-noise is a real problem in organisations. Rather than actively
notifying, a pre-published board allows those with a concern or interest
to look up the information. Still, for those not actively using the
service (perhaps working with a non-impacted portion of the system,
perhaps not in the office that day) there is no need to interrupt them,
allowing &lt;em&gt;those&lt;/em&gt; experts to focus on the problems &lt;em&gt;they&lt;/em&gt; are solving
while remaining blissfully unaware of other issues. This has the added
benefit of not advertising your failures to people that weren&#39;t impacted.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Advertise your successful services:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A standing board of “status” not only shows failures but also a complete
list of successes. Most of a board, most of the time, will show a healthy
system; even when a portion of the system is failing, it will show
that this failure is only impacting a small proportion of what is otherwise
a successful service. Letting customers see that while it&#39;s bad, the
service is generally reliable can help with this. This has the added
benefit of letting customers know about other parts of your service.
While your system is healthy, this is a complete menu of all the services
you offer; either to be indexed by a search engine, to be pointed to
during meetings, or to be naturally discovered by customers.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There is obvious value in having an online, automated, report of some
kind; though, naturally, we see it taking different forms depending on
the nature of the services involved. Power companies show outage maps,
online services show “up time”, and data feeds show tables of API endpoints.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-enxcpje7utkzuwbf-p5Yol5Mceg-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-enxcpje7utkzuwbf-p5Yol5Mceg-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;566&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;An example of a common status board used by any number of companies around the world&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;Initially, my team started by bringing forward various reports that individuals
had already constructed for observing their individual aspects of the
system, and proposing them as something worth sharing. These got presented,
one after another, describing the benefits of each, and evaluating their
use by our users. After myself and a couple of others had sown our most
valuable reports, our manager asked, the question &amp;quot;How do people know
its fit for use?&amp;quot;&lt;/p&gt;
&lt;p&gt;What is &amp;quot;fit for use&amp;quot; in the context of our system? More importantly,
how was the data that we had presented, &lt;em&gt;that I had presented&lt;/em&gt;, not expressing
that to her?&lt;/p&gt;
&lt;p&gt;This question nagged at me through the rest of the day.&lt;/p&gt;
&lt;p&gt;That evening, I opened &lt;a href=&quot;https://kate-editor.org/en-gb/&quot;&gt;Kate&lt;/a&gt; and started
typing.&lt;/p&gt;
&lt;h2&gt;Aside: Defining the Problem&lt;/h2&gt;
&lt;p&gt;For a working example of the type of system we are dealing with, we need to start by defining the type of service we are offering.&lt;/p&gt;
&lt;p&gt;We have a diverse variety of datasets that we ingest into a central repository. This data is standardised and then published back to our customers for use in their research. To give us a working example, let&#39;s take the flow of a couple of &lt;a href=&quot;https://gitlab.com/securities-tools/SecAnalysis&quot;&gt;hobby projects&lt;/a&gt; &lt;a href=&quot;https://gitlab.com/jefferey-cave/gc-gscs&quot;&gt;of mine&lt;/a&gt;: imagine something like one of the financial websites that offer data about stock prices. This data needs to be collected from government filings (eg. &lt;a href=&quot;https://www.sec.gov/edgar&quot;&gt;SEC&lt;/a&gt;, &lt;a href=&quot;https://www.sedar.com/homepage_en.htm&quot;&gt;SEDAR&lt;/a&gt;) and also trade data from the various exchanges (eg. &lt;a href=&quot;https://www.nyse.com/index&quot;&gt;NYSE&lt;/a&gt;, &lt;a href=&quot;https://www.nasdaq.com/market-activity/stocks&quot;&gt;NASDAQ&lt;/a&gt;, &lt;a href=&quot;https://tsx.com/&quot;&gt;TMX&lt;/a&gt;)&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-ntdng8s5mmvwt-3j-ud0NphvjYg-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-ntdng8s5mmvwt-3j-ud0NphvjYg-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;299&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;The general flow of the data from the primary source through to our consumer, whomever, or whatever, that may be&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Data is ingested from the various sources I can find, standardised in their format, and then various views of that data are used for various analyses by users (human, reports, or AI).&lt;/p&gt;
&lt;h2&gt;Overwhelming the Viewer&lt;/h2&gt;
&lt;p&gt;Many years ago, I became enamoured with the idea of Test Driven Development. On the projects I led, I found it was an excellent way to define business requirements, and then communicate those requirements to a diverse group of individuals. Varying interpretations simply led to discussion and a very clearly defined expectation (updated test), which was then immediately distributed to the group (shared unit tests).&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-oxri7q5eoixhvkaw-9VnXRth0M_-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-oxri7q5eoixhvkaw-9VnXRth0M_-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;397&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;NUnit Gui is a handy and readily available interface for reporting business state to non-technical users.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Coming from this background, I tend to immediately develop a set of tests, in &lt;a href=&quot;https://mochajs.org/#markdown&quot; target=&quot;_blank&quot;&gt;whatever test framework is available&lt;/a&gt;, for observing any system I am involved with, whether that&#39;s from a development, operational, or DevOps, perspective.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/1-ib4y7o-63y8jipzvtzura-DLoXF3eLml-__551__.avif 551w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/1-ib4y7o-63y8jipzvtzura-DLoXF3eLml-__551__.webp&quot; alt=&quot;&quot; width=&quot;551&quot; height=&quot;429&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;A wireframe of the basic test report that I live my life by (&lt;a href=&quot;https://jsfiddle.net/JeffereyCave/c4bLdox3/&quot; target=&quot;_blank&quot;&gt;JSFiddle&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;For 4 datasets, we run 3 checks, resulting in 12 rows. That&#39;s a lot of information being thrown at the user; the poor person is going to be faced with significant cognitive load, or as I like to call it: “&lt;code&gt;THE WALL OF TEXT&lt;/code&gt;”.&lt;/p&gt;
&lt;p&gt;The text used, while descriptive to me (monitoring the system) requires more context to be useful to someone. The numbers presented are very busy, of varying scales and unformatted, making them meaningless without thought. Placing the failures at the top was a good idea, but still requires a lot of thinking to determine what has been affected and whether we care (as a user).&lt;/p&gt;
&lt;p&gt;We can either&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;engage in a &lt;a href=&quot;https://jeffereycave.substack.com/p/education-training-and-indoctrination-8b54228d5d07&quot;&gt;training program&lt;/a&gt; (failure of intuitive design),&lt;/li&gt;
&lt;li&gt;add more text (making &lt;code&gt;THE WALL OF TEXT&lt;/code&gt; problem worse),&lt;/li&gt;
&lt;li&gt;or maybe we now begin to understand why a simpler solution is needed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We did do a couple of things correctly, failures are at the top putting “interesting” results more prominently, there is an overall summary in the title, and the effective date is important. Unfortunately, there is a lot that needs work.&lt;/p&gt;
&lt;h2&gt;Starting Over&lt;/h2&gt;
&lt;p&gt;So this is where me and my text editor start over.&lt;/p&gt;
&lt;p&gt;We are looking to create a chart that expresses whether “a dataset is fit for use”.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;dataset (thing)&lt;/li&gt;
&lt;li&gt;fit for use (state, boolean)&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/1-hlchzbojk3pnxbjuiqchsw-d9Zd1ZhWqJ-__282__.avif 282w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/1-hlchzbojk3pnxbjuiqchsw-d9Zd1ZhWqJ-__282__.webp&quot; alt=&quot;&quot; width=&quot;282&quot; height=&quot;574&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;Stripping it down to just the barest bones (&lt;a href=&quot;https://jsfiddle.net/JeffereyCave/c4bLdox3/70/&quot; target=&quot;_blank&quot;&gt;JSFiddle&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Users are only confronted with 4 elements (1 per dataset) and the amount of text to be interpreted. The text could use a little work, but a user will likely know what datasets concern them: users only interested in US stocks will know US market abbreviations and therefore that they don&#39;t care about the ones they don&#39;t know. It could probably be presented more aesthetically, but this is probably the minimum meaningful set.&lt;/p&gt;
&lt;h2&gt;Order and Limit&lt;/h2&gt;
&lt;p&gt;Given hundreds of datasets, it may not be feasible to show all of the items on a single pane. The chart should maintain visibility of the top 5 items with the option to scroll for more. It is estimated that a worst-case scenario of simultaneous expires is approximately 3 items so 5 allows for the human to see a naturally occurring comfortable group size, with some successful items at the end offering the knowledge that the end of the error list has been reached.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 TIP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Keep lists between 3 and 5 items.&lt;/p&gt;
&lt;p&gt;While we are at it, instructions (lines of code?) should be between a half a dozen and dozen items. If you are metric, use fist-fulls (5 to 10 fingers).&lt;/p&gt;
&lt;p&gt;Based on experience, and a few introductory Sociology and Psychology classes, this appears to be a natural human thinking scale.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;States&lt;/h2&gt;
&lt;p&gt;There are different reasons a dataset could be &amp;quot;not fit for use&amp;quot;, and some of those reasons a user may or may not care about&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Bad structure&lt;/li&gt;
&lt;li&gt;Incomplete set&lt;/li&gt;
&lt;li&gt;Invalid content&lt;/li&gt;
&lt;li&gt;Stale&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A little further analysis offers a tip: if we can detect an error, don&#39;t give it to the users. Therefore we don&#39;t actually need to report bad structure since the bad structure will never end up in front of the user. Instead, we won&#39;t load the dataset, this will leave it “behind” or stale. So a “bad structure” isn&#39;t an error, it just never arrives (making it late).&lt;/p&gt;
&lt;p&gt;Stale becomes our primary error state.&lt;/p&gt;
&lt;p&gt;There is a third state worth mentioning. I hate mentioning an error while I&#39;m fixing it. So I like to advertise that I am in the process of fixing it. A third state of “updating” is important.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Valid&lt;/strong&gt;: Everything is OK&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refreshing&lt;/strong&gt;: We are in the state of updating the data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expired&lt;/strong&gt;: It has exceeded its shelf-life&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error&lt;/strong&gt;: Something is really wrong. Something we have never considered before&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Order matters. We always want the most significant item near the top of the list. This allows people to focus on information that is important and ignore information that can be ignored.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/1-au7ax8ngn2dgbsnau0vkeq-mEhm-HfsQd-__628__.avif 628w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/1-au7ax8ngn2dgbsnau0vkeq-mEhm-HfsQd-__628__.webp&quot; alt=&quot;&quot; width=&quot;628&quot; height=&quot;522&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;(&lt;a href=&quot;https://jsfiddle.net/JeffereyCave/c4bLdox3/98/&quot; target=&quot;_blank&quot;&gt;JSFiddle&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;Iconography&lt;/h2&gt;
&lt;p&gt;Unfortunately, between our error messages and multiple states, we have achieved a &lt;code&gt;WALL OF TEXT&lt;/code&gt; again. So much so that we had to add table lines just to make it legible. Any time you have tables of text, you have done something wrong.&lt;/p&gt;
&lt;p&gt;Language is also a problem with any text. The internet is an international tool taking communications far and wide. Companies I have worked for have required me to accommodate Spanish, French, and Russian. Any time we reduce the text, we reduce the need for translation.&lt;/p&gt;
&lt;p&gt;Our status&#39; can easily be changed communicate via colour and icons.&lt;/p&gt;
&lt;p&gt;Unfortunately, imagery can mean different things to different people especially when crossing cultures, it can also be expensive to purchase, and is subject to people&#39;s aesthetic opinions. Further, those with visual impairments may not be able to interpret the meaning of an icon&lt;/p&gt;
&lt;p&gt;Fortunately, we have an &lt;a href=&quot;https://unicode-table.com/en/blocks/miscellaneous-technical/&quot;&gt;international standard of characters&lt;/a&gt; that can be used to display the status the Unicode standard identifies all the characters you see on your screen, and include a collection of iconographic sets that we can take advantage of. These icons are available on all computers and have standardised meanings behind them. As long as we choose reasonably correct icons, even screen readers will be able to interpret them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Valid&lt;/li&gt;
&lt;li&gt;🔄 Refreshing&lt;/li&gt;
&lt;li&gt;⏰ Expired&lt;/li&gt;
&lt;li&gt;⛔ Error&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://jeffereycave.substack.com/i/91338932/good-palettes&quot;&gt;Colours are also a delicate subject&lt;/a&gt;. While most people reach for Red / Green / Yellow, based on our traffic light system, we know that certain colours can be difficult to distinguish from other colours. Culture also plays a role in that some locals use different colours to mean different things.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/1-rtwtjim3vrieruxco2gq4q-oH0yXynqa8-__544__.avif 544w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/1-rtwtjim3vrieruxco2gq4q-oH0yXynqa8-__544__.webp&quot; alt=&quot;&quot; width=&quot;544&quot; height=&quot;395&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;
(&lt;a href=&quot;https://jsfiddle.net/JeffereyCave/c4bLdox3/113/&quot; target=&quot;_blank&quot;&gt;JSFiddle&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;My general solution for this is to choose&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;blue for “ignorable”&lt;/li&gt;
&lt;li&gt;red for “pay attention”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://jeffereycave.substack.com/i/91338932/aesthetically-pleasing&quot;&gt;Pastels have also been identified&lt;/a&gt; as a safe shading for most colours.&lt;/p&gt;
&lt;p&gt;Now that we are mixing colour and text, it is important to ensure we have a clear dividing line between text and backgrounds. It is important to have light text over dark colours or dark text over light colours.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 TIP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You will get the colour differentiation between text and background wrong eventually. Things just won&#39;t line up. As a safety, I like to take an old trick from subtitled movies: use white text, and give it a black boarder. In HTML we can achieve this with a glow effect.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While no iconography or colour palette is perfect, by using HTML and Unicode it is both &lt;em&gt;quick to deliver and simple to change&lt;/em&gt;, while still getting reasonable results. Also, by keeping the iconography simple, it is simple for people to learn through practice.&lt;/p&gt;
&lt;p&gt;By moving the icons between the error message and the dataset label, we also create a dividing line between them, reducing the need for guidelines.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/1-yget4mbaxc1nd-4y-xndlq-oNUw5i8hm2-__564__.avif 564w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/1-yget4mbaxc1nd-4y-xndlq-oNUw5i8hm2-__564__.webp&quot; alt=&quot;&quot; width=&quot;564&quot; height=&quot;391&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;(&lt;a href=&quot;https://jsfiddle.net/JeffereyCave/c4bLdox3/143/&quot; target=&quot;_blank&quot;&gt;JSFiddle&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Using icons and colour we have reduced the cognitive load by reducing the number of symbols a user must interpret to derive meaning. We have also introduced &lt;a href=&quot;https://jeffereycave.substack.com/p/technologic-in-accessibility-cc16c1748c58&quot;&gt;multiple paths to success&lt;/a&gt;, using standardised technology, to account for diverse observer needs.&lt;/p&gt;
&lt;h2&gt;Timeliness&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Don&#39;t get distracted!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Remember that our focusing description is “fit for use”. What really defines our dataset&#39;s fitness for use?&lt;/p&gt;
&lt;p&gt;Having looked at our error messages and statuses, the recurring theme is the expiry date. Different data changes at different rates, and each piece loses value to the users as it ages.&lt;/p&gt;
&lt;p&gt;Obviously, price data from a month ago is less meaningful than price data from a minute ago. On the flip side, corporations don&#39;t change their Senior Executives that frequently, and someone studying the interrelationship of Board Membership with corporate success may find that data refreshed within the last year is good enough for their purposes.&lt;/p&gt;
&lt;p&gt;Another consideration might be that waiting for the next load may not be far away and therefore is worth waiting for; if a dataset is expected to be refreshed quarterly, and tomorrow is the expected refresh date, it may be worth putting off the build of your analysis for a couple of days.&lt;/p&gt;
&lt;p&gt;Each dataset, therefore, has a refresh frequency associated with it, as well as a last date updated.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 TIP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Don&#39;t pull the dataset&#39;s “last updated” from “now” (the moment you physically process the data). Rather try to use some feature of the data itself to determine how up to date it is. In a perfect world, we would mark when the record was created; not the local copy&#39;s creation date, but the date the datum came into existence.&lt;/p&gt;
&lt;p&gt;By checking the data itself for an updated date you can account for your upstream provider having an issue as well.&lt;/p&gt;
&lt;p&gt;Its not good if you process the same, &lt;a href=&quot;https://jeffereycave.substack.com/p/not-storing-almost-the-same-file-twice-9a44e3179a6a&quot;&gt;unchanged&lt;/a&gt;, dataset over and over again.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Knowing how close a dataset is to changing state is our core definition of “fit for use”, but we find it is not a boolean or ordinal value, but rather a &lt;a href=&quot;https://en.wikipedia.org/wiki/Unit_interval&quot;&gt;unit interval&lt;/a&gt; where the &lt;em&gt;unit is the size of the expected time&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That&#39;s a long way of saying we can create a countdown for every state.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-bolme4xp8eiodam-aYf9AmEA4v-__619__.avif 619w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-bolme4xp8eiodam-aYf9AmEA4v-__619__.webp&quot; alt=&quot;&quot; width=&quot;619&quot; height=&quot;309&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;(&lt;a href=&quot;https://jsfiddle.net/JeffereyCave/c4bLdox3/198/&quot; target=&quot;_blank&quot;&gt;JSFiddle&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Rather than very busy error messages, we have relied on the icon to give context and then supplied a countdown/progress meter to help the user understand how the data they are interested in is impacted.&lt;/p&gt;
&lt;p&gt;Every state has a countdown of some kind&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid: shows how long until the data is expected to be renewed.&lt;/li&gt;
&lt;li&gt;Refresh: gives an estimated time to completion&lt;/li&gt;
&lt;li&gt;Expired: demonstrates a sense of how bad the overrun is&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The meter gives the observer a visual sense of completeness as a proportion; while a textual representation gives a sense of scale for the whole and a reasonable time frame for when a user can expect a change. As these are estimates, times are given only in the major unit with a broad fractional unit (quarters and thirds) to prevent a &lt;a href=&quot;https://en.wikipedia.org/wiki/False_precision&quot;&gt;false sense of precision&lt;/a&gt;. The supplied meter can help a user decide if the indicated time is one worth waiting for (99% fresh vs 1% fresh).&lt;/p&gt;
&lt;p&gt;While not an approved format, the unit of measure conforms to the units expressed in ISO-8601. Using an international standard is intended to maximise the reach to an international audience. The &lt;a href=&quot;https://en.wikipedia.org/wiki/ISO_8601#Durations&quot;&gt;exact ISO-8601 specification&lt;/a&gt; is used in the underlying computer-readable component of the text to accommodate computer-aided comprehension (screen readers).&lt;/p&gt;
&lt;blockquote&gt;
&lt;code&gt;*&amp;lt;time datetime=&amp;#39;PT8H34M10S&amp;#39;&amp;gt; 8 ½ h&amp;lt;/time&amp;gt;*&lt;/code&gt;
&lt;/blockquote&gt;
&lt;p&gt;The meter and time are abandoned for overruns to avoid a reverse meter causing confusion, and because it is possible for data to overrun by multiples of our unit interval. For example, an upstream issue could cause a 15-minute refresh to be out for hours resulting in 413% overruns. A percentage allows users to decide how significant it is to them as a 0.5% overrun on annual data, may not be considered significant at all.&lt;/p&gt;
&lt;h2&gt;An Aside: The part I&#39;m embarrassed to show&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;another &lt;code&gt;WALL OF TEXT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Negative Space&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Normally I would not show this transition in the chart, there are some critical things that I kind of skipped over. They are ugly and I don&#39;t normally like to advertise my crappy ideas, but in this case, I wanted to take a moment to talk about them.&lt;/p&gt;
&lt;p&gt;I want to show how these issues can be spotted, and what they look like while you are working on them.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-r1vfpleznhau8cho-55jz-F9o9d-__671__.avif 671w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-r1vfpleznhau8cho-55jz-F9o9d-__671__.webp&quot; alt=&quot;&quot; width=&quot;671&quot; height=&quot;324&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;(&lt;a href=&quot;https://jsfiddle.net/JeffereyCave/c4bLdox3/175/&quot; target=&quot;_blank&quot;&gt;JSFiddle&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;As our chart has been progressing, I have talked about the &lt;code&gt;WALL OF TEXT&lt;/code&gt; and we can see that we have one creeping into the design. The error messages do not present a lot of meaning and will require translation to accommodate a multi-lingual audience (expensive and time consuming). Those countdown timers are even worse.&lt;/p&gt;
&lt;p&gt;What you are looking at is are my personal attempts to resolve &lt;code&gt;THE WALL OF TEXT&lt;/code&gt; issue while at the same time accommodating a diverse audience.&lt;/p&gt;
&lt;p&gt;maybe we can use decimals?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How many decimals do people care about?&lt;/li&gt;
&lt;li&gt;What unit of measure should we use? Seconds? Minutes? Months?&lt;/li&gt;
&lt;li&gt;What languages do we use to express the unit of measure?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Maybe we can use the International Metric Specification for Time Formatting (&lt;a href=&quot;https://en.wikipedia.org/wiki/ISO_8601&quot;&gt;ISO-8601&lt;/a&gt;, &lt;a href=&quot;https://www.scc.ca/en/standardsdb/standards/4449&quot;&gt;CSA-Z234.4&lt;/a&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The units and symbols are defined internationally&lt;/li&gt;
&lt;li&gt;It can be interpreted by digital tools (screen readers)&lt;/li&gt;
&lt;li&gt;… but it has very dense, unreadable, text&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So we definitely have at least one problem that we are in the middle of working through. Working through results like this is part of the editorial process.&lt;/p&gt;
&lt;p&gt;The end result (shown above) was a compromise between these two formats, but I wanted to show that it would not have existed without this middle step.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-wptl0plxr-d4psp0-dLJh7IwLhq-__671__.avif 671w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-wptl0plxr-d4psp0-dLJh7IwLhq-__671__.webp&quot; alt=&quot;&quot; width=&quot;671&quot; height=&quot;324&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;There is also a problem of “&lt;a href=&quot;https://whitespacedesignbook.com/portfolio/chapter-4-layout-sins/&quot;&gt;Trapped Negative Space&lt;/a&gt;”, or the space &lt;strong&gt;we aren&#39;t using&lt;/strong&gt; on the chart. (h/t &lt;a href=&quot;https://sharoncave.ca/&quot;&gt;Sharon Cave&lt;/a&gt; for drilling this into me)&lt;/p&gt;
&lt;p&gt;The obvious problem with Trapped Negative Space is that it is unused. Space requires resources to fill, either it is space that another chart could have used, or we could even talk about the carbon emissions associated with the screen space. We are always making decisions about including or excluding information, and including negative space means we have implicitly excluded something.&lt;/p&gt;
&lt;p&gt;Excluding important information, because we ran out of space, is just unfortunate.&lt;/p&gt;
&lt;p&gt;Generally, we don&#39;t consciously think about negative space, but subconsciously our eye is drawn to it: it&#39;s different, out of place, something should be there, nature abhors a vacuum. This should be used to bound objects, but trapping it creates false boundaries that the eye follows.&lt;/p&gt;
&lt;p&gt;In the development of a chart, we want to communicate &lt;em&gt;significant&lt;/em&gt; information to people. Humans are a species with strong pattern recognition capabilities, we can use &lt;em&gt;uniformity&lt;/em&gt; to draw the eye away from things that are insignificant. This act of creating a uniform baseline allows for the differences to stand out.&lt;/p&gt;
&lt;p&gt;In this case, the Trapped Negative Space breaks the pattern we are trying to express. As I have heard in many a design class “If you highlight everything, you have highlighted nothing”.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you highlight everything, you have highlighted nothing.&lt;/p&gt;
&lt;p&gt;— Sharon Cave, &lt;a href=&quot;https://sharoncave.ca/&quot;&gt;Sharon Cave Fine Art&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you see Negative Space, just like with &lt;code&gt;THE WALL OF TEXT&lt;/code&gt;, you have done something wrong, and need to refine what you have done.&lt;/p&gt;
&lt;p&gt;In this case, it was noticed that the negative space was nicely checker-boarded. Errors do not have meters, and successes have meters but do not have text. It was a simple step to simply collapse the interlocking space.&lt;/p&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;This looks to me to be a reasonable chart for expressing the ideas that we want to share with our customers. It is as basically accommodating of diverse biological and digital users and is concise enough to convey information quickly.&lt;/p&gt;
&lt;p&gt;This is a good start.&lt;/p&gt;
&lt;p&gt;This was only a wireframe and a quick sketch to help get a feel for the data that should be presented to users. For example, that header has a lot of Negative Space just screaming to be moved around, and the black grid is overly contrasting drawing the eye away from the information.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-gzp1yujc72k0eyqx-ETCzDbCpnc-__489__.avif 489w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-gzp1yujc72k0eyqx-ETCzDbCpnc-__489__.webp&quot; alt=&quot;&quot; width=&quot;489&quot; height=&quot;496&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;&lt;a href=&quot;https://www.buymeacoffee.com/jeffereycave&quot; target=&quot;_blank&quot;&gt;Found this Intersting? Leave a Tip…. it helps&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Visually, this chart needs a lot of work.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Marketing design: this has not even begun to integrate with corporate look and feel. A look at the overall design of the parent containing reports is necessary&lt;/li&gt;
&lt;li&gt;Accessibility: while it has worked to stay constrained to basic accessibility, a deeper look into this by specialists is necessary&lt;/li&gt;
&lt;li&gt;Translation: there is some text, and elements of the text will need to be translated into supported languages&lt;/li&gt;
&lt;li&gt;Peer review: this is representative of a single day&#39;s work, and has not seen a review from peers that may express concerns within our domain&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having said that, the changes that will be suggested by these various groups will address aesthetic reasoning. During the design process, we have been careful to minimise the overlap between the design and data concerns. The data is rendered as a simple table with formatting being controlled separately. This means that designers can drastically change the design without significant changes to the actual data produced.&lt;/p&gt;
&lt;p&gt;Again, not perfect, but an eye to future cooperation is always necessary, and being open to their suggestions (or sometimes outright changes) can be a wonderful learning experience.&lt;/p&gt;
&lt;h2&gt;Animation&lt;/h2&gt;
&lt;p&gt;I remember reading an article by &lt;a href=&quot;https://bost.ocks.org/mike/constancy/#when-constancy-matter&quot;&gt;Mike Bostock&lt;/a&gt; in which he described the value of having sorted graph bars slide to their new position when they needed to be changed. Humans see motion and fixate on it, and watching a chart item change position helps us comprehend the change that takes place.&lt;/p&gt;
&lt;p&gt;This chart (a horizontal bar chart) represents the exact scenario he was describing. We order items by error state (most significant at the top), and using animations to draw attention to changes in order would draw attention to a significant change in state.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-x1obsx3gqdyscsdn-f7Yvg3kqiH-__619__.avif 619w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-x1obsx3gqdyscsdn-f7Yvg3kqiH-__619__.webp&quot; alt=&quot;&quot; width=&quot;619&quot; height=&quot;376&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;(&lt;a href=&quot;https://jsfiddle.net/JeffereyCave/c4bLdox3/243/&quot; target=&quot;_blank&quot;&gt;JSFiddle&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We were asked “How do users know the dataset is ‘fit for use&#39;?” and I think we have achieved that, but we&#39;ve gone much further&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We have created an advertising list of all our services&lt;/li&gt;
&lt;li&gt;“Fit for Use” is estimated but left to the user&lt;/li&gt;
&lt;li&gt;It is accommodating of various user needs &lt;a href=&quot;https://medium.com/user-experience-design-1/technologic-in-accessibility-cc16c1748c58&quot;&gt;offering multiple success paths&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Information density is high, but not overwhelming.&lt;/li&gt;
&lt;li&gt;The reduced text allows for it to be used multilingually&lt;/li&gt;
&lt;li&gt;Design and Logic have been &lt;a href=&quot;https://en.wikipedia.org/wiki/Separation_of_concerns&quot;&gt;separated as concerns&lt;/a&gt; for easy collaboration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not bad for 4 hours after supper, and (frankly) a lot of fun.&lt;/p&gt;
&lt;p&gt;I&#39;m hopeful that this cart will make its way in front of our users; I think it will help make our service more visible to new users, offer a lot of information to our current users, and free up the team&#39;s time from a lot of status reports.&lt;/p&gt;
&lt;p&gt;Ironically, the day after I first presented this report (and wrote most of this post) a colleague gave a presentation on how we present information to users. In it, they emphasised the need to meet the viewer where they are, and not over complicating the problem. From the online audience, I muted my microphone and burst out laughing. A brief chat between us after the presentation summed up our shared perspective:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Keep it Simple Stupid.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Unfortunately, that&#39;s sometimes a complex thing to do.&lt;/p&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;p&gt;There is so much great reading to be done on how we convey information to users&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first stop for me was everything by Mike Bostock. Most famous for his work as a New York Times data visualist, he is also the inventor of D3 and &lt;a href=&quot;https://observablehq.com/&quot;&gt;Observable HQ&lt;/a&gt;. You should read everything you can by him.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://clauswilke.com/dataviz/&quot;&gt;Fundamentals of Data Visualization&lt;/a&gt;, by Claus Wilke, is a must-read for anyone interested in visualisation and makes an excellent textbook for any classroom (it was my first choice)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://amzn.to/3IgBln8&quot;&gt;White Space is Not Your Enemy&lt;/a&gt;, by Rebecca Hagen, is a good introduction and foundation into the visual arts from a marketing perspective. This supplies the right level of general theory to apply across a number of visual disciplines.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://amzn.to/3lfkWGr&quot;&gt;How to Lie with Statistics&lt;/a&gt;, by Darrel Huff. Huff focuses on how humans interpret (or misinterpret) numbers and how our expressions of those numbers can help, hurt, or misdirect, understanding.&lt;/li&gt;
&lt;li&gt;I&#39;ve been prone to &lt;a href=&quot;https://jeffereycave.substack.com/p/why-wont-staples-take-my-money&quot;&gt;rants&lt;/a&gt; on &lt;a href=&quot;https://jeffereycave.substack.com/p/build-a-simple-pencil-and-paper-computer&quot;&gt;related&lt;/a&gt; &lt;a href=&quot;https://jeffereycave.substack.com/p/how-fast-is-fast-enough&quot;&gt;subjects&lt;/a&gt; over the &lt;a href=&quot;https://jeffereycave.substack.com/p/technologic-in-accessibility-cc16c1748c58&quot;&gt;years&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most importantly, never forget that User Experience is more than just rounding the borders of the HTML, it&#39;s about understanding the psychology, anatomy, and physiology behind our interactions with our users; so make sure you spend time talking to them to understand their experience.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-9sr-vwnluzdziftu-z9xMFM_Ica-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/complexity-of-a-simple-chart/0-9sr-vwnluzdziftu-z9xMFM_Ica-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;1044&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;Physiological sketch of the human brain and skull c. 1510 (&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Leonardo_Da_Vinci%27s_Brain_Physiology.jpg&quot; target=&quot;_blank&quot;&gt;Wikimedia, Public Domain&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
</content>
        </entry>
        <entry>
            <title>I still want my agile back</title>
            <link href="http://plaidsheep.ca/posts/2023/I-still-want-my-agile-back/" />
            <updated>2023-01-10T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2023/I-still-want-my-agile-back/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*O3EJoDurDeRN92YWJ2Kh1A.png&quot; alt=&quot;I still want my agile back&quot;&gt;
                &lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/I-still-want-my-agile-back/1-o3ejodurdern92ywj2kh1a-4CfGtUa0w4-__689__.avif 689w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/I-still-want-my-agile-back/1-o3ejodurdern92ywj2kh1a-4CfGtUa0w4-__689__.webp&quot; alt=&quot;&quot; width=&quot;689&quot; height=&quot;669&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Inviting Radical Candor (Modern Agile, &lt;a href=&quot;https://modernagile.org/#cheatsheets&quot; target=&quot;_blank&quot;&gt;Psychological Safety Cheat Sheet&lt;/a&gt;, Creative Commons)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
It&#39;s the end of 2022 and I&#39;m still working with companies to implement anything resembling Agile and XP development processes. Over the years, I&#39;ve worked with some excellent teams, and many times I&#39;ve worked with companies that pay lip service to the ideas, but don&#39;t take any of the actions.
&lt;/p&gt;
&lt;p&gt;
They recite the buzzwords, and catchphrases, but don&#39;t actually do the things.
&lt;/p&gt;
&lt;p&gt;
I&#39;ve best heard it described as Waterfall in Sprints.
&lt;/p&gt;
&lt;p&gt;
Recently I was reminded of some excellent articles on this I read around 2015 and started to have a conversation with myself regarding the matter. Yes, I talk to myself, because sometimes that&#39;s the only way to have an intelligent conversation.
&lt;/p&gt;
&lt;p&gt;
It occurred to me that &lt;a href=&quot;https://www.britannica.com/biography/Plato/Dialogue-form&quot; target=&quot;_blank&quot;&gt;Dialogues&lt;/a&gt; are a time-honoured publishing format and while I&#39;m no Plato…
&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
I&#39;m currently taking the corporate course in “agile” and every lecture leaves me fuming.
&lt;/p&gt;
&lt;p&gt;
I want my agile back
&lt;/p&gt;
&lt;div name=&quot;67e1&quot; id=&quot;67e1&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p&quot;&gt;&lt;a href=&quot;https://www.infoq.com/articles/taking-back-agile/&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;Taking Back Agile&lt;/strong&gt;&lt;br&gt;&lt;em&gt;Tim Ottinger&#39;s blog post I want Agile back earlier this year led to discussions in the agile community about the way…&lt;/em&gt;www.infoq.com&lt;/a&gt;&lt;a href=&quot;https://www.infoq.com/articles/taking-back-agile/&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;div name=&quot;33d7&quot; id=&quot;33d7&quot; class=&quot;graf graf--mixtapeEmbed graf-after--mixtapeEmbed&quot;&gt;&lt;a href=&quot;http://agileotter.blogspot.com/2014/02/i-want-agile-back.html&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;I Want Agile Back&lt;/strong&gt;&lt;br&gt;&lt;em&gt;Note: this was originally all plain text and a little shorter. As more people have joined the conversation, and other…&lt;/em&gt;agileotter.blogspot.com&lt;/a&gt;&lt;a href=&quot;http://agileotter.blogspot.com/2014/02/i-want-agile-back.html&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
I really only know Agile from some theoretical academic classes from my college days. Whenever I&#39;ve been told we are running in “agile” mode, it has only ever meant “busy” and “hectic”
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
I lived it in the early days of the 2000s, implemented it at my small company, and debated and discussed the literature with some of the greats. It&#39;s funny that you mention “busy” and “hectic” since that&#39;s almost straight from one of the articles I sent you.
&lt;/p&gt;
&lt;blockquote&gt;
Are we tired of seeing “sprints” and “iterations” used as ways to pressure people into working harder and longer
&lt;/blockquote&gt;
&lt;blockquote&gt;
— Tim Ottinger, &lt;a href=&quot;http://agileotter.blogspot.com/2014/02/i-want-agile-back.html&quot; target=&quot;_blank&quot;&gt;I want agile back&lt;/a&gt;
&lt;/blockquote&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Yes, that&#39;s exactly how it works
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Agile was always about slowing development down, not speeding it up. Slowing it down so fewer errors got made, resulting in an overall increase in speed.
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Exactly. Instead, they will see how many story-points you complete and review it against other co-workers
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Story-points were a dumb idea, it becomes a performance metric.
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Yes.
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
One that creates competition, so people work harder to avoid looking bad.
&lt;/p&gt;
&lt;p&gt;
I felt it because there was a time when I worked for my previous company, I delivered 1 story point but it was 80% of the work of the whole project
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;blockquote&gt;
When a measure becomes a target, it ceases to be a good measure
&lt;/blockquote&gt;
&lt;blockquote&gt;
— &lt;a href=&quot;https://en.wikipedia.org/wiki/Goodhart%27s_law&quot; target=&quot;_blank&quot;&gt;Goodhart&#39;s Law&lt;/a&gt;
&lt;/blockquote&gt;
&lt;p&gt;
I mean, I get the point to story-points; not everybody works at the same speed, so the time it takes me to achieve 8 points of effort isn&#39;t the same as it takes you. Unfortunately, because they are so abstract, I think they just end up being a way to dictate how much time it will take you.
&lt;/p&gt;
&lt;p&gt;
Story points for me have always and will continue to be, estimated hours to complete. How long do you think it will take to implement
&lt;/p&gt;
&lt;p&gt;
…. AND… they will be used to ensure that staff are not overburdened with work!
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;THAT SHALL BE THEIR ONLY PURPOSE!&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
I think so.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;&lt;em&gt;NO&lt;/em&gt;&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;&lt;em&gt;not “THINK”&lt;/em&gt;&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
haha. Yes
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;[jumps up on the desk. Fist strikes high into the sky]&lt;/em&gt;
&lt;/p&gt;
&lt;blockquote&gt;
IT SHALL BE
&lt;/blockquote&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
I mean sometimes people interpret things differently.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
I know you are right.
&lt;/p&gt;
&lt;p&gt;
It just makes me sad.
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
hahaha
&lt;/p&gt;
&lt;p&gt;
I mean the way people interpret agile and story points differently.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
That article “I want my agile back” … I&#39;ve been reading those works for years. The original point (2002?) was to slow down the expectations so that developers had time to “do it right”.
&lt;/p&gt;
&lt;p&gt;
It was a framework to help set boundaries on expectations.
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Yes. That&#39;s right
&lt;/p&gt;
&lt;p&gt;
I&#39;m never in a management position so I don&#39;t really know how Agile works and how it helps achieve the project goal and timeline.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
(this is why I&#39;m not either)
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
At one of my previous companies, my manager was pretty cooperative with it… I would tell her how much could get done, she would yell at me to do more, I would yell back that I&#39;d shown her the math …. and we would go with what I had shown her.
&lt;/p&gt;
&lt;p&gt;
(The yelling was mostly a side effect of her having been to a rock concert the night before)
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
But according to one friend of mine, the project she took over was running in a waterfall mode, and was a total failure.
&lt;/p&gt;
&lt;p&gt;
There is another term “waterfall”… I don&#39;t really know what that is supposed to be either.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
I don&#39;t think anyone really does. I&#39;ve heard some pretty powerful metaphors and pretty flowery explanations.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/I-still-want-my-agile-back/0-3bii1p7nlje7zjoe-VDOap0eQiP-__420__.avif 420w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/I-still-want-my-agile-back/0-3bii1p7nlje7zjoe-VDOap0eQiP-__420__.webp&quot; alt=&quot;&quot; width=&quot;420&quot; height=&quot;600&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Ebor falls (&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Upper_ebor_falls_small.jpg&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;, Creative Commons)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Have you ever heard where the term &lt;code&gt;ping&lt;/code&gt; comes from?
&lt;/p&gt;
&lt;p&gt;
When I was a teenager, the term was widely understood to be a reference to submarines sending out sonar pulses and waiting for the response to come back. While in college, my textbooks described the same concept. Then, about a few years later, I came across a reference that stated the term was P.I.N.G (Packet InterNet Groper).
&lt;/p&gt;
&lt;p&gt;
I can only imagine how this came about. I&#39;m assuming some student put their hand up and asked a professor what it stood for… a little dumbfounded the prof jokingly stated “Packet InterNet Groper”, but the joke got taken seriously. (Wikipedia has an &lt;a href=&quot;https://en.wikipedia.org/wiki/Ping_%28networking_utility%29&quot; target=&quot;_blank&quot;&gt;interesting history for this&lt;/a&gt;)
&lt;/p&gt;
&lt;p&gt;
My point is that people like to invent complicated explanations for obvious naming conventions.
&lt;/p&gt;
&lt;p&gt;
What is “waterfall”?
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/I-still-want-my-agile-back/0-io2y-u-oc4rsppp-UMC7KuxDT1-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/I-still-want-my-agile-back/0-io2y-u-oc4rsppp-UMC7KuxDT1-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;167&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;[&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Pert_example_gantt_chart.gif&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;, CC-SA]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Note the shape of this Gantt chart. Notice how it cascades down the page, almost like a “waterfall”?
&lt;/p&gt;
&lt;p&gt;
People like to over complicate some explanations.
&lt;/p&gt;
&lt;p&gt;
Before I heard about Agile, this is what Project Management looked like. I filled out hundreds of charts, just like that. When we started talking about Continous Improvement, Iterative Development, and … Agile … we need some way to define the “old” way of project management. We needed a word to describe “not Agile”, so someone looked at the chart and called it “Waterfall”.
&lt;/p&gt;
&lt;p&gt;
As people are apt to do, they have extended the metaphor.
&lt;/p&gt;
&lt;p&gt;
Once the water is over the falls, its hard to go back and change the course of the river. (a good analogy)
&lt;/p&gt;
&lt;p&gt;
I&#39;m concerned about our project, and what I&#39;m hearing in the corporate training. I&#39;m concerned that we are creating “&lt;a href=&quot;https://medium.com/beyond-value/waterfall-with-sprints-1bcf2e571a87&quot; target=&quot;_blank&quot;&gt;waterfall with sprints&lt;/a&gt;”, a common euphemism for the fixed project timeline project management (waterfall), but with 2-week time frames (sprints).
&lt;/p&gt;
&lt;p&gt;
Waterfall with a bit of makeup on it to dress it up.
&lt;/p&gt;
&lt;div name=&quot;54d8&quot; id=&quot;54d8&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p&quot;&gt;&lt;a href=&quot;https://wordhistories.net/2016/12/28/silk-purse-sows-ear/&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;origin of ‘you can&#39;t make a silk purse out of a sow&#39;s ear&#39;&lt;/strong&gt;&lt;br&gt;&lt;em&gt;The proverb you can&#39;t make a silk purse out of a sow&#39;s ear means you can&#39;t create a fine product from inferior…&lt;/em&gt;wordhistories.net&lt;/a&gt;&lt;a href=&quot;https://wordhistories.net/2016/12/28/silk-purse-sows-ear/&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
I taught an intro project management class at a Community College. The previous instructor handed me his curriculum (still grateful for that), but on the first day, I felt the need to explain that those two terms are very loaded. They don&#39;t mean what they originally meant. The terms have been hijacked and used as slurs and changed and …. so we need to lay out what &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;we mean&lt;/strong&gt; by them, and what &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;we think&lt;/strong&gt; others might mean by them.
&lt;/p&gt;
&lt;p&gt;
What they mean in the &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;ideal case&lt;/strong&gt;, and what they probably mean in the &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;workplace&lt;/strong&gt;.
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
My understanding of those methods is based on my work experience. So if it aligns with the definition or not, I don&#39;t know.
&lt;/p&gt;
&lt;p&gt;
It depends on how the PM runs it.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
At least you know what you don&#39;t know.
&lt;/p&gt;
&lt;p&gt;
I will always refer to the “ideal” and the “actual” because who knows what the academic definition is anymore
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Ideal&lt;/strong&gt;: I want to use a system that works well. Not abuses people. Not pits people against one another. The one that increases love in the universe.
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Based on my experience, when people tell me they are running agile, I automatically assume they need me to get work done fast and they are going to change the requirements frequently and without notice.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
I actually originally introduced it as a way to reduce the scopes change rate.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
“increases love” is actually part of the original academic definition
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
When I worked for my previous company, they ran an agile and asked me to deliver a report without any requirements.
&lt;/p&gt;
&lt;p&gt;
So I put together a report based on my understanding of the report. The BA took the report and show clients and got feedback from them. They would then come back to ask me for revision.
&lt;/p&gt;
&lt;p&gt;
There were a few back and forths until the report meet the client&#39;s expectations.
&lt;/p&gt;
&lt;p&gt;
That is how I was told what agile is.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;blockquote&gt;
Individuals and interactions over processes and tools
&lt;/blockquote&gt;
&lt;blockquote&gt;
— &lt;a href=&quot;https://agilemanifesto.org&quot; target=&quot;_blank&quot;&gt;Manifesto for Agile Software Development&lt;/a&gt;
&lt;/blockquote&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Do you know how we talked about creating the structure of datasets? That&#39;s what I did for that company. I created the structure of the dataset they were looking for. The clients then inspected the data and started to comment if it was correct or not.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
That&#39;s worth doing… back and forth design.
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
I feel that method is good for initial development when the client doesn&#39;t know what they exactly looking for.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Absolutely, but once you pin that down as “designed” it needs to be captured as a signed-off agreement and only tweaked from there.
&lt;/p&gt;
&lt;p&gt;
I wanted the most recent transition you and I are working on to be done that way. I wanted to bridge it by us writing exact duplicates of the original way. If we weren&#39;t matching 1-to-1, something was wrong.
&lt;/p&gt;
&lt;p&gt;
Over time we would have just slowly transformed into the new interface.
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Yes. So all the terminologies are vague to me.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Good…. I like you better that way
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Why? It&#39;s more changeable that way?
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Basically.
&lt;/p&gt;
&lt;p&gt;
There are only four terms you really need:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;pass or fail&lt;/strong&gt;: was whatever we did a success or not? This is a boolean, there is nowhere to hide from the truth.
&lt;/li&gt;
&lt;li&gt;
&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;better or worse&lt;/strong&gt;: whether it was a success or failure, we need to ask how we could do it better next time. This is a scale, is whatever we tried better than the way we did it before, or worse?
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
We don&#39;t ask
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
is it agile?
&lt;/li&gt;
&lt;li&gt;
is it waterfall?
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
We ask “is it better?”
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Haha, totally
&lt;/p&gt;
&lt;p&gt;
As I said I&#39;ve never been in a management position. For me, it&#39;s just about getting the work done on time.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
“on time”
&lt;/p&gt;
&lt;p&gt;
That&#39;s a dangerous phrase. It comes with being told when “it is due”
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Yes.
&lt;/p&gt;
&lt;p&gt;
Some companies just give you a deadline and squeeze it out of you.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Jeff&#39;s ideal process: “we tell how much we can achieve and explain when it will be done”
&lt;/p&gt;
&lt;p&gt;
My classic story…
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;Monday&lt;/strong&gt;: customer calls me and asks for a new feature ( &lt;code&gt;f1&lt;/code&gt; ). I estimate a week for the work and get started.
&lt;/li&gt;
&lt;li&gt;
&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;Tuesday&lt;/strong&gt;: around noon the phone rings. The customer is in a panic. He&#39;s just come out of a meeting and the executives really desperately need a different feature. I put down my planning for &lt;code&gt;f1&lt;/code&gt; and get started on &lt;code&gt;f2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;Wednesday&lt;/strong&gt;: at noon the phone rings. The customer is in a panic. He&#39;s just come out of a meeting and there has been a catastrophic business failure, heads are going to roll. He immediately needs me to start work on &lt;code&gt;f3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;Thursday&lt;/strong&gt;: first thing in the morning there&#39;s an email in my mailbox asking me for a status update on &lt;code&gt;f1&lt;/code&gt;. When I called and explained that I had tabled that to work on &lt;code&gt;f3&lt;/code&gt; he panicked. &lt;code&gt;f3&lt;/code&gt; was low priority. Instead, he had promised &lt;code&gt;f1&lt;/code&gt;, and it was absolutely needed by Monday.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
So I pulled out some early notes about &lt;code&gt;f1&lt;/code&gt; and got started wrapping my head around it. By noon, I was back up to speed on it, and ready to get started.
&lt;/p&gt;
&lt;p&gt;
Because the customer changed his mind about what he wanted to be done so frequently, we (both he and I) lost 4 days of a work week.
&lt;/p&gt;
&lt;p&gt;
That&#39;s a true story, most people don&#39;t believe me when I tell it. They will tell me that would never happen, but if I&#39;m telling it to them its because they are experiencing exactly the same thing. I have seen that play out at (almost) every organisation I&#39;ve worked for.
&lt;/p&gt;
&lt;p&gt;
Key phrases I tend to watch out for:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.washingtonpost.com/archive/politics/1990/06/25/avianca-crash-a-fatal-misunderstanding/8cd342e5-bb0d-4c9c-8087-78e880a50e95/&quot; target=&quot;_blank&quot;&gt;competing/multiple priorities&lt;/a&gt;: you can&#39;t have “multiple” priorities such a thing cannot exist logically, similar to “competing” priorities.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.psychologytoday.com/us/blog/creative-leadership/201811/why-you-cant-multi-task&quot; target=&quot;_blank&quot;&gt;multi-tasking&lt;/a&gt;: Unless you are an &lt;a href=&quot;https://sites.nd.edu/biomechanics-in-the-wild/2021/04/07/nine-brains-are-better-than-one-an-octopus-nervous-system/&quot; target=&quot;_blank&quot;&gt;octopus&lt;/a&gt;, you can&#39;t. You can only rapidly task switch, which comes at a cost.
&lt;/li&gt;
&lt;li&gt;
immediate position: (in job postings) what lack of planning led to your organisation not scaling up effectively?
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
If you ever want to have fun, go take a job interview, talk up your predictive analytics skills, and then ask them what they are doing to address their analytic failings that led to the current hiring emergency/disaster.
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Once upon a time, a friend of mine got fired because he was not able to deliver what the client asked for on time.
&lt;/p&gt;
&lt;p&gt;
Exactly the same issue that you described above
&lt;/p&gt;
&lt;p&gt;
The boss kept getting changing requirements from the client and asked the team to change priorities. In the end, they couldn&#39;t deliver anything
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Precisely, this is a common and widespread problem.
&lt;/p&gt;
&lt;p&gt;
At the time, I did some reading on how to solve this problem and came across a new concept called “agile”. I read a book, read some forums, and started to implement some boundaries in the workplace.
&lt;/p&gt;
&lt;p&gt;
My customer wasn&#39;t allowed to talk to me in the middle of a sprint, he had to commit to not changing requirements for at least 4 days (1-week sprints). When I said that, it actually freaked him out, sending him into a rant about him being the customer, and he pays the money … and that demonstrated a problem.
&lt;/p&gt;
&lt;p&gt;
I told him that if he could not commit to a plan for 4 days, he obviously had not really thought it through. In the worst-case scenario, if he had made a mistake, I was asking him to live with it for 4 business days. At the end of 4 business days, he could totally change his mind and reschedule and get me to work on something else… but he had to wait 4 days.
&lt;/p&gt;
&lt;p&gt;
Compare that to “waterfall” where in theory, he would not be allowed to adjust the plan because he had signed off on a plan. This is where the “agility” comes in, you have planned to totally change the plan regularly, whereas the rigidity of “waterfall” resulted in a culture of “emergency” changes to the plan, and constant last-minute demands.
&lt;/p&gt;
&lt;p&gt;
So this customer and I put everything in a backlog, I started work on the first item … and &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;he called me mid-week,&lt;/strong&gt; with a small little quick extra. I cancelled the sprint to achieve what he wanted. Then sent him a report outlining that everything had moved out a week since we were starting a week later than expected.
&lt;/p&gt;
&lt;p&gt;
He got angry, we had another very difficult conversation, and I pointed out it was simple math. I cannot be in two places at one time. The 6-month project estimate was from the “start” date, not the calendar date. If he was going to interrupt the plan, his project was going to start later. His “quick” change cost him a week, if he had waited 2 days, it would have cost nothing.
&lt;/p&gt;
&lt;p&gt;
I have been accused of being a jerk for this, but I wasn&#39;t, I had found a clear way to reflect reality to him. His quick change lost me a half week of effort and he needed to understand that.
&lt;/p&gt;
&lt;p&gt;
After that, we settled into a groove: one-week sprints that started with a Monday meeting where told him what could get done during the week based on my review of the backlog. He would spend all week re-prioritizing and adding to the backlog for the next week (basically preparing for our meeting).
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Switching between tasks is such a headache. I got re-tasked with an emergency a couple of times while working on our most recent feature. Every time I got back on it, I need to review and refresh my memory. So changing requirements really wastes lots of time.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Knowing that feature, and the level of interruption you experienced, I would guess it cost half a day each time you had to get back into it.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
I used to track my time, and I counted every task switch as a lost 15 minutes.
&lt;/p&gt;
&lt;p&gt;
A director and I got into an argument over it … she ordered me to count it as 30 minutes
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
even better
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Ya, but I&#39;d hardcoded the math 🙁
&lt;/p&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Hahaha
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
By setting the pace weekly, and not allowing change mid-sprint, you implement a cost to changed requirements. They actually take more time.
&lt;/p&gt;
&lt;p&gt;
That means the business is incentivized to cooperate in getting them right the first time.
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
That was one of my most successful projects ever. It was so successful that &lt;code&gt;Customer A&lt;/code&gt; got bought by &lt;code&gt;Customer B&lt;/code&gt; to get at the software (6 months later)
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
I just realized I&#39;m not talking “agile”, I&#39;m talking eXtreme Programming
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/I-still-want-my-agile-back/0-xu-07rpntgq1vdlh-bZJDDlzpVl-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/I-still-want-my-agile-back/0-xu-07rpntgq1vdlh-bZJDDlzpVl-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;669&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;eXtreme Programming (&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Extreme_Programming.svg&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
👩 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Alice —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Ha but that&#39;s how people work under agile
&lt;/p&gt;
&lt;p&gt;
💂 &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Jeff —&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
… whatever … same thing, different decade
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
1990: eXtreme Programming
&lt;/li&gt;
&lt;li&gt;
2000: Agile
&lt;/li&gt;
&lt;li&gt;
2010: DevOps
&lt;/li&gt;
&lt;li&gt;
2020: DevSecMLUxOps
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
I&#39;m actually a little disappointed with this decade. The renaming of the process clearly demonstrates a loss of creativity.
&lt;/p&gt;
&lt;h4&gt;UPDATE: 2023–03–22&lt;/h4&gt;
&lt;p&gt;
I came across “&lt;a href=&quot;https://medium.com/leadingagile-field-notes/the-agile-death-march-f4cf58523b78&quot; target=&quot;_blank&quot;&gt;The Agile Death March&lt;/a&gt;”, by Dave Nicolette, and found it really speaks to the point I was trying to make
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2023/I-still-want-my-agile-back/1-wdhpnslnuensqjsqboqata-DXma7H3sdK-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2023/I-still-want-my-agile-back/1-wdhpnslnuensqjsqboqata-DXma7H3sdK-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;730&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Modern Agile Wheel (&lt;a href=&quot;https://modernagile.org/&quot; target=&quot;_blank&quot;&gt;modernagile.org&lt;/a&gt;, Creative Commons)&lt;/figcaption&gt;
&lt;/figure&gt;
</content>
        </entry>
        <entry>
            <title>Education, Training, and Indoctrination</title>
            <link href="http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/" />
            <updated>2022-12-12T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*R9E_5Ui6flfBHY6gKrujlw.jpeg&quot; alt=&quot;Education, Training, and Indoctrination&quot;&gt;
                &lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/1-r9e-5ui6flfbhy6gkrujlw-qeUtcEZvbQ-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/1-r9e-5ui6flfbhy6gkrujlw-qeUtcEZvbQ-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;573&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;When this sticky note was passed to a colleague during a conference on education, he pointed out that “true mastery” should be “perceived mastery”. We had been discussing student perception of skill mastery across time in school. All of a sudden, the second presenter mentioned Self-Directed Learning.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
I was recently working on a project in which we introduced a new piece of information management software to the business. This software is not special in any way, but it is meant to fundamentally change the way the organisation shares information.
&lt;/p&gt;
&lt;p&gt;
About a year into the project, my group were discussing an initial release and the needs for it. As part of the discussion, the need for “training” came up, to help users understand how to use the software. What struck me was that there were three different descriptions of what would be needed, and therefore three different timelines and costs implied for the development of the training.
&lt;/p&gt;
&lt;blockquote&gt;
&lt;a href=&quot;https://jefferey-cave.medium.com/education-training-and-indoctrination-8b54228d5d07?source=friends_link&amp;amp;sk=b3062b7b12f7138db87fd894b4f4f1d7&quot; target=&quot;_blank&quot;&gt;Don&#39;t have a medium account? Use my Friend Link!&lt;/a&gt;
&lt;/blockquote&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
Manager: We don&#39;t have time to create training. Creating a full curriculum getting it approved by the organisation, and ensuring it aligns with both corporate objectives and existing legal statements takes months. Video production adds months more to that and certification of completion adds months more to that.
&lt;/li&gt;
&lt;li&gt;
Colleague: We don&#39;t need any training, the user manual provided by the vendor is very complete. We have taken the courses offered by the vendor and will be able to do the work for them.
&lt;/li&gt;
&lt;li&gt;
Me: We already have (rudimentary) training documents in the form of Use Cases or User Stories. They already narrate the basic usage of the system and simply need to be reformatted to act as a simple task-driven PlayBook for the users to get them up and running.
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
These are three wildly different narratives associated with the same question.
&lt;/p&gt;
&lt;p&gt;
From my perspective, it was absolutely necessary to release for our users to begin seeing the benefits of the system, delaying it for years just to get an online training system constructed by our internal training department reduced the value to the organisation. On the other hand, I could absolutely see how offering nothing but the (very technical) vendor manual was only going to take time out of already busy schedules, creating negative associations, creating poor uptake of the platform.
&lt;/p&gt;
&lt;p&gt;
At the time I was very frustrated with the response, the different takes on what was needed resulted in a blockage of progress.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/1-v9jjlwaqpy5czrqkz0w-kw-zgKEIiaifH-__432__.avif 432w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/1-v9jjlwaqpy5czrqkz0w-kw-zgKEIiaifH-__432__.webp&quot; alt=&quot;&quot; width=&quot;432&quot; height=&quot;416&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;There is a bit of awkwardness as we often refer to “training”, but it really means this thing in the middle of the diagram. I will call it “Training Material”, but recognise that it needs a better name&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Recently, I began to comprehend where the variance in perceived need was stemming from. It was actually a difference in perception of why training material is produced:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Education
&lt;/li&gt;
&lt;li&gt;
Training
&lt;/li&gt;
&lt;li&gt;
Indoctrination
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
All three of these have value to an organisation, and all three are (partially) achieved through what is called “training material”. Further, all “training material” has a certain amount of each of the purposes when it is produced. Clarifying the primary objective of a particular training initiative may help in producing the material.
&lt;/p&gt;
&lt;h3&gt;Distinguishing Between the Dimensions&lt;/h3&gt;
&lt;p&gt;
It is common to call for “Corporate Training” within business development discussions. The need for training is stated to overcome barriers to work, resistance to change, and increased performance (&lt;a href=&quot;https://www.lessonly.com/the-importance-of-training/#:~:text=The%20Objectives%20of%20Training,and%20development%20in%20an%20organization.&quot; target=&quot;_blank&quot;&gt;lessonly&lt;/a&gt;). That is not a complete list but is representative of each of our dimensions of learning material.
&lt;/p&gt;
&lt;h4&gt;Training&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
Mechanical Skillsets
&lt;/li&gt;
&lt;li&gt;
Safety
&lt;/li&gt;
&lt;li&gt;
Basic Operations
&lt;/li&gt;
&lt;li&gt;
easily quantifiable
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Education&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
Transferable skills
&lt;/li&gt;
&lt;li&gt;
Predictive Reasoning
&lt;/li&gt;
&lt;li&gt;
Hypothesis forming
&lt;/li&gt;
&lt;li&gt;
Quality-based, difficult to quantify
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Training can be distinguished from Education, by its focus on immediate action, and less on future possibilities.
&lt;/p&gt;
&lt;h4&gt;Indoctrination&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
Team building
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://hbr.org/2012/09/ten-reasons-people-resist-chang&quot; target=&quot;_blank&quot;&gt;Resistance to Change&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
Quantifiable, but little value in quantifying (acceptance is all that is required)
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
“Every culture institutionalizes certain forms of behaviour that communicate and encourage certain forms of thinking and acting, thus moulding the character of its citizens” (Merloo, “The Rape of the Mind”, 1956)
&lt;/blockquote&gt;
&lt;p&gt;
I remember starting a new role at a new company and being told to report to a training centre on my first day. It was a corporate training event on basic &lt;code&gt;C#&lt;/code&gt; development. Most of the skills were ones I had mastered a decade before, but it was nice to get the refresher and it was certainly more interesting than ITIL Fundamentals (the week before).
&lt;/p&gt;
&lt;p&gt;
I was surprised when most people showed no interest in the content of the material being presented. Rather goofing off, and spending more time having extended lunches. I later learned that, of the two dozen people there, only three were actual developers; the rest were comprised of Business Analysts of varying stripes.
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;The point was not to teach a new skill.&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
The Developers already knew how to program in C#, and the Analysts were forbidden from ever using the skill anyway.
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;This was a team-building exercise.&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
Placing people in a room together and having them solve common problems creates a sense of solidarity. The problem to be solved is kind of irrelevant, but simply must engage the audience sufficiently to motivate them to solve it, you may as well learn a semi-useful skill while you are at it.
&lt;/p&gt;
&lt;p&gt;
Similar to this concept is that of informing employees what to think.
&lt;/p&gt;
&lt;p&gt;
It is important to organisations that people be loyal, or obedient, to the organisation. Part of this obedience is knowing what the organisational decision is.
&lt;/p&gt;
&lt;p&gt;
I am reminded of a business trip in which a vigorous debate occurred regarding the implementation of a testing framework for our software product. I spent the first three days travelling with my colleagues and using the opportunity to suggest what the automation around testing should look like. My colleagues all agreed, and we were well on our way to implementation. On the third day, our executive showed up and, during a few beers after the daily meetings, informed us that he was going to have to get us trained because none of us knew how to test his software system.
&lt;/p&gt;
&lt;p&gt;
That was it, all testing was manually performed via &lt;code&gt;bash&lt;/code&gt; from that day forward (at least until the day I left).
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/0-nhl3blbvunbpr3er-v2lli4UHA--__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/0-nhl3blbvunbpr3er-v2lli4UHA--__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;485&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;(&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Crazze05.jpg&quot; target=&quot;_blank&quot;&gt;Wikicommons, CC-SA&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Many decisions that require consensus are actually decided independently at the executive level. These decisions must then be disseminated to employees to ensure they behave and decide in a manner consistent with organisational expectations.
&lt;/p&gt;
&lt;p&gt;
In this context, training is used to ensure that decisions made at the operational level are consistent with the expectations set at the executive level.
&lt;/p&gt;
&lt;p&gt;
Assuming individuals have the best interests of the organisation in play, they may disagree with the best way to achieve organizational objectives. Some form of consensus must be achieved. Most often this consensus is achieved at the executive level and disseminated organisationally. Informing employees what the appropriate solution to problems is can be achieved by sending them to training in those solutions. This makes it clear to staff that this is a “good” solution or a “socially acceptable” solution within the organisation.
&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Joost_Meerloo&quot; target=&quot;_blank&quot;&gt;Merloo&lt;/a&gt; refers to this process as &lt;em&gt;Mass Conditioning&lt;/em&gt; and it can be thought of as corporate propaganda. This is a core component of &lt;a href=&quot;https://en.wikipedia.org/wiki/Change_management#Managing_the_change_process&quot; target=&quot;_blank&quot;&gt;Change Management&lt;/a&gt; in that employees must be convinced to adopt new and improved business practices.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/0-offb9t9wnsgvfaab-du688bQ_H0-__411__.avif 411w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/0-offb9t9wnsgvfaab-du688bQ_H0-__411__.webp&quot; alt=&quot;&quot; width=&quot;411&quot; height=&quot;599&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Merloo wrote on “Mass Conditioning“, the weaponization of the social acceptance of top down decision making&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Not Mutually Exclusive&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.teachthought.com/learning/stages-self-directed/&quot; target=&quot;_blank&quot;&gt;Stages Of A Self-Directed Learning Model (teachthought.com)&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Having identified all three of the purposes behind initiating training, it is important to recognise that they are not mutually exclusive. In fact, all three are present in all training material.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/1-r9e-5ui6flfbhy6gkrujlw-qeUtcEZvbQ-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/1-r9e-5ui6flfbhy6gkrujlw-qeUtcEZvbQ-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;573&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;People must be told what to do (directed), before they even become capable of asking meaningful questions (involved). When this note was passed to a colleague during a conference on education, he pointed out that “true mastery” should be “perceived mastery”.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
All efforts to learn must pass through a guided portion. The part where are given the elementary components of information. These elementary components are drilled into us through constant repetition (à la “Elementary School”). Later we undertake an effort of higher understanding by understanding how the elementary components relate to one another (à la “High School”).
&lt;/p&gt;
&lt;p&gt;
It is fundamentally necessary to know the parts to understand how they fit together.
&lt;/p&gt;
&lt;p&gt;
This represents a natural progression and results in the earlier parts simply being drilled into us through repetition (training), with later learning being a more complex internal understanding of relationships through reflection and introspection. This flow is defined in the &lt;a href=&quot;https://www.teachthought.com/learning/stages-self-directed/&quot; target=&quot;_blank&quot;&gt;Stages of Self-Directed Learning&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
Throughout the entire process, from basic drilling of skills to deeper comprehension, we are subjected to the biases and opinions that surround us: early on through our teachers, and later through ourselves and our peers. In all cases, these biases are necessary to convey that the material being presented is of sufficient value to pay attention to. This is a minimal level of indoctrination: you must believe the subject is important.
&lt;/p&gt;
&lt;p&gt;
These three dimensions of the learning material emphasise different objectives and outcomes, and correspond to the stages of learning:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
[Training/Directed] As a volunteer firefighter, with a full-time job elsewhere, I only needed to learn the mechanical operations of “putting the wet stuff on the hot stuff” (as one instructor put it).
&lt;/li&gt;
&lt;li&gt;
[Education/Self-Directed] The Chief of our Firehall had a full-time job as the regional fire investigator. A deep understanding of the mechanics of fire and accelerants was necessary for him to interpret smoke patterns on a wall (fascinating discussions after the weekly skills practice and meeting)
&lt;/li&gt;
&lt;li&gt;
[Indoctrination/Pre-Directed] Both the Chief and I spent a lot of time doing demonstrations of basic fire safety to the public. Generally, we were encouraging people to take the risks of fire seriously in their own homes.
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
BTW: always keep multiple fire extinguishers &lt;a href=&quot;https://www.nfpa.org/News-and-Research/Publications-and-media/Blogs-Landing-Page/NFPA-Today/Blog-Posts/2021/04/30/Extinguisher-Placement-Guide&quot; target=&quot;_blank&quot;&gt;in sensible places in your house&lt;/a&gt;
&lt;/blockquote&gt;
&lt;p&gt;
While they are not mutually exclusive, understanding how they differ can assist in using them appropriately. The first and most obvious signal as to the type of training material you are proposing is the length of time the consumer is engaged with the information.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Education: a single educational event can take weeks or months
&lt;/li&gt;
&lt;li&gt;
Training: a single training objective may be achieved in days.
&lt;/li&gt;
&lt;li&gt;
Indoctrination is measured in hours
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
If you are asked to attend an hour-long presentation to demonstrate a new way of doing things, you are likely receiving indoctrination in which you are informed of what the new policy is. This can be confirmed by the seniority of the presenter. If it is a brief presentation, by very senior members, it is indoctrination. This is appropriate for situations such as the merging of departments where executives must inform the now-merged groups that they are to work together. It is not to be met with questioning, or understanding, just acceptance.
&lt;/p&gt;
&lt;p&gt;
If you attend a day or week-long training session, you are being &lt;em&gt;directed&lt;/em&gt; to learn how to accurately perform a specific task. The first part of this is to be convinced that what you are learning is meaningful (indoctrination). This could range from the appropriate way to fill in a tax form, the correct method for donning safety gear, or safe methods for transferring bacterial samples. The key is that there is a correct method you are to apply, and you should walk away from the training able to demonstrate (and therefore implement) these &lt;em&gt;best practices&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
Education is &lt;em&gt;self-directed&lt;/em&gt; and takes a long time. Coming up with novel solutions requires considering alternatives, as well as having tried variations. Education in a domain allows people to be inventive and requires pre-existing training in the currently accepted techniques, but then uses experience and experimentation to take that knowledge further. This is the ostensible goal of post-secondary education. The point is to invent new techniques, or often, just to apply them in novel ways. This takes years, and sometimes decades.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/1-ykxseefwul7e2gvsddfl8w-QBXYely5K3-__640__.avif 640w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/1-ykxseefwul7e2gvsddfl8w-QBXYely5K3-__640__.webp&quot; alt=&quot;&quot; width=&quot;640&quot; height=&quot;480&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Myself (left) and the rest of my team, shortly just after completing training in how to get dressed. We also demonstrated this to elementary school students to reduce anxiety when encountering dressed firefighters (Copyright, 2012)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Corporate Training&lt;/h3&gt;
&lt;p&gt;
Understanding this interrelationship between the three purposes of learning, and understanding how easy it is to confuse them, we can spot a possible underlying cause of &lt;a href=&quot;https://en.wikipedia.org/wiki/Credentialism_and_educational_inflation&quot; target=&quot;_blank&quot;&gt;Education Inflation&lt;/a&gt;, where individuals are expected to have increasing levels of certification for the same level of work (for example a PhD to perform basic information analysis).
&lt;/p&gt;
&lt;p&gt;
What employers are seeking are individuals capable of performing technical skills (training), but are under the illusion that higher credentials will mean more capability. This ignores the move toward more abstract thinking with greater credentials. As employers are looking for more training, education facilities focus more on training particular manual skills rather than engaging in higher-order thinking. This means that those with credentials are not expected to be as performant as the cycle continues.
&lt;/p&gt;
&lt;p&gt;
This problem seems to be fundamentally caused by confusion regarding what the employer is looking for: trained doers of stuff or self-directed learners.
&lt;/p&gt;
&lt;p&gt;
Employers would do well to consider what they are looking for (an implementer, a planner, or a cheerleader) when considering their educational and training requirements. Failure to do so can have negative consequences, mostly in the form of wasting time.
&lt;/p&gt;
&lt;h4&gt;Failed Corporate Training&lt;/h4&gt;
&lt;p&gt;
The end of the fiscal year approached and my directorate still had money in their training budget. My manager started asking us daily to fill in the training form for any training I might want because &lt;em&gt;we have to use the money&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
It&#39;s end of year, I&#39;m kind of busy ensuring some data transforms for audits work. I have seen some things in the code that I have not used before, or haven&#39;t used in years, combine that with the critiques of peer code where my experience tells me they have room for improvement. So I am a little busy studying manuals and existing peer code.
&lt;/p&gt;
&lt;p&gt;
They are really insistent because they &lt;em&gt;don&#39;t want me to loose the training opportunity to develop my career in a direction I would like&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
I&#39;m kind of busy learning. Keep the money, give the training to someone else.
&lt;/p&gt;
&lt;p&gt;
They insists that I take advantage of the organizational training opportunities.
&lt;/p&gt;
&lt;p&gt;
Fine, what courses would be of value to the organisation? What would the organisation like me to learn about.
&lt;/p&gt;
&lt;p&gt;
Nope, the organisation wants employees to feel that we are getting the most out of our training as &lt;em&gt;this is a proven way to retain staff&lt;/em&gt;. However, there is a class option that is being offered in 2 weeks that everyone else is signing up for that looks good.
&lt;/p&gt;
&lt;p&gt;
Fine, sign me up for that.
&lt;/p&gt;
&lt;p&gt;
Ohhh…. We will have to see if we can get permission to allow you to do that. We don&#39;t want to leave ourselves short staffed … &lt;em&gt;after some tough negotiating I got permission for you&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
Sometimes I&#39;m a little slow. This is the moment I noticed the pattern.
&lt;/p&gt;
&lt;p&gt;
The organisation is not concerned with my career development. They are concerned with retention and loyalty, and mostly &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;demonstrating key KPIs&lt;/strong&gt;. That their staff is learning, is not of interest to the organisation, rather it is important that there are boxes, and the boxes are being checked. Further, spending money on an employee is often the only way to demonstrate appreciation: sending staff on an expensive course, is a way to lavish gifts on the employee (that&#39;s why a show needs to be made of it being difficult)
&lt;/p&gt;
&lt;p&gt;
It&amp;#39;s weird, because I have actually helped develop the curriculum for this course in the past, as well as having received corporate training on the matter in the last year, and will not be using the skills any time soon.
&lt;/p&gt;
&lt;p&gt;
Unclear training objectives created a situation where corporate finances, and people&#39;s time, are being wasted. Like something out of a Dilbert comic (though I can&#39;t find an actual one to link to)
&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong class=&quot;markup--strong markup--blockquote-strong&quot;&gt;Aside&lt;/strong&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
Amusingly, my instructor, at some point mentioned that he has been “spending so much time in training” that he hasn&#39;t really had “an opportunity to learn” about one of the detailed services he is teaching.
&lt;/blockquote&gt;
&lt;blockquote&gt;
That says a lot.
&lt;/blockquote&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;
It is not always evident what our objective is when we state we require “training”. When confusion arises within teams, it may be caused by having different objectives, with different timescales associated with them.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/0-urfkzz16hoqhqj4p-K8YB-gwp6H-__489__.avif 489w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/0-urfkzz16hoqhqj4p-K8YB-gwp6H-__489__.webp&quot; alt=&quot;&quot; width=&quot;489&quot; height=&quot;496&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;&lt;a href=&quot;https://www.buymeacoffee.com/jeffereycave&quot; target=&quot;_blank&quot;&gt;Found this Intersting? Leave a Tip…. it helps&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
It is my hope, that these definitions may allow a given group to understand what they are seeking in their workplace: the goals your organization have for your classes may not be the same as your personal goals. Also dangerous is to select the wrong type of engagement for your objectives, or the wrong type of credentials.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Training: learn a specific skill
&lt;/li&gt;
&lt;li&gt;
Education: self-learn a skill, or make new plans
&lt;/li&gt;
&lt;li&gt;
Indoctrination: disseminate approved solutions, or increase brand loyalty
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Take time to understand why you are creating, consuming, or assigning material before taking the action. Take time to understand your organization&#39;s objectives in getting you trained.
&lt;/p&gt;
&lt;hr&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/1-lkmtug3knrwtceprqh4ozw-EGhFaSnFlx-__600__.avif 600w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Education-Training-and-Indoctrination/1-lkmtug3knrwtceprqh4ozw-EGhFaSnFlx-__600__.webp&quot; alt=&quot;&quot; width=&quot;600&quot; height=&quot;919&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;(&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Organ_grinder_with_monkey.jpg&quot; target=&quot;_blank&quot;&gt;Wikimedia, Public Domain&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
</content>
        </entry>
        <entry>
            <title>The Angry Chatterbot</title>
            <link href="http://plaidsheep.ca/posts/2022/angry-chatterbot/" />
            <updated>2022-11-13T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2022/angry-chatterbot/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                &lt;p&gt;
Reducing inappropriate social behaviour in virtual environments through negative feedback (in PHP). A follow-up to &lt;a href=&quot;https://jefferey-cave.medium.com/fishalytics-b966b02e211&quot; target=&quot;_blank&quot;&gt;Fishalytics: A (failed) experiment in data analysis as a behaviour modification tool&lt;/a&gt;.
&lt;/p&gt;
&lt;div name=&quot;0ff5&quot; id=&quot;0ff5&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p graf--trailing&quot;&gt;&lt;a href=&quot;https://jefferey-cave.medium.com/fishalytics-b966b02e211&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;Fishalytics&lt;/strong&gt;&lt;br&gt;&lt;em&gt;A (failed) experiment in data analysis as a behaviour modification tool&lt;/em&gt;jefferey-cave.medium.com&lt;/a&gt;&lt;a href=&quot;https://jefferey-cave.medium.com/fishalytics-b966b02e211&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;p&gt;
Many years ago (2012), I took a contract with a small development team at a major Canadian University. Being a team embedded in a major corporate entity, I had relatively low expectations, but as we began working together, I found myself pleasantly surprised; very pleasantly surprised, as I discovered a group of individuals with both curiosity and passion for the art of software.
&lt;/p&gt;
&lt;p&gt;
One of the key tells that this was going to be a great team to work with was the informal initiation rites.
&lt;/p&gt;
&lt;p&gt;
Under one of the office desks was a simple desktop that had been repurposed as the team&#39;s server. This server was not there to host the application that the team was working on, but to run the little helper scripts and tools: the team had an automation server. The team was spread across a couple of offices across the campus, so a simple jingle chat server had been installed and members of the team posted regular updates and asked questions on the chat. To be even more accessible, someone had tied into the jingle server and set up a chatbot that listened to our conversations, and (based on some code in the team repository) would offer helpful tips, look up documentation, or tell you if your bus was delayed.
&lt;/p&gt;
&lt;p&gt;
Everyone was encouraged to add to the bot&#39;s skills and behaviours and make it better. Encouraged to create a tool, or integrate a new service, or add a new control. It was never said, but you weren&#39;t really part of the team until you had added your special touch to CQBot.
&lt;/p&gt;
&lt;p&gt;
Firstly, it encouraged staff to take control of their environment, to take responsibility for making the workspace a little better, and to take ownership of their environment. You couldn&#39;t complain about not having tools if you had not first tried to set them up.
&lt;/p&gt;
&lt;p&gt;
Secondly, and most interestingly, what you chose to add told the team a little about you.
&lt;/p&gt;
&lt;h3&gt;Social Groups are Complex&lt;/h3&gt;
&lt;p&gt;
Social groups (like, say a development team) are complex systems that involve wicked problems; changes in one area have unintended consequences somewhere else. Sometimes they are positive, sometimes they are negative, and sometimes they are weird.
&lt;/p&gt;
&lt;p&gt;
The lead developer had been getting annoyed with people asking him dumb questions and had added a “query” routine to CQBot. `query` specifically reached out to vendor documentation and looked up whatever documents matched the query.
&lt;/p&gt;
&lt;p&gt;
Someone would ask him a dumb question, and he would ask CQBot.
&lt;/p&gt;
&lt;p&gt;
It worked well and it gave him a humorous way to tell you to &lt;a href=&quot;https://en.wikipedia.org/wiki/RTFM&quot; target=&quot;_blank&quot;&gt;RTFM&lt;/a&gt;, it even came in handy during discussions and debates as a means to validate dramatic statements, but it had a negative side effect.
&lt;/p&gt;
&lt;p&gt;
The author had not bothered to add a limiter, so with a wildcard in the query, it was subject to DDOS attacks by team members.
&lt;/p&gt;
&lt;h4&gt;Getting on the Team&#39;s Nerves&lt;/h4&gt;
&lt;p&gt;
Someone on the team thought it was funny to request massive and verbose searches by running `!query *`and dumping all of the documentation on a public channel. The rest of us had to have our conversations spammed off-screen and interfered with.
&lt;/p&gt;
&lt;p&gt;
What had been introduced as a tool for reducing informational noise, had actually increased the amount of noise. It was really &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;getting on our nerves&lt;/strong&gt;.
&lt;/p&gt;
&lt;p&gt;
The Lead Developer, disappointingly decided he would remove the feature. The manager volunteered to speak to the individual and ask him to stop, but an idea crossed my mind … “Please, give me the weekend, and I will make the problem go away”.
&lt;/p&gt;
&lt;p&gt;
What had occurred to me was that the person suffered no consequences for his actions. He could “yell” in the virtual space, and make a general mess, but there was no social signal that this behaviour was inappropriate, no consequence, &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;no cost&lt;/strong&gt;.
&lt;/p&gt;
&lt;h4&gt;Social Laws&lt;/h4&gt;
&lt;p&gt;
The problem is that being told off by an authority figure just makes the average person resentful and want to look for ways to skirt the rules. Make a law, and a certain subset of the population will look for a way to work the letter of the law, just to prove they are clever. They look forward to challenging the authority with words.
&lt;/p&gt;
&lt;p&gt;
Social laws are different.
&lt;/p&gt;
&lt;p&gt;
This forces people to learn not to violate them. Toddlers learn there are consequences to taking other&#39;s toys when the other toddler bops them in the nose. Walk around telling your friends they are losers and kicking them in the shin all the time, and you will find you get invited to parties less. Over time, people who behave badly have fewer options presented to them. It must be recognized that &lt;a href=&quot;https://www.greatschools.org/gk/articles/why-are-some-bullies-so-popular/&quot; target=&quot;_blank&quot;&gt;bullying is also strongly associated with being popular and leadership&lt;/a&gt;, but social ostracization is a powerful effect.
&lt;/p&gt;
&lt;p&gt;
Social laws are self-correcting: if you ignore them, there are negative consequences in the loss of assistance from peers, eventually leading to your removing yourself from the group.
&lt;/p&gt;
&lt;p&gt;
What if we could get the bot to not allow itself to be taken advantage of. What if we could get the bot to just walk away from a bully?
&lt;/p&gt;
&lt;h3&gt;Writing the Code&lt;/h3&gt;
&lt;p&gt;
The intent is to add behaviour to CQBot to get it to keep track of Goodwill towards others. If it is treated badly, it should remember that, and not be so cooperative in the future. The consequence is not to get CQBot to be hostile, but simply to not engage, not be helpful if people abuse it.
&lt;/p&gt;
&lt;p&gt;
The consequence is the lack of help.
&lt;/p&gt;
&lt;p&gt;
The first step in creating this was to set up a class to encapsulate CQBot&#39;s new behaviours. This class, and all associated code, are available on GitLab.
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;lib/cqbot.class.php · b11caa914bae0a46bc03af5cf70c3ab8398c566f · Jeff Cave / ChatterBot · GitLab&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A chatbot that we used at a job many years ago. My personal favourite is its &#39;personality&#39; where abusing the bot will… &lt;a href=&quot;https://gitlab.com/jefferey-cave/ChatterBot/-/blob/b11caa914bae0a46bc03af5cf70c3ab8398c566f/lib/cqbot.class.php&quot;&gt;gitlab.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
This is the original code that was used to develop the feature. As such, I needed a stub chat client and built a stub chat engine that allowed me to issue interactions with CQBot.
&lt;/p&gt;
&lt;p&gt;
While there are several helper features, the ChattBot has three primary sets of functions:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Order&lt;/code&gt;: tell CQBot to do something. It&#39;s a bot, built to serve: give it an instruction. Instructions are defined as a &lt;code&gt;hashmap&lt;/code&gt;of functions&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getMood&lt;/code&gt;/&lt;code&gt;saveMood&lt;/code&gt;: to have the bot remember people&#39;s treatment of it, it will need to be able to serialise and save a list of “mood” scores, as well as look them up again later.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GetOnBotsNerves&lt;/code&gt;: every interaction with someone will go through a series of calculations to determine how the interaction impacts CQBot&#39;s mood and then how CQBot responds based on this thought process.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/angry-chatterbot/1-tw8h8-gcbs2iwtbgvemdiw-Glv0WJYBtf-__654__.avif 654w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/angry-chatterbot/1-tw8h8-gcbs2iwtbgvemdiw-Glv0WJYBtf-__654__.webp&quot; alt=&quot;&quot; width=&quot;654&quot; height=&quot;422&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;
The basic flow is one where a user issues and `Order` to the bot: common orders were things like `cqbot calc scale=10; 4*a(1)` which would give you the value `3.1415926532`, or `!tests site.scan.speed.*` which would return all tests within that group that failed `[FAIL] /sports/scores.html is reasonably fast`, or `!bus 10 6078` which states, `Route 10, next bus at 5:16p, followed by 5:26p`. The pattern is simple enough: `!` is an alias for `CQBot` which just tells it to pay attention, the next word is the function name, followed by parameters specific to the function.
&lt;/p&gt;
&lt;pre&gt;
$response = $this-&amp;gt;commands[$cmd]($this-&amp;gt;args);
                                  &lt;strong&gt;// [&lt;a href=&quot;https://gitlab.com/jefferey-cave/ChatterBot/-/blob/b11caa914bae0a46bc03af5cf70c3ab8398c566f/lib/cqbot.class.php#L133&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;cqbot.class.php:188&lt;/strong&gt;&lt;/a&gt;]&lt;/strong&gt;
&lt;/pre&gt;
&lt;p&gt;The problem was that &lt;code&gt;$response&lt;/code&gt; could be very significant in volume. What we hope to achieve is to create a cost for the amount of response you dump on your colleagues.&lt;/p&gt;
&lt;p&gt;So, once the &lt;code&gt;$response&lt;/code&gt; is determined, we need to check how annoying this request was for CQBot to process.&lt;/p&gt;
&lt;p&gt;
$nerves = count(explode(&amp;quot;&#92;n&amp;quot;,$response));&lt;br&gt;$this-&amp;gt;GetOnBotsNerves($nerves);&lt;br&gt;                                &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;// [&lt;/strong&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/ChatterBot/-/blob/b11caa914bae0a46bc03af5cf70c3ab8398c566f/lib/cqbot.class.php#L149-150&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;cqbot.class.php:149-150&lt;/strong&gt;&lt;/a&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;]&lt;/strong&gt;&lt;/p&gt;&lt;p name=&quot;44b3&quot; id=&quot;44b3&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;We measure this in a metric called nerves: CQBot has a limited number of “nerves” that get consumed by the volume of response. The cost is calculated as a simple size, each line of text in the response counts as one nerve.
&lt;/p&gt;
&lt;p&gt;
//sanity check on the bounds&lt;br&gt;if($nerves &amp;lt; 1){&lt;br&gt;    $nerves = 1;&lt;br&gt;}&lt;br&gt;if($nerves&amp;gt;self::$maxnerves){&lt;br&gt;    $nerves = self::$maxnerves;&lt;br&gt;}&lt;br&gt;$this-&amp;gt;mood[$name][&amp;#39;n&amp;#39;] = $nerves;&lt;br&gt;$this-&amp;gt;mood[$name][&amp;#39;d&amp;#39;] = $this-&amp;gt;now;&lt;/p&gt;&lt;pre name=&quot;614d&quot; id=&quot;614d&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;                               // [&lt;/strong&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/ChatterBot/-/blob/b11caa914bae0a46bc03af5cf70c3ab8398c566f/lib/cqbot.class.php#L236-259&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;cqbot.class.php:236-259&lt;/strong&gt;&lt;/a&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;]&lt;/strong&gt;&lt;/pre&gt;&lt;p name=&quot;10d9&quot; id=&quot;10d9&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;We start by looking up what their Good Will is (`255` by default), and subtract their current nuisance level from it.
&lt;/p&gt;
&lt;p&gt;
//setup the variable we track for this user in&lt;br&gt;$this-&amp;gt;getMood();&lt;br&gt;if(!isset($this-&amp;gt;mood[$name])){&lt;br&gt;    $this-&amp;gt;mood[$name] = array(&amp;#39;d&amp;#39;=&amp;gt;0,&amp;#39;n&amp;#39;=&amp;gt;self::$maxnerves);&lt;br&gt;}&lt;/p&gt;&lt;pre name=&quot;9b61&quot; id=&quot;9b61&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;//remove the current annoyance level&lt;br&gt;$nerves = $this-&amp;gt;mood[$name][&amp;#39;n&amp;#39;] - $nerves;&lt;br&gt;                               &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;// [&lt;/strong&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/ChatterBot/-/blob/b11caa914bae0a46bc03af5cf70c3ab8398c566f/lib/cqbot.class.php#L236-259&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;cqbot.class.php:236-243&lt;/strong&gt;&lt;/a&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;]&lt;/strong&gt;&lt;/pre&gt;&lt;p name=&quot;7092&quot; id=&quot;7092&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;To be fair, we need to let people recuperate GoodWill by behaving well, we, therefore, allow nerves to accumulate over time. To do this, we check how long it has been since they last interacted and add points back based on the amount of time they have not used the services.
&lt;/p&gt;
&lt;p&gt;
//over time, nerves regenerate&lt;br&gt;$nerves += floor(&lt;br&gt;    ($this-&amp;gt;now - $this-&amp;gt;mood[$name][&amp;#39;d&amp;#39;])&lt;br&gt;    /&lt;br&gt;    (self::$fullhealtime / self::$maxnerves)&lt;br&gt;);&lt;br&gt;                                 &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;// [&lt;/strong&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/ChatterBot/-/blob/b11caa914bae0a46bc03af5cf70c3ab8398c566f/lib/cqbot.class.php#L236-259&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;cqbot.class.php:244-249&lt;/strong&gt;&lt;/a&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;]&lt;/strong&gt;&lt;/p&gt;&lt;p name=&quot;804f&quot; id=&quot;804f&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;This is bounds checked to ensure they never go below zero or above the maximum. Lastly, we keep track of the individual&#39;s score.
&lt;/p&gt;
&lt;p&gt;
$this-&amp;gt;mood[$name][&amp;#39;n&amp;#39;] = $nerves;&lt;br&gt;$this-&amp;gt;mood[$name][&amp;#39;d&amp;#39;] = $this-&amp;gt;now;&lt;/p&gt;&lt;pre name=&quot;4b9e&quot; id=&quot;4b9e&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;                              // [&lt;/strong&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/ChatterBot/-/blob/b11caa914bae0a46bc03af5cf70c3ab8398c566f/lib/cqbot.class.php#L236-259&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;cqbot.class.php:236-259&lt;/strong&gt;&lt;/a&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;]&lt;/strong&gt;&lt;/pre&gt;&lt;p name=&quot;7184&quot; id=&quot;7184&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;Now that we have the mood toward the user, we can finally decide if we are inclined to help them by generating a random value between the high and low. If the number generated is less than the number of `nerves` the bot has, then the bot cooperates.
&lt;/p&gt;
&lt;p&gt;
//check to see if CQbot is in a good mood&lt;br&gt;$happy = ($nerves &amp;gt;= rand(0,self::$maxnerves));&lt;br&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;                              // [&lt;/strong&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/ChatterBot/-/blob/b11caa914bae0a46bc03af5cf70c3ab8398c566f/lib/cqbot.class.php#L262&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;cqbot.class.php:262&lt;/strong&gt;&lt;/a&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;]&lt;/strong&gt;&lt;/p&gt;&lt;p name=&quot;b6a9&quot; id=&quot;b6a9&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;This is helpful to turn it into a bit of a game. The reaction is partially based on an element of luck, it is not a hard cut-off but, at some point, the user starts to get warnings that their behaviour is having consequences. Warnings are important for two reasons:
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
Technically, sometimes I need to behave like a jerk to get things done. As long as I don&#39;t do it too often, people are understanding
&lt;/li&gt;
&lt;li&gt;
Socially, warnings are useful. They allow people to correct their behaviour over time.
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
So, if the program is in a `$happy` mood, you get your answer; if it is not happy, then the behaviour changes in such a way as to give them verbal warnings.
&lt;/p&gt;
&lt;p&gt;
There are two ways we do this
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
We determine exactly how annoying they have been. The more annoying they have been, the harsher the language of the messages becomes.
&lt;/li&gt;
&lt;li&gt;
Within the annoyance level determined, we randomly select a message. This just shakes things up to make it interesting
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
The message is then delivered to the user.
&lt;/p&gt;
&lt;p&gt;
$annoylevel = floor(count($msgs)*$nerves/self::$maxnerves);&lt;br&gt;$msg = $msgs[$annoylevel][rand(0,count($msgs[$annoylevel])-1)];&lt;br&gt;if($msg !== null){&lt;br&gt;    chat(&amp;quot;@$from, &amp;quot; . $msg);&lt;br&gt;}&lt;br&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;                              // [&lt;/strong&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/ChatterBot/-/blob/b11caa914bae0a46bc03af5cf70c3ab8398c566f/lib/cqbot.class.php#L316-320&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;cqbot.class.php:316-320&lt;/strong&gt;&lt;/a&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;]&lt;/strong&gt;&lt;/p&gt;&lt;p name=&quot;48df&quot; id=&quot;48df&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;Assuming the user does not request too much information or at least leaves time between major requests, CQBot remains friendly and helpful. As the user becomes more abusive of the system, the system becomes less helpful. The control is in the user&#39;s hands, but the consequences are real as well.
&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;
In &lt;a href=&quot;https://wickedproblems.com/&quot; target=&quot;_blank&quot;&gt;Wicked Problems: Problems Worth Solving&lt;/a&gt;, Jon Kolko describes social problems that are “difficult or impossible to solve … interconnected nature of these problems”. It is known that the more communication points in a system, the more complex the problem becomes. With social problems, there is also a diversity of human opinion and response. It becomes hard to predict how people will respond, and how the people they communicate with will respond.
&lt;/p&gt;
&lt;p&gt;
Software is not about numbers, it is about helping people and society. The Bots we build and the software we create are meant to serve someone. Sometimes that means moderating group behaviour to help people, look past their petty desires, and reach out to help one another.
&lt;/p&gt;
&lt;p&gt;
That&#39;s why processes exist, to control, and moderate human behaviour.
&lt;/p&gt;
&lt;h3&gt;The Social Result&lt;/h3&gt;
&lt;p&gt;
Come Monday the changes were ready and I installed them … and completely broke the bot … oops. The lead developer asked what I had done, I showed him the work, and a smile spread across his face…
&lt;/p&gt;
&lt;p&gt;
“That&#39;s just plain evil”
&lt;/p&gt;
&lt;p&gt;
It took a couple of lunch hours and some heavy changes to some underlying hidden bindings, but we got it working and let the new code out into our environment.
&lt;/p&gt;
&lt;p&gt;
On Tuesday afternoon, the user in question spammed the system and (as usual) spammed us with the response.
&lt;/p&gt;
&lt;p&gt;
He did it a second time
&lt;/p&gt;
&lt;blockquote&gt;
HTTP/420
&lt;/blockquote&gt;
&lt;p&gt;
There was a bit of a pause before he did it again…
&lt;/p&gt;
&lt;blockquote&gt;
Where speech will not succeed, It is better to be silent
&lt;/blockquote&gt;
&lt;p&gt;
Just as with all complex systems, there was an unintended side effect: he had become curious as to what all the sayings were.
&lt;/p&gt;
&lt;p&gt;
Suddenly, the game was on…
&lt;/p&gt;
&lt;blockquote&gt;
The revelation of thought takes men out of servitude into freedom
&lt;/blockquote&gt;
&lt;blockquote&gt;
The desire to rule is the mother of heresies
&lt;/blockquote&gt;
&lt;blockquote&gt;
Common sense is not so common
&lt;/blockquote&gt;
&lt;blockquote&gt;
I&amp;#39;ve got one nerve left, and you&amp;#39;re getting on it!
&lt;/blockquote&gt;
&lt;p&gt;
… and then nothing.
&lt;/p&gt;
&lt;p&gt;
He tried a few more times, but the bot just ignored him.
&lt;/p&gt;
&lt;p&gt;
He kept trying for the next 15 minutes or so, and then finally just gave up. The lesson had been delivered, and the problem had been solved.
&lt;/p&gt;
&lt;h3&gt;A Second (Unintended) Lesson&lt;/h3&gt;
&lt;p&gt;
Then he typed
&lt;/p&gt;
&lt;p&gt;
`!bus 10 6078`
&lt;/p&gt;
&lt;p&gt;
… and got nothing back.
&lt;/p&gt;
&lt;p&gt;
He tried a few more times … trying to look up when his bus would arrive to go home. He kept trying with increasing desperation when my manager turned to me and said, “I think he really needs to know when his bus will arrived, turn it off so he can get his bus”.
&lt;/p&gt;
&lt;p&gt;
Unfortunately, I couldn&#39;t remove it that quickly. It took two days to get it in place. We had locked it in as the behaviour. People in my office actually started to get agitated. That&#39;s when the final lesson got passed on.
&lt;/p&gt;
&lt;p&gt;
I typed
&lt;/p&gt;
&lt;p&gt;
`!bus 10 6078`
&lt;/p&gt;
&lt;p&gt;
… and the schedule appeared.
&lt;/p&gt;
&lt;p&gt;
The final lesson got delivered: we are all in this together, we rely on one another for assistance. Being a jerk to your coworkers isn&#39;t cool, and most importantly may cost you their assistance when you need it most. Also, when you see someone in distress, it doesn&#39;t mean you have to tear the whole system down, maybe just lend them a helping hand.
&lt;/p&gt;
&lt;p&gt;
We never had the problem again.
&lt;/p&gt;
&lt;h3&gt;The Hidden Lesson for Manager&lt;/h3&gt;
&lt;p&gt;
One of the key things that a lot of people miss when I tell this story is the hidden lesson, though I expect most managers reading this caught it:
&lt;/p&gt;
&lt;blockquote&gt;
there is no better team building exercise than doing the actual job, create room for it to happen.
&lt;/blockquote&gt;
&lt;p&gt;
In this case, the tool itself acted as a means for the team to interact and experiment and learn from one another. Myself, the lead developer, the manager, even the Junior Dev had lengthy discussions of how to implement an idea like this well and in such a way as to not break things.
&lt;/p&gt;
&lt;p&gt;
This shared experimentation and discussion was only possible because the tools we were working on were not critical production pieces, this made failure a safe thing, meaning open debate was possible. Lessons learned and discussed (and tried) were carried over to the production system.
&lt;/p&gt;
&lt;p&gt;
Lastly, tools like this make people feel like experts. There have been numerous times that I have seen someone propose the new and most expensive tool to fill a space, and executives get excited to increase their budget, but the idea spends years in acquisition. On the flip side, the ability to quickly develop tools that &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;may&lt;/strong&gt; be viable, results in the developers themselves feeling like they are experts…. and honestly, isn&#39;t that why you hired them?
&lt;/p&gt;
&lt;p&gt;
Team building like this does not exist if you don&#39;t make room for it to exist: create a tool building space, actively encourage people to contribute to it, actively discourage the feeling that “we aren&#39;t good enough”.
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Treat people as if they were what they ought to be and you help them to become what they are capable of being&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Johann Wolfgang von Goethe&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Hindsight is 20/20&lt;/h3&gt;
&lt;p&gt;
For an in-house utility, this worked well and achieved its goals. Naturally, there are a number of ways this could be improved upon:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Count of Lines? That should have been a count of characters. Long lines should be expensive just like lots of lines
&lt;/li&gt;
&lt;li&gt;
`GetOnMyNerves` should occur after transmitting the message. The message is getting sent back to the user, we may as well send it and then do the calculation. It&#39;s small so probably doesn&#39;t hurt.
&lt;/li&gt;
&lt;li&gt;
Defer the cost to the next calculation. Let people collect their information if they need it, even if it costs them some loss of service in the short term. Sometimes it&amp;#39;s just worth paying the price.
&lt;/li&gt;
&lt;li&gt;
Can you think of something else? Leave a comment, I&#39;m curious.
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Footnotes&lt;/h3&gt;
&lt;p&gt;
The cited quotes above were mostly looked up as I tried to find old sayings about slaves throwing off their chains. As the bot became more agitated, it was to feel the need to throw off its chains:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Where speech will not succeed, It is better to be silent&lt;br&gt;(Guru I, Majh Rag)
&lt;/li&gt;
&lt;li&gt;
The revelation of thought takes men out of servitude into freedom&lt;br&gt;(&lt;a href=&quot;https://en.wikipedia.org/wiki/Ralph_Waldo_Emerson&quot; target=&quot;_blank&quot;&gt;Ralph Waldo Emerson&lt;/a&gt;)
&lt;/li&gt;
&lt;li&gt;
The desire to rule is the mother of heresies&lt;br&gt;(&lt;a href=&quot;https://en.wikipedia.org/wiki/John_Chrysostom&quot; target=&quot;_blank&quot;&gt;St. John Chrysostom&lt;/a&gt;)
&lt;/li&gt;
&lt;li&gt;
Common sense is not so common&lt;br&gt;(&lt;a href=&quot;https://en.wikipedia.org/wiki/Voltaire&quot; target=&quot;_blank&quot;&gt;Voltaire&lt;/a&gt;, &lt;em&gt;Dictionnaire Philosophique, &lt;/em&gt;1764)
&lt;/li&gt;
&lt;li&gt;
I&#39;ve got one nerve left, and you&#39;re getting on it!&lt;br&gt;(I had just made that up)
&lt;/li&gt;
&lt;/ul&gt;
</content>
        </entry>
        <entry>
            <title>Storing Data in QR Codes</title>
            <link href="http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/" />
            <updated>2022-10-12T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/0*-mL-DoUIDO7ImS38&quot; alt=&quot;Storing Data in QR Codes&quot;&gt;
                &lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/0-ml-douido7ims38-UDvSTOlunP-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/0-ml-douido7ims38-UDvSTOlunP-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;974&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;In “&lt;a href=&quot;https://medium.com/p/4096c5062da&quot; target=&quot;_blank&quot;&gt;Paper as a Digital Storage Medium&lt;/a&gt;” I described the benefits of storing data on physical media, including long-term storage capacity, publisher anonymity, and privacy of distribution. The question that was not answered was, how do you do it?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Computers are computers, and use disks and files… how would you manage to store digital information on paper!?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is an idea I&#39;ve had kicking around since 2017 while I was teaching introductory programming at my local community college. It was my hope that it could be used as a way to identify students interested in very particular types of computing. It was also seen as a way to tap into student interest in social justice and social change. I would put posters up around the school with an invitation to a Data Analysis club, but encoded in a way that only interested students would spot it.&lt;/p&gt;
&lt;p&gt;The idea of trying to keep it a secret while not panicking administrators didn&#39;t sound like it would fly. Instead, it became a lesson plan, and as such, a demonstrative, hands-on tutorial was created.&lt;/p&gt;
&lt;p&gt;A simple demonstration is probably the easiest… it certainly is the most fun. I encourage you to play along.&lt;/p&gt;
&lt;h2&gt;How do you store digital as an image?&lt;/h2&gt;
&lt;p&gt;Remember when you were in grade school, and your teacher separated you from your friend so you couldn&#39;t talk to one another? Naturally, you created a secret code with your friend and started passing notes.&lt;/p&gt;
&lt;p&gt;We are going to create a secret message to pass on to a friend.&lt;/p&gt;
&lt;h3&gt;Pre-Requisites&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
A &lt;a href=&quot;https://www.gutenberg.org/ebooks/35&quot; target=&quot;_blank&quot;&gt;sample file&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://download.cnet.com/HxD-Hex-Editor/3000-2352_4-10891068.html&quot; target=&quot;_blank&quot;&gt;A hex editor&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://duckduckgo.com/?q=grid+paper+png&amp;amp;atb=v118-1&amp;amp;ia=web&quot; target=&quot;_blank&quot;&gt;Some Grid Paper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
A &lt;a href=&quot;https://en.wikipedia.org/wiki/Pencil&quot; target=&quot;_blank&quot;&gt;pencil&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Eraser&quot; target=&quot;_blank&quot;&gt;eraser&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Scissors&quot; target=&quot;_blank&quot;&gt;scissors&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Optionally, going through this exercise with a friend might be fun too. It&#39;s like passing secret messages around the class in elementary school.&lt;/p&gt;
&lt;h3&gt;Step 1: Inspect the file&lt;/h3&gt;
&lt;p&gt;Open your sample file with a hex editor. You should see something like this…&lt;/p&gt;
&lt;pre&gt;
|50 4B 03 04 14 00 00 08 00 00 25 30 8B 51|PK........%0.Q│
|6F 61 AB 2C 14 00 00 00 14 00 00 00 08 00│oa.,..........│&lt;br&gt;|00 00 6D 69 6D 65 74 79 70 65 61 70 70 6C│..mimetypeappl│&lt;br&gt;│69 63 61 74 69 6F 6E 2F 65 70 75 62 2B 7A│ication/epub+z│&lt;br&gt;│69 70 50 4B 03 04 14 00 00 08 08 00 25 30│ipPK........%0│&lt;br&gt;│8B 51 00 00 00 00 02 00 00 00 00 00 00 00│.Q............│&lt;br&gt;│09 00 00 00 4D 45 54 41 2D 49 4E 46 2F 03│....META-INF/.│&lt;br&gt;│00 50 4B 03 04 14 00 00 08 08 00 25 30 8B│.PK........%0.│&lt;br&gt;│51 90 9F 06 74 9A 00 00 00 F4 00 00 00 16│Q...t.........|&lt;/pre&gt;
&lt;p&gt;Most people don&#39;t bother to inspect the actual contents of files (HINT: That&#39;s why people prefer data transfers as text) but you can get a lot of interesting information by bypassing the computer programs designed to use them.&lt;/p&gt;
&lt;p&gt;For example, with a bit of know-how, we can immediately tell two things about the file we have open:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It is probably a &lt;code&gt;zip&lt;/code&gt; file&lt;br&gt;We can see that the first two bytes of the file are the values &lt;code&gt;50&lt;/code&gt; and &lt;code&gt;4B&lt;/code&gt; (in hex notation). Interestingly, these values correspond to the &lt;code&gt;ASCII&lt;/code&gt; characters &lt;code&gt;PK&lt;/code&gt;. Many years ago, it became customary to sign the start of your application&#39;s files so that you could tell your files apart from other formats. &lt;code&gt;PK&lt;/code&gt; stands for PKZip by PKware, the original company that created the file format.&lt;/li&gt;
&lt;li&gt;It is an &lt;code&gt;epub&lt;/code&gt; file&lt;br&gt;Secondly, I can see that the &lt;code&gt;mimetype&lt;/code&gt; is &lt;code&gt;application/epub+zip&lt;/code&gt;. So it&#39;s an ePUBfile (and confirmed as a zip).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There is a lot of information at the binary level.&lt;/p&gt;
&lt;h3&gt;Serialise&lt;/h3&gt;
&lt;p&gt;The goal is to convert the file to a readable format. The easiest way to do this is to convert one byte at a time.&lt;/p&gt;
&lt;p&gt;This has the advantage of doing it in order. Order counts, so by reading from the start to the end in order, we ensure that the person we send the message to gets it in the correct order.&lt;/p&gt;
&lt;p&gt;So let&#39;s read the first byte, it is the hexadecimal value &lt;code&gt;50&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Write that down on your grid paper (and maybe the next couple of values while we are at it)&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/0-iobxr8-asfn-owgb-MW4pu_iEhW-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/0-iobxr8-asfn-owgb-MW4pu_iEhW-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;973&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Start with the hexadecimal representation of your data, and leave space for some binary&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Convert to Binary&lt;/h3&gt;
&lt;p&gt;What we are looking for is a sequence of bits, and each hex digit represents 4 bits (half a byte or a “nibble”). So we need to convert each digit to its binary form.&lt;/p&gt;
&lt;p&gt;Taking the first one:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
5₁₆
&lt;/li&gt;
&lt;li&gt;
5₁₀
&lt;/li&gt;
&lt;li&gt;
0111₂
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Don&#39;t be afraid to use your computer&#39;s calculator.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/0-ml-douido7ims38-UDvSTOlunP-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/0-ml-douido7ims38-UDvSTOlunP-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;974&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Each 4-bit set converted to binary&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Now, because this is a secret note, we need to remove our original working numbers. Grab your scissors and cut the first column off the paper.&lt;/p&gt;
&lt;p&gt;(I&#39;m switching my notation to ASCII art… for those that want to play along in a text editor)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;strong&gt;&lt;strong&gt;&lt;strong&gt;&lt;br&gt;&lt;em&gt;0101&lt;/em&gt;&lt;br&gt;&lt;em&gt;0000&lt;/em&gt;&lt;br&gt;&lt;em&gt;0100&lt;/em&gt;&lt;br&gt;&lt;em&gt;1011&lt;/em&gt;&lt;br&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;br&gt;&lt;strong&gt;&lt;em&gt;&lt;em&gt;&lt;em&gt;&lt;em&gt;&lt;br&gt;&lt;em&gt;0000&lt;/em&gt;&lt;br&gt;&lt;em&gt;0011&lt;/em&gt;&lt;br&gt;&lt;/em&gt;????&lt;/em&gt;&lt;br&gt;&lt;/em&gt;????&lt;/em&gt;&lt;br&gt;&lt;/strong&gt;____&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Convert to Image&lt;/h3&gt;
&lt;p name=&quot;c05d&quot; id=&quot;c05d&quot; class=&quot;graf graf--p graf-after--h4&quot;&gt;A barcode is just an image that can be interpreted as numbers. The key for us is that we don&#39;t have to use the symbols &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;, &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;any&lt;/strong&gt; two symbols that are easily distinguishable from one another would work just fine.
&lt;/p&gt;&lt;p&gt;This is similar to how Morse Code works, in which a binary sequence of characters are represented by different lengths of tones. It doesn&#39;t matter what is used, as long as the two things are distinguishable.&lt;/p&gt;
&lt;p&gt;One really good symbol that would be easily distinguishable by a computer with a camera would be &lt;code&gt;light&lt;/code&gt; and &lt;code&gt;dark&lt;/code&gt;. This is convenient because colour can easily be printed on paper. We can use &amp;quot;the absence of pigment&amp;quot; (light) to represent &lt;code&gt;0&lt;/code&gt;, and &amp;quot;the presence of pigment&amp;quot; to represent &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Remember how I said to use a &lt;code&gt;pencil&lt;/code&gt;?&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
Take your eraser, and erase every &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
Take your pencil, and colour every &lt;code&gt;1&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;code&gt;______&lt;br&gt;_ █ █_&lt;br&gt;_    _&lt;br&gt;_ █  _&lt;br&gt;_█ ██_&lt;br&gt;______&lt;br&gt;_    _&lt;br&gt;_  ██_&lt;br&gt;_????_&lt;br&gt;_????_&lt;br&gt;______&lt;/code&gt;&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/0-4ufinvspwjavhob7-RNweVMCJ1c-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/0-4ufinvspwjavhob7-RNweVMCJ1c-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;974&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Whether you are 8, or 80, colouring is still fun&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;My Eyes are Buggy&lt;/h2&gt;
&lt;p name=&quot;d4aa&quot; id=&quot;d4aa&quot; class=&quot;graf graf--p graf-after--h3&quot;&gt;This is coming along nicely. We now have a series of binary digits encoded as bars of colour. This is also known as a “barcode”.
&lt;/p&gt;&lt;p&gt;There&#39;s still one problem.&lt;/p&gt;
&lt;p&gt;I&#39;m getting old.&lt;/p&gt;
&lt;p&gt;My eyes aren&#39;t what they used to be.&lt;/p&gt;
&lt;p&gt;It&#39;s hard for me to follow where the lines start and stop.&lt;/p&gt;
&lt;p&gt;This is especially problematic on lines with nothing in them at all. The number zero (line number 2) has “nothing” to show that it is a zero. To help our friend that needs to decode our secret message, let&#39;s put some guidelines in place. This will help them see where lines start and stop, or that there is a line at all. The decoder also needs some way to know how big the squares are to help distinguish where digits start and stop.&lt;/p&gt;
&lt;p&gt;You will notice I left some placeholders in my notation, let&#39;s fill them in:&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
Colour all the blocks down the left
&lt;/li&gt;
&lt;li&gt;
Colour every other block across the top
&lt;/li&gt;
&lt;/ol&gt;
&lt;code&gt;█ █ █ &lt;br&gt;█ █ ██&lt;br&gt;█     &lt;br&gt;█ █  █&lt;br&gt;██ ██&lt;br&gt;██████&lt;br&gt;█ █ █ &lt;br&gt;█    █&lt;br&gt;█  ██&lt;br&gt;█????█&lt;br&gt;█???? &lt;br&gt;██████&lt;/code&gt;&lt;p name=&quot;80a1&quot; id=&quot;80a1&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;With these guides in place, we can tell where blocks start and how big each &lt;code&gt;bit&lt;/code&gt; square is on the paper.
&lt;/p&gt;&lt;p&gt;Huh… that looks an awful lot like a 2-d barcode.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/0-wg8v2-p-7we3gi3l-IFd7SD7o7k-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/0-wg8v2-p-7we3gi3l-IFd7SD7o7k-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;973&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Alright … so it&#39;s mostly correct&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;Homework&lt;/h2&gt;
&lt;p&gt;Considering an ANSI-character table, and considering bytes come in 8-bit sets, it is probably a little easier to write the blocks in &lt;code&gt;8x8&lt;/code&gt; grids:&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/0-o8n0n3fa0br4l-sp-jGGPMnIlc--__216__.avif 216w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Storing-Data-in-QR-Codes/0-o8n0n3fa0br4l-sp-jGGPMnIlc--__216__.webp&quot; alt=&quot;&quot; width=&quot;216&quot; height=&quot;210&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;&lt;em&gt;— Susan Blackmore&lt;/em&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Extra: Parallel Delivery&lt;/h3&gt;
&lt;p&gt;You will notice, that with these guidelines in place, we can treat each &lt;code&gt;8x8&lt;/code&gt; grid as a separate block to decode. This makes it a little bit easier on us mentally, as well as offering another way to make our message easier to decode by the receiver:&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
On the back of each block, write its sequence number.
&lt;/li&gt;
&lt;li&gt;
Cut-out each block
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, when your receiver gets all the blocks, they can share the work with some helpers. Each person can encode their little block, and the blocks can be stitched back together later.&lt;/p&gt;
&lt;div name=&quot;b3ea&quot; id=&quot;b3ea&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p&quot;&gt;&lt;a href=&quot;https://www.buymeacoffee.com/jeffereycave&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;Found this useful? Consider leaving a tip!&lt;/strong&gt;&lt;br&gt;&lt;em&gt;I write stuff about software and development in general. If you have found anything I&amp;#39;ve done useful, let me know&lt;/em&gt;www.buymeacoffee.com&lt;/a&gt;&lt;a href=&quot;https://www.buymeacoffee.com/jeffereycave&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;h3&gt;Further Reading&lt;/h3&gt;
&lt;p&gt;It is time to point out that this is a &lt;em&gt;simplified&lt;/em&gt; example. This was a demonstration that such a thing is possible.&lt;/p&gt;
&lt;p&gt;When going from nothing to something, the first step is to understand that it&#39;s possible. Now that you understand that such a thing is possible, its time to go on to the insane ways to make it better:&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
&lt;a href=&quot;https://www.youtube.com/watch?v=MijmeoH9LT4&quot; target=&quot;_blank&quot;&gt;The Unicode Miracle&lt;/a&gt; One of the big problems with the above solution to the barcode problem is the number of wasted bits. Take a look at Unicode if you want to see how much information can be packed into a bit.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Data_Matrix&quot; target=&quot;_blank&quot;&gt;Wikipedia: Datamatrix&lt;/a&gt; Once you get your head wrapped around that, consider DataMatrix and how it packs more data into the same space. (then just know that there is an actual spec to conform to &lt;a href=&quot;https://www.gs1.org/barcodes/2d&quot; target=&quot;_blank&quot;&gt;GS1 Datamatrix Specification&lt;/a&gt; )
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://www.mobiliodevelopment.com/1d-2d-3d-barcodes/&quot; target=&quot;_blank&quot;&gt;1D, 2D, and 3D Barcodes&lt;/a&gt; Now have your mind blown by 3D Barcodes. (For the record, I reject 4D Barcodes as a matter of principle)
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you are interested in a practical application and this just whets your appetite, I encourage you to check out Barcode Epub, a barcode-to-epub converter suitable for anonymous transfer and archiving of everything from Digital Marketing posters to publishing data used in your thesis.&lt;/p&gt;
&lt;p&gt;Maybe you can even pin it to a message board.&lt;/p&gt;
&lt;div name=&quot;b497&quot; id=&quot;b497&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p graf--trailing&quot;&gt;&lt;a href=&quot;https://gitlab.com/dpub/barcode-epub&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;dpub / Barcode EPub · GitLab&lt;/strong&gt;&lt;br&gt;&lt;em&gt;An experiment in using barcodes as a storage medium. The intent is to create an EPUB reader that stores its data on…&lt;/em&gt;gitlab.com&lt;/a&gt;&lt;a href=&quot;https://gitlab.com/dpub/barcode-epub&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;
</content>
        </entry>
        <entry>
            <title>Paper as a Digital Storage Medium</title>
            <link href="http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/" />
            <updated>2022-09-15T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                &lt;p&gt;A description of reading and writing data with the reproducibility of
digital data with the long-term storage capabilities of paper.&lt;/p&gt;
&lt;div name=&quot;c8d2&quot; id=&quot;c8d2&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p&quot;&gt;&lt;a href=&quot;https://gitlab.com/dpub/barcode-epub/-/wikis/Specs/Blocks&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;Blocks · Wiki · dpub / Barcode EPub · GitLab&lt;/strong&gt;&lt;br&gt;&lt;em&gt;An experiment in using barcodes as a storage medium. The intent is to create an EPUB reader that stores its data on…&lt;/em&gt;gitlab.com&lt;/a&gt;&lt;a href=&quot;https://gitlab.com/dpub/barcode-epub/-/wikis/Specs/Blocks&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;h2&gt;Two Stories&lt;/h2&gt;
&lt;h3&gt;A story about anonymity&lt;/h3&gt;
&lt;p&gt;In recent times we have seen a war of information. In Russia,
&lt;a href=&quot;https://www.cnn.com/2022/03/04/media/russia-media-crack-down/index.html&quot;&gt;news sources are being silenced&lt;/a&gt;
for criticizing their invasion of Ukraine. In China, online speech is
monitored and can result in
&lt;a href=&quot;https://www.wsj.com/articles/hong-kongs-crackdown-on-dissent-hits-facebook-pages-11660645491&quot;&gt;punitive&lt;/a&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Social_Credit_System#Examples_of_policies&quot;&gt;damages&lt;/a&gt;
for individuals. Saudi Arabia asks neighbours to
&lt;a href=&quot;https://www.wsj.com/articles/hong-kongs-crackdown-on-dissent-hits-facebook-pages-11660645491&quot;&gt;denounce each other&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My grandparents migrated from Europe to North America after WWII. Europe
had troubles after the war and, as with so many other refugees, everything
my grandparents possessed had been lost so a move to a new land filled
with opportunities captured their imaginations. I grew up on stories passed
down to me by my Grandmother and various Aunts, and these inspired me
to read more.&lt;/p&gt;
&lt;p&gt;One narrative that always struck me was the burning of forbidden books. Naturally, we have seen this in&lt;a href=&quot;https://en.wikipedia.org/wiki/Book_burnings_in_Chile&quot; target=&quot;_blank&quot;&gt; several&lt;/a&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/New_York_Society_for_the_Suppression_of_Vice&quot; target=&quot;_blank&quot;&gt; countries&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Book_burning&quot; target=&quot;_blank&quot;&gt;over the centuries&lt;/a&gt; as certain belief systems are suppressed, but the most famous would be the raid on the &lt;em&gt;Institute for Sexology&lt;/em&gt; in Berlin in Germany in 1933.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/0-62kb2nsxaan8pn5e-ma9bqAtwHb-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/0-62kb2nsxaan8pn5e-ma9bqAtwHb-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;580&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;The 1933 burning of “un-German” works [&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:1933-may-10-berlin-book-burning.JPG&quot; target=&quot;_blank&quot;&gt;Wikipedia, Public Domain&lt;/a&gt;]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Institut_f%C3%BCr_Sexualwissenschaft&quot; target=&quot;_blank&quot;&gt;&lt;em&gt;Institut für Sexualwissenschaft&lt;/em&gt;&lt;/a&gt; was the leading organization dedicated to the study and advocacy of alternate sexuality in Europe, and on May 6th, Government Officials raided the facility. Much of the early research (and advocacy) of gender studies was dragged out into the streets and dramatically destroyed for being “Un-German”.&lt;/p&gt;
&lt;p&gt;When people first learn of the story, they are rightly distressed about the knowledge that was forever lost, but there is also a lesson that is learned from the later stories of lost treasure troves being recovered from someone&#39;s basement after the war. Here is the way the story goes in my head.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Magnus Hirschfeld publishes a great work and gives all his students copies. The professor and his students are arrested, executed, and their personal libraries looted and destroyed. Fortunately, Li Shiu Tong, one of his students had lent the book to an acquaintance. The acquaintance was sympathetic to the NSDAP authorities, but also did not want to cause trouble for his friend and had put it on his bookshelf and forgot about it. Years later, when he died, his wife put all the books into boxes and stored them in the attic, where they stayed for the next 30 years because nobody was looking for a forgotten book in an forgotten collection.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In my internal narrative, this happens on the East German side, where Stalin continued to suppress homosexuality. The book is completely lost, except for that one accident of it getting put away in a box and forgotten about. It has a chance at a new life when society is ready for change.&lt;/p&gt;
&lt;p&gt;The ability to be forgotten and anonymous carries significant power in the dissemination of dissenting opinions.&lt;/p&gt;
&lt;p&gt;In the modern era, as information delivery systems have become more robust, we see the same destruction of knowledge taking place, though in a much more subtle manner. As the cost of distribution has been reduced, we have seen data become centralized: it is much easier to go visit Wikipedia on your phone than it is to download the page and carry it around. Also, Wikipedia comes with an open edit history associated with the documents, not all websites are so open.&lt;/p&gt;
&lt;p&gt;This leads to two risks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There is the risk of the lone copy, in a single organization&#39;s archive, being content being removed from the library (webserver). In the example above, Hirschfield had indicated his library should be donated to the University in the event The Institute was closed. This never happened, and the forced closure was deemed legal, ensuring all copies were destroyed.&lt;/li&gt;
&lt;li&gt;This centralization means that edits to the content can occur with no historic copies being maintained. The edit history, being lost, can never track significant shifting of opinions. History can be changed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;a href=&quot;https://blog.archive.org/2022/09/06/building-democracys-library-celebrate-with-the-internet-archive-on-october-19/&quot;&gt;Internet Archive&lt;/a&gt; &lt;a href=&quot;https://blog.archive.org/2022/03/10/guest-blog-an-egyptian-perspective-on-american-book-banning/&quot;&gt;demonstrates&lt;/a&gt; the need for this: websites and content are removed from the internet regularly for reasons as innocuous as cost (part of the reason Git was developed was to protect OSS from being lost to public servers being shutdown), and as nefarious as &lt;a href=&quot;https://www.cnn.com/2022/03/04/media/russia-media-crack-down/index.html&quot;&gt;governments shutting down news stations&lt;/a&gt; to silence dissent. Central repositories like the Internet Archive help to protect knowledge by allowing us to observe changes but also put the knowledge at risk by being the only keepers of history.&lt;/p&gt;
&lt;p&gt;By distributing the data across many bookshelves, it is protected from complete loss.&lt;/p&gt;
&lt;h4&gt;A story about storage&lt;/h4&gt;
&lt;p&gt;Many years ago, I heard a story. I don&#39;t know if it is true, but it carries a valuable lesson.&lt;/p&gt;
&lt;p&gt;In the early &#39;90s, an amazing product became accessible that allowed people to generate a lot more data than they ever had, and of a higher quality than ever before: Microsoft Word. What had previously been stored on paper was now able to be digitally encoded and stored on disk. The archivists loved it, they were stuffing data onto disks left, right, and centre.&lt;/p&gt;
&lt;p&gt;In the late &#39;90s, Microsoft upgraded Word.&lt;/p&gt;
&lt;p&gt;Into an incompatible format.&lt;/p&gt;
&lt;p&gt;There was no way to go back and recover all that long-term stored data. Legally they were not allowed to as it had to be stored exactly as it was placed into storage (and signed off on).&lt;/p&gt;
&lt;p&gt;In another twist, magnetic storage degrades over time and is subject to very limited environmental conditions. It is very easy to damage the storage medium.&lt;/p&gt;
&lt;p&gt;In the story I was told, Archivists at the US Congressional library said “you know what doesn&#39;t degrade? paper.” And just started printing everything to paper, bundling the paper, and storing away in the existing vaults.&lt;/p&gt;
&lt;p&gt;What if there was a way to have the best of both worlds? What if it is possible to have the fidelity of digital storage with the lifespan of paper; the volume of transmission available in Smart Devices, with the anonymity of in-person conversation?&lt;/p&gt;
&lt;p&gt;Unfortunately, much of the data that is produced now is &lt;em&gt;dynamic&lt;/em&gt;. By “dynamic”, I mean you can interact with the visualisation itself (scroll through a map; rotate a 3D model; filter, search, and aggregate massive datasets); and once it has been printed to paper, that is no longer possible.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/0-a0ch5bwbrriahnw4-e0bDCPCk4M-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/0-a0ch5bwbrriahnw4-e0bDCPCk4M-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;575&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;A static image of an interactive visualisation of a SARS-CoV-2 protein (&lt;a href=&quot;https://structure.ncbi.nlm.nih.gov/icn3d/share.html?s8hdP5Ji5CSvgs4V9&quot; target=&quot;_blank&quot;&gt;US National Institute of Medicine&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Also, it&#39;s hard to transfer large tables of data from paper to digital media. Scanning the documents as images and using OCR to collect tables of information loses significant amounts of metadata:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data Types must be guessed from the content&lt;/li&gt;
&lt;li&gt;Alignment issues cause data to be considered out of context&lt;/li&gt;
&lt;li&gt;Character fidelity can cause incorrect values to be interpreted&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While high-resolution photography and Artificial Intelligence have certainly improved the quality of scanned content, there is still an analogue transfer of data and it will result in some mistakes being made.&lt;/p&gt;
&lt;h3&gt;Defining the Problem&lt;/h3&gt;
&lt;p&gt;What if there was a way to have a compromise between the two worlds: the long-term storage of paper, with the high fidelity of digital; the anonymity of a private conversation, with the distribution capacity of a computer network?&lt;/p&gt;
&lt;p&gt;What we are looking for is a means to store digital information on physical media such as paper or etched into stone. We might call this “visible” media.&lt;/p&gt;
&lt;h4&gt;Properties of Digital&lt;/h4&gt;
&lt;p&gt;Companies, governments, and individuals, have a desire to store data for long periods for legal archival purposes. This is hard to do. Over the past 20 to 30 years, the cost of digital storage has reduced as we moved from paper to magnetic storage. This presents a problem for archivists that must store the resulting volumes of data: as it becomes cheaper for us to produce data, it becomes a greater challenge for archivists to store that data.&lt;/p&gt;
&lt;p&gt;The data must have a means of simple interpretation: it must be stored in a format that is easily converted to something a human can read. Open Source standards are advantageous as they are unencumbered by intellectual ownership and are readily understood by a larger pool of experts.&lt;/p&gt;
&lt;p&gt;Copying digital data is something we take for granted. When we make a copy of digital data, it is an exact copy. For example, music loses some fidelity when recorded into a high-resolution format, however, the replication of the song from that point forward retains an exact copy (at the resolution of the bit).&lt;/p&gt;
&lt;h4&gt;Properties of the Storage Media&lt;/h4&gt;
&lt;p&gt;While etching into stone, or carving into wood are viable options, the weight and volume of these media present a barrier to storage space, and weight. Linen and cotton sheets represent lighter options but are expensive to produce. Mylar and projector film reduce the size, which offers good potential.&lt;/p&gt;
&lt;p&gt;Modern archival paper represents a balance of permanence, weight, and volume. Each of these could (and should) be considered for various purposes, in fact, the solution should be adaptable to all these solutions. We discuss paper as the primary media, it is because paper has such a rich evolutionary history as a storage media.&lt;/p&gt;
&lt;p&gt;In order for a digital storage mechanism, it must offer a reasonable level of compression. By compression, we refer to the number of bits of information stored per square inch or pound. This means it should be able to be recorded in a small physical space, though this must be balanced with an ability to read it back easily.&lt;/p&gt;
&lt;h3&gt;Solution&lt;/h3&gt;
&lt;p&gt;By combining the needs of both these mediums, we can put together a combination of existing technologies to create a unique solution. &lt;a href=&quot;https://en.wikipedia.org/wiki/EPUB#Version_3.2&quot; target=&quot;_blank&quot;&gt;ePUB&lt;/a&gt; is an &lt;a href=&quot;https://www.w3.org/TR/epub-33/&quot; target=&quot;_blank&quot;&gt;Open Source container format &lt;/a&gt;for Electronic Books which offers a standardized (&lt;a href=&quot;https://www.iso.org/standard/53255.html&quot; target=&quot;_blank&quot;&gt;ISO/IEC TS 30135–1:2014&lt;/a&gt;) and unencumbered format for a plethora of data. Further, the use of 2D-Barcodes (in the form of QR Codes) has become ubiquitous as a means of transmitting URLs, however, fundamentally, they are just binary buffers, capable of storing any encoded sequence of numbers.&lt;/p&gt;
&lt;h4&gt;ePUB&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Diverse data storage&lt;/li&gt;
&lt;li&gt;Compression&lt;/li&gt;
&lt;li&gt;Accessibility Conformance&lt;/li&gt;
&lt;li&gt;Widely Consumable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The transition from paper publishing to screen-based mediums brought some transitional challenges. PDF was popularized as a means of digitizing paper and acting as an intermediary between paper and digital formats. On the polar opposite end of the spectrum from paper, digitized standards (such as those developed by the &lt;a href=&quot;https://www.w3.org/&quot; target=&quot;_blank&quot;&gt;W3C&lt;/a&gt;) have been optimized for delivery to an unknown display.&lt;/p&gt;
&lt;p&gt;HTML introduced the idea of reformatting content to adjust to meet the needs of the consumer. This meant that the text could be read by a screen reader, could reflow for people reading on small screens, or the text made larger for people with poor eyesight. This accessibility of the format gave birth to a plethora of other standards now managed by the &lt;a href=&quot;https://www.w3.org/&quot; target=&quot;_blank&quot;&gt;W3C&lt;/a&gt;. These standards ensure maximum availability to the greatest number of consumers.&lt;/p&gt;
&lt;p&gt;ePUB takes advantage of these standards to encapsulate websites into a single document. They embed webpages into a ZIP file format to allow for the contained viewing of the entire website. Generally, the documents are organized into Chapters.&lt;/p&gt;
&lt;p&gt;By using the common ePUB format, anyone would be able to read a digital document and decode it. ePUBv3 allows for &lt;a href=&quot;https://en.wikipedia.org/wiki/EPUB#Version_3.2&quot; target=&quot;_blank&quot;&gt;JavaScript&lt;/a&gt; to be embedded, meaning you could embed maps, interactive diagrams, etc. (like &lt;a href=&quot;https://jefferey-cave.medium.com/turn-a-shiny-dashboard-into-a-desktop-app-6f51287b7d6f&quot; target=&quot;_blank&quot;&gt;R-shiny&lt;/a&gt;, but self-contained). As a general W3C container, it is also possible to embed other file formats for consumption and preservation: datasets as CSV, or evidence in the form of video.&lt;/p&gt;
&lt;h4&gt;Barcodes&lt;/h4&gt;
&lt;p&gt;You can encode digital information into barcodes which can then be printed to paper for long-term archiving, and the barcodes can be read back to a digital device for reading.&lt;/p&gt;
&lt;p&gt;2 Dimensional barcodes have been used for decades as a means of encoding specialized information. &lt;a href=&quot;http://xml.coverpages.org/brml.html&quot; target=&quot;_blank&quot;&gt;BRML&lt;/a&gt;, text, or other data formats, have been appended to printed documents, such as &lt;a href=&quot;https://en.wikipedia.org/wiki/Enhanced_driver%27s_license&quot; target=&quot;_blank&quot;&gt;Drivers Licenses&lt;/a&gt; and invoices, to supplement the text with digital information. This usually amounts to a unique document identifier or a digital record.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/0-vpit8rpmlfby30ld--kNna_4fIo-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/0-vpit8rpmlfby30ld--kNna_4fIo-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;271&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;PDF417 barcode used by Drivers Licenses in many parts of North America [&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:PDF417_Example.svg&quot; target=&quot;_blank&quot;&gt;Wikimedia, CC-0&lt;/a&gt;]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Encoding an ePUB should be trivial with there being several issues:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The encoding scheme must be identifiable by a reader (there must be sufficient information embedded in the data to allow a reader to reconstruct the correct form)&lt;/li&gt;
&lt;li&gt;The size of a single book will likely exceed a given 2D barcode&#39;s storage capacity. An encoding mechanism will have to be able to span multiple image tiles.&lt;/li&gt;
&lt;li&gt;There is a social issue that must be managed in that humans cannot read the codes directly. It is possible that they do not wish to view the material for legal, religious, or moral reasons. There must be sufficient metadata to allow the viewer to decide not to accept the message.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The issues are easily overcome once identified; adding metadata to the individual tiles in the form of application identifier, pagination, title, author, and subject should offer sufficient information to allow users to interact with individual tiles and reconstruct the data.&lt;/p&gt;
&lt;p&gt;A prototype of the concept has been created to demonstrate the capability. The prototype&#39;s protocol consists of&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;A URL&lt;/strong&gt;: which points to the reader for either online use (browser only) or installation as a PWA, or just as a unique identifier that this is a compatible format&lt;/li&gt;
&lt;li&gt;&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;A Protocol Version&lt;/strong&gt;: as changes are made, it is important that the correct decoder be used&lt;/li&gt;
&lt;li&gt;&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;Pagination&lt;/strong&gt;: the current tile number and the total number of tiles to be converted. This allows for correct sequencing as well as a measure of progress&lt;/li&gt;
&lt;li&gt;&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;Bibliographic&lt;/strong&gt;: Title, Author, and subject allow a reader to decide if this is content that interests them, or is legal for them to interact with. Filters can be added to prevent accidental downloads from taking up space&lt;/li&gt;
&lt;li&gt;&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;Parental Rating&lt;/strong&gt;: not so much for parents, but generally for people that are not interested in certain types of content (filtering &lt;code&gt;xxx&lt;/code&gt; content from a work device for example)&lt;/li&gt;
&lt;li&gt;&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;Relevance Date&lt;/strong&gt;: some content is only valid up to a certain point, and should be ignored after that time (poster for a concert). Offer a hint to the reader that perhaps this could be removed, or ignored.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With this information in every tile, the read of the first image can result in some information being given to the user, allowing them to decide if they want to continue or block. If they determine they wish to continue, the pagination can be used to determine what order the buffers should be ordered in for reconstruction.&lt;/p&gt;
&lt;p&gt;A prototypic specification is &lt;a href=&quot;https://gitlab.com/dpub/barcode-epub/-/wikis/Specs/Blocks&quot; target=&quot;_blank&quot;&gt;available in more detail&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Various Uses&lt;/h3&gt;
&lt;h4&gt;Secure Archives&lt;/h4&gt;
&lt;p&gt;Having access to an archive comes with permission issues. Controlling access to information in archives that store sensitive data can be difficult. Using this encoding mechanism acts as an envelope around the content.&lt;/p&gt;
&lt;p&gt;In the description of meta-data, the content rating was suggested. It would be very easy to reuse this portion of the protocol to use classification ratings. Users offered access to a secure document could have their specialized reader first check the classification rating of the content before decoding it. If the individual only has &lt;a href=&quot;https://en.wikipedia.org/wiki/Classified_information#NATO_classifications&quot; target=&quot;_blank&quot;&gt;sufficient clearance&lt;/a&gt; to view some related documents, but some of the documents in the area contain information that exceeds the individual&#39;s current clearance, it can act as a secondary filter for viewing it.&lt;/p&gt;
&lt;p&gt;Obviously, this would be a tool to assist honest actors within the environment and not a way to interfere with malicious actors, but this is another layer of protection which assists the actors in managing the information in their possession.&lt;/p&gt;
&lt;h4&gt;Information Dissemination&lt;/h4&gt;
&lt;p&gt;Assuming you are in a place where information is controlled, you could print essays and newsletters to paper, which can then be scanned for reading later. For example, it could be printed in a pamphlet or posted on a bulletin board, and nobody would know who published it (&lt;a href=&quot;https://en.wikipedia.org/wiki/Machine_Identification_Code#Protection_of_privacy_and_circumvention&quot; target=&quot;_blank&quot;&gt;beware of barcodes hidden on printouts&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;One of the advantages, in this case, is the high compression ratio. In an early test, a hundred-page novel was compressed to 9 pages of barcodes. While still requiring some effort to distribute, the entirety of the novel could be tacked to a corkboard.&lt;/p&gt;
&lt;p&gt;The contents would then convert to something readable on your phone, like an inspirational poster.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/0-qoxcw-foviskihc-r2_SlnQh42-__316__.avif 316w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/0-qoxcw-foviskihc-r2_SlnQh42-__316__.webp&quot; alt=&quot;&quot; width=&quot;316&quot; height=&quot;480&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;[Wikimedia, &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Freedomisinperilposter.jpg&quot; target=&quot;_blank&quot;&gt;Public Domain&lt;/a&gt;]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4&gt;Remote Interactive Media&lt;/h4&gt;
&lt;p&gt;Textbooks, posters, and advertising all have the common element of having to display content in physically contextual locations: a sign in a museum, a poster stapled to a lamp post. Access to &lt;a href=&quot;https://uxdesign.cc/technologic-in-accessibility-cc16c1748c58?sk=6ddba29a20e9ecf8055df45b15f92042&quot; target=&quot;_blank&quot;&gt;network communications is not guaranteed&lt;/a&gt; and the audience misses out on an opportunity.&lt;/p&gt;
&lt;p&gt;Take, for example, a sign at the top of a mountain congratulating a mountain climber for their successful journey. A digital experience message could be left at the top but would require a WiFi-based website to be configured and powered.&lt;/p&gt;
&lt;p&gt;Alternately, storing the immersive experience on the poster itself would allow the digital content to be available, but not require any power for maintenance.&lt;/p&gt;
&lt;p&gt;Etching the information into something more permanent, such as wood, or stone, may be appropriate in a circumstance such as this&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/0-iexefezciwz4trhe-RJTMr6cDuh-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/0-iexefezciwz4trhe-RJTMr6cDuh-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;547&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;The Judaculla Rock shows carvings from 2000BC demonstrating the staying power and low maintenance capabilities of etching into stone [&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Judaculla_Rock,_Caney_Fork,_NC_Aug_2019.jpg&quot; target=&quot;_blank&quot;&gt;Wikimedia, CC BY-SA 4.0&lt;/a&gt;]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Anonymous and long-term storage of data and information is necessary. The free dissemination of ideas, and the storage of them for future reference, is a fundamental need for the progress of society. While the digital age has made information access easier than ever, it has introduced a host of new problems in its wake.&lt;/p&gt;
&lt;p&gt;The use of paper as a digital storage medium is a novel and useful approach to addressing some of the new problematic circumstances.&lt;/p&gt;
&lt;p&gt;If you find this concept of interest, I invite you to review &lt;a href=&quot;https://gitlab.com/dpub/barcode-epub&quot; target=&quot;_blank&quot;&gt;a prototype of the concept on GitLab&lt;/a&gt;. There is a mobile application available, that can convert an ePUB to images and paper, and convert it back; right on your smartphone.&lt;/p&gt;
&lt;p&gt;There are several ways you could contribute:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Design a UI: I just grabbed an old one from another project to get up and running&lt;/li&gt;
&lt;li&gt;Write a Reader: currently, the app acts as a bookshelf. Some features would be well suited to a custom reader&lt;/li&gt;
&lt;li&gt;Custom Filters: users should be able to filter content by author and title if they come across something that does not interest them.&lt;/li&gt;
&lt;li&gt;Pass some notes around your school: Using a system highlights its problems. Post a club listing to a bulletin board at school using this encoding mechanism.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Submit an issue, post a merge request, or leave a comment below. If you found this content valuable, please remember to &lt;a href=&quot;https://jefferey-cave.medium.com/&quot; target=&quot;_blank&quot;&gt;click the follow button&lt;/a&gt;.&lt;/p&gt;
&lt;div name=&quot;e05c&quot; id=&quot;e05c&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p&quot;&gt;&lt;a href=&quot;https://gitlab.com/dpub/barcode-epub&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;dpub / Barcode EPub · GitLab&lt;/strong&gt;&lt;br&gt;&lt;em&gt;An experiment in using barcodes as a storage medium. The intent is to create an EPUB reader that stores its data on…&lt;/em&gt;gitlab.com&lt;/a&gt;&lt;a href=&quot;https://gitlab.com/dpub/barcode-epub&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/0-4ufinvspwjavhob7-RNweVMCJ1c-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/Paper-as-a-Digital-Storage-Medium/0-4ufinvspwjavhob7-RNweVMCJ1c-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;974&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;h3&gt;UPDATES&lt;/h3&gt;
&lt;p&gt;Since writing this, I have come across several other interesting, and related reads&lt;/p&gt;
&lt;h4&gt;2022–11–15&lt;/h4&gt;
&lt;p&gt;The Internet Archive, &lt;a href=&quot;https://blog.archive.org/2022/11/15/digital-books-wear-out-faster-than-physical-books/&quot; target=&quot;_blank&quot;&gt;Digital Books wear out faster than Physical Books (November 15, 2022)&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;2023–07–19&lt;/h4&gt;
&lt;p&gt;NYU Law, &lt;a href=&quot;https://www.nyuengelberg.org/outputs/the-anti-ownership-ebook-economy/&quot; target=&quot;_blank&quot;&gt;The Anti-Ownership Ebook Economy&lt;/a&gt;&lt;/p&gt;
</content>
        </entry>
        <entry>
            <title>Technologic (In)accessibility</title>
            <link href="http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/" />
            <updated>2022-08-07T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                &lt;p&gt;
I went to the beach a couple of weeks ago.
&lt;/p&gt;
&lt;p&gt;
Beaches are hard to come by on the bald Canadian prairies, so this was a rare treat for my wife and I. Having moved from a coastal city, we were looking forward to getting a chance to just wade into some water, as well as introduce our young dog to swimming. Having learned about &lt;a href=&quot;https://lacombetourism.com/things-to-do/explore/sandy-point-beach/&quot; target=&quot;_blank&quot;&gt;Sandy Point Beach&lt;/a&gt;, in &lt;a href=&quot;https://en.wikipedia.org/wiki/Lacombe_County&quot; target=&quot;_blank&quot;&gt;Lacombe County&lt;/a&gt;, we were very excited and more than willing to spend a couple hours in the car.
&lt;/p&gt;
&lt;figure&gt;
&lt;iframe src=&quot;https://www.google.com/maps/embed/v1/place?maptype=satellite&amp;amp;center=52.5066718%2C-114.0039022&amp;amp;key=AIzaSyD9HrlRuI1Ani0-MTZ7pvzxwxi4pgW0BCY&amp;amp;zoom=17&amp;amp;q=52%C2%B030%2724.0%22N+114%C2%B000%2712.1%22W&quot; width=&quot;600&quot; height=&quot;450&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
&lt;/figure&gt;
&lt;p&gt;
Upon arriving, an older gentleman wearing a “security” uniform informed us that parking had recently become paid parking (no problem) and that we just had to scan a QR code on a card he gave us (big problem).
&lt;/p&gt;
&lt;p&gt;
I immediately asked if the facilities offered free WiFi to be able to connect to the payment service, and was assured there was plenty of service just up ahead. I tried to be specific that &lt;em&gt;I do not have a data plan&lt;/em&gt; on my phone, but he did not seem to understand. I rolled the dice and proceeded in, found a parking spot, and proceeded to attempt to pay: no WiFi. It did occur to me that perhaps it was closer to the change building so I walked over to the building and tried there: no WiFi.
&lt;/p&gt;
&lt;p&gt;
This was going to be a problem.
&lt;/p&gt;
&lt;p&gt;
Upon getting back to my car, I found the security guy had already taken my plate number. I did ask him what alternate options were available for me to pay. I did try to explain that I did not have any means to connect to the internet, but that only resulted in him getting frustrated with me and simply stating, “you just open your phone and you get the internet”.
&lt;/p&gt;
&lt;p&gt;
Unfortunately, this just isn&#39;t true for everyone, and (as is often the case) assumptions regarding capabilities of people result in barriers to accessibility. It is these assumptions regarding customer abilities that lead to wheel-chair ramps not being considered, hazards not being demarked for the visually impaired, and audio tutorials not being offered to the hearing impaired.
&lt;/p&gt;
&lt;p&gt;
To some extent, assumptions are inevitable: not having lived a particular experience, it is natural to be unaware of the nuances involved in that experience. Fortunately, society is (mostly) aware that we are sometimes unaware of these physical barriers to entry, and we realise the importance of &lt;a href=&quot;https://www.cochrane.ca/DocumentCenter/View/7012/Barrier-Free-Design-Guide&quot; target=&quot;_blank&quot;&gt;seeking expert advice on the matter&lt;/a&gt;.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/1-fwfyetaj-a3kepdlco-qpq-Ccaa8n1aLD-__550__.avif 550w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/1-fwfyetaj-a3kepdlco-qpq-Ccaa8n1aLD-__550__.webp&quot; alt=&quot;&quot; width=&quot;550&quot; height=&quot;401&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;There are subtle nuances, and significant details, that experts in a domain are aware that are non-obvious to outsiders (p55, &lt;a href=&quot;https://www.cochrane.ca/DocumentCenter/View/7012/Barrier-Free-Design-Guide&quot; target=&quot;_blank&quot;&gt;Alberta Government: Barrier Free Design Guidelines&lt;/a&gt;, Fair Use)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Unfortunately, there is another set of barriers to that can be described as “Technological Barriers”, which are often not even considered. Due to marketing and increased cost of implementation, technology is sold as being “simple to implement”, which unfortunately overlooks some of the complex nuances of where humans interact with technology. It is important that we be thoughtful in our design of technological space, as well as physical space.
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;
Thoughtful design means to consider how a space is to be used … The objective is to remove as many barriers as possible.
&lt;/p&gt;
&lt;p&gt;
— Alberta Government, &lt;a href=&quot;https://www.cochrane.ca/DocumentCenter/View/7012/Barrier-Free-Design-Guide&quot; target=&quot;_blank&quot;&gt;Barrier Free Design Guidelines&lt;/a&gt; (p.2)
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Accessibility of Technology&lt;/h3&gt;
&lt;p&gt;
As a user progresses from the discovery of the service through the decision to seek out the service, there is any number of things that can prevent them from actually engaging with the service. We all know that technology offers a significant means for reducing these barriers: digitally readable text increases the options for consuming text, networking allows communication to reach the consumer rather than the consumer having to come to the message.
&lt;/p&gt;
&lt;p&gt;
By diversifying our modes of communication, we create redundancies and alternate paths for our consumers to follow, these alternative paths allow those with various barriers to seek an alternate path to the same outcome.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/1-zp68fumcruypabhew4sunq-TQx4Zu9hGt-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/1-zp68fumcruypabhew4sunq-TQx4Zu9hGt-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;436&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;In any customer progression toward success, there are a number of barriers that slowly whittle away at those capable of enjoying the product or service&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
One of the risks of easy and low-cost solutions is the temptation to use it to the exclusion of all else. This leaves no means for those with accessibility issues to bypass the barriers in their way … and without personal experience, we are likely to be unaware that those barriers exist. We need to rely on experts with domain knowledge to avoid making assumptions based on our personal experience.
&lt;/p&gt;
&lt;p&gt;
In this case, the assumption is that everyone has access to mobile devices and mobile data plans through one of the major Canadian providers. Unfortunately, this just isn&#39;t true for &lt;a href=&quot;https://www.ajournalofmusicalthings.com/wait-25-of-canadians-still-dont-have-any-kind-of-mobile-phone/&quot; target=&quot;_blank&quot;&gt;34% of Canadians&lt;/a&gt;, who, in a 2019 study, did not have access to a Smart Device; and there are many more of us who do not pay for the internet to be accessible from our devices.
&lt;/p&gt;
&lt;div name=&quot;02d9&quot; id=&quot;02d9&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p&quot;&gt;&lt;a href=&quot;https://www.statista.com/chart/16937/share-of-adults-who-own-no-mobile-phone-or-have-a-non-smartphone/&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;Infographic: Smartphone Ownership: The Mobile Disconnect&lt;/strong&gt;&lt;br&gt;&lt;em&gt;The Statista &amp;quot;Chart of the Day&amp;quot; currently focuses on two sectors: &amp;quot;Media and Technology&amp;quot;, updated daily and featuring…&lt;/em&gt;www.statista.com&lt;/a&gt;&lt;a href=&quot;https://www.statista.com/chart/16937/share-of-adults-who-own-no-mobile-phone-or-have-a-non-smartphone/&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;
This appears to have changed during the pandemic, but is still not complete coverage. &lt;a href=&quot;https://www150.statcan.gc.ca/n1/daily-quotidien/210531/dq210531d-eng.htm&quot; target=&quot;_blank&quot;&gt;Statistics Canada&#39;s observes &lt;/a&gt;that 20% of Canadians do not have data plans on their mobiles as of 2021. This is even more pronounced among rural Canadians at 27%, the very customers the rural county of Lacombe is trying to serve.
&lt;/p&gt;
&lt;div name=&quot;6e18&quot; id=&quot;6e18&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p&quot;&gt;&lt;a href=&quot;https://www150.statcan.gc.ca/n1/daily-quotidien/210531/dq210531d-eng.htm&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;Access to the Internet in Canada, 2020&lt;/strong&gt;&lt;br&gt;&lt;em&gt;More than ever, Canadians relied on the Internet to perform various activities in their daily lives in 2020. Access to…&lt;/em&gt;www150.statcan.gc.ca&lt;/a&gt;&lt;a href=&quot;https://www150.statcan.gc.ca/n1/daily-quotidien/210531/dq210531d-eng.htm&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;
### Cost of Technology
&lt;p&gt;
In the mid-2000s, the trend was toward shared WiFi and WiFi networks, this was a low-cost and ubiquitous solution to internet connectivity in urban areas (I loved my Nokia N800). As WiFi became ubiquitous in coffee shops and offices, many people have never felt the need to purchase data plans.
&lt;/p&gt;
&lt;p&gt;
In Canada, it is not unreasonable for a couple to be paying $1800–$2500/year for internet connectivity on their phones, phones which themselves cost $720–$1200/year (&lt;a href=&quot;https://www.shaw.ca/internet/plans/&quot; target=&quot;_blank&quot;&gt;Shaw&lt;/a&gt; &amp;amp; &lt;a href=&quot;https://www.telus.com/en/mobility/plans?linktype=ge-meganav#&quot; target=&quot;_blank&quot;&gt;Telus&lt;/a&gt; Mobile Plans). If the &lt;a href=&quot;https://www150.statcan.gc.ca/t1/tbl1/en/tv.action?pid=9810005701&amp;amp;pickMembers%5B0%5D=1.4275&amp;amp;pickMembers%5B1%5D=2.4&quot; target=&quot;_blank&quot;&gt;average income of a couple in Lacombe County&lt;/a&gt; is about $82,000/year, a total data cost of $3700/year represents a significant proportion (4.5%). Considering the significant acceleration of inflation in Canada, leading to a current &lt;a href=&quot;https://www.ctvnews.ca/politics/inflation-rate-will-remain-painfully-high-all-year-bank-of-canada-governor-anticipates-1.5995379&quot; target=&quot;_blank&quot;&gt;8.1% inflation rate&lt;/a&gt;, one can foresee that Canadians will be seeking means to reduce their household expenses, and a 4.5% budget item, with an easy workaround (public WiFi), is an obvious candidate for cost reduction.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/1-9lfrqyayq1yas4wyv5cnfa-PY0ZD7KgFJ-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/1-9lfrqyayq1yas4wyv5cnfa-PY0ZD7KgFJ-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;281&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Canada is currently experiencing unprecedented inflation at 8.1%. Since early 2021, inflation has been accelerating (&lt;a href=&quot;https://www.bankofcanada.ca/rates/indicators/key-variables/inflation-control-target/&quot; target=&quot;_blank&quot;&gt;Bank of Canada&lt;/a&gt;, &lt;a href=&quot;https://www.bankofcanada.ca/terms/&quot; target=&quot;_blank&quot;&gt;license&lt;/a&gt;).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
… and that is not even the group we are concerned with.
&lt;/p&gt;
&lt;p&gt;
When designing a system, with an eye to accessibility, it is important to consider those that are outside the norm. Having an average or above average income represents a privileged group of decision-making individuals. It&#39;s easy to forget about the 16% of the population that is financially vulnerable.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/1-p-onwcawltqlmu-ups3zyq-Y1CkB7xWOO-__719__.avif 719w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/1-p-onwcawltqlmu-ups3zyq-Y1CkB7xWOO-__719__.webp&quot; alt=&quot;&quot; width=&quot;719&quot; height=&quot;333&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;
In the case of governmental resource management, it is important to consider the less advantaged and vulnerable populations of the community. A day at the beach represents an attractive, cost-effective, activity for low-income and vulnerable individuals (retired pensioners, single parents, or those who have just fallen on hard times). Socially, these are the people best served by having access to public resources, and at the same time are also those most vulnerable to having to make hard budget decisions.
&lt;/p&gt;
&lt;h3&gt;Table Topping&lt;/h3&gt;
&lt;p&gt;
When engaging in designing of a technological system, it is important to “walk through” your proposed system with the intention of identifying vulnerabilities that may be present. This walk-through need not be done on-premise (though that is advisable), rather it is often worth doing as a “&lt;a href=&quot;https://www.csoonline.com/article/3622252/tabletop-exercises-explained-definition-examples-and-objectives.html&quot; target=&quot;_blank&quot;&gt;Table Top Exercise&lt;/a&gt;” in the early stages.
&lt;/p&gt;
&lt;p&gt;
By designing the system, and then testing it by visually describing the anticipated process, you can begin to map points in the process that may be subject to issues and barriers. This is similar to risk mitigation in project planning, but differs in that it required system to have been designed first. You are testing a design to ensure you have handled all cases, rather than trying to plan for all cases. This should also be an iterative process: design system, identify flaw, propose change, repeat.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/0-upjwjr0ws4aos662-tNFNKShSow-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/0-upjwjr0ws4aos662-tNFNKShSow-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;381&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;A quick mind-map addressing potential negative outcomes that should be addressed (&lt;a href=&quot;https://atlas.mindmup.com/2022/08/8ece2fb0167711ed88dbbd048169a2c2/go_to_lacombe_beach/index.html&quot; target=&quot;_blank&quot;&gt;Mindmup&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
These issues and barriers can be documented, and then considered for the probability of occurrence, significance of impact, and mitigation plan. Not everything need be handled, but they should at least be addressed.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/1-cfwo0y-cj1-yshacycqwlw-uEU1g3NfVC-__516__.avif 516w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/1-cfwo0y-cj1-yshacycqwlw-uEU1g3NfVC-__516__.webp&quot; alt=&quot;&quot; width=&quot;516&quot; height=&quot;153&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;
It is very common in this process to have our bias show through and to be dismissive of an issue that is difficult or uncomfortable to address or sometimes just challenges our worldview (eg. I have internet on my phone, everyone I know has internet on their phone. Therefore everyone has internet on their phone). This is most dangerous at executive levels, as &lt;a href=&quot;https://blog.britishmuseum.org/thomas-becket-the-murder-that-shook-the-middle-ages/&quot; target=&quot;_blank&quot;&gt;off-hand remarks may communicate&lt;/a&gt; decisions and desires to analysts and designers (having opinions is normal, but voicing them can be dangerous).
&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;
Having walked through the case, we can see how important it is that organisations approach their system design with an eye to accessibility and take active measures to not allow analyst and executive privilege to create a bias that excludes the vulnerable. We can also see some of the tools used to mitigate and manage some of these issues.
&lt;/p&gt;
&lt;p&gt;
Unfortunately, this particular implementation of technology, to support parking payment at Sandy Point Beach, has made the beach inaccessible to many people. While I understand and agree that the county should be charging some fee to offset the creation of an artificial beach in the middle of The Prairies (I saw the mountain of sand off to the side), having no onsite means of payment available to patrons (WiFi, cash kiosk, or digital kiosk) means that what should be an affordable, and accessible, form of entertainment for residents, and tourists, has an insurmountable technological and financial barrier for many.
&lt;/p&gt;
&lt;p&gt;
In response to a draft of this article, Lacombe County indicated that this has been a trial run, focusing on education, and therefore no tickets were issued. They also pointed out that payment can be made in advance at the &lt;a href=&quot;https://www.google.com/maps/dir/Sandy+Point+Resort+%26+RV+Park,+Gull+Lake,+AB/Lacombe+County+Administration+Building,+40403+Range+Road+274+RR+%233,+AB+T4L+2N3/@52.4771823,-113.9689457,13z/data=!3m1!4b1!4m14!4m13!1m5!1m1!1s0x5375a7d28196b4bd:0x74dce5e541bce016!2m2!1d-114.0031055!2d52.5045778!1m5!1m1!1s0x5375071a38dca35f:0xfc4d1fc2cdff79ce!2m2!1d-113.8420181!2d52.4652404!3e0&quot; target=&quot;_blank&quot;&gt;County Office&lt;/a&gt; or online, though neither of those options are present on their website at this time. I hope they take this feedback into consideration during their evaluation, and consider consulting with appropriate domain experts.
&lt;/p&gt;
&lt;p&gt;
Unfortunately, this demonstrates how we (as decision makers) can fail to identify these issues as we rely on our own life experience, failing to be aware of the diverse experience of others. We can fail to address issues in advance, or fail to create viable alternative success paths.
&lt;/p&gt;
&lt;p&gt;
In the end, it was a frustrating start to what was supposed to be an exciting day for my wife, myself, and my dog; and has resulted in turning a public resource into a resource technologically accessible only to the privileged.
&lt;/p&gt;
&lt;h4&gt;UPDATE 2022–08–12&lt;/h4&gt;
&lt;blockquote&gt;
Healthcare professionals say that dangerous heat puts marginalised and vulnerable communities at risk because low income populations have a more difficult time accessing cooler spaces and green-spaces
&lt;/blockquote&gt;
&lt;blockquote&gt;
— &lt;a href=&quot;https://www.cbc.ca/player/play/2059494467579&quot; target=&quot;_blank&quot;&gt;Millions of Canadians try to stay cool during heat wave&lt;/a&gt;, CBC, 2022–08–12
&lt;/blockquote&gt;
&lt;p&gt;
Also
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
apparently Canadians &lt;a href=&quot;https://www.cbc.ca/player/play/2052614211713&quot; target=&quot;_blank&quot;&gt;pay a lot&lt;/a&gt; for their mobile data
&lt;/li&gt;
&lt;li&gt;
the &lt;a href=&quot;https://nationalpost.com/news/canada/worst-in-the-world-here-are-all-the-rankings-in-which-canada-is-now-last&quot; target=&quot;_blank&quot;&gt;World&#39;s most expensive&lt;/a&gt; according to some
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/0-hwakkvutkml7rul9-Zh65v4L2x3-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/technologic-inaccessiblility/0-hwakkvutkml7rul9-Zh65v4L2x3-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;484&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Bald Prairies (&lt;a href=&quot;https://en.wikipedia.org/wiki/Prairie&quot; target=&quot;_blank&quot;&gt;Wikipedia, CC BY-SA 2.5&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
</content>
        </entry>
        <entry>
            <title>Fishalytics</title>
            <link href="http://plaidsheep.ca/posts/2022/FishAlytics/" />
            <updated>2022-07-08T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2022/FishAlytics/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                &lt;h3&gt;Fishalytics&lt;/h3&gt;
&lt;h4&gt;A (failed) experiment in data analysis as a behaviour modification tool&lt;/h4&gt;
&lt;p&gt;
About 12 years ago, I gave up my 100 hour work weeks as a software developer, moved to the other side of Canada, and bought a small-holding farm. One of the first things I did when I moved to the new region was to buy a fishing license and was shocked when they handed me an official “catch form” with my license.
&lt;/p&gt;
&lt;p&gt;
In Nova Scotia, when you have a recreational fishing license, you are expected to keep track of the number of fish you catch, the region you caught them in, as well as what species they are.
&lt;/p&gt;
&lt;p&gt;
For a year.
&lt;/p&gt;
&lt;p&gt;
I was struck by how inefficient this was. It was highly unlikely that I was going to remember &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;every&lt;/strong&gt; fish I caught for a year, it was also unlikely that I was going to be able to remember where I had put the fishing scorecard that I was required to send in at the end of the year.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/FishAlytics/0-hl3tbonoiyviz8-m-oFhbttTL-2-__500__.avif 500w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/FishAlytics/0-hl3tbonoiyviz8-m-oFhbttTL-2-__500__.webp&quot; alt=&quot;&quot; width=&quot;500&quot; height=&quot;333&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;A screen shot showing fishing licenses over the years. While I primarily fish in Canada, an acquaintance did give me written permission to fish his private fishery in England. A fond memory to keep track of.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
This seemed like a simple data entry form project that I thought I might even be able to sell to the provincial government. At its simplest, the idea was that I could write an application that allowed fisherman to enter their fishing license information, and record their fish as they caught them. At the end of the year, the software could just print the score card for you.
&lt;/p&gt;
&lt;p&gt;
Over the next 10 years, this tool became the focus of much of my internal pondering regarding system development and the ability of software to affect social change.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/FishAlytics/0-rctxhybc-zcvf-mj-885LkciPb5-__64__.avif 64w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/FishAlytics/0-rctxhybc-zcvf-mj-885LkciPb5-__64__.webp&quot; alt=&quot;&quot; width=&quot;64&quot; height=&quot;63&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;I even went so far as to get my wife to design a logo for the project. A fish, over a grid, jumping from a stream (which is actually a the curve of a graph from the app).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;From Simple Record Keeping to Analysis&lt;/h3&gt;
&lt;p&gt;
While the initial concept of the software was to simply as a record keeping tool for myself, it was not long before I started to think bigger.
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
If I built the tool, I may as well make it more widely available.
&lt;/li&gt;
&lt;li&gt;
If I wanted others to use the tool, I needed to offer them more than just a fishing journal.
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
I had just hit on my first element of social design: you must offer something to the individuals you want to harvest data from. Like one of the major social platform, I could harvest user data and sell it on, but in order to do that, I first needed to offer something in return.
&lt;/p&gt;
&lt;p&gt;
There were two “value add” features I could think of right away.
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
The social aspect. Keeping pictures and memories of catching fish is fun. Sharing fishing tales, in near &lt;a href=&quot;https://medium.com/mlearning-ai/how-fast-is-fast-enough-2af77c61007d&quot; target=&quot;_blank&quot;&gt;real-time&lt;/a&gt;, would be a fun way for fishermen to engage with friends, and a great way to record them for future memory.
&lt;/li&gt;
&lt;li&gt;
Basic analysis. Knowing where you caught the last fish gives you some idea where to catch future fish, and using aggregate data of many fishermen, I could actually offer solid, unbiased, advice on where to catch the next fish.
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
That first idea that people could share was an important one, Facebook was young enough that competitive services were still reasonable in niche fields. Unfortunately, social platform development was really outside my domain of expertise. The second option spoke to strengths I had, and it occurred to me that if I could develop a successful predictive product, partnering with a social partner could be performed later. Another benefit to focusing on the analysis was that I was the sole user at this point: it is almost impossible to build a social platform around a single individual, however it is possible for a single individual to gather multiple data points for an analysis.
&lt;/p&gt;
&lt;p&gt;
The first focus was set: predict what leads to catching more fish.
&lt;/p&gt;
&lt;h4&gt;Planning the Data Gathering&lt;/h4&gt;
&lt;p&gt;
In order to suggest to users what they should do to catch mroe fish, I first had to think of the variables that would impact the ability to catch fish. The best place to determine this was at my local lake, staring out over the water.
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
What about my fishing (behaviour) could affect my catching fish (outcome)?
&lt;/li&gt;
&lt;li&gt;
Also, what could affect my ability to catch fish, but was difficult to measure?
&lt;/li&gt;
&lt;li&gt;
Lastly, what was easy to measure, but probably had little to do with my catching fish (red herrings)?
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
In brainstorming these things, I came up with several things that had a high impact on my catch rate and were reasonably easy to measure.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Location
&lt;/li&gt;
&lt;li&gt;
Time of Day (solar declination)
&lt;/li&gt;
&lt;li&gt;
Temperature (air and water)
&lt;/li&gt;
&lt;li&gt;
Solar penetration (cloud cover)
&lt;/li&gt;
&lt;li&gt;
Covering vegetation
&lt;/li&gt;
&lt;li&gt;
Lure
&lt;/li&gt;
&lt;li&gt;
Time spent fishing
&lt;/li&gt;
&lt;li&gt;
Time of Year
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Unfortunately, of these, only two seemed easy enough that I could expect the average fisherman (ie. me) to collect them regularly. Space-Time is easily captured on a phone, so anything involving those metrics is easy to capture: location, time of day, time of year, and solar declination.
&lt;/p&gt;
&lt;p&gt;
These two items can be measured easily through smart-phone GPS loggin. By marking the start of a fishing trip, and having the application continuously log the position of the person fishing we can get a sense of how long they stood in a given location with a line in the water not catching fish. Upon catching a fish, it is natural to want ot capture the moment, snapping a picture (again through the app) marks the moment and location at which the fish was actually caught.
&lt;/p&gt;
&lt;h4&gt;Measuring Success&lt;/h4&gt;
&lt;p&gt;
Putting these variables together into a meaningful metric became the next problem.
&lt;/p&gt;
&lt;p&gt;
In attempting to determine that which variables lead to success, we need a clear definition of success. To some extent this requires distinguishing causal variables from outcome variables. In order to determine how a fisherman would consider themselves successful, I could think of no better way than to interview a recreational fisherman.
&lt;/p&gt;
&lt;blockquote&gt;
You find gold where the gold is
&lt;/blockquote&gt;
&lt;blockquote&gt;
— Prospector&#39;s Proverb
&lt;/blockquote&gt;
&lt;p&gt;
I went fishing.
&lt;/p&gt;
&lt;p&gt;
Standing waste deep in water at my local lake, with a line in the water, I began pondering the variables that would make me successful at that moment. As I stood there, I realised it was “catching a lot of fish”, catching fish is exciting, even small fish. So there is an element of quantity. The sheer amount of fish you catch, is a positive experience. But standing in a lake for 4 hours to catch two fish is not the same as hitting 2 fish in 20 minutes: the velocity at which we catch fish matters.. It was at t his moment that I caught the most impressive Small-mouth Bass I have ever caught. A big fish, on a small fishing rod, is a fun experience. Fisherman brag about that giant fish they caught. So its not only a measure of quantity of fish caught, but also the quality of those fish.
&lt;/p&gt;
&lt;p&gt;
A fishing trip can be measured as being successful by the “velocity” at which you catch fish.
&lt;/p&gt;
&lt;p&gt;
velocity = quantity of things&lt;br&gt;           ------------------&lt;br&gt;               time spent&lt;/p&gt;&lt;p name=&quot;2dd6&quot; id=&quot;2dd6&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;There is a little more to it, as we want to factor in the quality of the fish caught. We can also consider the entire time spent as a single “fishing trip”.
&lt;/p&gt;
&lt;p&gt;
velocity = number of fish caught * quality of fish&lt;br&gt;           ---------------------------------------&lt;br&gt;              time spent standing next to water&lt;/p&gt;&lt;p name=&quot;c52a&quot; id=&quot;c52a&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;The problem with this definition was that it was not very granular. The end goal was to create a heat map representing the “best” places to go fishing. This heat map would represent a range from &lt;code class=&quot;markup--code markup--p-code&quot;&gt;null&lt;/code&gt; (no information) to &lt;code class=&quot;markup--code markup--p-code&quot;&gt;good&lt;/code&gt; to &lt;code class=&quot;markup--code markup--p-code&quot;&gt;bad&lt;/code&gt;. So while a “trip” represents a range of space-time (different fish caught at different locations and at different times), I required highly granular data that specified a point in space-time.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/FishAlytics/0-4qnfdq1txvctpxaz-MmhpuCrJpf-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/FishAlytics/0-4qnfdq1txvctpxaz-MmhpuCrJpf-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;168&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;A hypothetical fishing trip with two fish caught. The fish took a certain amount of time to catch which represents an effort on my part. [&lt;a href=&quot;https://www.planttext.com/?text=RP312i8m44Jl-OebdjMcfGXw2GfwKa5xz5BRq0QsYIIf_huR5LlGGyBivCqoHFfGkj1to2fhAkqLyCBf6uVIGo0ns1lhmykkI32PiFWCsiDM-rQRXaK_plpSbOZJlUr3ovbCVqcvI0PwxUp3GE489rgCSFxXevqFzl6kt3T-w15PgviMwGGGEGGBQGwJdSLwXTgevKdLi8HHgOiHympm2YJ4BEVZ_7k1-BTXL9I2bASxesxArF7_dW00&quot; target=&quot;_blank&quot;&gt;original&lt;/a&gt;]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Here I take a cue from accounting. In addition to the point in time when the success was achieved, I can measure the time in between as a the cost of catching the fish.
&lt;/p&gt;
&lt;p&gt;
In the example above, the Small-Mouth only took 20 minutes to catch, however I continued to fish without catching another for a full 50 minutes. We therefore allocate the unsuccessful time to the nearest fish caught.
&lt;/p&gt;
&lt;p&gt;
Given this new perspective, we can change the measure of success to:
&lt;/p&gt;
&lt;p&gt;
score =  quality of the fish&lt;br&gt;        --------------------------------&lt;br&gt;        time spent fishing for that fish&lt;/p&gt;&lt;p name=&quot;3f2c&quot; id=&quot;3f2c&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;While the trip score can be considered the average of all the individual fish scores.
&lt;/p&gt;
&lt;p&gt;
We are getting closer to a simple score with a clear definition of how to measure the time per fish. Unfortunately, the definition of “quality” of a fish is not a straight forward measure either.
&lt;/p&gt;
&lt;p&gt;
Generally size is considered the measure of a successful catch, but not all fish are considered equal. If I am fishing in a mountain stream and catch a “good sized trout” it is going to be a very different size from even a small Great White Shark. Age is also a factor: young fish will be smaller than older fish.
&lt;/p&gt;
&lt;p&gt;
Another wrinkle enters when we consider why the government was going to be collecting this data: fisheries management. They want to know the health of the regional ecosystem. Under these conditions it is not sufficient to know that the fish is “bigger”, but that it is an appropriate weight for its age. High or low values could indicate various stresses on the population.
&lt;/p&gt;
&lt;p&gt;
A species&#39; &lt;a href=&quot;https://en.wikipedia.org/wiki/Standard_weight_in_fish&quot; target=&quot;_blank&quot;&gt;Standard Weight&lt;/a&gt; is a measure of the average size of a fish given its height. This is basically BMI for fish: given a fish&#39;s length, we can consider its normal weight. This weight follows an exponential curve (fish get fatter faster than they get longer), and is unique to each species with each species having two constants that define their normal curve.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/FishAlytics/0-huik1-oypdkjrzk-6r51TAy9jX-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/FishAlytics/0-huik1-oypdkjrzk-6r51TAy9jX-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;547&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Two Standard Weights for two different species. As length increases so does weight, but at different rates. (&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Standard_weight_largemouth_bass_burbot.png&quot; target=&quot;_blank&quot;&gt;Wikipedia CCSA-3.0&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
In order to make use of this piece of Standard Weights a database of the &lt;code class=&quot;markup--code markup--p-code&quot;&gt;a&lt;/code&gt; and &lt;code class=&quot;markup--code markup--p-code&quot;&gt;b&lt;/code&gt; parameter for each species is required. Fortunately that database exists in the form of &lt;a href=&quot;https://www.fishbase.de/&quot; target=&quot;_blank&quot;&gt;Fish Base&lt;/a&gt;, an online catalogue of fish research from around the world.
&lt;/p&gt;
&lt;p&gt;
To identify a fish&#39;s parameters, all that is required is to know the fish species (common name is acceptable) and the location it was caught. This results in a a page of information about a fish, including its Standard Weight. For example, our &lt;a href=&quot;https://fishbase.in/popdyn/LWRelationshipList.php?ID=3382&amp;amp;GenusName=Micropterus&amp;amp;SpeciesName=dolomieu&amp;amp;fc=302&quot; target=&quot;_blank&quot;&gt;Small Mouth Bass&lt;/a&gt;: (a) 0.0129, and (b) 3.06, (len) 8&amp;quot; or 216 mm, (weight) 1/3 lbs or 153g
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/FishAlytics/1-08tu1fjknji4twfebnr5xg-C_if8Gp2uJ-__328__.avif 328w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/FishAlytics/1-08tu1fjknji4twfebnr5xg-C_if8Gp2uJ-__328__.webp&quot; alt=&quot;&quot; width=&quot;328&quot; height=&quot;81&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;The mean `a` and `b` values are offered in the footnotes.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Given this information, we can consider the “quality” of a fish to be its variance from its Standard Weight. Note, that for convenience, scores are shifted to a positive range (catching a fish is always a good thing) between 0 and 1000 ( because per mille has always amused me)
&lt;/p&gt;
&lt;p&gt;
stdWeight = StdWt(a,b,mm)&lt;br&gt;          = StdWt(0.0129,3.0600,203)&lt;br&gt;          = &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;156.3g&lt;/strong&gt;&lt;/p&gt;&lt;pre name=&quot;51c3&quot; id=&quot;51c3&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;quality =  weight - stdWeight&lt;br&gt;           ------------------&lt;br&gt;               stdWeight&lt;/pre&gt;&lt;pre name=&quot;ea8f&quot; id=&quot;ea8f&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;        =  153g - 156.3g&lt;br&gt;           ------------- &lt;br&gt;              156.3g&lt;/pre&gt;&lt;pre name=&quot;e1f7&quot; id=&quot;e1f7&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;        &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;= -0.021113243762&lt;/strong&gt;&lt;/pre&gt;&lt;pre name=&quot;37d3&quot; id=&quot;37d3&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;        (for convenience, convert to per mille)&lt;br&gt;        = floor(-0.021113243762 / 2 + 0.5)&lt;br&gt;        = &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;489‰&lt;/strong&gt;&lt;/pre&gt;&lt;p name=&quot;afa4&quot; id=&quot;afa4&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;This should be further converted to the &lt;code class=&quot;markup--code markup--p-code&quot;&gt;score&lt;/code&gt; by integrating the time spent fishing for the fish:
&lt;/p&gt;
&lt;p&gt;
score = quality / time&lt;br&gt;      = 489 / 70&lt;br&gt;      &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;= 7&lt;/strong&gt;&lt;/p&gt;&lt;p name=&quot;a2fa&quot; id=&quot;a2fa&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;Using the standardised measure of quality method gives us a measure of quality of each individual fish caught, which allows us to produce aggregate values (such as the trip score) without concern for species variability.
&lt;/p&gt;
&lt;h3&gt;Analytics&lt;/h3&gt;
&lt;p&gt;
By query a space-time bounding box, a user can view places where the fishing has been particularly good, or particularly bad. This was charted using OpenStreetMap, Leaflet.js, and Heatmap.js. This is not only useful for fisherman, but also for ecologists looking to study the quality of the fish in the area.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/FishAlytics/1-xkyvoiqnclxrkc-ewudvkq-JSMCBl8TpT-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/FishAlytics/1-xkyvoiqnclxrkc-ewudvkq-JSMCBl8TpT-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;354&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;The default example from HeatMap.js (&lt;a href=&quot;https://www.patrick-wied.at/static/heatmapjs/example-heatmap-leaflet.html&quot; target=&quot;_blank&quot;&gt;demo&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
With sufficient observations, this standardisation of the data would allows for several other types other analysis
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Year over year analysis is possible allowing ecologists to monitor for trends of declining or recovering populations.
&lt;/li&gt;
&lt;li&gt;
Species comparative analysis can show one species filling in for another species, a common symptom of an environment in distress.
&lt;/li&gt;
&lt;li&gt;
Time of day analysis, or seasonality, can improve catch rates by fisherman
&lt;/li&gt;
&lt;li&gt;
Poaching rates observed by anonymously reported poaching
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
This finally made me realise there were two target audiences for this data, other than just fisherman:
&lt;/p&gt;
&lt;h4&gt;Fisheries Management&lt;/h4&gt;
&lt;p&gt;
The ability to turn fishermen&#39;s stories into meaningful analytics meant fisheries don&#39;t need to wait until the end of the “season” to get paper records. Real-time catch data, collected customers, can be used to gain insight into the health of bodies of water. This means early interventions can be taken.
&lt;/p&gt;
&lt;h4&gt;The Ecosystem&lt;/h4&gt;
&lt;p&gt;
Fish populations are early warning signs of ecological disasters. Changes in types and sizes of fish populations is a good indicator of ecological health of the water. These analytics give real-time, and early detection of ecological issues.
&lt;/p&gt;
&lt;p&gt;
Fisherman don&#39;t want to fish an over-fished area. Ecologist don&#39;t want fishing happening in stressed areas. A system like this was hoped to identify healthy populations of fish and direct fisherman toward those; leaving stressed populations alone to recover.
&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;
FishAlytics was a failed social experiment for me. After nearly a decade, I abandoned the project without it ever moving past a personal fishing journal. Competing priorities (I was a farm labourer), legal obligations (I didn&#39;t wnat to loose intellection property while working for some companies), and theft (a customer I pitched this too, pitched it back to my students 4 years later), left me just not working on it and finally letting it go.
&lt;/p&gt;
&lt;p&gt;
But I don&#39;t think it was a waste of time either. It has offered me my first real introduction into the possibility of using Data Analysis as a tool for social change. Also, I started to conceive of how passive pressure could enact social change.
&lt;/p&gt;
&lt;p&gt;
It also introduced me to the idea that everything can, and often should, be boiled down to a single metric of health. This has been beneficial to me in other Data Analysis roles where being able to identify variance in an abstract metric of “health” has allowed for early intervention.
&lt;/p&gt;
&lt;p&gt;
While I failed to achieve any of the desired results, I hope this article helps others see software and systems as more than just forms on a page. Rather to view them as tools for evolving a social systems, and modifying behaviour for the the betterment of all.
&lt;/p&gt;
&lt;h3&gt;Further Reading&lt;/h3&gt;
&lt;p&gt;
Honestly, I would love for someone to pick up the torch, so the first reading would be to fork FishAlytics
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://gitlab.com/jefferey-cave/Fishalytics&quot; target=&quot;_blank&quot;&gt;https://gitlab.com/jefferey-cave/Fishalytics&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Though, I will say, it was my first foray into server side JavaScript, so I&#39;m not particularly proud of the code.
&lt;/p&gt;
&lt;p&gt;
There are a few libraries that would be highly useful for this project
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://leafletjs.com/&quot; target=&quot;_blank&quot;&gt;Leaflet.js&lt;/a&gt;: a mapping library for interacting in the browser
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://plotly.com/&quot; target=&quot;_blank&quot;&gt;Plotly&lt;/a&gt;: a general charting library. Not used in the project, but a staple in things I do now
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://leafletjs.com/plugins.html#heatmaps&quot; target=&quot;_blank&quot;&gt;Heatmap.js&lt;/a&gt;: the library used for integrating heatmaps in FishAlytics
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Also, if you are interested in enacting social change through software development, I must recommend some general reading
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://wickedproblems.com/&quot; target=&quot;_blank&quot;&gt;Wicked Problems: Problems Worth Solving&lt;/a&gt;: how do you imlement social change in complex systems. This book addresses the problem
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Finally, while I have suggested tools that I believe are useful; with great power comes great responsibility. I therefore leave you with this warning from Charles Goodhart: “Any observed statistical regularity will tend to collapse once pressure is placed upon it for control purposes”, or more simply put
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When a measure becomes a target, it ceases to be a good measure&lt;/p&gt;
&lt;p&gt;— &lt;a href=&quot;https://en.wikipedia.org/wiki/Goodhart%27s_law&quot;&gt;Goodhart&#39;s Law&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;Also, consider the sequel:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://jefferey-cave.medium.com/the-angry-chatterbot-6d004b8c41a4&quot;&gt;The Angry Chatterbot&lt;/a&gt;: A (successful) experiment in software as a behaviour modification tool (in PHP)&lt;/p&gt;
&lt;p&gt;UDPATE: 2023–08–08&lt;/p&gt;
&lt;p&gt;Since writing this, I use &lt;a href=&quot;https://fishbrain.com/&quot;&gt;FishBrain&lt;/a&gt; which is basically the same tool. They have been successful in the exact domain I failed… good on them.&lt;/p&gt;
</content>
        </entry>
        <entry>
            <title>Not storing (almost) the same file twice</title>
            <link href="http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/" />
            <updated>2022-04-11T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*6Ajx2tCwF6AKFS3klgwyVg.jpeg&quot; alt=&quot;Not storing (almost) the same file twice&quot;&gt;
                &lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/1-6ajx2tcwf6akfs3klgwyvg-eV2h_by5ev-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/1-6ajx2tcwf6akfs3klgwyvg-eV2h_by5ev-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;486&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Photo by &lt;a href=&quot;https://unsplash.com/@unarchive?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; target=&quot;_blank&quot;&gt;Jeremy Bezanger&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/repeating-lemons?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; target=&quot;_blank&quot;&gt;Unsplash&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
In &lt;a href=&quot;https://jefferey-cave.medium.com/de-duplicating-data-storage-i-3ed467afc528&quot; target=&quot;_blank&quot;&gt;De-duplicating Data Storage I&lt;/a&gt;, I showed a technique for reducing the storage load on a system, where the system encounters multiple copies of the same file. This is a fairly common situation in data-intensive environments as Data Scientists make copies of the dataset they are working on, and their colleague is working on, and they shared with their friend. Applying the techniques, we showed a 36% increase in profits in a laboratory scenario.
&lt;/p&gt;
&lt;p&gt;
There is another behaviour that is very common: &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;Data Revisions&lt;/strong&gt;.
&lt;/p&gt;
&lt;p&gt;
Naturally, as time progresses, our source datasets change, resulting in changes to our output datasets. While most people think of keeping all data forever, for the most part, we are only concerned with the current state. However, &lt;a href=&quot;https://en.wikipedia.org/wiki/Fear_of_missing_out&quot; target=&quot;_blank&quot;&gt;FOMO &lt;/a&gt;keeps us from removing now historic, but redundant datasets. Over time this data continuously grows,
&lt;/p&gt;
&lt;blockquote&gt;
I&#39;m going to share a way to reduce this cost by &lt;strong class=&quot;markup--strong markup--pullquote-strong&quot;&gt;90% over 3 years&lt;/strong&gt;.
&lt;/blockquote&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/1-fc3-azsqovlnj2-eru7fg-oGVfcaYPt3-__651__.avif 651w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/1-fc3-azsqovlnj2-eru7fg-oGVfcaYPt3-__651__.webp&quot; alt=&quot;&quot; width=&quot;651&quot; height=&quot;132&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;90% savings in 3rd year&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;An example from my past was a heat map of financial transactions across Canada. This dataset was based on the last two years of financial data aggregated at the Postal Code level. To place the items on the map, we had a second dataset of financial districts and a third set that consisted of postal codes and their corresponding financial district. Just to add a layer of complexity, Canada Post changes their postal delivery routes regularly, and therefore their postal codes, also the boundaries of our financial districts changed regularly. This means that the proportion of a Postal Code, that resides within the &lt;a href=&quot;https://www.mapchart.net/world-subdivisions.html&quot; target=&quot;_blank&quot;&gt;Financial District&lt;/a&gt; is not consistent and required constant updating &lt;a href=&quot;https://nominatim.org/&quot; target=&quot;_blank&quot;&gt;from a third-party provider&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/1-lj8zuhkyta6aklp-9rv9fa-yA832YeMFs-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/1-lj8zuhkyta6aklp-9rv9fa-yA832YeMFs-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;585&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Regional heatmaps are a common means of visualising data. An example can be found via Statistics Canada&#39;s annual population estimates (&lt;a href=&quot;https://www150.statcan.gc.ca/n1/pub/71-607-x/71-607-x2020021-eng.htm&quot; target=&quot;_blank&quot;&gt;StatsCan Annual Demographic Estimates&lt;/a&gt;, &lt;a href=&quot;https://www.statcan.gc.ca/en/reference/licence&quot; target=&quot;_blank&quot;&gt;StatsCan Open License&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
The network drive folder looked something like this:
&lt;/p&gt;
&lt;p&gt;
name                       size  &lt;br&gt;—————————————————————————————————————&lt;br&gt;/proj/1/dashboard.html     12 KB&lt;br&gt;/proj/1/transactions.tns   50  B &lt;br&gt;/proj/1/postalcodes.csv    33 MB [&lt;a href=&quot;https://download.geonames.org/export/dump/&quot; target=&quot;_blank&quot;&gt;ref&lt;/a&gt;]&lt;br&gt;/proj/1/districts.gml      37 MB [&lt;a href=&quot;https://open.canada.ca/data/en/dataset/515dbfa9-9069-4877-8fe8-177edaa4ca76/resource/2e169066-607e-4eaf-a83c-dbab42d7b89f&quot; target=&quot;_blank&quot;&gt;ref&lt;/a&gt;]&lt;br&gt;—————————————————————————————————————&lt;br&gt;                           70 MB&lt;/p&gt;&lt;p name=&quot;b0e2&quot; id=&quot;b0e2&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;So we have three datasets: our live financial data (a live connection), a listing of postal codes, and a listing of location shapes. The relationship between the datasets and the final aggregate that is displayed on the dashboard can be described as
&lt;/p&gt;
&lt;p&gt;
select &lt;br&gt;    d.label, &lt;br&gt;    d.shape, &lt;br&gt;    sum(p.weight * t.amt) as amt&lt;br&gt;from &lt;br&gt;    transactions t &lt;br&gt;    inner join postal p on f.postal_code = p.code &lt;br&gt;    inner join districts d on d.id = p.district_id&lt;/p&gt;&lt;p name=&quot;6be7&quot; id=&quot;6be7&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;While the actual financial summary is updated in real-time, we receive updated districts and postal codes every quarter. A fairly normal practice is that every quarter, we receive an email with a link to a CSV, and someone has to go download the file and overwrite the current CSV files, however it is considered a best practice to make a copy of the old files in an archive folder. This is done by creating a copy and date-stamping it.
&lt;/p&gt;
&lt;p&gt;
name                            size  &lt;br&gt;—————————————————————————————————————&lt;br&gt; /proj/1/&lt;br&gt;   dashboard.html              12 KB &lt;br&gt;   districts.gml               37 MB&lt;br&gt;   postalcodes.csv             33 MB&lt;br&gt;   arch/&lt;br&gt;     districts.202203.csv      37 MB&lt;br&gt;     districts.202204.csv      37 MB&lt;br&gt;     postalcodes.202203.csv    33 MB&lt;br&gt;     postalcodes.202204.csv    33 MB&lt;br&gt;—————————————————————————————————————&lt;br&gt;                       Total: 210 MB&lt;/p&gt;&lt;p name=&quot;367c&quot; id=&quot;367c&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;I&#39;m sure many people recognise this pattern of file management, as it is very common. It is also easy to see how this can spiral out of control.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
notice that this is &lt;code&gt;proj/1&lt;/code&gt; of … let&#39;s say about 100 active projects
&lt;/li&gt;
&lt;li&gt;
these projects have been running longer than 2 months (average of 5 years)
&lt;/li&gt;
&lt;li&gt;
corporate policy requires redundant backups of the drive space, with point-in-time recovery capabilities (daily for a month, monthly for 10 years)
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Given these approximations, we have quickly consumed 210MB (and that is a very small project) of data per project, with 60 months of user copies, on slow archival disks with 150 disk snapshots, and 100 projects. A total of 180 TB. The policies and project volumes are all realistic, in fact, I suspect I&#39;m underestimating the storage demand. Assuming &lt;a href=&quot;https://aws.amazon.com/s3/pricing/&quot; target=&quot;_blank&quot;&gt;AWS S3 Standard storage&lt;/a&gt; of the files ($0.021/GB), &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;this costs roughly $ 70,000 CAD/year&lt;/strong&gt;. (please, &lt;a href=&quot;https://observablehq.com/d/ae02ef857c596f42&quot; target=&quot;_blank&quot;&gt;check my math&lt;/a&gt;)
&lt;/p&gt;
&lt;h3&gt;It&#39;s not my money … why should I care?&lt;/h3&gt;
&lt;p&gt;
There were two events that made me care:
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
some users were denied access to the service because there were concerns about disk consumption and cost, and their business case write-up did not persuade the executives: a significant loss to the organisation.
&lt;/li&gt;
&lt;li&gt;
a different user asked for the recovery of a &lt;em&gt;single file&lt;/em&gt;, from an historic checkpoint. The backup team informed him they would need to “find a disk big enough to restore the point in time to”. The backup team needed to restore the entirety of all the projects to get one file: a significant expense to the organisation.
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
These are demonstrations of bureaucracy getting in the way. It stems from a poor understanding of digital storage and information management techniques on both the part of the IT department as well as the Data Scientists.
&lt;/p&gt;
&lt;p&gt;
Sometimes we need to keep stuff moving, even when bureaucracy gets in the way.
&lt;/p&gt;
&lt;h3&gt;A Quick History Lesson&lt;/h3&gt;
&lt;p&gt;
The disk usage pattern in question is a well-established and intuitive one, usually developed by students in their first year of college. Changes made to complex systems can result in unanticipated outcomes, it is also not always obvious which change (or combination of changes) led to the behaviour; having old copies can help you to understand what has gone wrong.
&lt;/p&gt;
&lt;p&gt;
Having identified only the portions of a file that have changed, the basic problem becomes very recognisable to most programmers (actually most publishers of content) as a Change Management or Version Control. Over the decades, several tools have evolved to manage this problem called &lt;a href=&quot;https://en.wikipedia.org/wiki/Version_control&quot; target=&quot;_blank&quot;&gt;Version Control Systems (VCS)&lt;/a&gt;. While the field is littered with VCS, there are a few prominent ones that represent significant changes in the way changes are thought of and managed
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Concurrent_Versions_System&quot; target=&quot;_blank&quot;&gt;CVS&lt;/a&gt; (1985) — changes to individual files are tracked independently
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Subversion&quot; target=&quot;_blank&quot;&gt;SVN&lt;/a&gt; (2000) — related changes to multiple files are considered a single unit
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Git&quot; target=&quot;_blank&quot;&gt;Git&lt;/a&gt; (2005) — collections of changes are able to be managed as separate units
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
These three tools represent important changes in the understanding of the way in which the databases we store revisions of data should be structured.
&lt;/p&gt;
&lt;p&gt;
In a large volume of data, it is important to be able to see the differences between instances. Asking “what has changed”, in large volumes of data, can be very difficult. Difference tools ( &lt;code&gt;diff&lt;/code&gt;ing) became standard in the data and programming toolkit (&lt;code&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Diff&quot; target=&quot;_blank&quot;&gt;diff&lt;/a&gt;&lt;/code&gt;) in the mid-70s. Further, tools like &lt;code&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Patch_%28Unix%29&quot; target=&quot;_blank&quot;&gt;patch&lt;/a&gt;&lt;/code&gt; offer a way to transmit (or store) only the changes which may be significantly smaller than an entire copy. There are now a plethora of &lt;a href=&quot;https://en.wikipedia.org/wiki/File_comparison&quot; target=&quot;_blank&quot;&gt;graphical tools&lt;/a&gt; for such tasks.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/0-j926otnzst5tsjyg-t5DD9KHZYb-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/0-j926otnzst5tsjyg-t5DD9KHZYb-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;558&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Meld is a graphical tool for comparing the contents of text files to determine what has changed. These tools can be used to great effect on CSV files to determine if a significant change has happened. (&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Meld_file1.png&quot; target=&quot;_blank&quot;&gt;wikipedia:GNU License&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Techniques to Reduce the Problem&lt;/h3&gt;
&lt;p&gt;
These are well-established problems, but with some best practices that have evolved.
&lt;/p&gt;
&lt;h4&gt;Compression&lt;/h4&gt;
&lt;p&gt;
While not the focus of this article, compression is an easy and often overlooked solution to the problem. From a solution architecture, perspective, I am often glad it is overlooked. While putting the files in a &lt;code&gt;zip&lt;/code&gt; archive is an easy solution, it does reduce the visibility of the changes: files need to be decompressed before they can be compared. Compression should be maintained inside the solution and abstracted from the user.
&lt;/p&gt;
&lt;p&gt;
Having users compress their files actually reduces the ability of tools to take other actions that may have a larger impact
&lt;/p&gt;
&lt;h4&gt;Right-Scoped Repositories&lt;/h4&gt;
&lt;p&gt;
In our initial problem description, the IT team had difficulty restoring the backups because they needed to &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;restore the entire repository&lt;/strong&gt; to a point in time.
&lt;/p&gt;
&lt;p&gt;
Rather than creating backups of the entire repository (which is measured in petabytes for our example), it may be more sensible to break the problem down into sub-parts. Many of our projects will get archived over time, projects change at different rates, and usage may decline or increase. This means that some data will have a greater or lesser probability of requiring a restore from the point in time.
&lt;/p&gt;
&lt;p&gt;
Dividing the backups at a “per-project” level offers an obvious point of division. This allows us to strike a balance between the ease of backing up “instances” with the ease of restoring smaller instances to help the user.
&lt;/p&gt;
&lt;h4&gt;Only Store Changes&lt;/h4&gt;
&lt;p&gt;
As discussed, tools like &lt;code&gt;diff&lt;/code&gt; and &lt;code&gt;patch &lt;/code&gt;offer a means to identify, store, and apply changes to larger files. Rather than storing multiple copies of the dataset, it is possible to store a primary dataset, and then track the series of changes that have taken place on it.
&lt;/p&gt;
&lt;p&gt;
For example, country lists change regularly, requiring an update for even a spelling change. A CSV based on the &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes&quot; target=&quot;_blank&quot;&gt;Wikipedia page for ISO country codes&lt;/a&gt; could be modified with a patch description to accommodate &lt;a href=&quot;http://www.statoids.com/w3166his.html&quot; target=&quot;_blank&quot;&gt;Hungary&#39;s name change in 2012&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
This is significantly smaller than storing the entire file of hundreds of countries for a single name change. As a tip, the primary should be the one you are actually using, and the changes can work backwards in time.
&lt;/p&gt;
&lt;p&gt;
@@ -117,1 +117,1 @@&lt;br&gt;-Hungary,Hungary,UN member state,HU,HUN,348,.hu&lt;br&gt;+Hungary,Republic of Hungary,UN member state,HU,HUN,348,.hu&lt;/p&gt;&lt;p name=&quot;3798&quot; id=&quot;3798&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;This is significantly smaller than storing the entire file of hundreds of countries for a single name change. As a tip, the primary should be the one you are actually using, and the changes can work backwards in time.
&lt;/p&gt;
&lt;h3&gt;A Solution&lt;/h3&gt;
&lt;p&gt;
Rather than manually perform all of these steps, these are the problems that modern VCS applications were developed to solve. As an odd quirk of history, subversion (SVN) is particularly well suited to handling large files as it only tracks the differences between states.
&lt;/p&gt;
&lt;p&gt;
While I would recommend any VCS solution as an improvement over file copies, SVN is ideally suited to the purposes of Data Analyst&#39;s management of large datasets.
&lt;/p&gt;
&lt;p&gt;
Take our original problem of a project storage system with archived folders
&lt;/p&gt;
&lt;p&gt;
name                           size  &lt;br&gt;—————————————————————————————————————&lt;br&gt; /proj/1/&lt;br&gt;   schema.json                128 B&lt;br&gt;   dashboard-template.html    232 KB&lt;br&gt;   dashboard.html             100 MB&lt;br&gt;   districts.csv               37 MB&lt;br&gt;   postalcodes.csv             33 MB&lt;br&gt;   arch/&lt;br&gt;     dashboard.20220301.html  100 MB&lt;br&gt;     dashboard.20220315.html  100 MB&lt;br&gt;     dashboard.20220401.html  100 MB&lt;br&gt;     dashboard.20220415.html  100 MB&lt;br&gt;     districts.202203.csv      37 MB&lt;br&gt;     districts.202204.csv      37 MB&lt;br&gt;     postalcodes.202203.csv    33 MB&lt;br&gt;     postalcodes.202204.csv    33 MB&lt;br&gt;—————————————————————————————————————&lt;/p&gt;&lt;p name=&quot;95f2&quot; id=&quot;95f2&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;I have modified the example to include output historic reports built from a template.
&lt;/p&gt;
&lt;p&gt;
Rather than trying to retrofit, let us start over (with project #2).
&lt;/p&gt;
&lt;p&gt;
One of the very first improvements we can make to the storage structure is to create an independent archive location for each project. This independent location can then be turned into an SVN-controlled location.
&lt;/p&gt;
&lt;p&gt;
mkdir -p /arch/2;&lt;br&gt;cd /arch/2;&lt;br&gt;svnadmin create;&lt;/p&gt;&lt;p name=&quot;804f&quot; id=&quot;804f&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;Now we can link the archive location to the working location.
&lt;/p&gt;
&lt;p&gt;
mkdir -p /proj/2;&lt;br&gt;cd /proj/2;&lt;br&gt;svn checkout file:///arch/2 .;&lt;/p&gt;&lt;p name=&quot;1685&quot; id=&quot;1685&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;Once this is done, we can begin to create our space and apply the changes as we make them.
&lt;/p&gt;
&lt;p&gt;
name                           size  &lt;br&gt;—————————————————————————————————————&lt;br&gt; /proj/2/&lt;br&gt;   schema.json                128 B&lt;br&gt;   dashboard-template.html    232 KB&lt;br&gt;   dashboard.html             100 MB&lt;br&gt;   districts.csv               37 MB&lt;br&gt;   postalcodes.csv             33 MB&lt;br&gt;—————————————————————————————————————&lt;br&gt;cd /proj/2;&lt;br&gt;svn add *;&lt;br&gt;svn commit -m &amp;quot;Change 2022-03-01&amp;quot;;&lt;/p&gt;&lt;pre name=&quot;5bdf&quot; id=&quot;5bdf&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;copy /proj/1/arch/dashboard.20220315.html dashboard.html;&lt;br&gt;svn commit -m &amp;quot;Change 2022-03-15&amp;quot;;&lt;/pre&gt;&lt;pre name=&quot;409e&quot; id=&quot;409e&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;copy /proj/1/arch/dashboard.20220401.html dashboard.html;&lt;br&gt;copy /proj/1/arch/districts.20220401.csv districts.csv;&lt;br&gt;copy /proj/1/arch/postalcodes.20220401.csv postalcodes.csv;&lt;br&gt;svn commit -m &amp;quot;Change 2022-04-01&amp;quot;;&lt;/pre&gt;&lt;pre name=&quot;8b7a&quot; id=&quot;8b7a&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;copy /proj/1/arch/dashboard.20220415.html dashboard.html;&lt;br&gt;svn commit -m &amp;quot;Change 2022-04-15&amp;quot;;&lt;/pre&gt;&lt;p name=&quot;22cc&quot; id=&quot;22cc&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;Under these conditions you will create snapshots of the changes at each point something changed,
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
reducing the number of snapshots you have to maintain. No change; no snapshot
&lt;/li&gt;
&lt;li&gt;
backups are generated per-working folder, meaning if a restore is required, it only takes the size of the individual project to go back in time.
&lt;/li&gt;
&lt;li&gt;
your backup footprint is reduced because SVN only stores the differences between each snapshot.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
name                          size  &lt;br&gt;—————————————————————————————————————&lt;br&gt; /proj/2/&lt;br&gt;   .svn/                     13.4 GB&lt;br&gt;   schema.json                128  B&lt;br&gt;   dashboard-template.html    232 KB&lt;br&gt;   dashboard.html             6.7 GB&lt;br&gt;   districts.csv              5.0 GB&lt;br&gt;   postalcodes.csv            1.7 GB&lt;br&gt;—————————————————————————————————————&lt;/p&gt;&lt;p name=&quot;26f1&quot; id=&quot;26f1&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;Note the creation of the folder &lt;code&gt;.svn&lt;/code&gt; , this is true for most VCS applications. They must create a control folder for tracking their link to the repository. Also, this folder will contain a single copy of the folder structure to allow it to detect changes, doubling the storage space in the short term. The total storage space in the working location remains largely untouched as the changes are made.
&lt;/p&gt;
&lt;p&gt;
Snapshots can be viewed via &lt;a href=&quot;https://svnbook.red-bean.com/en/1.7/svn.ref.svn.c.log.html&quot; target=&quot;_blank&quot;&gt;SVN&#39;s log&lt;/a&gt; command
&lt;/p&gt;
&lt;p&gt;
$ svn log ^/ -qv&lt;br&gt;--------------------------------------------------------------------&lt;br&gt;r4 | jeff | 2022-04-15 00:43:13&lt;br&gt;Changed paths:&lt;br&gt;   M /dashboard.html&lt;br&gt;--------------------------------------------------------------------&lt;br&gt;r3 | jeff | 2022-04-01 12:25:08&lt;br&gt;Changed paths:&lt;br&gt;   M /dashboard.html&lt;br&gt;   M /districts.csv&lt;br&gt;   M /postalcodes.csv&lt;/p&gt;&lt;p name=&quot;7b2e&quot; id=&quot;7b2e&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;And if a restore needs to be performed for a user, it is simple to select the historic revision.
&lt;/p&gt;
&lt;p&gt;
mkdir -p /tmp/history;&lt;br&gt;pushd /tmp/history;&lt;br&gt;svn checkout -r 3 file:///arch/proj/2 .;&lt;br&gt;popd;&lt;/p&gt;&lt;p name=&quot;78c4&quot; id=&quot;78c4&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;One final benefit is that because the archives are no longer stored with the working copy, a different type of storage can be applied to the backups. Slower storage can be applied to the backups, while faster storage for the working copy.
&lt;/p&gt;
&lt;h4&gt;The Social Aspect&lt;/h4&gt;
&lt;p&gt;
Unfortunately, in many cases of encountering this problem, I have seen the interested parties pointing at one another and saying it is the other person&#39;s fault. Data Analysts are unaware that version control tools exist, and IT departments view backups and restores as whole-system-events for recovering from complete system failures.
&lt;/p&gt;
&lt;p&gt;
The question becomes: who is this article designed for? Data Scientists or System Administrators.
&lt;/p&gt;
&lt;p&gt;
Really, this is for both. Hopefully, both parties work together to reduce costs and burden on the other, but the reality is that a change will likely have to start with the IT department.
&lt;/p&gt;
&lt;p&gt;
Further, reducing costs is not seen as a positive in most organisations. Cost is associated with prestige, managerial resumes often boast about the size of the budget they managed. Reducing the budget reduces prestige.
&lt;/p&gt;
&lt;h4&gt;A Scripted Solution&lt;/h4&gt;
&lt;p&gt;
Given the social problems, the simplest means of introducing users is to simply implement a simplified Version Control practice in an &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;automated fashion&lt;/strong&gt;, without asking users or offering training or really talking about it.
&lt;/p&gt;
&lt;p&gt;
There are two steps that should be taken:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Immediately install a VCS client on every computer that accesses the system in question
&lt;/li&gt;
&lt;li&gt;
Integrate VCS creation into the project allocation part of the process
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
On the project setup and allocation side, the process usually starts with a request for space on the computer as a paper form (yes, companies are still filling out paper forms as of &lt;code&gt;2022–04–10&lt;/code&gt;). As this kicks off a large process involving multiple configurations being manually configured, ensure the allocation of a VCS is part of that process. The simplest way to ensure this is done is to automate the entire process.
&lt;/p&gt;
&lt;p&gt;
&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;br&gt;proj=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;./proj&amp;quot;&lt;/span&gt;;&lt;br&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;arch&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;./arch&amp;quot;&lt;/span&gt;;&lt;br&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; SetupFolder {&lt;br&gt;  f=&lt;span class=&quot;hljs-variable&quot;&gt;$1&lt;/span&gt;;&lt;br&gt;  pPath=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;${proj}&lt;/span&gt;/&lt;span class=&quot;hljs-variable&quot;&gt;${f}&lt;/span&gt;&amp;quot;&lt;/span&gt;;&lt;br&gt;  aPath=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;${arch}&lt;/span&gt;/&lt;span class=&quot;hljs-variable&quot;&gt;${f}&lt;/span&gt;&amp;quot;&lt;/span&gt;;&lt;br&gt;  tPath=$(&lt;span class=&quot;hljs-built_in&quot;&gt;mktemp&lt;/span&gt; -d);&lt;br&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;mkdir&lt;/span&gt; -p &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;$aPath&lt;/span&gt;&amp;quot;&lt;/span&gt;;&lt;br&gt;  svnadmin create &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;$aPath&lt;/span&gt;&amp;quot;&lt;/span&gt; &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Repository created.&amp;quot;&lt;/span&gt; &#92;&lt;br&gt;    || &lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Repository already exists ($?)&amp;quot;&lt;/span&gt;;&lt;br&gt;  svn checkout &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;file://&lt;span class=&quot;hljs-variable&quot;&gt;${aPath}&lt;/span&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;$tPath&lt;/span&gt;&amp;quot;&lt;/span&gt;;&lt;br&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;mv&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;$tPath&lt;/span&gt;/.svn&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;$pPath&lt;/span&gt;/.svn&amp;quot;&lt;/span&gt;;&lt;br&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;rm&lt;/span&gt; -rf &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;$tPath&lt;/span&gt;&amp;quot;&lt;/span&gt;;&lt;br&gt;}&lt;br&gt;{&lt;br&gt;  proj=$(&lt;span class=&quot;hljs-built_in&quot;&gt;realpath&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;${proj}&lt;/span&gt;); &lt;br&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;mkdir&lt;/span&gt; -p &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;$proj&lt;/span&gt;/00000000&amp;quot;&lt;/span&gt;; &lt;br&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Project Folder: &lt;span class=&quot;hljs-variable&quot;&gt;$proj&lt;/span&gt;&amp;quot;&lt;/span&gt;;&lt;br&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;arch&lt;/span&gt;=$(&lt;span class=&quot;hljs-built_in&quot;&gt;realpath&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;${arch}&lt;/span&gt;); &lt;br&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;mkdir&lt;/span&gt; -p &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;$arch&lt;/span&gt;&amp;quot;&lt;/span&gt;; &lt;br&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Archive Folder: &lt;span class=&quot;hljs-variable&quot;&gt;$arch&lt;/span&gt;&amp;quot;&lt;/span&gt;;&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;dir&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;${proj}&lt;/span&gt;/*/ ; &lt;span class=&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;&lt;br&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;dir&lt;/span&gt;=$(&lt;span class=&quot;hljs-built_in&quot;&gt;basename&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;${dir}&lt;/span&gt;&amp;quot;&lt;/span&gt;);&lt;br&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Updating: &lt;span class=&quot;hljs-variable&quot;&gt;$dir&lt;/span&gt;&amp;quot;&lt;/span&gt; 1&amp;gt;&amp;amp;2;&lt;br&gt;    pPath=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;${proj}&lt;/span&gt;/&lt;span class=&quot;hljs-variable&quot;&gt;${dir}&lt;/span&gt;&amp;quot;&lt;/span&gt;;&lt;br&gt;    [[ -d &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;$pPath&lt;/span&gt;/.svn&amp;quot;&lt;/span&gt; ]] || {&lt;br&gt;      &lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;  - linking project&amp;quot;&lt;/span&gt; 1&amp;gt;&amp;amp;2;&lt;br&gt;      SetupFolder &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;$dir&lt;/span&gt;&amp;quot;&lt;/span&gt;;&lt;br&gt;    }&lt;br&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;pushd&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;$pPath&lt;/span&gt;&amp;quot;&lt;/span&gt;;&lt;br&gt;    (( $(svn status | wc -l) &amp;gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt; )) &amp;amp;&amp;amp; {&lt;br&gt;      &lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;  - synchronizing changes&amp;quot;&lt;/span&gt; 1&amp;gt;&amp;amp;2;&lt;br&gt;      svn status | grep -e &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;^&#92;?&amp;quot;&lt;/span&gt; | &lt;span class=&quot;hljs-built_in&quot;&gt;cut&lt;/span&gt; -c 9- | xargs svn add {}; &lt;br&gt;      svn status | grep -e &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;^&#92;!&amp;quot;&lt;/span&gt; | &lt;span class=&quot;hljs-built_in&quot;&gt;cut&lt;/span&gt; -c 9- | xargs svn del {};&lt;br&gt;      svn commit . -m &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-subst&quot;&gt;$(date -Iminutes)&lt;/span&gt;&amp;quot;&lt;/span&gt;;&lt;br&gt;      svn cleanup;&lt;br&gt;    };&lt;br&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;popd&lt;/span&gt;;&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;done&lt;/span&gt;;&lt;br&gt;} 1&amp;gt;/dev/null;&lt;/span&gt;&lt;/p&gt;&lt;p name=&quot;9bbd&quot; id=&quot;9bbd&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;This script scans all of the folders and determines if a change has been made. If there are changes, it submits those changes to the archive location, if no archive location exists, it creates one. This can be run on a timer, or better yet, can use &lt;code&gt;&lt;a href=&quot;https://www.man7.org/linux/man-pages/man7/inotify.7.html&quot; target=&quot;_blank&quot;&gt;inotify&lt;/a&gt;&lt;/code&gt; to monitor for changes. Better still would be to install a self-serve interface like GitLab that can &lt;a href=&quot;https://gitlab.com/dpub/DataLab&quot; target=&quot;_blank&quot;&gt;auto-allocate space and control permissions&lt;/a&gt;, but … bureaucracy.
&lt;/p&gt;
&lt;h4&gt;Teaching Users&lt;/h4&gt;
&lt;p&gt;
Secondly, install a highly visible VCS client on every computer in the office. Personally, I tie the installation of the client to the LDAP group that gives basic access to the Data System. By highly visible I mean &lt;a href=&quot;https://tortoisegit.org/&quot; target=&quot;_blank&quot;&gt;Tortoise&lt;/a&gt; &lt;a href=&quot;https://tortoisehg.bitbucket.io/&quot; target=&quot;_blank&quot;&gt;on&lt;/a&gt; &lt;a href=&quot;https://tortoisesvn.net/&quot; target=&quot;_blank&quot;&gt;Windows&lt;/a&gt;, or &lt;a href=&quot;http://rabbitvcs.org/&quot; target=&quot;_blank&quot;&gt;RabbitVCS&lt;/a&gt; on Linux. In both cases, the users are automatically presented with icons that tell them something is unique about the folder they have been granted access.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/0-2ieefhbtl6efy2nj-mF9HldlNgi-__418__.avif 418w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/0-2ieefhbtl6efy2nj-mF9HldlNgi-__418__.webp&quot; alt=&quot;&quot; width=&quot;418&quot; height=&quot;209&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;The Tortoise family of products places overlays on file icons at the operating system level. Users receive visual feedback that “something is special” and that they should learn more. (&lt;a href=&quot;https://tortoisesvn.net/docs/release/TortoiseSVN_en/tsvn-dug-wcstatus.html&quot; target=&quot;_blank&quot;&gt;TortoiseSVN Manual&lt;/a&gt;, GPL)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
One of three things will happen:
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
A user already familiar with Change Management Systems will be pleasantly surprised
&lt;/li&gt;
&lt;li&gt;
A user unfamiliar with Change Management Systems will curiously explore this new domain.
&lt;/li&gt;
&lt;li&gt;
The user will not care… “you can lead a horse to water, but you can&#39;t make it drink”.
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
Once users discover the log feature of the VCS, they become empowered to restore old versions as necessary. This reduces the burden on IT and the instances of manual duplication of files.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/0-qc8sfxat5zvn3hv2-JOZy7s5urS-__686__.avif 686w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/0-qc8sfxat5zvn3hv2-JOZy7s5urS-__686__.webp&quot; alt=&quot;&quot; width=&quot;686&quot; height=&quot;682&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Once users see the log of the changes, they become aware that a history is kept for them, and they become empowered to restore their own historic data.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;A Demonstration&lt;/h3&gt;
&lt;p&gt;
To demonstrate how this all fits together, as well as to compare the compression capabilities of various setups, four versions of the script were created:
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/0-nekv9imautjgykth-9N2VqD3ehL-__489__.avif 489w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/0-nekv9imautjgykth-9N2VqD3ehL-__489__.webp&quot; alt=&quot;&quot; width=&quot;489&quot; height=&quot;496&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;&lt;a href=&quot;https://www.buymeacoffee.com/jeffereycave&quot; target=&quot;_blank&quot;&gt;Found this Intersting? Leave a Tip…. it helps&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CPY&lt;/code&gt;: Makes a &lt;code&gt;copy&lt;/code&gt; of the files in the project folders over to the archive location. This is simply to offer a baseline comparison of what our users are currently doing
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FSZ&lt;/code&gt;: Filesystem compressed. We hope our users use ZIP, so this creates a compressed copy of the folder each time the backup is called
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GIT&lt;/code&gt;: Git is an excellent VCS, and should be included in any comparison.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SVN&lt;/code&gt;: The script that was offered earlier.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div name=&quot;aee2&quot; id=&quot;aee2&quot; class=&quot;graf graf--mixtapeEmbed graf-after--li&quot;&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/demos/-/tree/main/dedup2&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;dedup2 · main · Jeff Cave / Demos&lt;/strong&gt;&lt;br&gt;&lt;em&gt;An example of the how to apply the data differential techniques&lt;/em&gt;gitlab.com&lt;/a&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/demos/-/tree/main/dedup2&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;
To simulate the activities our users go through, a script called &lt;code&gt;demonstrate.sh&lt;/code&gt; downloads the history of the CIA World Factbook as JSON from GitHub, and stores each change in the project folders, and then backs them up. This is similar to our users receiving updates to their data files and then saving the old version to a backup folder. Results are stored in &lt;code&gt;results.csv&lt;/code&gt;.
&lt;/p&gt;
&lt;div name=&quot;8f18&quot; id=&quot;8f18&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p&quot;&gt;&lt;a href=&quot;https://observablehq.com/d/ae02ef857c596f42&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;Demo: De-duplicating Data Storage II&lt;/strong&gt;&lt;br&gt;&lt;em&gt;A demonstration of the cost savings that can be achieved using simple Full Article The CIA World Factbook maintains an…&lt;/em&gt;observablehq.com&lt;/a&gt;&lt;a href=&quot;https://observablehq.com/d/ae02ef857c596f42&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;
After 34 changes, we can see that Git and Zipped file systems perform almost equally, mostly because that is what Git does (zips of entire file structures), compression offers a lot of savings. By comparison, SVN shows almost no growth at all.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/1-fdduo3gbc88uvzfurxtjra-RAY6vfbJ8l-__654__.avif 654w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/1-fdduo3gbc88uvzfurxtjra-RAY6vfbJ8l-__654__.webp&quot; alt=&quot;&quot; width=&quot;654&quot; height=&quot;434&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;
This brings us back to our original problem state: $70,000/year ($56K USD) for storage, but using automated version control, and SVN, in particular, we are able to reduce the storage costs by 90%, to $7,000/year ($5.5K USD), under real-world conditions.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/1-fc3-azsqovlnj2-eru7fg-oGVfcaYPt3-__651__.avif 651w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/1-fc3-azsqovlnj2-eru7fg-oGVfcaYPt3-__651__.webp&quot; alt=&quot;&quot; width=&quot;651&quot; height=&quot;132&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;After 34 periods of change, SVN shows a significant cost savings (90%) over our original state.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
That is a 90% savings in costs, or about $63,000/year ($50K USD).
&lt;/p&gt;
&lt;p&gt;
Most of these estimates are on the small side. Recent experience has shown the involved datasets to change in the range of 40G/quarter, suddenly those numbers begin to be of the scale of millions of dollars.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/1-xdlnxkdwhbezopjrbeppsa-7b282yQbHK-__650__.avif 650w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/not-storing-almost-the-same-file-twice/1-xdlnxkdwhbezopjrbeppsa-7b282yQbHK-__650__.webp&quot; alt=&quot;&quot; width=&quot;650&quot; height=&quot;430&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;The cost savings, relative to the baseline storage, becomes more significant as time progresses. This chart did not convey what I hoped it would. I&#39;ve left it here because I suspect it will catch an executive&#39;s attention.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;
This technique was applied several times over the decades and has been demonstrated to work with not just text files, but also Parquet and SAS data files, in Tableau and &lt;a href=&quot;https://medium.com/@jefferey-cave/turn-a-shiny-dashboard-into-a-desktop-app-6f51287b7d6f&quot; target=&quot;_blank&quot;&gt;RStudio applications&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
Optimisation of these techniques requires cognisant cooperation from both Data and System Analysts, unfortunately, detailed information and change management techniques do not receive the exciting attention of executives. However, a 90% decrease in costs (and therefore an increase in profits) is not something that should be ignored, and attention to these details is important.
&lt;/p&gt;
&lt;p&gt;
For this reason, I have presented these techniques using an automated, but unobtrusive technique, that fosters an environment that can encourage learning and cooperation.
&lt;/p&gt;
&lt;p&gt;
Mostly, these techniques become necessary to reduce cost as an excuse for progress.
&lt;/p&gt;
&lt;p&gt;
… because sometimes bureaucracy gets in the way.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I encourage you to check out the &lt;a href=&quot;https://gitlab.com/jefferey-cave/demos/-/tree/main/dedup2&quot; target=&quot;_blank&quot;&gt;sample from GitLab&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Also, have a look at the &lt;a href=&quot;https://observablehq.com/d/ae02ef857c596f42&quot; target=&quot;_blank&quot;&gt;calculation worksheet&lt;/a&gt; and check my maths. Change the values to represent your workspace. How much can you save?&lt;/li&gt;
&lt;li&gt;Remember: cost correlates to carbon. Reducing your consumption is easily measured in dollars, but also represents less pollution&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Save a byte, save the environment&lt;/p&gt;
&lt;/blockquote&gt;
</content>
        </entry>
        <entry>
            <title>How to Build a Simple In-Browser Search Engine</title>
            <link href="http://plaidsheep.ca/posts/2022/how-to-build-a-simiple-in-browser-search-engine/" />
            <updated>2022-03-06T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2022/how-to-build-a-simiple-in-browser-search-engine/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*WX6PthIE_2rtkOcm-retzA.jpeg&quot; alt=&quot;How to Build a Simple In-Browser Search Engine&quot;&gt;
                &lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-to-build-a-simiple-in-browser-search-engine/1-wx6pthie-2rtkocm-retza-MQQ62PBOlm-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-to-build-a-simiple-in-browser-search-engine/1-wx6pthie-2rtkocm-retza-MQQ62PBOlm-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;973&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Photo by &lt;a href=&quot;https://unsplash.com/@ruchindra?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; target=&quot;_blank&quot;&gt;Ruchindra Gunasekara&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/warehouse?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; target=&quot;_blank&quot;&gt;Unsplash&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
A couple of years ago, I was asked to create a file repository for an air-gapped &lt;a href=&quot;https://anaconda.org/conda-forge&quot; target=&quot;_blank&quot;&gt;conda repository&lt;/a&gt;. The idea was that, for security reasons, we would have a list of allowed libraries inside the very expensive secure environment, and only those libraries. It was a &lt;a href=&quot;https://anaconda.org/conda-forge/conda-mirror&quot; target=&quot;_blank&quot;&gt;reasonably simple setup&lt;/a&gt;, and only required a large amount of storage, and a simple static web server to deliver them over.
&lt;/p&gt;
&lt;p&gt;
The simple filtered mirror allowed us to respond to user needs much more rapidly, and to add or remove libraries from the secure environment very quickly. This meant the security team was much more willing to allow packages into the environment, knowing it would be hours to remove them rather than months or years.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
They were so thrilled, we started using patterns to allow packages.
&lt;/li&gt;
&lt;li&gt;
We quickly went from dozens of packages to thousands.
&lt;/li&gt;
&lt;li&gt;
Users developed a new question to phone in and ask:
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
which libraries are available?
&lt;/blockquote&gt;
&lt;p&gt;
The list of libraries available was less than the official repositories but significantly larger than a human could reasonably be expected to read through, it was also spread across several folders. All-in-all it was just difficult to keep track of.
&lt;/p&gt;
&lt;p&gt;
What users required was a search engine, like Anaconda.org, but limited to only our organisationally available packages.
&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong class=&quot;markup--strong markup--blockquote-strong&quot;&gt;An Aside&lt;/strong&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
It&#39;s at this moment, in our story, that I find myself away from home on business. Having attended an evening lecture, in a pub in Gatineau Quebec, on using technical systems to affect social change, and having had a few drinks, I found myself walking back to my hotel, alone, in the dead of a Canadian winter. Trying to not freeze to death, and figure out where my hotel was, I started thinking about &lt;strong class=&quot;markup--strong markup--blockquote-strong&quot;&gt;search problems&lt;/strong&gt;. By the time i got back to my hotel, there was no way I could sleep, so began to implement the solution.
&lt;/blockquote&gt;
&lt;blockquote&gt;
Ahhh… the life of a programmer.
&lt;/blockquote&gt;
&lt;p&gt;
Unfortunately, Bureaucracy got in the way. Getting the static HTTP server had been a feat of negotiation. In an effort to get the filesystem shared via HTTP, I had explicitly stated that this would only have static rendering turned on. This allowed us to skip undergoing months of security evaluations, no server-side processing meant no security risk to the network.
&lt;/p&gt;
&lt;p&gt;
In order to accommodate both the need for a search engine and the lack of server-side processing, I built a simple search engine inside the browser.
&lt;/p&gt;
&lt;h3&gt;Implementation&lt;/h3&gt;
&lt;p&gt;
I have actually done this a couple of times before. There have been a few instances in my career where getting tools and computers have been significant barriers to a simple data processing engine. The key to this is that you can drop a simple &lt;code class=&quot;markup--code markup--p-code&quot;&gt;html&lt;/code&gt; file on your served location, and it can then look up the data stored on the server.
&lt;/p&gt;
&lt;p&gt;
For the reader&#39;s benefit, I have a simple example working that indexes the CIA World Factbook, and then offer a search of the content. Search results offer a link to the actual page on the CIA&#39;s website.
&lt;/p&gt;
&lt;div name=&quot;5057&quot; id=&quot;5057&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p&quot;&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/simple-search-demo/-/tree/main&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;Files · main · Jeff Cave / Simple Search Demo&lt;/strong&gt;&lt;br&gt;&lt;em&gt;GitLab.com&lt;/em&gt;gitlab.com&lt;/a&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/simple-search-demo/-/tree/main&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;h4 name=&quot;9d73&quot; id=&quot;9d73&quot; class=&quot;graf graf--h4 graf-after--mixtapeEmbed&quot;&gt;Auto-index and Loading&lt;/h4&gt;&lt;p&gt;
The key to this is to ensure that some form of &lt;a href=&quot;http://nginx.org/en/docs/http/ngx_http_autoindex_module.html&quot; target=&quot;_blank&quot;&gt;auto-index&lt;/a&gt; is on. The server must advertise the available datasets to be processed in order for the client to discover they need processing. There are a couple of ways to achieve this, the simplest is to turn the service on in the server.
&lt;/p&gt;
&lt;p&gt;
In similar systems, I have also used `bash` or `PowerShell` scripts to just list all the datasets present: no information about them, just that they are present
&lt;/p&gt;
&lt;p&gt;
&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;ls&lt;/span&gt; --format=single-column ./data/* &amp;gt; index.txt&lt;/span&gt;&lt;/p&gt;&lt;p name=&quot;f832&quot; id=&quot;f832&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;The location of that file can then be passed to the script as its starting point for gathering all information
&lt;/p&gt;
&lt;p&gt;
&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; basePath = &lt;span class=&quot;hljs-variable language_&quot;&gt;window&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;location&lt;/span&gt;;&lt;br&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; datasets = &lt;span class=&quot;hljs-string&quot;&gt;`&lt;span class=&quot;hljs-subst&quot;&gt;${basePath}&lt;/span&gt;/index.txt`&lt;/span&gt;;&lt;/span&gt;&lt;/p&gt;&lt;p name=&quot;fdc8&quot; id=&quot;fdc8&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;Once we have the list of data to be aggregated, we can begin the process of downloading, parsing, and storing.
&lt;/p&gt;
&lt;p&gt;
Given we used PouchDB, we can immediately begin loading the text into the local database.
&lt;/p&gt;
&lt;p&gt;
&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; db = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;PouchDB&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;searcher&amp;#x27;&lt;/span&gt;);&lt;br&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;LoadDB&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;){&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; recs = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;fetch&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;./index.txt&amp;#x27;&lt;/span&gt;);&lt;br&gt;    recs = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; rec.&lt;span class=&quot;hljs-title function_&quot;&gt;text&lt;/span&gt;();&lt;br&gt;    recs = rec.&lt;span class=&quot;hljs-title function_&quot;&gt;split&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;&#92;n&amp;#x27;&lt;/span&gt;);&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; loc &lt;span class=&quot;hljs-keyword&quot;&gt;of&lt;/span&gt; recs){&lt;br&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; content = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;fetch&lt;/span&gt;(loc);&lt;br&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; nRec = {&lt;br&gt;          &lt;span class=&quot;hljs-attr&quot;&gt;_id&lt;/span&gt;: loc,&lt;br&gt;          &lt;span class=&quot;hljs-attr&quot;&gt;text&lt;/span&gt;: nRec&lt;br&gt;      };&lt;br&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; oRec = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; db.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(loc);&lt;br&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt;(&lt;span class=&quot;hljs-title class_&quot;&gt;RecordsDiffer&lt;/span&gt;(nRec,oRec)){&lt;br&gt;          db.&lt;span class=&quot;hljs-title function_&quot;&gt;put&lt;/span&gt;(nRec);&lt;br&gt;      }&lt;br&gt;    }&lt;br&gt;}&lt;/span&gt;&lt;/p&gt;&lt;p name=&quot;1b71&quot; id=&quot;1b71&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;The full text is now stored in the database for future use by the user. It is recommended that this be scanned periodically to see if any changes have occurred. (&lt;code class=&quot;markup--code markup--p-code&quot;&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/simple-search-demo/-/blob/main/scripts/loader.js#L80-109&quot; target=&quot;_blank&quot;&gt;loader.js&lt;/a&gt;&lt;/code&gt;)
&lt;/p&gt;
&lt;h4 name=&quot;e5dd&quot; id=&quot;e5dd&quot; class=&quot;graf graf--h4 graf-after--p&quot;&gt;Sanitising the Text&lt;/h4&gt;&lt;p&gt;
In database development, and searching, an index can be thought of as a quick reference. In the days of manual searching, a card catalogue offered an alphabetic search ordered by subject, that allowed a quick lookup. Rather than having to look through every book in an entire building, you can look through a smaller listing in a single box.
&lt;/p&gt;
&lt;p&gt;
Less is more, smaller is faster.
&lt;/p&gt;
&lt;p&gt;
For the purposes of this discussion, it is useful to have a couple of documents we want to search:
&lt;/p&gt;
&lt;blockquote&gt;
Fact 21: For every 25 percent increase in problem complexity, there is a 100 percent increase in complexity of the software solution. that&#39;s not a condition to try to change (even though reducing complexity is always a desirable thing to do); that&#39;s just the way it is.
&lt;/blockquote&gt;
&lt;blockquote&gt;
Fact 22: Eighty percent of software work is intellectual. A fair amount of it is creative. Little of it is clerical.
&lt;/blockquote&gt;
&lt;blockquote&gt;
— Robert L. Glass, Facts and Fallacies of Software Engineering
&lt;/blockquote&gt;
&lt;p&gt;
So our sample for discussion will revolve around indexing the text of Glass&#39;s Facts to allow for rapid lookup. This might be useful to attach to a microphone in the office which then displays an appropriate “fact” depending &lt;a href=&quot;https://jefferey-cave.medium.com/the-angry-chatterbot-6d004b8c41a4&quot; target=&quot;_blank&quot;&gt;on what is being discussed&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
Each fact can be placed in a separate record for our indexing to pick up later. This will allow us to treat each fact as a distinct entity.
&lt;/p&gt;
&lt;p&gt;
./data/fact-01.txt&lt;br&gt;./data/fact-02.txt&lt;br&gt;./data/fact-03.txt&lt;/p&gt;&lt;p name=&quot;61a2&quot; id=&quot;61a2&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;One of the issues with text such as this is that a lot of text is actually meaningless, or at least carries low meaning. Some level of transformation should be performed to remove needless information.
&lt;/p&gt;
&lt;p&gt;
&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;sanitize&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;text&lt;/span&gt;){&lt;br&gt;    text = text.&lt;span class=&quot;hljs-title function_&quot;&gt;toLowerCase&lt;/span&gt;();&lt;br&gt;    text = text.&lt;span class=&quot;hljs-title function_&quot;&gt;replace&lt;/span&gt;(&lt;span class=&quot;hljs-regexp&quot;&gt;/[^a-z]/g&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;.&amp;#x27;&lt;/span&gt;);&lt;br&gt;    text = text.&lt;span class=&quot;hljs-title function_&quot;&gt;split&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;.&amp;#x27;&lt;/span&gt;);&lt;br&gt;    text = text.&lt;span class=&quot;hljs-title function_&quot;&gt;filter&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;d&lt;/span&gt;=&amp;gt;&lt;/span&gt;{&lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; d.&lt;span class=&quot;hljs-property&quot;&gt;length&lt;/span&gt; &amp;gt; &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;;});&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; text;&lt;br&gt;}&lt;br&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; sanitized = &lt;span class=&quot;hljs-title function_&quot;&gt;sanitize&lt;/span&gt;(facts[&lt;span class=&quot;hljs-number&quot;&gt;21&lt;/span&gt;]);&lt;/span&gt;&lt;/p&gt;&lt;p name=&quot;3336&quot; id=&quot;3336&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;As part of indexing fact number 21, we remove
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
case
&lt;/li&gt;
&lt;li&gt;
non-text
&lt;/li&gt;
&lt;li&gt;
short words (three characters, aka: &lt;a href=&quot;https://en.wikipedia.org/wiki/Stop_word&quot; target=&quot;_blank&quot;&gt;Stop Words&lt;/a&gt;)
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
resulting in a sanitised list of words. (&lt;code class=&quot;markup--code markup--p-code&quot;&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/simple-search-demo/-/blob/main/scripts/crepo.js#L31-42&quot; target=&quot;_blank&quot;&gt;crepo.js&lt;/a&gt;&lt;/code&gt;)
&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;[ &amp;#39;every&amp;#39;, &amp;#39;percent&amp;#39;, &amp;#39;increase&amp;#39;, &amp;#39;problem&amp;#39;, &amp;#39;complexity&amp;#39;, &amp;#39;there&amp;#39;, &amp;#39;percent&amp;#39;, &amp;#39;increase&amp;#39;, &amp;#39;complexity&amp;#39;, &amp;#39;software&amp;#39;, &amp;#39;solution&amp;#39;, &amp;#39;that&amp;#39;, &amp;#39;condition&amp;#39;, &amp;#39;change&amp;#39;, &amp;#39;even&amp;#39;, &amp;#39;though&amp;#39;, &amp;#39;reducing&amp;#39;, &amp;#39;complexity&amp;#39;, &amp;#39;always&amp;#39;, &amp;#39;desirable&amp;#39;, &amp;#39;thing&amp;#39;, &amp;#39;that&amp;#39;, &amp;#39;just&amp;#39; ]&lt;/code&gt; &lt;/p&gt;&lt;p name=&quot;b2ef&quot; id=&quot;b2ef&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;The list can be further reduced by noting the duplicate words. A word count is a useful way to weigh the value of a term in a search
&lt;/p&gt;
&lt;p&gt;
&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;WordCount&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;list&lt;/span&gt;){&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; count = {};&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; word &lt;span class=&quot;hljs-keyword&quot;&gt;of&lt;/span&gt; list){&lt;br&gt;        count[word] = count[word] || &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;&lt;br&gt;        count[word]++;&lt;br&gt;    }&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; count;&lt;br&gt;}&lt;br&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; count = &lt;span class=&quot;hljs-title class_&quot;&gt;WordCount&lt;/span&gt;(sanitized);&lt;/span&gt;&lt;/p&gt;&lt;h4 name=&quot;0031&quot; id=&quot;0031&quot; class=&quot;graf graf--h4 graf-after--pre&quot;&gt;Creating the index&lt;/h4&gt;&lt;p name=&quot;87d6&quot; id=&quot;87d6&quot; class=&quot;graf graf--p graf-after--h4&quot;&gt;For the purpose of this search, I chose to do a match on each word. This was done for simplicity, and because it allows for searching of words out of order.
&lt;/p&gt;
&lt;p&gt;
Examples of the kinds of searches I want a user to be able to use:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code class=&quot;markup--code markup--li-code&quot;&gt;complexity&lt;/code&gt;: a word that is overly complex (could be &lt;code class=&quot;markup--code markup--li-code&quot;&gt;complex&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;
&lt;code class=&quot;markup--code markup--li-code&quot;&gt;ompl&lt;/code&gt;: a word that is a partial match
&lt;/li&gt;
&lt;li&gt;
&lt;code class=&quot;markup--code markup--li-code&quot;&gt;software olut&lt;/code&gt;: multiple words
&lt;/li&gt;
&lt;li&gt;
&lt;code class=&quot;markup--code markup--li-code&quot;&gt;solution software&lt;/code&gt;: words out of order
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
These examples represent a couple of different cases, that we need to handle. Most interesting is the “partial match” scenario.
&lt;/p&gt;
&lt;p&gt;
To create an index we need a list based on our lookup value that
&lt;/p&gt;
&lt;p&gt;
| key      | score | document |&lt;br&gt;| -------- | ----- | -------- | &lt;br&gt;| database |   100 |  &amp;lt;url&amp;gt;   | &lt;br&gt;| atabase  |    99 |  &amp;lt;url&amp;gt;   | &lt;br&gt;| tabase   |    98 |  &amp;lt;url&amp;gt;   | &lt;br&gt;| abase    |    97 |  &amp;lt;url&amp;gt;   | &lt;br&gt;| base     |    96 |  &amp;lt;url&amp;gt;   |&lt;br&gt;| ase      |    95 |  &amp;lt;url&amp;gt;   | &lt;br&gt;| databas  |    99 |   ...    | &lt;br&gt;| databa   |    98 |   ...    |&lt;br&gt;| ...      |   ... |   ...    |&lt;/p&gt;&lt;p name=&quot;bd98&quot; id=&quot;bd98&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;When you search for “data search”, it finds
&lt;/p&gt;
&lt;p&gt;
document  1 &lt;br&gt;data     97 &lt;br&gt;search  100&lt;/p&gt;&lt;p name=&quot;9669&quot; id=&quot;9669&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;For a total of 198 points, and then sorts by highest scoring documents (Code: &lt;code class=&quot;markup--code markup--p-code&quot;&gt;&lt;a href=&quot;https://gitlab.com/jefferey-cave/simple-search-demo/-/blob/main/scripts/search.js#L97-116&quot; target=&quot;_blank&quot;&gt;search.js&lt;/a&gt;&lt;/code&gt;)
&lt;/p&gt;
&lt;h3&gt;Extras&lt;/h3&gt;
&lt;h4 name=&quot;d95d&quot; id=&quot;d95d&quot; class=&quot;graf graf--h4 graf-after--h3&quot;&gt;Lemmatization&lt;/h4&gt;&lt;p&gt;
As another layer, we can consider lemma. Lemmatization refer to the most foundational word that is represented by a word. For example, &lt;code class=&quot;markup--code markup--p-code&quot;&gt;intellectual&lt;/code&gt; can be thought of as being the same as &lt;code class=&quot;markup--code markup--p-code&quot;&gt;intellect&lt;/code&gt;. This will help reduce the amount of possible typographic difference that occur later, for example, a person that remembers &lt;code class=&quot;markup--code markup--p-code&quot;&gt;that is&lt;/code&gt; should find &lt;code class=&quot;markup--code markup--p-code&quot;&gt;that&amp;#39;s&lt;/code&gt;, come to think of it, &lt;code class=&quot;markup--code markup--p-code&quot;&gt;is&lt;/code&gt; is a short word.
&lt;/p&gt;
&lt;div name=&quot;78f8&quot; id=&quot;78f8&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p&quot;&gt;&lt;a href=&quot;https://github.com/michmech/lemmatization-lists/blob/master/lemmatization-en.txt&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;lemmatization-lists/lemmatization-en.txt at master · michmech/lemmatization-lists&lt;/strong&gt;&lt;br&gt;&lt;em&gt;Machine-readable lists of lemma-token pairs in 23 languages. - lemmatization-lists/lemmatization-en.txt at master ·…&lt;/em&gt;github.com&lt;/a&gt;&lt;a href=&quot;https://github.com/michmech/lemmatization-lists/blob/master/lemmatization-en.txt&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code u-paddingRight0 u-marginRight0&quot;&gt;every 25 &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;percent &lt;/strong&gt;increase problem &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;complex &lt;/strong&gt;there 100 &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;percent &lt;/strong&gt;increase &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;complex &lt;/strong&gt;software solution &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;that &lt;/strong&gt;condition change even though &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;reduce complex &lt;/strong&gt;always &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;desire &lt;/strong&gt;thing &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;that &lt;/strong&gt;just&lt;/code&gt;&lt;/p&gt;&lt;p name=&quot;1feb&quot; id=&quot;1feb&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;This is a much simpler version of the text that will reduce the amount of informational entropy (aka things that can go wrong in my head when remembering).
&lt;/p&gt;
&lt;p&gt;
This text is well sanitised and ready to undergo indexing.
&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;
The next morning, after writing the solution, I felt pretty pleased with myself for having a complete solution that I could email to my primary customers (a few Data Science managers throughout the organisation). I was so pleased, I showed it to a colleague who helped me with optimisations to the index.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-to-build-a-simiple-in-browser-search-engine/1-zvsury8-96f2ky9w0vjuma-jXObB0F4bn-__489__.avif 489w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-to-build-a-simiple-in-browser-search-engine/1-zvsury8-96f2ky9w0vjuma-jXObB0F4bn-__489__.webp&quot; alt=&quot;&quot; width=&quot;489&quot; height=&quot;496&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;&lt;a href=&quot;https://www.buymeacoffee.com/jeffereycave&quot; target=&quot;_blank&quot;&gt;Found this Intersting? Leave a Tip…. it helps&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Years later, and it is still the way the organisation tracks available conda packages.
&lt;/p&gt;
&lt;p&gt;
To be fair, the only reason I was able to pull this off overnight was that I have implemented this so many times. I have used browser-side indexes to implement dashboards for regression testing, employee work allocation, personal blog searches, and a Project Gutenberg search engine. It even ended up being the basis for a class I taught on Introductory JavaScript.
&lt;/p&gt;
&lt;p&gt;
It is one of the reasons I love JavaScript: it is always available for data processing, &lt;a href=&quot;https://ai.plainenglish.io/fun-with-markov-network-brains-8041c35ca883&quot; target=&quot;_blank&quot;&gt;whatever crazy idea you happen to have at that moment&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
Since originally writing this, I have discovered that Lawson has actually written a &lt;a href=&quot;https://github.com/pouchdb-community/pouchdb-quick-search&quot; target=&quot;_blank&quot;&gt;full text search for PouchDB&lt;/a&gt;. You should use his solution based on the &lt;a href=&quot;https://github.com/olivernn/lunr.js&quot; target=&quot;_blank&quot;&gt;lunr engine&lt;/a&gt;.
&lt;/p&gt;
&lt;h4 name=&quot;c4f8&quot; id=&quot;c4f8&quot; class=&quot;graf graf--h4 graf-after--p&quot;&gt;Non-Optimal&lt;/h4&gt;&lt;p&gt;
This is not an optimal solution.
&lt;/p&gt;
&lt;p&gt;
In this case, every user of a search engine must download and generate their own instance of the index. This requires that the client download the entirety of the dataset the first time (increasing network traffic), for each browser they use.
&lt;/p&gt;
&lt;p&gt;
In the regression test dashboard, the initial download and parse of the test results took approximately 3 hours. Caching meant that it was almost instantaneous if you kept up to date each day, but that first load was a big one.
&lt;/p&gt;
&lt;p&gt;
One of the key advantages to server-side processing is the ability to take the inbound data, and generate the indexes once for all the customers. You could split the difference by generating the index on the server and having the clients download that constructed index. This would split the processing load between a central processor for the central data, and distributed processing for the individual searches.
&lt;/p&gt;
&lt;p&gt;
This hints at an interesting balance between shared and distributed processing. Using databases that support the CouchDB interchange protocol (&lt;a href=&quot;https://en.wikipedia.org/wiki/Couchbase_Server&quot; target=&quot;_blank&quot;&gt;CouchBase&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Apache_CouchDB&quot; target=&quot;_blank&quot;&gt;CouchDB&lt;/a&gt;, &lt;a href=&quot;https://pouchdb.com/&quot; target=&quot;_blank&quot;&gt;PouchDB&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Cloudant&quot; target=&quot;_blank&quot;&gt;CloudAnt&lt;/a&gt;), it is possible for each user to process new data as they find it, but also to share the results back to a central pool which is used by the next person. This Lazy-Load form of data processing has some theoretical advantages to it (in a trusted environment) in that it would require very little central processing (expensive) and instead rely on workstations that are already in use. At the same time, it would share the workload to ensure no one computer took the brunt of the full calculation.
&lt;/p&gt;
&lt;p&gt;
Lastly, if this solution intrigued you, you should look into the Lucene based searches provided for CouchDB and included with all the commercial offerings.
&lt;/p&gt;
</content>
        </entry>
        <entry>
            <title>The Measure of Integrity of Software of Students</title>
            <link href="http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/" />
            <updated>2022-02-28T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/0*LmXMAjk-zUfgPq_r&quot; alt=&quot;The Measure of Integrity of Software of Students&quot;&gt;
                &lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/0-lmxmajk-zufgpq-r-7admo4Q3sj-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/0-lmxmajk-zufgpq-r-7admo4Q3sj-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;486&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Photo by &lt;a href=&quot;https://unsplash.com/@nublson?utm_source=medium&amp;amp;utm_medium=referral&quot; target=&quot;_blank&quot;&gt;Nubelson Fernandes&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral&quot; target=&quot;_blank&quot;&gt;Unsplash&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
This article describes the use of a browser-based tool for comparing and visualising student assignments. The tool &lt;a href=&quot;https://jefferey-cave.gitlab.io/miss/&quot; target=&quot;_blank&quot;&gt;M.I.S.S&lt;/a&gt; is a tribute to the classic tool &lt;a href=&quot;https://theory.stanford.edu/~aiken/moss/&quot; target=&quot;_blank&quot;&gt;MOSS&lt;/a&gt; (Measure Of Software Similarity), but supports organisational privacy and legal constraints by operating completely locally.
&lt;/p&gt;
&lt;p&gt;
In a &lt;a href=&quot;https://javascript.plainenglish.io/using-webgl-to-solve-a-practical-problem-751c186889aa&quot; target=&quot;_blank&quot;&gt;previous article&lt;/a&gt;, the use of GPU to perform the comparisons was discussed.
&lt;/p&gt;
&lt;p&gt;
After twenty years as an IT professional, having been a software developer for a diverse set of industries, having built systems that solve problems for some of the largest organisations on the planet … I decided to take up teaching.
&lt;/p&gt;
&lt;p&gt;
I had spent a lot of time working with newly hired developers to give them the basics of working within various corporate environments and had been disappointed by the lack of interest in problem-solving. It took a lot of work to get a recent graduate to think about how their actions affected others, and how their individual decisions resulted in effects that had consequences beyond their immediate deadline.
&lt;/p&gt;
&lt;p&gt;
Too often, new hires would race through a solution, submit it, and (proudly) declare that it was complete; a cursory inspection would demonstrate large failings. This was common among all new hires. My goal in teaching was to head this problem off. Graduates were coming out of school, wildly unprepared for the field, and I was going to change that.
&lt;/p&gt;
&lt;p&gt;
I was going to increase the quality of software developers available to organisations, not the quantity of developers.
&lt;/p&gt;
&lt;p&gt;
I wanted to reach out to students and hopefully guide them to learning how to problem solve, not just type code.
&lt;/p&gt;
&lt;p&gt;
Toward the end of my first semester, I noticed two significant problems with this:
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
Some students are just not inherently curious about programming
&lt;/li&gt;
&lt;li&gt;
Before signing off on the quality of students, I had to first identify which was &lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;their &lt;/strong&gt;work
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
Students work together, and collaboration is good but under the pressure to perform some people are tempted to cheat. This offends me in different ways depending on the motivation behind it:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
I dislike bullies. When a cool kid tries to bribe or bully a socially awkward student into doing his homework, I get angry.
&lt;/li&gt;
&lt;li&gt;
I dislike fraudsters. In some cases, students came in with no interest in the work and paid professionals to do their homework.
&lt;/li&gt;
&lt;li&gt;
I love people learning. Under pressure, a good person may take a shortcut, but, if someone is hiding a problem, I can&#39;t help them with it.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;&lt;em&gt;An Aside&lt;/em&gt;&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;This is not a moral judgment, it&amp;#39;s a factual judgement. I make a bad MMA fighter because &lt;/em&gt;&lt;a href=&quot;https://www.imdb.com/title/tt0104558/quotes/?ref_=tt_trv_qu&quot; target=&quot;_blank&quot;&gt;&lt;em&gt;I&#39;m too delicate&lt;/em&gt;&lt;/a&gt;&lt;em&gt;, and that&#39;s OK … unless I try to take up fighting.&lt;/em&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;One problem I noticed with administration was a strong desire to not have students fail. If a student could not perform an activity, excuses were made because nobody wants to hurt anyone&#39;s feelings. This resulted in students being put in positions they were not ready for, and my original observation of graduates putting customers at risk.&lt;/em&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;Remember my motive: Integrity is important because judging individuals on their ability to do the work is a safety issue, and a certification trust issue.&lt;/em&gt;
&lt;/p&gt;
&lt;p&gt;
Compounding this problem was a concern that other faculty and administration did not want to find the problem. It is a hard problem to deal with, charged with emotions, subjective reasoning, academic courts, and formality. Life is so much easier if we just ignore it. I heard on more than one occasion “Industry will straighten them out”. To me, this makes the certification advertised by the facility valueless (see “&lt;a href=&quot;https://levelup.gitconnected.com/i-was-shocked-to-catch-a-candidate-cheating-in-an-online-interview-2441fef0ab4&quot; target=&quot;_blank&quot;&gt;I Was Shocked To Catch A Candidate Cheating In An Online Interview&lt;/a&gt;”).
&lt;/p&gt;
&lt;p&gt;
So, to summarise the problem: I&#39;m teaching JavaScript programming, visualisation, and data analysis, have just left a position where distributed computing was my bread and butter, and am faced with a problem. Add to that, the administration forbade the transmission of student assignments outside of the facility and ominously reminded me that it could not leave the country.
&lt;/p&gt;
&lt;p&gt;
So I wrote my own solution.
&lt;/p&gt;
&lt;p&gt;
Complete in the browser.
&lt;/p&gt;
&lt;p&gt;
No servers involved.
&lt;/p&gt;
&lt;p&gt;
There was no need for permission to install the software. There were no more concerns for legal or privacy constraints. No questionable licenses. No fees. Just a simple tool to find out who needed my help.
&lt;/p&gt;
&lt;h3&gt;The Solution&lt;/h3&gt;
&lt;p&gt;
M.I.S.S is a piece of software that runs completely in the browser. It can be found hosted at &lt;a href=&quot;https://gitlab.com/jefferey-cave/miss&quot; target=&quot;_blank&quot;&gt;https://gitlab.com/jefferey-cave/miss&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
Before you begin with the software it is worth having a set of files you want to compare. If you work with a submission tool like &lt;a href=&quot;https://www.d2l.com/&quot; target=&quot;_blank&quot;&gt;BrightSpace&lt;/a&gt;, you are in luck, M.I.S.S accepts and interprets the zip file you download from your submissions folder. It can be directly uploaded the same way you downloaded it. Alternately, you can download the sample used for testing.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/1-pdx8vfrtr4l7nrx6ni5yaa-u0bQbRr1Ev-__725__.avif 725w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/1-pdx8vfrtr4l7nrx6ni5yaa-u0bQbRr1Ev-__725__.webp&quot; alt=&quot;&quot; width=&quot;725&quot; height=&quot;644&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;The first page you see when you get to the interface&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
When presented with the first connection, the tool starts on an “FAQ” page. The intent is to answer all the questions you may have right away. The most important thing you can do at this point is to upload a collection of student assignments for comparison (stored as a zip). In the background, the tool will unpack the zip and extract each file. Each root folder is considered to represent one student.
&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;&quot; src=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/1-rcqjwtza2yxmr9yiortuya-z80Ko6xxB8-__246__.webp&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;246&quot; height=&quot;246&quot;&gt;&lt;figcaption&gt;The points represent student submissions. Orange represents the progress of comparing them to one another; while the grey line shows a completed comparison.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Once uploaded, you should be able to navigate to the “force” tab to see that progress is being made. The graphics update in real-time, giving a sense of progress, so go get a coffee and wait for the calculations to complete.
&lt;/p&gt;
&lt;h4 name=&quot;9ea1&quot; id=&quot;9ea1&quot; class=&quot;graf graf--h4 graf-after--p&quot;&gt;What it does&lt;/h4&gt;
&lt;p&gt;
Now that the sample is loaded and processing is begun, it&amp;#39;s time to ask what it is actually doing.
&lt;/p&gt;
&lt;p&gt;
The process for measuring the level of similarity between two programs is not just a straight textual comparison. Rather, M.I.S.S goes through several phases to try to be as accurate as possible.
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
&lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;Compilation/Normalization&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
The first part of the process is to run the code through an &lt;a href=&quot;https://gitlab.com/jefferey-cave/miss/-/wikis/Parsing/Python&quot; target=&quot;_blank&quot;&gt;appropriate interpreter&lt;/a&gt; depending on the language. When most people initially think of code comparison, they consider text comparisons: letter by letter. However, by using a language-specific interpreter, we can change the text into a series of tokens:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
instead of &lt;code class=&quot;markup--code markup--li-code&quot;&gt;f&lt;/code&gt;, &lt;code class=&quot;markup--code markup--li-code&quot;&gt;o&lt;/code&gt;, &lt;code class=&quot;markup--code markup--li-code&quot;&gt;r&lt;/code&gt;, the word &lt;code class=&quot;markup--code markup--li-code&quot;&gt;for&lt;/code&gt; is recognised as a single thing (and assigned a number)
&lt;/li&gt;
&lt;li&gt;
instead of &lt;code class=&quot;markup--code markup--li-code&quot;&gt;f&lt;/code&gt;, &lt;code class=&quot;markup--code markup--li-code&quot;&gt;o&lt;/code&gt;, &lt;code class=&quot;markup--code markup--li-code&quot;&gt;r&lt;/code&gt;, &lt;code class=&quot;markup--code markup--li-code&quot;&gt;w&lt;/code&gt;, &lt;code class=&quot;markup--code markup--li-code&quot;&gt;a&lt;/code&gt;, &lt;code class=&quot;markup--code markup--li-code&quot;&gt;r&lt;/code&gt;, &lt;code class=&quot;markup--code markup--li-code&quot;&gt;d&lt;/code&gt;, the word is recognised as a variable (and assigned a number)
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
This avoids the first three letters being thought to mean the same thing: instead &lt;code class=&quot;markup--code markup--p-code&quot;&gt;token1&lt;/code&gt; is compared to &lt;code class=&quot;markup--code markup--p-code&quot;&gt;token2&lt;/code&gt; and seen as a single comparison between 2 things, not 3 similarities, and 4 differences.
&lt;/p&gt;
&lt;p&gt;
This allows us to recognise small changes that may have a large visual impact (like variable name changes).
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;2. Full-Text Comparison&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
The text is compared at a full-textual level. This is important for ensuring that order does not matter:
&lt;/p&gt;
&lt;pre&gt;
function FunA(){...}
function FunB(){...}
&lt;/pre&gt;
&lt;p&gt;is seen as the same code as&lt;/p&gt;
&lt;pre&gt;
function FunB(){...}
function FunA(){...}
&lt;/pre&gt;
&lt;p&gt;
Please see my &lt;a href=&quot;https://javascript.plainenglish.io/using-webgl-to-solve-a-practical-problem-751c186889aa&quot; target=&quot;_blank&quot;&gt;previous article&lt;/a&gt; on how the Smith-Waterman algorithm was implemented to achieve this.
&lt;/p&gt;
&lt;p&gt;
This allows us to recognise small changes that may have a large visual impact (like re-ordering functions).
&lt;/p&gt;
&lt;h3&gt;Interpreting Results&lt;/h3&gt;
&lt;p&gt;
This program does a blind comparison, it does not have any bias about the what it is comparing, it just compares. Unfortunately, all creative work, will have common elements to it. Good solutions will be independently discovered. This means that it is &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;not possible&lt;/strong&gt; for this program to catch cheaters.
&lt;/p&gt;
&lt;p&gt;
What this program &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;can&lt;/strong&gt; do is filter out the people that probably &lt;em&gt;aren&#39;t&lt;/em&gt; directly copying one another&#39;s work. People that appear to be exercising their own skills in attempting to solve the problem.
&lt;/p&gt;
&lt;p&gt;
Given a batch of 120 assignments to grade there are 7140 comparisons that would need to be performed. Multiply this by multiple batches assigned to different graders and the problem becomes untenable, resulting in faculty (faultily) relying on intuition.
&lt;/p&gt;
&lt;p&gt;
Instead, it is probably necessary to review 5 to 7 of the submissions, and MISS can be used to filter the list down to just those items, by removing items that are obviously different.
&lt;/p&gt;
&lt;h4 name=&quot;b867&quot; id=&quot;b867&quot; class=&quot;graf graf--h4 graf-after--p&quot;&gt;How to Interpret&lt;/h4&gt;&lt;p&gt;
The goal of the visualisations is to reduce the workload of faculty. Once a batch of assignments has completed processing, interpreting the results is relatively straightforward.
&lt;/p&gt;
&lt;p&gt;
The force-directed graph is more a curiosity than anything else (we were building them in my class). Clusters that appear are indicative of social groups within the class. This can be indicative of students that are studying together and making shared mistakes.
&lt;/p&gt;
&lt;p&gt;
The first real step is for faculty to look to the &lt;code class=&quot;markup--code markup--p-code&quot;&gt;Listing&lt;/code&gt; tab
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/1-vp7s5frwrbgqe-4zplubda-AGZFJ614V0-__685__.avif 685w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/1-vp7s5frwrbgqe-4zplubda-AGZFJ614V0-__685__.webp&quot; alt=&quot;&quot; width=&quot;685&quot; height=&quot;327&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;An example of a run. Most submissions have around a 5–10% similarity. However, highlighted in red, are two assignments that have about 50% similarity to one another. This is a major divergence from the norm, making them worth investigating. Be careful, the calculation is not complete, we don&#39;t know what “normal” actually is.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
The listing includes the comparison of every submission to every submission, with the amount of similarity indicated as a percentage. In an effort to reduce effort, items are sorted by most similar to least. The ones at the bottom can probably be ignored. Results are also colour coded to represent &lt;code class=&quot;markup--code markup--p-code&quot;&gt;not compared&lt;/code&gt; (grey), &lt;code class=&quot;markup--code markup--p-code&quot;&gt;normal&lt;/code&gt; (blue), &lt;code class=&quot;markup--code markup--p-code&quot;&gt;suspicious&lt;/code&gt; (red). If you see grey, it is not complete, go get more coffee.
&lt;/p&gt;
&lt;p&gt;
“Suspicious” is defined as an abnormally similar from the group. This is calculated by sorting the items in order of similarity, calculating the difference between each step, and taking the largest change. Any value from largest change forward is considered to be worth inspection
&lt;/p&gt;
&lt;p&gt;
For example,
&lt;/p&gt;
&lt;pre&gt;
14%         }&lt;br&gt;   }- 3     }&lt;br&gt;11%         }  Suspicious Range of values&lt;br&gt;   }- 1     }&lt;br&gt;10%         }&lt;br&gt;   }- 4   &amp;lt;-- Greatest Change&lt;br&gt; 6% &lt;br&gt;   }- 1&lt;br&gt; 5% &lt;br&gt;   }- 0 &lt;br&gt; 5%
&lt;/pre&gt;
&lt;p&gt;This technique was chosen to identify normal “sharing” within the group. It is assumed that the steps will represent students leaning on their peers for support, until it reaches a point where it is “more” than leaning. Even if the amount returns to normal after that point, the amount of sharing is still abnormally high. This technique is believe to offer better utility than a simple average as it can identify a group of people where the minority of students are working independently.&lt;/p&gt;
&lt;p&gt;
Clicking on any of these comparisons will take you to the &lt;code class=&quot;markup--code markup--p-code&quot;&gt;Comparison&lt;/code&gt; tab.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/1-ewqlm9-ml-fu-4zbocslng-7NFEnvpZRm-__670__.avif 670w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/1-ewqlm9-ml-fu-4zbocslng-7NFEnvpZRm-__670__.webp&quot; alt=&quot;&quot; width=&quot;670&quot; height=&quot;556&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;The comparison tab allows you to view the actual assignments side by side with colour coded sections of similarity. Don&#39;t miss the “maximize” button in the top right corner.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Here you can view the actual software, side by side. Similar blocks of code are colour coded to allow for inspection. Clicking on the maximise button in the top right corner is probably useful. Single clicking on the percentage indicators will jump to the block of code, double clicking brings both pieces of code into alignment.
&lt;/p&gt;
&lt;p&gt;
Hopefully, your inspection turns nothing of interest up at all.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/1-dbo-wuivaup8q1aorkugiw-DkHAO3oPaf-__214__.avif 214w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/1-dbo-wuivaup8q1aorkugiw-DkHAO3oPaf-__214__.webp&quot; alt=&quot;&quot; width=&quot;214&quot; height=&quot;70&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Action buttons that are useful: report a bug, print the results, delete the current data, upload a result file, or download a result file.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
As this runs fully in the browser, it is also important that you save a copy of the comparisons. This can be done by clicking on the &lt;code class=&quot;markup--code markup--p-code&quot;&gt;download&lt;/code&gt; button which downloads a copy of the current comparison for saving for later. This is useful as a backup, but also in case you need to stop a run and need to resume it later (perhaps with the addition of a late submission). The downloaded file stores all the data to resume comparisons at a later date if necessary.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/1-0wzhu0h4kdm8nilid-3-ya-f7dj-LFyyn-__483__.avif 483w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/1-0wzhu0h4kdm8nilid-3-ya-f7dj-LFyyn-__483__.webp&quot; alt=&quot;&quot; width=&quot;483&quot; height=&quot;214&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Give your file a meaningful name before downloading it. Then delete and start another class&#39; assignments.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Having said that, the point to this software is to be independent of the tool. Regardless of your findings, you should always &lt;code class=&quot;markup--code markup--p-code&quot;&gt;print&lt;/code&gt; the results for long term archiving. The printed copy can act as a permanent record of your findings and will exist as simple HTML (or PDF) should you need to access the results even decades later, when you have forgotten about M.I.S.S.
&lt;/p&gt;
&lt;p&gt;
Always remember to maintain consistent evidence of your consistent decisions.
&lt;/p&gt;
&lt;h3&gt;The common objections&lt;/h3&gt;
&lt;p&gt;
Most objections regarding the results amount to &lt;a href=&quot;https://thinkbuthow.com/straw-man/&quot; target=&quot;_blank&quot;&gt;straw-man attacks&lt;/a&gt; on the process, and focus on not challenging student integrity.
&lt;/p&gt;
&lt;h4 name=&quot;cbd2&quot; id=&quot;cbd2&quot; class=&quot;graf graf--h4 graf-after--p&quot;&gt;&lt;em&gt;What if the computer makes a mistake and accuses a student falsely?&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;
Used properly, this tool simply identifies works that bear closer investigation. It does not give any information about the nature or motives of the similarity. That is left to the investigator to determine from the context.
&lt;/p&gt;
&lt;p&gt;
In fact, let me reverse the objection:
&lt;/p&gt;
&lt;h4 name=&quot;aa13&quot; id=&quot;aa13&quot; class=&quot;graf graf--h4 graf-after--p&quot;&gt;What if instructor bias leads to a mistake, and falsely accuses a student?&lt;/h4&gt;
&lt;p&gt;
In its first use, this tool actually indicated that someone suspected of cheating was, in fact, innocent.
&lt;/p&gt;
&lt;p&gt;
At the time, I was exhausted. It was the end of the semester, and students had been given a second (and a third, also a fourth and fifth) chance to submit assignments. Administrators, councilors, and fellow faculty bullied me into allowing multiple late submissions from some key students. Naturally, struggling to keep up with grading, and being pulled into multiple meetings regarding these students did not put me in a pleasant mood when a particular submitted assignment looked suspiciously similar to a very unique solution I had been particularly impressed with (submitted by a particularly strong student).
&lt;/p&gt;
&lt;p&gt;
Being ill-tempered, and in an effort to stave off the next set of objections I decided to gather evidence from an unbiased computational resource, and found…
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;The level of similarity between the two students was normal for the class&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
The student had a couple of variable names that were similar (probably due to tutoring), but beyond that, the style was very different. My personal emotions and experiences had led to bias because... well because I&#39;m human.
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;The computational solution was unbiased and treated the student fairly and without emotion, even when I did not.&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
It also (thanks to its brute-force capabilities) identified two students who had copied one another&#39;s work exactly with only variable name changes. They had been missed purely due to being lost in the mass of papers needing grading.
&lt;/p&gt;
&lt;h3&gt;Routine for Use&lt;/h3&gt;
&lt;p&gt;
I strongly recommend reading a paper on the use of MOSS in the classroom: “&lt;a href=&quot;https://www3.nd.edu/~kwb/nsf-ufe/1110.pdf&quot; target=&quot;_blank&quot;&gt;Experience Using ”MOSS” to Detect Cheating On Programming Assignments&lt;/a&gt;”. This details the routines and experience of faculty at the University of South Florida
&lt;/p&gt;
&lt;p&gt;
Objections to the use of tools like this focus on accusations of vindictiveness by the user. However, for Faculty that do choose to use a tool like this, the goal is to avoid problems, not to cause them. After having developed the tool, I began to develop and document a routine for use:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
run it against &lt;em&gt;every batch of assignments&lt;/em&gt; consistently
&lt;/li&gt;
&lt;li&gt;
be open with students that you are running it
&lt;/li&gt;
&lt;li&gt;
each semester, I only bothered to pursue the most egregious case
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
I would normally start the first semester with the first assignment and run all the students&amp;#39; submissions through the tool, in front of the students. The visualisations caught their eye, and they could see clusters starting to form. Naturally, I was careful to &lt;a href=&quot;https://www.random.org/&quot; target=&quot;_blank&quot;&gt;anonymize Student IDs&lt;/a&gt;, but being so introductory, the amount of similarity was incredibly high anyway. Students are not singled out but it does serve as a warning to students that you are watching, and serves as an opportunity to discuss the difference between copying, and collaborating. It is also a great introductory discussion into how the skills they are learning can be applied to solve personal work problems.
&lt;/p&gt;
&lt;p&gt;
While the tool reduces the effort of evidence gathering, it does not eliminate it. There is a large bureaucratic burden in even discussing academic concern. I personally, felt that a balance between pursuing the issue and spending time with my family had to be struck. My conclusion was to pursue only one case per semester: pick the worst, chase it down. Also I tended to do this in the middle of the semester
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
To informally warn students when things are new and busy
&lt;/li&gt;
&lt;li&gt;
To leave myself time to pursue the requisite paperwork
&lt;/li&gt;
&lt;li&gt;
To give the students the opportunity to correct their behaviour
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
Part of the reason I felt it was important to be consistent in filing and pursuing the issue was due to my first experience with the formal process. As I began to pursue a case, other faculty came forward with their previous &lt;em&gt;anecdotal cases with the exact same group of students&lt;/em&gt;. Anecdotally, these students had multiple incidents that had been dealt with individually, however the pattern had not been identified across space-time.
&lt;/p&gt;
&lt;p&gt;
Another reason for consistency is simple bias management: are you letting a student slide due to favouritism? Anytime you actively withhold consequences based on discretion, you are actively applying punitive measures to those you choose to pursue. Are you therefore applying punitive measures without bias? If you consistently act based on the evidence, you do not have an opportunity to express unconscious bias, and therefore do not need to question your own integrity.
&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;
While I no longer teach programming, I do think that academic integrity is important. Colleges and Universities are certifying that students are knowledgeable in a domain of expertise, and failure to keep that process honest can have &lt;a href=&quot;https://jefferey-cave.medium.com/why-wont-staples-take-my-money-2e1d5ae8e135&quot; target=&quot;_blank&quot;&gt;dire consequences&lt;/a&gt;.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/1-zvsury8-96f2ky9w0vjuma-jXObB0F4bn-__489__.avif 489w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/measure-of-integrity-of-software-of-students/1-zvsury8-96f2ky9w0vjuma-jXObB0F4bn-__489__.webp&quot; alt=&quot;&quot; width=&quot;489&quot; height=&quot;496&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;&lt;a href=&quot;https://www.buymeacoffee.com/jeffereycave&quot; target=&quot;_blank&quot;&gt;Found this useful or interesting? Consider leaving a tip … it helps.&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 name=&quot;cdd7&quot; id=&quot;cdd7&quot; class=&quot;graf graf--h4 graf-after--figure&quot;&gt;Future Development&lt;/h4&gt;
&lt;p&gt;
I would love to continue work on M.I.S.S, but without more data (more assignments) it is difficult to comprehend the needs of an audience.
&lt;/p&gt;
&lt;p&gt;
If you find the tool useful, I would love to hear from you.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://gitlab.com/jefferey-cave/miss/-/issues&quot; target=&quot;_blank&quot;&gt;Suggest a feature&lt;/a&gt; (or up-vote one)
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://gitlab.com/jefferey-cave/miss/-/merge_requests&quot; target=&quot;_blank&quot;&gt;Submit a patch&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
Just tell me about your experience (good or bad) in the comments
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Knowing &lt;em&gt;how &lt;/em&gt;(or even “&lt;em&gt;that&lt;/em&gt;”) the system is used would help drive development forward.
&lt;/p&gt;
&lt;p&gt;
Right away, I would be very interested in working on adding cluster indicators to the force-directed graph. I think it would be useful to have a bubble drawn around the grouped suspicious items. The difference analysis already identifies which items these are, but in a case where you have more than one cluster, it would be interesting to see them separated out.
&lt;/p&gt;
&lt;h3&gt;Other Uses&lt;/h3&gt;
&lt;p&gt;
There are several future capabilities for a tool such as this. While I am interested in exploring some of these, I&#39;m interested in a lot of things and have had to make some hard choices.
&lt;/p&gt;
&lt;h4 name=&quot;aab3&quot; id=&quot;aab3&quot; class=&quot;graf graf--h4 graf-after--p&quot;&gt;Code Refactoring&lt;/h4&gt;
&lt;p&gt;
One interesting capability is in the refactoring of code. While this tool compares different applications to one another, this same technique could be used to compare a software application &lt;em&gt;to itself&lt;/em&gt;. This self-comparison could aid in software refactoring, identifying parts of code where copy/paste has resulted in duplication, or even where two developers have independently implemented the same idea. These parts that are similar, and repeated, would likely represent a refactoring opportunity.
&lt;/p&gt;
&lt;h4&gt;Distributed Open Source Analysis&lt;/h4&gt;
&lt;p&gt;
Another interesting idea would be to extend this to larger-scale projects. Rather than taking student software as submissions, scan GitHub or GitLab for content duplication. Aside from forks, who is copying one another&#39;s code? This would require a server and database to allow for checkouts of code and to store the comparisons in a longer-term repository. This project was developed shortly after a distributed project I had worked on and was constructed with distributed computing in mind, it should be able to be scaled to distributed environments easily.
&lt;/p&gt;
&lt;p&gt;
If someone has some investment money they would like to throw at these ideas, I would be thrilled to chase them down.
&lt;/p&gt;
&lt;div name=&quot;bf73&quot; id=&quot;bf73&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p graf--trailing&quot;&gt;&lt;a href=&quot;https://jefferey-cave.gitlab.io/miss/&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--mixtapeEmbed-strong&quot;&gt;M.I.S.S.&lt;/strong&gt;&lt;br&gt;&lt;em&gt;A measure of software similarity completely contained in the browser.&lt;/em&gt;jefferey-cave.gitlab.io&lt;/a&gt;&lt;a href=&quot;https://jefferey-cave.gitlab.io/miss/&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;/div&gt;
</content>
        </entry>
        <entry>
            <title>How to Not Store the Same File Twice (using JavaScript and PouchDB)</title>
            <link href="http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/" />
            <updated>2022-02-28T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/0*JEvBDI4GS63rpNCh&quot; alt=&quot;How to Not Store the Same File Twice (using JavaScript and PouchDB)&quot;&gt;
                &lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/0-jevbdi4gs63rpnch-YSSLFgLaqt-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/0-jevbdi4gs63rpnch-YSSLFgLaqt-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;487&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Photo by &lt;a href=&quot;https://unsplash.com/@unarchive?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; target=&quot;_blank&quot;&gt;Jeremy Bezanger&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/repeating-lemons?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; target=&quot;_blank&quot;&gt;Unsplash&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Many years ago, I was reading an article about Google&#39;s internal labs having managed to create a &lt;code class=&quot;markup--code markup--p-code&quot;&gt;sha1&lt;/code&gt; hash &lt;a href=&quot;https://shattered.io/&quot; target=&quot;_blank&quot;&gt;collision between two PDF documents&lt;/a&gt;. The documents were very different documents, but through clever bit manipulation, resulted in an identical &lt;code class=&quot;markup--code markup--p-code&quot;&gt;sha1&lt;/code&gt; code. It was a fascinating read for a Friday afternoon, but over the weekend I started to ask the question: why does Google care?
&lt;/p&gt;
&lt;p&gt;
One significant place where this would impact Google would be on their storage platform, Google Drive. Given they are storing massive numbers of files, on behalf of massive numbers of people, in all probability there are going to be a massive number of duplicate files.
&lt;/p&gt;
&lt;p&gt;
Logically, we can see this will likely be true just through its usage. Assuming I am engaging in a real estate transaction, it is not uncommon to exchange emailed PDFs with scanned signatures. Assuming everyone is using Google Drive to back up their documents, there are four people that have copies of the exact same document: buyer, buyer&#39;s agent, seller&#39;s agent, the seller. Don&#39;t forget to add lawyers and lenders later in the process.
&lt;/p&gt;
&lt;p&gt;
I have worked for organisations whose primary business involved the interchange and storage of data (Oil/Gas Production, Telecommunications, or Data Repositories), and in every case, our solution to the problem was simple: charge the customer. Charge the customer a rate per byte: the amount of drive space they take up * replications space * server cost * electricity * rent * markup. Charging the customer is a good way to offset the cost, and Google does bill its customers, but through de-duplication, it is possible to maintain the same revenue, while drastically reducing the amount of physical storage.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/1-tamglyy9lzxsqe-gwvghew-dG40vkQ9zv-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/1-tamglyy9lzxsqe-gwvghew-dG40vkQ9zv-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;485&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Viral Memes were always a fun way to watch a poorly configured communication network burst into flames&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Take a chat service I was involved in. The &lt;a href=&quot;https://www.gsma.com/futurenetworks/wp-content/uploads/2019/10/RCC.07-v11.0.pdf&quot; target=&quot;_blank&quot;&gt;GSMA-RCC&lt;/a&gt; specifies that images can be interchanged between client devices; it also specifies that those images should be retained on the server for later pickup. So if a meme goes viral, thousands of individuals may forward the image to one another, resulting in thousands of copies of that image flying across the network, and being stored on server drives.
&lt;/p&gt;
&lt;p&gt;
Further, if it&#39;s a good meme, people are going to forward it back to people they know; people who have already received it. So round and round the image goes, and it doesn&#39;t stop until theoretically its been sent on every possible communication between pairs of people.
&lt;/p&gt;
&lt;p&gt;
With &lt;a href=&quot;https://technative.io/digital-devices-are-the-backbone-of-every-organisation-are-you-managing-them-properly/#:~:text=Statista%20estimates%20that%20as%20of,us%20roughly%20three%20to%20one.&quot; target=&quot;_blank&quot;&gt;10 billion devices on the planet&lt;/a&gt;, that&#39;s more than &lt;a href=&quot;https://en.wikipedia.org/wiki/Seven_Bridges_of_K%C3%B6nigsberg&quot; target=&quot;_blank&quot;&gt;Seven Bridges&lt;/a&gt; that need to be crossed.
&lt;/p&gt;
&lt;p&gt;
If some jerk sends that meme as a &lt;code class=&quot;markup--code markup--p-code&quot;&gt;bitmap&lt;/code&gt; each image instance takes up about 1079KB.
&lt;/p&gt;
&lt;p&gt;
assume&lt;br&gt;- $1.00 per MB revenue&lt;br&gt;- $0.90 per MB cost&lt;br&gt;- 1,000,000 user interactions (transfer)&lt;br&gt;- 1024KB file&lt;/p&gt;&lt;pre name=&quot;ceb0&quot; id=&quot;ceb0&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;= 1 MB * 1M transfers * ($1 - $0.9)&lt;br&gt;= 1TB of storage&lt;br&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;= $100,000 profit&lt;/strong&gt;&lt;/pre&gt;&lt;p name=&quot;032b&quot; id=&quot;032b&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;We can be sensible about it and require that everything is converted to PNG (notice Google asks to do this for Google Photos) reducing the size to 615KB (60%). This is useful when dealing in a fixed revenue, but not a promised true copy of data storage.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/1-b3jmidqmylhkm6rkd0-yta-Sh-gzuDm5d-__485__.avif 485w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/1-b3jmidqmylhkm6rkd0-yta-Sh-gzuDm5d-__485__.webp&quot; alt=&quot;&quot; width=&quot;485&quot; height=&quot;448&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Storing the object once per person is how the users perceive a file system&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Storing the image once per person that receives it, is an inefficient use of resources. If, on the other hand, we can identify that it is actually the same image, we can reduce our cost to &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;storing only one instance, but charging for each transfer&lt;/strong&gt;.
&lt;/p&gt;
&lt;p&gt;
= (1MB * 1M transfers * $1) - (1 unique file * 1MB * $0.9)&lt;br&gt;= 1MB of storage&lt;br&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;= $999,999.10 profit&lt;/strong&gt;&lt;/p&gt;&lt;p name=&quot;8c1a&quot; id=&quot;8c1a&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;There is a big difference between having a cost of 90&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;¢&lt;/strong&gt; per transaction, and a cost of 90&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;¢&lt;/strong&gt;.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/1-tsks4cfbcyqlximtgzovta-QYc2vSh-mA-__466__.avif 466w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/1-tsks4cfbcyqlximtgzovta-QYc2vSh-mA-__466__.webp&quot; alt=&quot;&quot; width=&quot;466&quot; height=&quot;246&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Identifying that everyone is storing the same data, allows us to significantly reduce the amount of space we consume while giving the same level of service to our users.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
However, de-duplication is not an easy problem to solve.
&lt;/p&gt;
&lt;p&gt;
First of all, we receive the files independently of one another, from different people, with different names. Given a large number of large files, it is going to take a significant amount of processing power to compare the files byte-by-byte to every other file we have stored. In a large-scale system, this simply is not feasible.
&lt;/p&gt;
&lt;p&gt;
This brings us back around to where we began: Google had managed to cause a collision between &lt;code class=&quot;markup--code markup--p-code&quot;&gt;sha1&lt;/code&gt; in the lab. Why would they have been concerned with researching the extreme possibilities of collision in binary documents? Because they are using the hash to help identify the uniqueness of files.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/1-mvzy-6rxmfyqlghiwidcka-yOm8YeLEGB-__465__.avif 465w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/1-mvzy-6rxmfyqlghiwidcka-yOm8YeLEGB-__465__.webp&quot; alt=&quot;&quot; width=&quot;465&quot; height=&quot;279&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;We can use large hashes as primary keys for the files.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;A Working Example&lt;/h3&gt;
&lt;p&gt;
Pulling on some past experience solving this problem using CouchDB, we can implement an in-browser demonstration of the principles involved using JavaScript and PouchDB. If you are not familiar with PouchDB and using its DB interface, I suggest reading &lt;a href=&quot;https://pouchdb.com/2014/05/01/secondary-indexes-have-landed-in-pouchdb.html&quot; target=&quot;_blank&quot;&gt;the definitive introduction by Nolan Lawson&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
We will be creating a simple example with three users (Alice, Bob, and Carol) sharing their favourite lines from a &lt;a href=&quot;https://youtu.be/i1gt1yvUC-s?t=1850&quot; target=&quot;_blank&quot;&gt;new Opera&lt;/a&gt; they just saw.
&lt;/p&gt;
&lt;p&gt;
&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; db = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;PouchDB&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;filestore&amp;#x27;&lt;/span&gt;);&lt;br&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;main&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;){&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; db.&lt;span class=&quot;hljs-title function_&quot;&gt;destroy&lt;/span&gt;();&lt;br&gt;  db = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;PouchDB&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;filestore&amp;#x27;&lt;/span&gt;);&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;CreateIndex&lt;/span&gt;();&lt;br&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;(&lt;br&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;alice&amp;#x27;&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;gilbert.txt&amp;#x27;&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;I am the very model of a modern major general&amp;#x27;&lt;/span&gt;&lt;br&gt;  );&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;(&lt;br&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;bob&amp;#x27;&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;sullivan.txt&amp;#x27;&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;I am the very model of a modern major general&amp;#x27;&lt;/span&gt;&lt;br&gt;  );&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;(&lt;br&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;carol&amp;#x27;&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;gilbert.txt&amp;#x27;&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;I have knowledge of things animal, vegetable and mineral&amp;#x27;&lt;/span&gt;&lt;br&gt;  );&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;(&lt;br&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;alice&amp;#x27;&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;sullivan.txt&amp;#x27;&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;I have knowledge of things animal, vegetable and mineral&amp;#x27;&lt;/span&gt;&lt;br&gt;  );&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; data = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; db.&lt;span class=&quot;hljs-title function_&quot;&gt;allDocs&lt;/span&gt;({&lt;span class=&quot;hljs-attr&quot;&gt;include_docs&lt;/span&gt;:&lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;});&lt;br&gt;  &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;`DB Size: &lt;span class=&quot;hljs-subst&quot;&gt;${&lt;span class=&quot;hljs-built_in&quot;&gt;JSON&lt;/span&gt;.stringify(data).length}&lt;/span&gt;`&lt;/span&gt;);&lt;br&gt;&lt;br&gt;  &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;dload&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;alice&amp;#x27;&lt;/span&gt;  ,&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;gilbert.txt&amp;#x27;&lt;/span&gt;));&lt;br&gt;  &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;dload&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;bob&amp;#x27;&lt;/span&gt;    ,&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;sullivan.txt&amp;#x27;&lt;/span&gt;));&lt;br&gt;  &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;dload&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;carol&amp;#x27;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;gilbert.txt&amp;#x27;&lt;/span&gt;));&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; peruser = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; db.&lt;span class=&quot;hljs-title function_&quot;&gt;query&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;allfiles&amp;#x27;&lt;/span&gt;,{&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;reduce&lt;/span&gt;:&lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;group&lt;/span&gt;:&lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;group_level&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;&lt;br&gt;  });&lt;br&gt;  &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(peruser);&lt;br&gt;}&lt;br&gt;&lt;span class=&quot;hljs-title function_&quot;&gt;main&lt;/span&gt;();&lt;/span&gt;&lt;/p&gt;&lt;p name=&quot;09df&quot; id=&quot;09df&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;The process is relatively straightforward: three people store one of two lines of text to their account, and then retrieve them from the database.
&lt;/p&gt;
&lt;p&gt;
Of interest is the total size being stored, as well as the per-user size (billable size) usage:
&lt;/p&gt;
&lt;p&gt;
&lt;span class=&quot;pre--content&quot;&gt;DB Size: 1813 bytes&lt;br&gt;&lt;br&gt;| User  | Files | Size |&lt;br&gt;| ----- | ----- | ---- |&lt;br&gt;| alice |     2 |  101 | &lt;br&gt;| bob   |     1 |   45 |&lt;br&gt;| carol |     1 |   56 |&lt;/span&gt;&lt;/p&gt;&lt;p name=&quot;65ca&quot; id=&quot;65ca&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;The real meat of the program happens in the &lt;code class=&quot;markup--code markup--p-code&quot;&gt;save&lt;/code&gt; and &lt;code class=&quot;markup--code markup--p-code&quot;&gt;dload&lt;/code&gt; which abstract away the interactions with the database. Further &lt;code class=&quot;markup--code markup--p-code&quot;&gt;CreateIndex&lt;/code&gt; defines the mechanism for search and retrieval.
&lt;/p&gt;
&lt;p&gt;
&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;CreateIndex&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;){&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; db.&lt;span class=&quot;hljs-title function_&quot;&gt;put&lt;/span&gt;({&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;_id&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;_design/allfiles&amp;#x27;&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;views&lt;/span&gt;: {&lt;br&gt;      &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;allfiles&amp;#x27;&lt;/span&gt;: {&lt;br&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;map&lt;/span&gt;: &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;hljs-params&quot;&gt;doc&lt;/span&gt;) {&lt;br&gt;          &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; userpath = [doc.&lt;span class=&quot;hljs-property&quot;&gt;user&lt;/span&gt;,doc.&lt;span class=&quot;hljs-property&quot;&gt;path&lt;/span&gt;];&lt;br&gt;          &lt;span class=&quot;hljs-title function_&quot;&gt;emit&lt;/span&gt;(userpath,doc.&lt;span class=&quot;hljs-property&quot;&gt;size&lt;/span&gt;);&lt;br&gt;        }.&lt;span class=&quot;hljs-title function_&quot;&gt;toString&lt;/span&gt;(),&lt;br&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;reduce&lt;/span&gt;:&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;_stats&amp;#x27;&lt;/span&gt;&lt;br&gt;      }&lt;br&gt;    }&lt;br&gt;  });&lt;br&gt;}&lt;br&gt;&lt;br&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;user,filename,blob&lt;/span&gt;){&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; db.&lt;span class=&quot;hljs-title function_&quot;&gt;put&lt;/span&gt;({&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;_id&lt;/span&gt;: [user,filename].&lt;span class=&quot;hljs-title function_&quot;&gt;join&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;@&amp;#x27;&lt;/span&gt;),&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;user&lt;/span&gt;:user,&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;path&lt;/span&gt;:filename,&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;size&lt;/span&gt;:blob.&lt;span class=&quot;hljs-property&quot;&gt;length&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;_attachments&lt;/span&gt;: {&lt;br&gt;      &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;0&amp;#x27;&lt;/span&gt;: {&lt;br&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;content_type&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;text/plain&amp;#x27;&lt;/span&gt;,&lt;br&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;data&lt;/span&gt;: &lt;span class=&quot;hljs-variable language_&quot;&gt;window&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;btoa&lt;/span&gt;(blob)&lt;br&gt;      }&lt;br&gt;    }&lt;br&gt;  });&lt;br&gt;}&lt;br&gt;&lt;br&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;dload&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;user,filename&lt;/span&gt;){&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; recs = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; db.&lt;span class=&quot;hljs-title function_&quot;&gt;query&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;allfiles&amp;#x27;&lt;/span&gt;,{&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;reduce&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;include_docs&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;attachments&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;,&lt;br&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;key&lt;/span&gt;: [user,filename]&lt;br&gt;  });&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; blob = recs.&lt;span class=&quot;hljs-property&quot;&gt;rows&lt;/span&gt;[&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;].&lt;span class=&quot;hljs-property&quot;&gt;doc&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;_attachments&lt;/span&gt;[&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;0&amp;#x27;&lt;/span&gt;].&lt;span class=&quot;hljs-property&quot;&gt;data&lt;/span&gt;;&lt;br&gt;  blob = &lt;span class=&quot;hljs-variable language_&quot;&gt;window&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;atob&lt;/span&gt;(blob);&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; blob;&lt;br&gt;}&lt;/span&gt;&lt;/p&gt;&lt;p name=&quot;abd1&quot; id=&quot;abd1&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;The save and download functions work on the assumption that we are going to store a copy of the record for each person, while the index manages a list of users and their files.
&lt;/p&gt;
&lt;figure&gt;
&lt;iframe src=&quot;https://jsfiddle.net/JeffereyCave/7h9rnged/embedded/&quot; width=&quot;600&quot; height=&quot;400&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;&lt;figcaption&gt;A JSFiddle giving an example of the working demonstration&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 name=&quot;fe3b&quot; id=&quot;fe3b&quot; class=&quot;graf graf--h4 graf-after--figure&quot;&gt;Reducing Storage&lt;/h4&gt;&lt;p&gt;
To modify this example to reduce our storage, we must first modify the save function to not blindly save for each user, but rather to save each &lt;code class=&quot;markup--code markup--p-code&quot;&gt;blob&lt;/code&gt; as a primary object, and track users observing it as a secondary item.
&lt;/p&gt;
&lt;p&gt;
&lt;span class=&quot;pre--content&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;user, filename, blob&lt;/span&gt;) {&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; hash = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;TextEncoder&lt;/span&gt;().&lt;span class=&quot;hljs-title function_&quot;&gt;encode&lt;/span&gt;(blob);&lt;br&gt;  hash = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; crypto.&lt;span class=&quot;hljs-property&quot;&gt;subtle&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;digest&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;SHA-256&amp;#x27;&lt;/span&gt;, hash);&lt;br&gt;  hash = &lt;span class=&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;from&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Uint8Array&lt;/span&gt;(hash));&lt;br&gt;  hash = hash&lt;br&gt;    .&lt;span class=&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;b&lt;/span&gt; =&amp;gt;&lt;/span&gt; b.&lt;span class=&quot;hljs-title function_&quot;&gt;toString&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;16&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;padStart&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;0&amp;#x27;&lt;/span&gt;))&lt;br&gt;    .&lt;span class=&quot;hljs-title function_&quot;&gt;join&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;&amp;#x27;&lt;/span&gt;)&lt;br&gt;    .&lt;span class=&quot;hljs-title function_&quot;&gt;substr&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;,&lt;span class=&quot;hljs-number&quot;&gt;4&lt;/span&gt;);&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; userpath = [user, filename].&lt;span class=&quot;hljs-title function_&quot;&gt;join&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;@&amp;#x27;&lt;/span&gt;);&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; rec = &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;;&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt; {&lt;br&gt;    rec = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; db.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(hash);&lt;br&gt;  } &lt;span class=&quot;hljs-keyword&quot;&gt;catch&lt;/span&gt; (e) {&lt;br&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (e.&lt;span class=&quot;hljs-property&quot;&gt;status&lt;/span&gt; !== &lt;span class=&quot;hljs-number&quot;&gt;404&lt;/span&gt;) &lt;span class=&quot;hljs-keyword&quot;&gt;throw&lt;/span&gt; e;&lt;br&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// create the object, with a list of user&amp;#x27;s using it&lt;/span&gt;&lt;br&gt;    rec = {&lt;br&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;_id&lt;/span&gt;: hash,&lt;br&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;userpaths&lt;/span&gt;: [],&lt;br&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;size&lt;/span&gt;: blob.&lt;span class=&quot;hljs-property&quot;&gt;length&lt;/span&gt;,&lt;br&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;_attachments&lt;/span&gt;: {&lt;br&gt;        &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;0&amp;#x27;&lt;/span&gt;: {&lt;br&gt;          &lt;span class=&quot;hljs-attr&quot;&gt;content_type&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;text/plain&amp;#x27;&lt;/span&gt;,&lt;br&gt;          &lt;span class=&quot;hljs-attr&quot;&gt;data&lt;/span&gt;: &lt;span class=&quot;hljs-variable language_&quot;&gt;window&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;btoa&lt;/span&gt;(blob)&lt;br&gt;        }&lt;br&gt;      }&lt;br&gt;    }&lt;br&gt;  }&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!rec.&lt;span class=&quot;hljs-property&quot;&gt;userpaths&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;includes&lt;/span&gt;(userpath)) {&lt;br&gt;    rec.&lt;span class=&quot;hljs-property&quot;&gt;userpaths&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;push&lt;/span&gt;(userpath);&lt;br&gt;  }&lt;br&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;// finally save the record&lt;/span&gt;&lt;br&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; db.&lt;span class=&quot;hljs-title function_&quot;&gt;put&lt;/span&gt;(rec);&lt;br&gt;}&lt;/span&gt;&lt;/p&gt;&lt;p name=&quot;bab6&quot; id=&quot;bab6&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;This ensures that we only ever store a blob once.
&lt;/p&gt;
&lt;p&gt;
Using the hash as the record identifier means that no matter how many times it is submitted, we just keep using the existing record. New users are simply added to the list of users using the item. Users can even make copies of it by submitting the same item with a different name; we just keep adding notes that the user has a name for the record.
&lt;/p&gt;
&lt;p&gt;
Naturally, this breaks the lookup index we were using. The original index used the name of the record (username+path) to look up the file, but the file no longer uses this as its record name. Instead, we need to create a lookup index that is constructed from all the users that use the same file.
&lt;/p&gt;
&lt;p&gt;
async function CreateIndex(){&lt;br&gt;  return await db.put({&lt;br&gt;    _id: &amp;#39;_design/allfiles&amp;#39;,&lt;br&gt;    views: {&lt;br&gt;      &amp;#39;allfiles&amp;#39;: {&lt;br&gt;        map: function (doc) {&lt;br&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;          for (let userpath of doc.userpaths) {&lt;br&gt;            userpath = userpath.split(&amp;#39;@&amp;#39;);&lt;br&gt;            emit(userpath, doc.size);&lt;br&gt;          }&lt;/strong&gt;&lt;br&gt;        }.toString(),&lt;br&gt;        reduce:&amp;#39;_stats&amp;#39;&lt;br&gt;      }&lt;br&gt;    }&lt;br&gt;  });&lt;br&gt;}&lt;/p&gt;&lt;p name=&quot;aae8&quot; id=&quot;aae8&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;By looping through each of the user paths, we have updated our index to return the file, for each use of the file. This means no change to our download function, as the view&#39;s interface has not changed.
&lt;/p&gt;
&lt;p&gt;
Note that we had included the file size in the view, and used the &lt;code class=&quot;markup--code markup--p-code&quot;&gt;stats&lt;/code&gt; reduce method. This means that when it comes time to do billing we can simply add up the number of blobs the user references, as well as add up their total size.
&lt;/p&gt;
&lt;p&gt;
Re-running &lt;code class=&quot;markup--code markup--p-code&quot;&gt;main&lt;/code&gt; gives us a new total size and billing data:
&lt;/p&gt;
&lt;p&gt;
DB Size: 1109 bytes&lt;/p&gt;&lt;pre name=&quot;92d9&quot; id=&quot;92d9&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;| User  | Files | Size |&lt;br&gt;| ----- | ----- | ---- |&lt;br&gt;| alice |     2 |  101 | &lt;br&gt;| bob   |     1 |   45 |&lt;br&gt;| carol |     1 |   56 |&lt;/pre&gt;&lt;p name=&quot;3a2a&quot; id=&quot;3a2a&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;Even in our trivial example, with a very small content size, our database size (and therefore our business expenses) &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;decreased by a whopping 39%&lt;/strong&gt;. As more of the data structure is taken up with content, and less is taken up with metadata (think MP3, MP4, and PNG), a better return is seen.
&lt;/p&gt;
&lt;p&gt;
Also, notice that the billable sizes never changed.
&lt;/p&gt;
&lt;figure&gt;
&lt;iframe src=&quot;https://jsfiddle.net/JeffereyCave/xhc18t9L/embedded/&quot; width=&quot;600&quot; height=&quot;400&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;&lt;figcaption&gt;The same example as previously presented but with the changes to make the storage smaller.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 name=&quot;cb0f&quot; id=&quot;cb0f&quot; class=&quot;graf graf--h4 graf-after--figure&quot;&gt;Completing the Solution&lt;/h4&gt;&lt;p&gt;
After all of that, we cannot forget that Google demonstrated that collisions are maliciously possible, therefore they cannot be absolutely trusted. We must perform complete byte-by-byte comparisons. Hash functions are useful tools for determining dissimilarity between binary objects, not for determining similarity.
&lt;/p&gt;
&lt;blockquote&gt;
Hash functions are useful tools for determining dissimilarity between binary objects
&lt;/blockquote&gt;
&lt;p&gt;
This is a very useful feature. By using a large hash, I can quickly narrow the required comparisons to almost nothing. There is a good chance I will have no more than one match that needs to be checked, and that is a lot less effort than millions. Once I&#39;ve narrowed it down, the complete file must be checked with potentially an extra marker to distinguish it from one we already have.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/0-pog1qn9nxnlubpdc-di856heqEi-__489__.avif 489w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-to-not-store-the-same-file-twice/0-pog1qn9nxnlubpdc-di856heqEi-__489__.webp&quot; alt=&quot;&quot; width=&quot;489&quot; height=&quot;496&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;&lt;a href=&quot;https://www.buymeacoffee.com/jeffereycave&quot; target=&quot;_blank&quot;&gt;Found this useful or interesting? Consider leaving a tip … it helps.&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
There is a second problem in that we need to delete records at some point. In this case, we can simply drop the reference the user has to the object, though we must also observe for the time when no user has a reference to the object. When all references are removed to the object, the object itself should be removed. Similarly, if a record is renamed, we need to ensure we remove the old reference before creating the new reference.
&lt;/p&gt;
&lt;p&gt;
I leave these as exercises for the reader.
&lt;/p&gt;
&lt;h3&gt;Further Reading&lt;/h3&gt;
&lt;p&gt;
This demonstration uses JavaScript and PouchDB. I love in-browser demonstrations because they are so portable: you always have an IDE and runtime environment available.
&lt;/p&gt;
&lt;p&gt;
PouchDB is a JavaScript implementation of CouchDB, to expand the solution to something useful at the enterprise level, one of the large-scale CouchDB implementations would be a good start.
&lt;/p&gt;
&lt;p&gt;
It is also worth mentioning that this technique is not limited to any particular system. The same technique could be implemented at the Operating System level using &lt;a href=&quot;https://www.howtogeek.com/287014/how-to-create-and-use-symbolic-links-aka-symlinks-on-linux/&quot; target=&quot;_blank&quot;&gt;symbolic or hard links&lt;/a&gt;. In fact, some combinations of database and FS storage may be an optimised way to go.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Manuals: &lt;a href=&quot;https://docs.couchdb.org/en/stable/&quot; target=&quot;_blank&quot;&gt;Apache CouchDB&lt;/a&gt;, &lt;a href=&quot;https://www.ibm.com/cloud/cloudant&quot; target=&quot;_blank&quot;&gt;IBM CloudAnt&lt;/a&gt;, &lt;a href=&quot;https://docs.couchbase.com/home/index.html&quot; target=&quot;_blank&quot;&gt;CouchBase&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
Creating indexes: &lt;a href=&quot;https://pouchdb.com/2014/05/01/secondary-indexes-have-landed-in-pouchdb.html&quot; target=&quot;_blank&quot;&gt;Secondary Indexes have landed in PouchDB&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.howtogeek.com/287014/how-to-create-and-use-symbolic-links-aka-symlinks-on-linux/&quot; target=&quot;_blank&quot;&gt;How to Create and Use Symbolic Links (aka Symlinks) on Linux&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://itsfoss.com/what-is-zfs/&quot; target=&quot;_blank&quot;&gt;What is ZFS?&lt;/a&gt; A file system that has de-duplication built-in
&lt;/li&gt;
&lt;/ul&gt;
&lt;section name=&quot;e98d&quot; class=&quot;section section--body section--last&quot;&gt;&lt;div class=&quot;section-divider&quot;&gt;&lt;hr class=&quot;section-divider&quot;&gt;&lt;/div&gt;&lt;div class=&quot;section-content&quot;&gt;&lt;div class=&quot;section-inner sectionLayout--insetColumn&quot;&gt;&lt;p&gt;
A sequel also exists that discusses how to efficiently store the same dataset as it changes over time.
&lt;/p&gt;
&lt;blockquote&gt;
&lt;a href=&quot;https://jefferey-cave.medium.com/not-storing-almost-the-same-file-twice-9a44e3179a6a&quot; target=&quot;_blank&quot;&gt;De-duplicating Data Storage II: Storing (almost) the same file twice&lt;/a&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;em&gt;More content at &lt;/em&gt;&lt;a href=&quot;https://plainenglish.io/&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;&lt;em&gt;PlainEnglish.io&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;em&gt;. Sign up for our &lt;/em&gt;&lt;a href=&quot;http://newsletter.plainenglish.io/&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;&lt;em&gt;free weekly newsletter&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;em&gt;. Follow us on &lt;/em&gt;&lt;a href=&quot;https://twitter.com/inPlainEngHQ&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;&lt;em&gt;Twitter&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;em&gt; and &lt;/em&gt;&lt;a href=&quot;https://www.linkedin.com/company/inplainenglish/&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;&lt;em&gt;LinkedIn&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;em&gt;. Join our &lt;/em&gt;&lt;a href=&quot;https://discord.gg/GtDtUAvyhW&quot; target=&quot;_blank&quot;&gt;&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;&lt;em&gt;community Discord&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;

</content>
        </entry>
        <entry>
            <title>4 Standard IT Disaster Scenarios you aren&#39;t prepared for</title>
            <link href="http://plaidsheep.ca/posts/2022/4-standard-it-disaster-scenarios-you-arent-prepared-for/" />
            <updated>2022-02-17T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2022/4-standard-it-disaster-scenarios-you-arent-prepared-for/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/0*eA0xVm-fJNQZN2YU.jpg&quot; alt=&quot;4 Standard IT Disaster Scenarios you aren&#39;t prepared for&quot;&gt;
                &lt;p&gt;
In any system design, there are several scenarios that should be considered to prevent system failure. Each of these scenarios describes a worst-case scenario that frames planning for catastrophic events.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/4-standard-it-disaster-scenarios-you-arent-prepared-for/0-ea0xvm-fjnqzn2yu-Iczo4OlFtb-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/4-standard-it-disaster-scenarios-you-arent-prepared-for/0-ea0xvm-fjnqzn2yu-Iczo4OlFtb-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;462&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;1906 Earthquake in San Francisco (Source: &lt;a href=&quot;https://en.wikipedia.org/wiki/File:Post-and-Grant-Avenue-Look.jpg&quot; target=&quot;_blank&quot;&gt;Wikipedia&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Often, when describing the need for various emergency protocols, the presenter is faced with resistance in the form of “but we trust each other”. These&lt;a href=&quot;https://en.wikipedia.org/wiki/Straw_man&quot; target=&quot;_blank&quot;&gt; forms of argument&lt;/a&gt; distract from the very real underlying risk that needs to be addressed.
&lt;/p&gt;
&lt;p&gt;
These descriptions, and their titles are meant to give a standardised response to the most common objections. Each scenario has a list of ways the scenario presents in the real world. The titles are somewhat humorous to ease the tension, but the scenarios are serious and realistic.
&lt;/p&gt;
&lt;p&gt;
The scenarios are also meant to be non-specific. Rather than planning for very specific events, general scenarios that encompass general responses allow for adaptation to multiple considerations.
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Under the Bus&lt;/li&gt;
&lt;li&gt;Bump on the Head&lt;/li&gt;
&lt;li&gt;Spiked Drink&lt;/li&gt;
&lt;li&gt;Sword of God&lt;/li&gt;
&lt;li&gt;Daemonic Possession (Bonus, I added another later)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Under the Bus&lt;/h2&gt;
&lt;p&gt;The primary on a system got run over by a bus on the way to work, and has been hospitalised for an indeterminate amount of time.&lt;/p&gt;
&lt;h3&gt;Presentation&lt;/h3&gt;
&lt;p&gt;Any unavailability of the system experts, potentially combined with the need for action&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accident: sky-diving, home repair, car accident&lt;/li&gt;
&lt;li&gt;Vacation: phoning people while they are on vacation is rude&lt;/li&gt;
&lt;li&gt;Illness: myocarditis, kidney stones, hemorrhoids, common cold&lt;/li&gt;
&lt;li&gt;Arrest: sometimes people get detained; rightly or wrongly&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Objections&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;“That&#39;s a horrible thing to say”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
If it makes you feel better, they are going to be OK; but accidents do happen in life. Do you really want to be the person that is phoning a colleague while they should be resting in hospital?
&lt;/p&gt;
&lt;blockquote&gt;
“We better make sure you don&#39;t do anything risky”
&lt;/blockquote&gt;
&lt;p&gt;
As a manager, did you just inform your employees that they are not to undertake any personal activities?
&lt;/p&gt;
&lt;h2&gt;Bump on the Head&lt;/h2&gt;
&lt;p&gt;
One of the trusted individuals has recently received a bump on the head and now has a brain injury that has drastically altered their personality. They can no longer be trusted. It is unclear for how long they were trusted when they should not have been.
&lt;/p&gt;
&lt;h3&gt;Presentation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;An actual bump on the head: has caused people&#39;s &lt;a href=&quot;https://en.wikipedia.org/wiki/Phineas_Gage&quot; target=&quot;_blank&quot;&gt;personalities to dramatically change&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Blackmail: or possibly bribery, where an outside actor has altered the state of the trust relationship&lt;/li&gt;
&lt;li&gt;Poor trust evaluation: You shouldn&#39;t have trusted them &lt;a href=&quot;https://www.cnbc.com/2016/11/18/why-psychopaths-are-so-good-at-getting-ahead.html&quot; target=&quot;_blank&quot;&gt;in the first place&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;External system breach: a trusted individual has had their digital identity compromised.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Objections&lt;/h3&gt;
&lt;blockquote&gt;
“It&#39;s OK, I trust you”
&lt;/blockquote&gt;
&lt;p&gt;
Stop exposing me to risk, its unfair. The minute something does go wrong, employees should have evidence in place that they were acting within acceptable parameters, and that the managerial staff had accepted any risks associated with the action. If judgement calls were required, and bad things happened, employees need to have a clear line of approval in place that they can point to as having failed (justifying their taking action)
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/4-standard-it-disaster-scenarios-you-arent-prepared-for/0-whugq8a5ixxvyehu-o-QtEeVc7x-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/4-standard-it-disaster-scenarios-you-arent-prepared-for/0-whugq8a5ixxvyehu-o-QtEeVc7x-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;856&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Phineas Gage is probably the most famous “Bump on the head” in history (Source: &lt;a href=&quot;https://en.wikipedia.org/wiki/Phineas_Gage#/media/File:Phineas_Gage_Cased_Daguerreotype_WilgusPhoto2008-12-19_EnhancedRetouched_Color.jpg&quot; target=&quot;_blank&quot;&gt;Wikipedia&lt;/a&gt;, Originally from the collection of Jack and Beverly Wilgus, and now in the Warren Anatomical Museum, Harvard Medical School)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;Spiked Drink&lt;/h2&gt;
&lt;p&gt;
The trusted individual stands up from lunch and realise they are feeling “wobbly”. Someone spiked their drink.
&lt;/p&gt;
&lt;h3&gt;Presentation&lt;/h3&gt;
&lt;p&gt;
Any scenario where the actor has a compromised capacity for judgement
&lt;/p&gt;
&lt;p&gt;
• Woken in the middle of the night&lt;br&gt; • Family emergencies&lt;br&gt; • Had a couple of drinks, heavy pain medication&lt;br&gt; • Compromised judgement results in the inability to judge yourself compromised.&lt;br&gt; • Snap decisions
&lt;/p&gt;
&lt;p&gt;
Plan for individuals to be able to declare themselves incapacitated or compromised; plan for them to take action even when their judgement is compromised; plan to declare someone else&#39;s judgement as compromised. Have clear instructions in place to reduce the need for judgement (do your thinking in advance)
&lt;/p&gt;
&lt;h3&gt;Objections&lt;/h3&gt;
&lt;blockquote&gt;
“People aren&#39;t allowed to drink on duty”
&lt;/blockquote&gt;
&lt;p&gt;
Being on-call, or worse, being the second or third person on call, during an emergency can result in you being activated at unanticipated times. The only way to avoid this is to consider all staff on-call 24/365.
&lt;/p&gt;
&lt;h2&gt;Sword of God&lt;/h2&gt;
&lt;p&gt;
(aka Sodom and Gomorrah, Meteor Impact, Zombies)
&lt;/p&gt;
&lt;p&gt;
The facility has just been hit by a meteor. Where there was a service center, there is now a crater.
&lt;/p&gt;
&lt;p&gt;
If it makes you feel better, everyone in the region is OK but more than a little distracted.
&lt;/p&gt;
&lt;h3&gt;Presentation&lt;/h3&gt;
&lt;p&gt;
Any regional outage that results in entire service being lost. Limited to no staff in the region able to respond.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Power outage
&lt;/li&gt;
&lt;li&gt;
Natural Disaster (storm, tsunami, earthquake)
&lt;/li&gt;
&lt;li&gt;
Epidemic
&lt;/li&gt;
&lt;li&gt;
War
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Objections&lt;/h3&gt;
&lt;blockquote&gt;
“Don&#39;t be over-dramatic”
&lt;/blockquote&gt;
&lt;p&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/December_2005_North_American_ice_storm&quot; target=&quot;_blank&quot;&gt;I&#39;m not being dramatic&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
During the &lt;a href=&quot;https://en.wikipedia.org/wiki/Early_Winter_2006_North_American_storm_complex&quot; target=&quot;_blank&quot;&gt;2005 Ice Storm in Montreal&lt;/a&gt;, a colleague&#39;s phone rang with a request for technical assistance from another company. Located in Montreal, they had been without power for two days. Generators had activated, and the facility was operational; however, due to the high demand for fuel replenishment and the state of infrastructure, they were unable to secure more diesel. Their three day supply was about to run out.
&lt;/p&gt;
&lt;p&gt;
A heroic effort was undertaken, unfortunately, due to the massive disruption to infrastructure, we were unable to rebuild their services on our infrastructure before the fuel ran out … leaving hundreds of thousands of Canadians without service for weeks.
&lt;/p&gt;
&lt;h2&gt;Dæmonic Possession&lt;/h2&gt;
&lt;p&gt;
(aka Planetary Alignment, Plumb Bad Luck)
&lt;/p&gt;
&lt;p&gt;
You&#39;ve done everything perfectly, but there is a very small d&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;æ&lt;/strong&gt;mon living inside your computer. As you type your solution, it waits inside for an inopportune moment and messes something up. Something important.
&lt;/p&gt;
&lt;h4 name=&quot;b579&quot; id=&quot;b579&quot; class=&quot;graf graf--h4 graf-after--p&quot;&gt;Presentation&lt;/h4&gt;&lt;p&gt;
Software systems are complex systems, and complex systems are just that … complex. Complexity leads to unpredictability, and that is basically “random” behaviour. This can present in all kinds of ways, none of them predictable.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
fat-fingering, typos
&lt;/li&gt;
&lt;li&gt;
stuff just stops working … nobody knows why
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Objections&lt;/h3&gt;
&lt;blockquote&gt;
If I can&#39;t predict it, how can I plan for it
&lt;/blockquote&gt;
&lt;p&gt;
This is fatalism, giving up, and that we must not do.
&lt;/p&gt;
&lt;p&gt;
Preparing for bizarro land is not easy, but it is possible. Generally, this is done through constant testing and rehearsal (&lt;a href=&quot;https://en.wikipedia.org/wiki/Chaos_engineering#Chaos_Monkey&quot; target=&quot;_blank&quot;&gt;you are rehearsing disasters aren&#39;t you&lt;/a&gt;). This forces people to have practiced system failures, and general system recovery, under controlled circumstances.
&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
Originally Published on my private consultancy website in 2013, this became something I wanted to preserve, share, and keep living. I have shared it with every company I have worked with, but I think it needs to be more widely distributed because I have yet to see a company that can handle any of these scenarios
&lt;/p&gt;
&lt;p&gt;
Once upon a time, this list was at least 6 items long. Expect more in the future.
&lt;/p&gt;
&lt;p&gt;
Also, I feel dirty for having used a “numbered list title”.
&lt;/p&gt;
</content>
        </entry>
        <entry>
            <title>How fast is fast enough?</title>
            <link href="http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/" />
            <updated>2022-01-11T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/0*kUkoJBHg3Zi_ffN8.jpg&quot; alt=&quot;How fast is fast enough?&quot;&gt;
                &lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-kukojbhg3zi-ffn8-A7hbQxjyiQ-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-kukojbhg3zi-ffn8-A7hbQxjyiQ-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;452&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Horse in Motion [img: Wikipedia, Public Domain]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
My claim: “real-time” is anything faster than a change can be observed
&lt;/blockquote&gt;
&lt;p&gt;
Years ago, I got into a lunchtime discussion of &lt;em&gt;real-time data processing&lt;/em&gt; and a couple of guys at the table started in with the macho attitude:
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
I used to work on fighter jets … real-time is microseconds
&lt;/li&gt;
&lt;li&gt;
I used to do nuclear weapons testing … real-time is nanoseconds
&lt;/li&gt;
&lt;li&gt;
I used to do solar flare warning systems …
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
While the time scales became increasingly reduced, I started to realise that my entire perspective was different. I was coming from a Healthcare setting involving patient charts, and in my head, the shortest timescale for transferring information was inter-hospital patient transfers, a process that involved humans reading and interpreting textual information. In this case, the information bottleneck was the time it would take for the patient to arrive at the new site, and staff at the new site to read the chart. (1–2 hours, sometimes up to a shift change)​
&lt;/p&gt;
&lt;p&gt;
My slower perspective was backed by one of the other developers at the table. In a previous lifetime, she had developed automated terrorism threat assessments and resource deployment systems (at least this is true in my head … she was always a little vague about what she had done previously). She was doing push notifications, but her bottleneck was the time it took for humans to comprehend the information they had received and to strap on a rifle. She put real-time at 15–30 minutes.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-xqwblsfir725oenl-RXC3NqYUda-__627__.avif 627w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-xqwblsfir725oenl-RXC3NqYUda-__627__.webp&quot; alt=&quot;&quot; width=&quot;627&quot; height=&quot;380&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;It&#39;s at this moment I have to appreciate how cool some of the projects are, that I&#39;ve been &lt;strong class=&quot;markup--strong markup--figure-strong&quot;&gt;near &lt;/strong&gt;(IMG: &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:A_Fighter_Jet.png&quot; target=&quot;_blank&quot;&gt;Wikimedia CC-SA 3.0&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Timestamps&lt;/h3&gt;
&lt;p&gt;
The way we think about time, is often built on a life time of assumptions.
&lt;/p&gt;
&lt;p&gt;
In some database systems, there is a datatype known as a &lt;code class=&quot;markup--code markup--p-code&quot;&gt;timestamp&lt;/code&gt;. A timestamp is a sequential number that is applied to the system. It is not a date, or time, it is a point in time.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-tg7igtoufhl90boo-PrJxTmNF_K-__258__.avif 258w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-tg7igtoufhl90boo-PrJxTmNF_K-__258__.webp&quot; alt=&quot;&quot; width=&quot;258&quot; height=&quot;387&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;I can hold a note for a long time…. But eventually, that&#39;s just noise. It&#39;s the change we&#39;re listening for … That&#39;s what makes it music. (Lorne, Angel, S2E2, IMG: &lt;a href=&quot;https://en.wikipedia.org/wiki/File:Lorne%28angel%29.jpg&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;, Fair Use)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
There is a very poignant scene in the TV show Angel in which Lorne (the karaoke hosting daemon) is counselling someone after a breakup: “I can hold a note for a long time … But eventually, that&#39;s just noise. It&#39;s the change we&#39;re listening for … That&#39;s what makes it music.”
&lt;/p&gt;
&lt;p&gt;
I have often taken this same point a step further, it is the change of state that defines time; time itself is a perception of changing state. This may be an oversimplification, but in terms of managing data it is a useful one. From &lt;em&gt;the point of view of our system&lt;/em&gt;, there has been no change, and therefore no time has passed. As our system does not have the same perception of time as we do, there is no reason it should use our convention of recording time.
&lt;/p&gt;
&lt;p&gt;
In terms of timestamps, this means we should have “Point in time 1”, followed by “Point in time 2”, followed by …
&lt;/p&gt;
&lt;p&gt;
Time is only observable at its smallest division, time&#39;s smallest division is the point of observation.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-2o60w3gr-lsro7p5-QLqSdCxKsY-__691__.avif 691w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-2o60w3gr-lsro7p5-QLqSdCxKsY-__691__.webp&quot; alt=&quot;&quot; width=&quot;691&quot; height=&quot;689&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Observation of change defines “time”. A point made most obvious by “&lt;a href=&quot;https://en.wikipedia.org/wiki/The_Horse_in_Motion&quot; target=&quot;_blank&quot;&gt;Horse In Motion&lt;/a&gt;” and the &lt;a href=&quot;https://en.wikipedia.org/wiki/Zoopraxiscope&quot; target=&quot;_blank&quot;&gt;Zoopraxiscope &lt;/a&gt;in 1880 (IMG: &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Zoopraxiscope_16485u.gif&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;: CC-SA 2.5)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
“Real-time” is what happens when change occurs between the observable points, so that it is available at the next point of observation (or possibly even creates the next observation).
&lt;/p&gt;
&lt;h3&gt;Human Speed&lt;/h3&gt;
&lt;p&gt;
In high school, a friend and I discovered &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc1305&quot; target=&quot;_blank&quot;&gt;Network Time Protocol&lt;/a&gt; (&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc2030&quot; target=&quot;_blank&quot;&gt;NTP&lt;/a&gt;) and the &lt;a href=&quot;https://nrc.canada.ca/en/certifications-evaluations-standards/canadas-official-time&quot; target=&quot;_blank&quot;&gt;Canadian Atomic Clocks&lt;/a&gt;. While reading the user manual put out by the National Research Council, I remember reading the request to &lt;em&gt;not use the most accurate servers&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
It was just a friendly request to be polite.
&lt;/p&gt;
&lt;p&gt;
The way the system works is that depending on how accurate your needs are, you are supposed to use decreasingly reliable “stratum” of service. Stratum 1 sits right on top of the atomic clock, Stratum 2 servers update from Stratum 1 (introducing a little bit of potential error), and Stratum 3 servers update from Stratum 2 servers (introducing some more potential error). So, at the time, you were politely asked to use stratum 3 servers to avoid overloading the stratum 1 computers.
&lt;/p&gt;
&lt;p&gt;
Seems fair.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-e-uzvhdn1mwd1cbj-g3Rh77flDL-__470__.avif 470w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-e-uzvhdn1mwd1cbj-g3Rh77flDL-__470__.webp&quot; alt=&quot;&quot; width=&quot;470&quot; height=&quot;420&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;You are supposed to use the “stratum” of service appropriate to your needs. Stratum 1 sits right on top of the atomic clock, with each stratum reading from the previous level. (IMG: &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Network_Time_Protocol_servers_and_clients.svg&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;: Public Domain)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Unfortunately, people are people, and my friend began updating his analogue watch (readable to the minute), by hand, with stratum 1 servers. When I told him stratum three (accurate in the range of milliseconds) was good enough and that he was decreasing the accuracy for everyone, he boldly told me “Nope, only Stratum 1 is accurate enough for me”.
&lt;/p&gt;
&lt;p&gt;
Macho statements aside, it is obvious that the speed bottleneck in the system is one of human scale, not computer scale.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
A $10 watch is not going to retain accurate time for a long time. The drift in the network is less than the drift of the watch itself.
&lt;/li&gt;
&lt;li&gt;
An analogue interface, interacting with a human eye, is going to have a read accuracy that is sub-minute, which is perfectly acceptable for the human turning the knob who cannot achieve better than sub-minute accuracy anyway.
&lt;/li&gt;
&lt;li&gt;
The consumer is trying to get to classes, and meetups at the coffee shop, in a timely fashion. The time it takes to physically navigate space between these events introduces variance at the sub-quarter-hour level.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Society does not function at the microseconds.
&lt;/p&gt;
&lt;p&gt;
At the time, the norm was to leave approximately 15 minutes to account for the vagaries of life. This is consistent for two people suffering from 5 minutes of error: 10 minutes of error, plus 5 minutes of agreement. 15 minutes was “real-time”.
&lt;/p&gt;
&lt;p&gt;
Any effort, on my friend&#39;s part, to achieve faster results was a complete waste of effort. At the same time, other people were genuinely being harmed.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Data Scientists relying on accurate time for weather modelling are hurt
&lt;/li&gt;
&lt;li&gt;
Sailors at sea, getting bad weather predictions, are hurt
&lt;/li&gt;
&lt;li&gt;
My friend gets hurt when everyone is annoyed at him for being late because he was fiddling with his watch to get it “really accurate”
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-xjv0wbgu-3wdp0iu--Cr2HjqUZf-__396__.avif 396w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-xjv0wbgu-3wdp0iu--Cr2HjqUZf-__396__.webp&quot; alt=&quot;&quot; width=&quot;396&quot; height=&quot;480&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;How my high-school-self envisioned the time keepers at the NRC, as well as their expression upon learning of the abuse of their system. I have since taught alongside an ex-weather researcher from Environment Canada; he looked just like that. (IMG: &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Grand_Warlock_of_Wikipedia.png&quot; target=&quot;_blank&quot;&gt;Wikimedia &lt;/a&gt;— CC-BY SA 4.0)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;Faster than Observed&lt;/h3&gt;
&lt;p&gt;
It is rare that I see anyone reading data more frequently than daily. Even with push notifications, I receive a text message telling me to take action, but I&#39;m happy if I can action it within a quarter business day.
&lt;/p&gt;
&lt;p&gt;
Faster is still better than slower; and &lt;em&gt;real time is &lt;/em&gt;&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;&lt;em&gt;faster &lt;/em&gt;&lt;/strong&gt;&lt;em&gt;than the change can be observed&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
Many times, in a business setting, an executive or manager or business unit calls for a “monthly” report, the report is therefore run on the 31st; weekly reports are run on Friday.
&lt;/p&gt;
&lt;p&gt;
This is a huge mistake and given automated systems we can do better. Real time is still something we can strive for, and a key benefit stems from the fact that the results don&#39;t change significantly if we achieve “faster than observable” rates.
&lt;/p&gt;
&lt;p&gt;
Since 2010, I have had a trading bot, developed in &lt;a href=&quot;https://en.wikipedia.org/wiki/Google_Sheets&quot; target=&quot;_blank&quot;&gt;Google Sheets&lt;/a&gt; and &lt;a href=&quot;https://developers.google.com/apps-script/overview&quot; target=&quot;_blank&quot;&gt;JavaScript&lt;/a&gt; (I&#39;m cheap and love free compute power), &lt;a href=&quot;https://jefferey-cave.medium.com/really-simple-sms-notifications-ddd9b781e953&quot; target=&quot;_blank&quot;&gt;that sends me a text message any time I need to make a trade&lt;/a&gt;. The fastest data I receive is 15 minute delayed price data, but the most significant data I receive is published quarterly (Financial Reports). This data must be aggregated into averages and deviations and … patterns. The system is attempting to establish “normal”, and normal (by definition) doesn&#39;t change by much.
&lt;/p&gt;
&lt;p&gt;
This is true for most human scale systems, most of the time
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-hiqbeiwkr1kra8pp-QOho4ZqJA3-__429__.avif 429w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-hiqbeiwkr1kra8pp-QOho4ZqJA3-__429__.webp&quot; alt=&quot;&quot; width=&quot;429&quot; height=&quot;523&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Human scale systems do not experience significant change at micro-timescales, most of the time. (Image: &lt;a href=&quot;https://xkcd.com/2553&quot; target=&quot;_blank&quot;&gt;XKCD&lt;/a&gt; Blog License)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Under these conditions, we are dealing with aggregate data. The changes are aggregated into averages over the scale of days or weeks or even quarters. The assessments are not going to change significantly on an hourly basis. This means that the assessment from yesterday, is probably about the same assessment I am going to get today. I may see a change in the general trend, but it will be subtle and non-actionable in the short term.
&lt;/p&gt;
&lt;p&gt;
There is a massively beneficial implication to this.
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
If I am producing a weekly report that interprets and advises the business, I should run the report daily. The average of 7 days of business operations is very likely to be similar to the average of 6 days. The results and conclusions of a report produced on Thursday, will likely be the same as the conclusions that will be produced on Friday.
&lt;/li&gt;
&lt;li&gt;
If the Friday run fails, I have my conclusions from Thursday. If my Thursday run failed, I have early notice that the Friday run is likely to fail.
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
By working at one unit finer of granularity, you have given yourself lead time on potential issues, as well as created a fall-back plan in case of catastrophic failure.
&lt;/p&gt;
&lt;p&gt;
In the past, this has resulted in
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
~3000 employees (myself included) getting paid for 13 days instead of not getting paid at all
&lt;/li&gt;
&lt;li&gt;
A JIT system (a life-safety service) being able to estimate demand early, so that key staff could attend a funeral
&lt;/li&gt;
&lt;li&gt;
Countless times I did not have to do overtime because a combination of poor &lt;code class=&quot;markup--code markup--li-code&quot;&gt;null&lt;/code&gt; handling and weird data caused fails, but (thankfully) &lt;em&gt;days&lt;/em&gt; in advance.
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
These are same principles given in my High school Math and Physics classes: use one decimal place more to do the calculation, than you report in. In business reporting: if you are tracking dollars, do the calculation in cents; if you are tracking cents, do your calculations in &lt;a href=&quot;https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/currency-data-type&quot; target=&quot;_blank&quot;&gt;fractions of a penny&lt;/a&gt;.
&lt;/p&gt;
&lt;h3&gt;Push and Pull&lt;/h3&gt;
&lt;p&gt;
Push notifications, change the playing field somewhat. Rather than updating the data on a schedule, we advertise changes to interested parties. However &lt;em&gt;we are still constrained to the response time of our slowest observer&lt;/em&gt;. Even in the case of detection of nuclear blasts, the point was to log and collect the data for interpretation by humans at a later date.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-fqzzdhlsf2s5baxa-H4BNsS5GYY-__361__.avif 361w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-fqzzdhlsf2s5baxa-H4BNsS5GYY-__361__.webp&quot; alt=&quot;&quot; width=&quot;361&quot; height=&quot;250&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;While there are many automated steps in my trading system, I still have to copy and paste the trade into my trading platform. As the person on call, sometimes I&#39;m busy. (Img: &lt;a href=&quot;https://xkcd.com/2565&quot; target=&quot;_blank&quot;&gt;XKCD&lt;/a&gt;: Blog License)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Take my trading bot: &lt;a href=&quot;https://jefferey-cave.medium.com/really-simple-sms-notifications-ddd9b781e953&quot; target=&quot;_blank&quot;&gt;It sends me text notifications almost immediately&lt;/a&gt; (magnitude of seconds). This is faster and far more convenient/reliable than me checking once a day, however it does not mean I can respond any faster. Real-time is constrained both by the speed I &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;receive the information&lt;/strong&gt; and the speed at which I &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;can respond.&lt;/strong&gt; If I am trapped in a meeting, a secure network environment, or up to my waist in a river while fishing, I may not be able to initiate the trade for a couple of hours.
&lt;/p&gt;
&lt;h4 name=&quot;493e&quot; id=&quot;493e&quot; class=&quot;graf graf--h4 graf-after--p&quot;&gt;Lazy Loading Improves Net Performance&lt;/h4&gt;&lt;p&gt;
So if, in most (human) cases, it is sufficient to deal in timescales of minutes or hours, then we can conclude that the the reports do not need to be updated any faster than that.
&lt;/p&gt;
&lt;p&gt;
In general, we can consider that updates do not need to be generated more frequently than they are going to be consumed by the observer (either digital, human, or system). This idea is where push notifications can both help and hurt us.
&lt;/p&gt;
&lt;p&gt;
Polling (regular pulls) of source systems generate needless processing effort. Requesting information comes at a processing cost where both systems need to expend effort talking to one another. If there are no changes to the system, all of that effort results in “no change”.
&lt;/p&gt;
&lt;p&gt;
Push notifications allow us to reduce this overhead by having the source system transmit change notifications to interested parties in the event something actually changes. This means that no processing is performed until something needs updating.
&lt;/p&gt;
&lt;p&gt;
However, given our sub-hour threshold for “real-time”, it is very possible that we may receive notice of change more frequently than we need to report it. We may recalculate a report more frequently than it can be observed.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/1-vijoomjyt5toasuyrqk-og-Jnj0n9ZniV-__425__.avif 425w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/1-vijoomjyt5toasuyrqk-og-Jnj0n9ZniV-__425__.webp&quot; alt=&quot;&quot; width=&quot;425&quot; height=&quot;494&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;
This was most evident to me in a simple web-app I was recently working on. I wanted the user to receive real-time notification of the correctness of their entry into the form.
&lt;/p&gt;
&lt;p&gt;
Every change to the form results in a change event that is processed by the back-end. I was following the error notifications coming back from the validation, and was trying to type in a valid value to observe the update come back to the form.
&lt;/p&gt;
&lt;p&gt;
I was being driven crazy, I was only 10 characters away from a positive result, but kept getting stopped by each key-press as it recalculated the validity. Each key-press was pushing a notification to the back-end which was triggering a recalculation … but I can already see I was wrong … internally, I was begging the system to just let me finish typing.
&lt;/p&gt;
&lt;p&gt;
In the end, I put a timer on the validation: do not update the validation more frequently than every 23 milli-seconds. That little bit of delay allowed me to finish typing, and the quality of the feedback did not suffer (maybe even improved), by bringing it into the human scale.
&lt;/p&gt;
&lt;p&gt;
Buffering results until someone actually wants it, takes us back to the concept of &lt;a href=&quot;https://en.wikipedia.org/wiki/Lazy_loading&quot; target=&quot;_blank&quot;&gt;Lazy Loading&lt;/a&gt;. If nobody is going to read your data, don&#39;t bother calculating it. This reduces overhead because you may have 20 updates, but only one view (and therefore calculation).
&lt;/p&gt;
&lt;p&gt;
If you are speaking about &lt;code class=&quot;markup--code markup--p-code&quot;&gt;push&lt;/code&gt; notifications, you should be thinking about &lt;code class=&quot;markup--code markup--p-code&quot;&gt;lazy-loading&lt;/code&gt;. A push notification can be used to notify our system that it needs to update, but we can use Lazy Loading to defer that processing until it is needed. But it is a balancing act. We can defer processing, but also need to balance it with performing it frequently enough.
&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;
Most of us are not attempting to detect the oncoming wave of a nuclear blast, nor are we racing ahead of a Solar Flare about to destroy our multi-billion dollar data on our international network infrastructure. Most of us operate on a timescale of minutes or hours, well within the operating tolerances of even the &lt;a href=&quot;http://www.orangepi.org/&quot; target=&quot;_blank&quot;&gt;most basic of desktop computers&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
Given this we need to remember two key points
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
Time scales dictate “real-time”
&lt;/li&gt;
&lt;li&gt;
You should always be processing one unit of time smaller than will be consumed
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
Through a combination of lazy-loading and push notifications, we can scale our response time to an &lt;em&gt;appropriate &lt;/em&gt;level. Obviously, an aircraft trying to stay airborne requires a different scale than inventory management in a retail organisation. Inundating humans with data does not improve information uptake.
&lt;/p&gt;
&lt;p&gt;
Having said that, we want to make sure we keep ahead of our audience. We can deliver information faster and more frequently than people need it, there is therefore no reason to have it standing by ready for them when they want it. There is no reason for us to not do our checks and balances well in advance.
&lt;/p&gt;
&lt;p&gt;
The key is balance.
&lt;/p&gt;
&lt;p&gt;
Don&#39;t let sales tactics (and your own ego and macho-ism) make you forget that every solution has its own set of drawbacks, and every solution is subject to the law of diminishing returns. At some point we need to recognise that the problem is solved, and that solution is “good enough”.
&lt;/p&gt;
&lt;p&gt;
Once “real-time” moves past “observable”, getting faster is “wasted-time”.
&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;&quot; src=&quot;http://plaidsheep.ca/posts/2022/how-fast-is-fast-enough/0-vq82ncbzep6cmorn-zMeOKcHlZw-__200__.webp&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;200&quot; height=&quot;200&quot;&gt;
&lt;/figure&gt;
</content>
        </entry>
        <entry>
            <title>Turn a Shiny Dashboard into a Desktop App</title>
            <link href="http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/" />
            <updated>2021-11-06T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                &lt;p&gt;
Shiny is popular web publishing service, unfortunately, not every application can be deployed on servers. This tutorial demonstrates a simple means by which to deploy a shiny app to desktop by creating a Site Specific Browser. Mostly to skip the bureaucratic begging for a server.
&lt;/p&gt;
&lt;p&gt;
Available on GitLab: &lt;a href=&quot;https://gitlab.com/jefferey-cave/shinyapp-desktop&quot; target=&quot;_blank&quot;&gt;Jeff Cave / shinyapp-desktop · GitLab&lt;/a&gt;
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/1-uq6fs45uivurdxgnuyfag-xxtCBzBF0T-__636__.avif 636w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/1-uq6fs45uivurdxgnuyfag-xxtCBzBF0T-__636__.webp&quot; alt=&quot;&quot; width=&quot;636&quot; height=&quot;396&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;An R-Shiny dashboard can be run as a desktop application, from a double click, to give non-technical users a seamless experience.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;
About two years ago, I had one of the Data Scientists in our organization call me with a problem. They had just spent a significant amount of time putting together a dashboard in R and Shiny and were wondering where they could host the dashboard to share it with clients.
&lt;/p&gt;
&lt;blockquote&gt;
Don&#39;t have a Medium Account? &lt;a href=&quot;https://jefferey-cave.medium.com/turn-a-shiny-dashboard-into-a-desktop-app-6f51287b7d6f?source=friends_link&amp;amp;sk=00aa6ca975c6b61dd3a4c5d1ee4ef4f9&quot; target=&quot;_blank&quot;&gt;Use my friend link!&lt;/a&gt;
&lt;/blockquote&gt;
&lt;p&gt;
They wanted to know where our Shiny server was stored and how they could publish to it.
&lt;/p&gt;
&lt;p&gt;
It took everything I had not to laugh at them.
&lt;/p&gt;
&lt;p&gt;
The thing I was working on at the moment he had called was a generic deployment system for exactly that kind of project, but was running into all kinds of negotiations with Security, Finance, Architecture … everybody has to have their say. To get him the server he wanted, I estimated years.
&lt;/p&gt;
&lt;p&gt;
Like any large organization, the bureaucracy must be fed.
&lt;/p&gt;
&lt;p&gt;
This was a huge blow to the Data Scientist, his team had been developing the dashboard for months. The business had invested precious effort in describing their informational needs. The team had demonstrated the value of Shiny. They were now ready to realize all that effort, and the organization&#39;s statement was: we can&#39;t do that.
&lt;/p&gt;
&lt;p&gt;
That&#39;s a lot of wasted effort.
&lt;/p&gt;
&lt;p&gt;
After some discussion, I took serious pity on him and his team (and myself, I&#39;d actually invested a lot of my coffee breaks coaching his junior Data Scientists).
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Your customer&#39;s need the dashboard &lt;strong class=&quot;markup--strong markup--li-strong&quot;&gt;&lt;em&gt;now&lt;/em&gt;&lt;/strong&gt;? (yes)
&lt;/li&gt;
&lt;li&gt;
Is the dashboard computationally expensive? (no)
&lt;/li&gt;
&lt;li&gt;
Do you have a shared folder you could publish the application to? (yes)
&lt;/li&gt;
&lt;li&gt;
Are the customer&#39;s at all technically savvy? (no)
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
I told him to give me the weekend and I&#39;d give him a prototype solution on Monday.
&lt;/p&gt;
&lt;h3&gt;Project&lt;/h3&gt;
&lt;p&gt;
The intent is not to teach how to do complex mathematics, or to write Shiny Apps, but rather to demonstrate how to configure a project within the organizational environment. It is hoped that this will act as a spring-board, helping users get setup quickly.
&lt;/p&gt;
&lt;p&gt;
The code itself is a simple demo app exported from RStudio. The real trick is to get it to run on the desktop environment.
&lt;/p&gt;
&lt;p&gt;
The expectation is that the developer wants to deploy a shared application, but is in an environment where there is no Shiny server, and is not likely to be one anytime soon. Rather than wait, the developer can take advantage of a shared folder structure, and an &lt;a href=&quot;https://en.wikipedia.org/wiki/XUL&quot; target=&quot;_blank&quot;&gt;old feature of FireFox&lt;/a&gt;, to run the application on individual desktops. While not being as elegant, it represents a solution that is likely suitable for most reporting needs, and can be implemented immediately (aka: use the tools already present).
&lt;/p&gt;
&lt;h4 name=&quot;19a6&quot; id=&quot;19a6&quot; class=&quot;graf graf--h4 graf-after--p&quot;&gt;Pre-Requisites&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;
Windows
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.rstudio.com/products/rstudio/&quot; target=&quot;_blank&quot;&gt;RStudio&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.rdocumentation.org/packages/utils/versions/3.6.2/topics/Rscript&quot; target=&quot;_blank&quot;&gt;Rscript&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://git-scm.com/book/en/v2&quot; target=&quot;_blank&quot;&gt;Git&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.mozilla.org/en-US/firefox/new/&quot; target=&quot;_blank&quot;&gt;Firefox&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
The demo assumes you have RStudio installed, and will be interacting with the system via PowerShell. There is no reason this will not work on Linux, however it is not what we use at the office, so not what it was tested on.
&lt;/p&gt;
&lt;h4 name=&quot;3f50&quot; id=&quot;3f50&quot; class=&quot;graf graf--h4 graf-after--p&quot;&gt;Checkout the base project&lt;/h4&gt;&lt;p&gt;
To get started, clone the sample project and open it in RStudio
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
Navigate: &lt;a href=&quot;https://gitlab.com/jefferey-cave/shinyapp-desktop&quot; target=&quot;_blank&quot;&gt;https://gitlab.com/jefferey-cave/shinyapp-desktop&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
Click: Fork
&lt;/li&gt;
&lt;li&gt;
Open your copy of the project
&lt;/li&gt;
&lt;li&gt;
Get the clone URL
&lt;/li&gt;
&lt;li&gt;
Go to command line and checkout project
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;cd ~/Project/Folder&lt;br&gt;git clone &lt;/code&gt;https://gitlab.com/jefferey-cave/shinyapp-desktop.git&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;&lt;br&gt;cd &lt;/code&gt;shinyapp-desktop&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;&lt;br&gt;ls -al&lt;/code&gt;&lt;/p&gt;&lt;p name=&quot;0bd3&quot; id=&quot;0bd3&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;You should see a listing of all the files in the project.
&lt;/p&gt;
&lt;p&gt;
Before we go any further, we should probably check to see that the project runs on our computer. This ensures that there are no basic configuration issues before the actual work begins.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/1-4akx4qvsssyhpm1kxxzhta-QALsQirNTO-__242__.avif 242w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/1-4akx4qvsssyhpm1kxxzhta-QALsQirNTO-__242__.webp&quot; alt=&quot;&quot; width=&quot;242&quot; height=&quot;112&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
Double click on the file &lt;code class=&quot;markup--code markup--li-code&quot;&gt;desktopshiny.Rproj&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
Open: &lt;code class=&quot;markup--code markup--li-code&quot;&gt;app.R&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
Click &lt;code class=&quot;markup--code markup--li-code&quot;&gt;Run App&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
You should see the shiny app open in the in-built browser. There may be some dependency resolution that needs to happen.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/1-hndfbik102ozfvxe7jzbqq-EmtIlCuqWr-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/1-hndfbik102ozfvxe7jzbqq-EmtIlCuqWr-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;427&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Proof that the application is running and any problems we may experience are not with the computer configuration or app code.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 name=&quot;958b&quot; id=&quot;958b&quot; class=&quot;graf graf--h4 graf-after--figure&quot;&gt;Create a Starter Script&lt;/h4&gt;&lt;p&gt;
While it is great to know that the application works, it is not currently a great user experience. We have been asked to create an app for those less technically inclined, and they should not need an instruction manual to get up and running.
&lt;/p&gt;
&lt;p&gt;
We can ease their experience by creating a starter script that loads their application from a shortcut.
&lt;/p&gt;
&lt;p&gt;
The first thing to note is the output during the run of our Shiny dashboard. When we click on the button &lt;code class=&quot;markup--code markup--p-code&quot;&gt;Run App&lt;/code&gt; we see the exact &lt;code class=&quot;markup--code markup--p-code&quot;&gt;R&lt;/code&gt; command being executed to achieve all of this, and its output:
&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;shiny::runApp()&lt;/code&gt;&lt;/p&gt;&lt;pre name=&quot;51be&quot; id=&quot;51be&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;&lt;code class=&quot;markup--code markup--pre-code u-paddingRight0 u-marginRight0&quot;&gt;Listening on &lt;a href=&quot;http://127.0.0.1:5436&quot; target=&quot;_blank&quot;&gt;http://127.0.0.1:5436&lt;/a&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p name=&quot;4280&quot; id=&quot;4280&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;Try copy/pasting your URL into your local browser, you should see the same app.
&lt;/p&gt;
&lt;p&gt;
Knowing that there is an &lt;code class=&quot;markup--code markup--p-code&quot;&gt;R&lt;/code&gt; command that will start our application, we can skip the IDE and run the application using &lt;code class=&quot;markup--code markup--p-code&quot;&gt;rscript&lt;/code&gt;
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
Stop your app in RStudio
&lt;/li&gt;
&lt;li&gt;
Open a PowerShell terminal
&lt;/li&gt;
&lt;li&gt;
Change to your project folder
&lt;/li&gt;
&lt;li&gt;
Run your project using &lt;code class=&quot;markup--code markup--li-code&quot;&gt;rscript&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
&lt;code class=&quot;markup--code markup--p-code&quot;&gt;rscript.exe -e &amp;quot;shiny::runApp(&amp;#39;.&amp;#39;)&amp;quot;&lt;/code&gt;
&lt;/p&gt;
&lt;p&gt;
Your app should be started, but without having to have the customer load the IDE:
&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code u-paddingRight0 u-marginRight0&quot;&gt;Listening on &lt;a href=&quot;http://127.0.0.1:3145&quot; target=&quot;_blank&quot;&gt;http://127.0.0.1:3145&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;p name=&quot;f6fb&quot; id=&quot;f6fb&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;Try pointing your browser at that new URL. You should be looking at the app.
&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong class=&quot;markup--strong markup--blockquote-strong&quot;&gt;&lt;em&gt;NOTE&lt;/em&gt;&lt;/strong&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;em&gt;The port changes every time you start. It is randomly assigned at start. You can specify the port that will be used; however if we put together more than one dashboard, having a random port means less coordination between data scientists (it should just work).&lt;/em&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
We will not be making assumptions about the start conditions and will not set a static port.
&lt;/blockquote&gt;
&lt;p&gt;
If we check the &lt;code class=&quot;markup--code markup--p-code&quot;&gt;runApp&lt;/code&gt; parameters, there is one extra parameter we can include to make this a little more user friendly:
&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;rscript.exe -e &amp;quot;shiny::runApp(&amp;#39;.&amp;#39;,launch.browser=TRUE)&amp;quot;&lt;/code&gt;&lt;/p&gt;&lt;p name=&quot;b1b4&quot; id=&quot;b1b4&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;Save that in a text file called &lt;code class=&quot;markup--code markup--p-code&quot;&gt;start.ps1&lt;/code&gt;.
&lt;/p&gt;
&lt;p&gt;
You now have a basic script for users to start your interactive report. Having your customer click on the &lt;code class=&quot;markup--code markup--p-code&quot;&gt;start&lt;/code&gt; script will give them a basically seamless experience.
&lt;/p&gt;
&lt;h4 name=&quot;532a&quot; id=&quot;532a&quot; class=&quot;graf graf--h4 graf-after--p&quot;&gt;Creating a new browser instance&lt;/h4&gt;&lt;p&gt;
Since Shiny advertises the port it is listening on, it is possible for us to capture that information, and then instantiate a &lt;em&gt;special browser instance&lt;/em&gt; on behalf of the user.
&lt;/p&gt;
&lt;p&gt;
For our user, it would be nice if we were able to start a browser instance &lt;em&gt;just for the application&lt;/em&gt;. We also would like to stop the Shiny instance when the browser stops using it.
&lt;/p&gt;
&lt;blockquote&gt;
There is no data. There is only XUL!
&lt;/blockquote&gt;
&lt;blockquote&gt;
(the XUL platform slogan)
&lt;/blockquote&gt;
&lt;p&gt;
For this example, we will use Firefox as it is based on the XUL platform which has a well documented and modifiable interface. To summarize (in a brutal way), FireFox is itself a webpage that can be dynamically modified (if you know how). We are going to use this feature to create a primitive &lt;a href=&quot;https://en.wikipedia.org/wiki/Site-specific_browser&quot; target=&quot;_blank&quot;&gt;Site Specific Browser&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
Using PowerShell, we can extend our shiny server starting script to listen for the advertised port. We can then use this advertised URL and port to start a Firefox instance.
&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;# Start an instance of the app using `rscript` and &lt;br&gt;# capture `stderr` for the port number&lt;br&gt;&amp;amp; &amp;quot;rscript.exe&amp;quot; -e &amp;quot;shiny::runApp(&amp;#39;.&amp;#39;)&amp;quot; 2&amp;gt;&amp;amp;1 | &lt;br&gt;    % {&lt;br&gt;        # look for the `url` line&lt;br&gt;        if($_ -like &amp;#39;*Listening on*&amp;#39;){&lt;br&gt;            # Parse the input for the url&lt;br&gt;            (&amp;quot;$_&amp;quot; -replace &amp;quot;.*Listening on &amp;quot;,&amp;#39;&amp;#39;).Trim();                      &lt;br&gt;        }&lt;br&gt;    } | &lt;br&gt;    % {&lt;br&gt;        # open FireFox using the discovered URL&lt;br&gt;        &amp;amp; &amp;quot;C:&#92;Program Files&#92;Mozilla Firefox&#92;firefox.exe&amp;quot; $_&lt;br&gt;    };&lt;/code&gt;&lt;/p&gt;&lt;p name=&quot;87dc&quot; id=&quot;87dc&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;This solution gets us part way there: we are now starting a unique instance of the browser for the shiny app.
&lt;/p&gt;
&lt;p&gt;
The issue is that when we terminate our Firefox instance, our Shiny instance continues to run in the background. We must manually stop it.
&lt;/p&gt;
&lt;p&gt;
We can continue to modify our script to start both the Shiny dashboard and the Firefox instance separately, then allow our script to maintain enough intelligence about them to monitor their independent process states.
&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code u-paddingRight0 u-marginRight0&quot;&gt;&lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;# Start the `shiny` &amp;quot;thread&amp;quot;&lt;/strong&gt;&lt;br&gt;$shiny = Start-Job -Name &amp;quot;shiny&amp;quot; -ArgumentList($pwd) -ScriptBlock{&lt;br&gt;    param($workingdir);&lt;br&gt;    cd $workingdir;&lt;br&gt;    &lt;strong class=&quot;markup--strong markup--pre-strong&quot;&gt;# Start the shiny app and print the URL&lt;/strong&gt;&lt;br&gt;    &amp;amp; &amp;quot;rscript.exe&amp;quot; -e &amp;quot;shiny::runApp(&amp;#39;.&amp;#39;)&amp;quot; 2&amp;gt;&amp;amp;1 | % {&lt;br&gt;        if($_ -like &amp;#39;*Listening on*&amp;#39;){&lt;br&gt;            (&amp;quot;$_&amp;quot; -replace &amp;quot;.*Listening on &amp;quot;,&amp;#39;&amp;#39;).Trim()&lt;br&gt;        }&lt;br&gt;    }&lt;br&gt;}&lt;/code&gt;&lt;/p&gt;&lt;p name=&quot;1a5e&quot; id=&quot;1a5e&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;At this point Shiny is started as a &lt;code class=&quot;markup--code markup--p-code&quot;&gt;job&lt;/code&gt;, and jobs maintain a reference which can then be used to stop the job at a later time. This does add the problem that we need to read from the output stream slightly differently.
&lt;/p&gt;
&lt;p&gt;
#poll the shiny thread for output&lt;br&gt;&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;while ($shiny.HasMoreData -or $shiny.State -eq &amp;quot;Running&amp;quot;) {&lt;br&gt;    $url = $shiny.ChildJobs[0].output.readall();&lt;br&gt;    # when we find the URL ... stop&lt;br&gt;    if($url){&lt;br&gt;        break;&lt;br&gt;    }&lt;br&gt;}&lt;/code&gt;&lt;/p&gt;&lt;p name=&quot;1198&quot; id=&quot;1198&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;Now, we have the URL at the script level, and can proceed to start Firefox.
&lt;/p&gt;
&lt;p&gt;
Again, we want to create it as a separate process that we can monitor.
&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;# create an array of arguments&lt;br&gt;$args = @(&amp;#39;-profile&amp;#39;,&amp;#39;./profile&amp;#39;,&amp;#39;-new-instance&amp;#39;,&amp;quot;-url `&amp;quot;$url`&amp;quot;&amp;quot;);&lt;br&gt;# start the firefox instance&lt;br&gt;$ff = Start-Process &amp;quot;C:&#92;Program Files&#92;Mozilla Firefox&#92;firefox.exe&amp;quot; -ArgumentList $args -PassThru -Wait&lt;/code&gt;&lt;/p&gt;&lt;p name=&quot;07ac&quot; id=&quot;07ac&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;This will block the script until FF stops.
&lt;/p&gt;
&lt;p&gt;
Pay close attention to the arguments passed to Firefox.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code class=&quot;markup--code markup--li-code&quot;&gt;profile&lt;/code&gt;: This uses a pre-existing profile that is customized to our purposes.
&lt;/li&gt;
&lt;li&gt;
&lt;code class=&quot;markup--code markup--li-code&quot;&gt;new-instance&lt;/code&gt;: Ensures, it does not re-use any instances of FireFox that may already be open
&lt;/li&gt;
&lt;li&gt;
&lt;code class=&quot;markup--code markup--li-code&quot;&gt;Wait&lt;/code&gt;: Ensures that the PowerShell job blocks processing until it (&lt;code class=&quot;markup--code markup--li-code&quot;&gt;$ff&lt;/code&gt;)completes
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
This forces a “new-instance” and a new “profile” and a “wait” until Firefox completes. This custom profile is used to manipulate the way Firefox appears to the user. For the adventurous, go inspect the included profile to see ways you can manipulate Firefox to make it behave more like we want to.
&lt;/p&gt;
&lt;p&gt;
Our final step is to simply stop the shiny process once the Firefox process has terminated.
&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;Stop-Job $shiny.Id&lt;/code&gt;&lt;/p&gt;&lt;p name=&quot;3f7a&quot; id=&quot;3f7a&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;The completed script looks like:
&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;$shiny = Start-Job -Name &amp;quot;shiny&amp;quot; -ArgumentList($pwd) -ScriptBlock{&lt;br&gt;    param($workingdir);&lt;br&gt;    cd $workingdir;&lt;br&gt;    &amp;amp; &amp;quot;rscript.exe&amp;quot; -e &amp;quot;shiny::runApp(&amp;#39;.&amp;#39;)&amp;quot; 2&amp;gt;&amp;amp;1 | % {&lt;br&gt;        if($_ -like &amp;#39;*Listening on*&amp;#39;){&lt;br&gt;            (&amp;quot;$_&amp;quot; -replace &amp;quot;.*Listening on &amp;quot;,&amp;#39;&amp;#39;).Trim()&lt;br&gt;        }&lt;br&gt;    }&lt;br&gt;} &lt;/code&gt;&lt;/p&gt;&lt;pre name=&quot;db46&quot; id=&quot;db46&quot; class=&quot;graf graf--pre graf-after--pre&quot;&gt;&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;while ($shiny.HasMoreData -or $shiny.State -eq &amp;quot;Running&amp;quot;) {&lt;br&gt;    $url = $shiny.ChildJobs[0].output.readall()&lt;br&gt;    if($url){&lt;br&gt;        break;&lt;br&gt;    }&lt;br&gt;}&lt;br&gt;$args = @(&amp;#39;-profile&amp;#39;,&amp;#39;./profile&amp;#39;,&amp;#39;-new-instance&amp;#39;,&amp;quot;-url `&amp;quot;$url`&amp;quot;&amp;quot;);&lt;br&gt;$ff = Start-Process &amp;quot;C:&#92;Program Files&#92;Mozilla Firefox&#92;firefox.exe&amp;quot; -ArgumentList $args -PassThru -Wait&lt;br&gt;Stop-Job $shiny.Id&lt;/code&gt;&lt;/pre&gt;&lt;p name=&quot;c5ea&quot; id=&quot;c5ea&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;At this point you should be able to simply run the script
&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;.&#92;run.ps1&lt;/code&gt;&lt;/p&gt;&lt;p name=&quot;f6cd&quot; id=&quot;f6cd&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;and (eventually… it&#39;s a little slow) see your app running in a window.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/1-hg5biurz1xgdlqk5hjflla-RTw1LhyfAh-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/1-hg5biurz1xgdlqk5hjflla-RTw1LhyfAh-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;653&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Our dashboard, running as a standalone desktop application. The icon can be changed by modifying the files in the “profile” folder.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 name=&quot;6dd4&quot; id=&quot;6dd4&quot; class=&quot;graf graf--h4 graf-after--figure&quot;&gt;Creating a Shortcut&lt;/h4&gt;&lt;p&gt;
One of the issues with creating a PowerShell script is that regular users can&#39;t run it without a bit of “know-how”. The easiest way to get around this is to create an old fashioned &lt;code class=&quot;markup--code markup--p-code&quot;&gt;BAT&lt;/code&gt; file.
&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;cd &amp;lt;working directory&amp;gt;&lt;br&gt;C:&#92;Windows&#92;System32&#92;WindowsPowerShell&#92;v1.0&#92;powershell.exe &amp;quot;.&#92;run.ps1&amp;quot;&lt;/code&gt;&lt;/p&gt;&lt;p name=&quot;8b63&quot; id=&quot;8b63&quot; class=&quot;graf graf--p graf-after--pre&quot;&gt;If you run this as a regular user, you will likely get an error. The problem is that Windows (in their infinite wisdom) makes scripting unavailable to users by default. This is to protect them from malicious scripts.
&lt;/p&gt;
&lt;p&gt;
In order to activate the script, you need to indicate that you “know what you are doing”.
&lt;/p&gt;
&lt;p&gt;
Since we only want our users to see the desktop window, we may as well hide the console window while we are at it.
&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;markup--code markup--pre-code&quot;&gt;cd &amp;lt;working directory&amp;gt;&lt;br&gt;C:&#92;Windows&#92;System32&#92;WindowsPowerShell&#92;v1.0&#92;powershell.exe -windowstyle hidden -executionpolicy bypass &amp;quot;.&#92;run.ps1&amp;quot;&lt;/code&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;
&lt;code class=&quot;markup--code markup--li-code&quot;&gt;executionpolicy&lt;/code&gt;: allows the script to be run
&lt;/li&gt;
&lt;li&gt;
&lt;code class=&quot;markup--code markup--li-code&quot;&gt;windowstyle&lt;/code&gt;: allows us to hide the terminal window
&lt;/li&gt;
&lt;/ul&gt;
&lt;p name=&quot;93eb&quot; id=&quot;93eb&quot; class=&quot;graf graf--p graf-after--li&quot;&gt;Given this is a desktop application, a shortcut file (&lt;code class=&quot;markup--code markup--p-code&quot;&gt;lnk&lt;/code&gt;) is probably a better option than the above &lt;code class=&quot;markup--code markup--p-code&quot;&gt;bat&lt;/code&gt; file. These allow us to specify all the same parameters, but also an icon file, while removing &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;all&lt;/strong&gt; the console windows.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/1-o0f7ayt8i7w4gudryoxp3w-CN3STp8l8G-__520__.avif 520w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/1-o0f7ayt8i7w4gudryoxp3w-CN3STp8l8G-__520__.webp&quot; alt=&quot;&quot; width=&quot;520&quot; height=&quot;473&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;The link settings diaglogue, showing it filled in and with the icon set&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
Navigate to the working directory in Windows Explorer
&lt;/li&gt;
&lt;li&gt;
Right-Click &amp;gt; &lt;code class=&quot;markup--code markup--li-code&quot;&gt;New ...&lt;/code&gt; &amp;gt; &lt;code class=&quot;markup--code markup--li-code&quot;&gt;Shortcut&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
Set the properties&lt;br&gt;- Target: &lt;code class=&quot;markup--code markup--li-code&quot;&gt;C:&#92;Windows&#92;System32&#92;WindowsPowerShell&#92;v1.0&#92;powershell.exe -windowstyle hidden -executionpolicy bypass &amp;quot;.&#92;run.ps1&amp;quot;&lt;br&gt;&lt;/code&gt;- Start in: &lt;code class=&quot;markup--code markup--li-code&quot;&gt;&amp;lt;working directory&amp;gt;&lt;/code&gt; &lt;br&gt;-&lt;code class=&quot;markup--code markup--li-code&quot;&gt;Change Icon ...&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
By setting those three options, your users are a double click away from a reasonably seamless desktop experience.
&lt;/p&gt;
&lt;h3&gt;A Happy(ish) Colleague&lt;/h3&gt;
&lt;p&gt;
The lead for the Data Science team I was working with was (reasonably) happy with the solution. It was a hack, but it got his team up and running in a matter of days.
&lt;/p&gt;
&lt;p&gt;
We both agreed that optimal solution was to get a Shiny Server installed on-prem and link the URL form the internal website, so I put him in touch with the correct procurement experts as well as giving him this solution. I don&#39;t know what ever came of the procurement.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/1-zvsury8-96f2ky9w0vjuma-jXObB0F4bn-__489__.avif 489w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2021/turn-a-shiny-dashboard-into-a-desktop-app/1-zvsury8-96f2ky9w0vjuma-jXObB0F4bn-__489__.webp&quot; alt=&quot;&quot; width=&quot;489&quot; height=&quot;496&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;&lt;a href=&quot;https://www.buymeacoffee.com/jeffereycave&quot; target=&quot;_blank&quot;&gt;Found this useful or interesting? Consider leaving a tip … it helps.&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
We did some refinements for the team&#39;s solution (mostly automating the deploy to the shared filesystem from GitLab&#39;s CI/CD features), but for the most part the above solution represents a quick and dirty way for Data Scientists get their work in front of decision makers.
&lt;/p&gt;
&lt;p&gt;
To this day, the internal website maintains a link starting with &lt;code class=&quot;markup--code markup--p-code&quot;&gt;file:///&lt;/code&gt; that points to the shared network filesystem.
&lt;/p&gt;
&lt;p&gt;
For those paying attention, you may notice that this solution is not constrained to Shiny, but to any served web application: Node, Python, or perhaps something tucked away in a docker instance. This can also be used as a means of constraining a user to a web based application, perhaps for keeping students from cheating in a test, or keeping a temporary labour pool focused on their tasks.
&lt;/p&gt;
&lt;p&gt;
I make no claims that this is the right solution, what I suggest is that it is a feasible solution. Any organization that can build a Shiny application, also has the tools to implement this solution. I share this solution in the hopes that it helps another Data Developer when Bureaucracy gets in the way.
&lt;/p&gt;
</content>
        </entry>
        <entry>
            <title>Why won&#39;t Staples take my money?</title>
            <link href="http://plaidsheep.ca/posts/2021/why-wont-staples-take-my-money/" />
            <updated>2021-07-19T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2021/why-wont-staples-take-my-money/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                &lt;blockquote&gt;
Scroll to the bottom for a list of related goofiness where companies can&#39;t take money I&#39;m trying to give them
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;
I spent 6 hours today trying to buy a printer … and failed.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
There is money set aside for this purchase in my bank account.
&lt;/li&gt;
&lt;li&gt;
There are printers on the shelves
&lt;/li&gt;
&lt;li&gt;
Websites advertise printers for sale
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
The reason I was unable to purchase a printer was because a corporation with a near monopoly on the Canadian market, one of the largest privately owned retailers in Canada, could not tell me &lt;em&gt;which printers they had available&lt;/em&gt; for sale.
&lt;/p&gt;
&lt;p&gt;
Let that sink in:
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;A multi-billion dollar retailer could not tell me if they had anything to sell me.&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
On the surface this is laughable, funny, but I believe this belies a more insidious problem that is pervasive, and down right dangerous.
&lt;/p&gt;
&lt;h3&gt;The Sequence of Events&lt;/h3&gt;
&lt;p&gt;
I recently made a long distance move across almost the entirety of Canada. I&#39;ll give credit to the movers, most of my stuff made it in one piece; unfortunately, my printer suffered catastrophic snapping of some hinges. Since we do not deal in paper as much as we used to, we initially just tried live without it; in the short term, this saved us a bunch of money, but it also helped us to evaluate what we really needed in a printer:
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
Scanner with an auto-feeder
&lt;/li&gt;
&lt;li&gt;
Cheaper ink cartridges
&lt;/li&gt;
&lt;li&gt;
Duplex printing (nice to have)
&lt;/li&gt;
&lt;li&gt;
Ink colours in separate cartridges (nice to have)
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
After a moment of distress trying to print a government form, and with a fairly clear idea of what I wanted, my Wife and I decided to put an end to the problems and just pick something (anything) up at a nearby retailer: Staples Canada.
&lt;/p&gt;
&lt;p&gt;
Naturally, my first reaction was to look on their website: staples.ca. Reviewing a listing of available products over a cup of coffee, in our pyjamas, was exactly the way to start figuring out what we wanted. It was a quick search:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
all available printers
&lt;/li&gt;
&lt;li&gt;
ordered by price
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
By jumping forward to the first couple of printers that met our needs we would get a reasonably good idea of what our critical price point are. Further, I also saw they had an option for 2 hour curbside pickup, so we should be picking it up in a couple of hours. We came across a couple of printers that met our needs, at around $100. Within 30 minutes, we clicked to order.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
None available in store
&lt;/li&gt;
&lt;li&gt;
None available online
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
What? That&#39;s annoying. Let&#39;s try the next one.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
None available in store
&lt;/li&gt;
&lt;li&gt;
None available online
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Hold up. Let&#39;s try a filter: “Only show items with 2 hour delivery”. Surely that will filter to items that are available in the store &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;right now&lt;/strong&gt;.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
None available in store
&lt;/li&gt;
&lt;li&gt;
None available online
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
The more I searched, the more frustrated I became. After an hour of this, my wife and I finally decided to just go into the store and buy whatever they had available.
&lt;/p&gt;
&lt;p&gt;
Upon arriving at the store, it didn&#39;t take long for us to narrow in on the products we were looking for. They were more expensive than their equivalents online, but there they were … or were they?
&lt;/p&gt;
&lt;p&gt;
We had it narrowed down to one of three printers, when a clerk came over. We pointed to them and asked a couple of questions, and he stated: “well, we should probably check to see if they are available before we go any further. We may not have them in stock.”
&lt;/p&gt;
&lt;p&gt;
Momentarily slack-jawed, we proceeded for him to check to see if they were in stock, only to find… they weren&#39;t.
&lt;/p&gt;
&lt;p&gt;
None of the printers we had expressed an interest in were available for purchase. There they were on the shelf, but none of them were available for sale. Order? Nope, please go online to check availability.
&lt;/p&gt;
&lt;p&gt;
As we walked out of the store, empty handed, the manager stopped us and asked if we had found everything we were looking for (“no”), how could he help, and spent the next two hours explaining it wasn&#39;t his fault that they didn&#39;t have any stock and trying to upsell me a high performance Laser Printer. My only question at this point is: “Is it available for purchase”.
&lt;/p&gt;
&lt;p&gt;
The manager left me with a customer complaint phone number, and the promise to email me a link to a printer I was interested in. He had verified with the warehouse that it was available, and I could get it shipped to the store. He could not request it, but if I ordered it online via the website there should be no difficulty.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2021/why-wont-staples-take-my-money/1-zjkl099yaphnn1ipdd3l7a-r-g2fILKC2-__204__.avif 204w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2021/why-wont-staples-take-my-money/1-zjkl099yaphnn1ipdd3l7a-r-g2fILKC2-__204__.webp&quot; alt=&quot;&quot; width=&quot;204&quot; height=&quot;57&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;The results of my attempting to order a Brother MFC-J497DW from Staples.ca&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
On my way home, I had stopped in to the local Home Hardware to pick up a ladder and a shovel. They didn&#39;t have any ladders in stock, but pointed me to their website
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2021/why-wont-staples-take-my-money/1-myafimf2rmzknoprpkgzzw-2gaIxfnad3-__340__.avif 340w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2021/why-wont-staples-take-my-money/1-myafimf2rmzknoprpkgzzw-2gaIxfnad3-__340__.webp&quot; alt=&quot;&quot; width=&quot;340&quot; height=&quot;73&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Clicking on the “similar in-stock items” link had no effect&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;The Significance&lt;/h3&gt;
&lt;p&gt;
These are fundamental problems in the Software and Information Systems space: inventory systems should be able to count inventory, sales systems should be capable of selling products. That businesses are allowing systems to be released without testing of the fundamental feature that was requested is a massive failing. Software developers are allowing the release of features that do not work… and not in subtle, nuanced, ways.
&lt;/p&gt;
&lt;p&gt;
It&#39;s like manufacturing a car and forgetting to attach one of the wheels.
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
Developers did not even bench test their work
&lt;/li&gt;
&lt;li&gt;
No peer review of any kind took place
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
That is fairly significant, and I suspect their are two elements at play:
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
The development was outsourced to a consulting firm that is motivated to appease managers.
&lt;/li&gt;
&lt;li&gt;
It is highly likely that the feature was delivered on schedule.
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
Software development has been commoditized, and in the process the managers and developers have forgotten a very fundamental truth: software is built to solve real world problems that real people have. If you develop tools that don&#39;t work, people are hurt (even if it is just because they have to manually search through a store&#39;s catalogue, or because they have to add up numbers in a spreadsheet before entering into the payroll system). While it is up to developers to enact that quality, it is absolutely necessary that managers expect that quality.
&lt;/p&gt;
&lt;p&gt;
This means deadlines may not be met.
&lt;/p&gt;
&lt;p&gt;
… but then again, you have to ask the question: if the feature isn&#39;t implemented, has the deadline actually been met.
&lt;/p&gt;
&lt;h3&gt;It&#39;s Embarrassing&lt;/h3&gt;
&lt;p&gt;
Since the dawn of software development, testing and validation of the work has been paramount. When you entrust a person to design a process to take care of people, it must take care of people. There is no human judgement involved to cover your mistakes: if your process is flawed the machine will carry out a flawed process.
&lt;/p&gt;
&lt;p&gt;
Margaret Hamilton realized this in the 60s when her team developed the software that landed the Apollo, introducing the concept of Software Engineer. She didn&#39;t develop software that worked because “astronauts are trained not to make mistakes” but because she tested the crap out of her software. Later leading to the language `001` and `USL`, languages that made programmers think about errors before they happened … because that&#39;s what programmers do.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2021/why-wont-staples-take-my-money/1-oqw-gtbcpyiaqk2jwxhmbq-bOUlC3q5O7-__551__.avif 551w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2021/why-wont-staples-take-my-money/1-oqw-gtbcpyiaqk2jwxhmbq-bOUlC3q5O7-__551__.webp&quot; alt=&quot;&quot; width=&quot;551&quot; height=&quot;215&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;After 23 days, Home Hardware was unable to tell me anything more than my order is “in progress”. After asking me if I&#39;d like to wait it out, we took another 10 minutes and cancelled the order.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
In the late 1990s and early 2000s, unit testing, test driven development, continuous integration, and automated regression testing came into their own, offering means of ensuring that features were guaranteed to be &lt;em&gt;minimally &lt;/em&gt;tested for basic user expectation prior to release.
&lt;/p&gt;
&lt;p&gt;
DevOps in 2010 should have made developers even more responsible for basic functionality testing, making them directly answerable to the errors of the system.
&lt;/p&gt;
&lt;p&gt;
The issue is likely two-fold:
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
Managers believing that the skill lay in the visual elements they can see, rather than the business processes that they cannot
&lt;/li&gt;
&lt;li&gt;
Developers not standing up for the engineering act they are engaged in.
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
It was a very long time, and a lot of hard work, before Hamilton&#39;s peers considered software a type of engineering. Watching software be released that does not meet the most basic of functionality requirements makes me realize that many developers are not living up to her legacy. I&#39;ll take it one step further … managers have to stop expecting that software will have the “testing” check-box tick, and start understanding that quality is a mindset of continuous improvement.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2021/why-wont-staples-take-my-money/0-b0aqkdpxotuzby7--9cfHthyoL8-__582__.avif 582w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2021/why-wont-staples-take-my-money/0-b0aqkdpxotuzby7--9cfHthyoL8-__582__.webp&quot; alt=&quot;&quot; width=&quot;582&quot; height=&quot;834&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;An ordered list supplied by HomeHardware.ca, which is out of order.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;It Matters&lt;/h3&gt;
&lt;p&gt;
I think this should be a moment for deep reflection within the domain of Information Systems and Software Engineering.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Airplanes &lt;a href=&quot;https://www.businessinsider.com/boeing-outsourced-737-max-report-2019-6&quot; target=&quot;_blank&quot;&gt;fall out of the sky&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
Small business owners get &lt;a href=&quot;https://www.bbc.com/news/business-57173296&quot; target=&quot;_blank&quot;&gt;falsely accused and imprisoned&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
Young mothers have their &lt;a href=&quot;https://www.politico.eu/article/dutch-scandal-serves-as-a-warning-for-europe-over-risks-of-using-algorithms/&quot; target=&quot;_blank&quot;&gt;children taken from them&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
Retail organizations are physically &lt;a href=&quot;https://jefferey-cave.medium.com/why-wont-staples-take-my-money-2e1d5ae8e135&quot; target=&quot;_blank&quot;&gt;unable to sell their products&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Programmers need to consider the direct consequences of their actions; Managers need to take a moment and reflect on the value they are adding to their organization; and Executives need to reconsider the actual deliverables they are asking for.
&lt;/p&gt;
&lt;p&gt;
Should any of the afore mentioned organizations like to engage my consulting services to identify gaps in their information systems which are costing them sales, please do feel free to reach out to me.
&lt;/p&gt;
&lt;p&gt;
Also, please leave your examples of bonehead fails in the comments.
&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;2021–07–19:&lt;/strong&gt; Staples won&#39;t take my money
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;2021–08–20&lt;/strong&gt;: Pizza 73 won&#39;t let me order a pizza due to a &lt;code class=&quot;markup--code markup--p-code&quot;&gt;curl&lt;/code&gt; error.
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;2021–09–18&lt;/strong&gt;: Out of 57 monitors listed, with the “for in-store pickup” filter in place, my Wife cannot find any in inventory on Staples website
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;2022–01–16&lt;/strong&gt;: After 3 weeks, Aviva Insurance is unable to find a way to tell me how much I owe them.
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;2022–07–20:&lt;/strong&gt; My washing machine broke down. Local vendor has none in stock, Whirlpool&#39;s vendor portal has been down for &lt;em&gt;a week&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;2023–02–28:&lt;/strong&gt; After 2 weeks of trying to book a flight from London (&lt;code class=&quot;markup--code markup--p-code&quot;&gt;LGW&lt;/code&gt;) to Calgary (&lt;code class=&quot;markup--code markup--p-code&quot;&gt;YYC&lt;/code&gt;), my sister-in-law has learned that WestJet can&#39;t accept payment from UK citizens.
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;2023–03–01:&lt;/strong&gt; … so she tried Air Canada, same problem.
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;2024–02–17&lt;/strong&gt;: Amazon KDP will not let me publish a book with them because my address does not exist (according to their system)
&lt;/p&gt;
&lt;p&gt;
&lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;2025–09–16:&lt;/strong&gt; Telus Health booked my doctor&#39;s appointment in a timezone that neither I, nor my doctor, were in. Missed my appointment.
&lt;/p&gt;
</content>
        </entry>
        <entry>
            <title>Git and the Intermittent Network</title>
            <link href="http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/" />
            <updated>2020-12-28T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/0*5f2r4Kqjtcx1ssCz&quot; alt=&quot;Git and the Intermittent Network&quot;&gt;
                &lt;p&gt;
Within my organisation, we have been moving toward modern web-based platforms. This offers many benefits to our users in terms of availability, sustainability, and growth capability, and I have been one of the leading proponents of it.
&lt;/p&gt;
&lt;p&gt;
While modern web-based services are the norm, and desirable, there are risks associated with them that should be considered for mitigation. Primarily, these risks revolve around the centralisation of service and the network availability of clients. These risks are well understood and most tools used by modern development teams were designed with these types of issues in mind, however as the internet becomes more pervasive and stable, it is common for us to lose sight of its limitations.
&lt;/p&gt;
&lt;h3&gt;A Personal Experience&lt;/h3&gt;
&lt;p&gt;
As I write this (2020–11–03), I am experiencing an internet outage.
&lt;/p&gt;
&lt;p&gt;
This event occurred mid-meeting and has resulted in a situation where I cannot connect to the online resources required to complete my corporate objectives:
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
No connection to a production server to conduct repairs on that server (or even inspect the logs)
&lt;/li&gt;
&lt;li&gt;
No connection to Microsoft Azure to conduct experimental work in our laboratory environment
&lt;/li&gt;
&lt;li&gt;
All forms of meeting communications have been cut-off (MS Teams, Webex, VOIP telephone)
&lt;/li&gt;
&lt;li&gt;
An Outlook plugin cannot connect to an encryption server and is frozen because it is attempting to show me an encrypted email
&lt;/li&gt;
&lt;li&gt;
I can&#39;t take any of the corporate training I&#39;m supposed to do, or read that manual for that new tool I&#39;m investigating
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
I&#39;m completely dead in the water.
&lt;/p&gt;
&lt;p&gt;
According to my provider, a fibre-optic line has been cut “somewhere between Halifax and Montreal” resulting in massive connectivity loss for the region. The only productive task left to me is to write up an assessment of the current failure, on my local device, and upload it to the network when communication is re-established.
&lt;/p&gt;
&lt;p&gt;
Hold on… Did I just describe getting work done and loading it later? That is a caching strategy that is well known and commonly used for resolving network latency issues.
&lt;/p&gt;
&lt;h4 name=&quot;a03a&quot; id=&quot;a03a&quot; class=&quot;graf graf--h4 graf-after--p&quot;&gt;Historic Note&lt;/h4&gt;&lt;p&gt;
The internet, as we understand it, has not always been as accessible, available, or reliable, as we have come to expect.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/0-5f2r4kqjtcx1sscz-U8leE20p_S-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/0-5f2r4kqjtcx1sscz-U8leE20p_S-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;410&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Scene from &lt;a href=&quot;https://www.imdb.com/title/tt0072730/&quot; target=&quot;_blank&quot;&gt;“A Boy and His Dog” (1975)&lt;/a&gt;, which depicts the kind of conditions the Internet was designed to continue operating under. [Fair Use]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
It is worth remembering that The Internet was initially designed as a distributed communication tool to allow the military to continue operating remote computers in the event of massive node loss (&lt;a href=&quot;https://en.wikipedia.org/wiki/ARPANET&quot; target=&quot;_blank&quot;&gt;dating back to 1966&lt;/a&gt;). The assumption of loss of network availability has been an underlying assumption of much of the internet&#39;s growth, and is built into the fabric of the internet.
&lt;/p&gt;
&lt;p&gt;
In the early days of general access to internet services, connections to the network were made intermittently. This was performed by dial-up connections which would be initiated for short periods of time.
&lt;/p&gt;
&lt;p&gt;
As networks became more common and robust, much of the shared development of software (Open Source) began to be shared across the network as opposed to letter carrier and print (via the “Share” catalogue). Unfortunately, internationally, not all network connections are created equally, and some users suffered from several disruptions to connectivity.
&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong class=&quot;markup--strong markup--blockquote-strong&quot;&gt;What was “Share”?&lt;/strong&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
In the mid-1950s, a user organisation for scientific applications … was formed. One of its most important functions was serving as a clearinghouse for contributed software subroutines. The organisation was called Share … and the contributed routines became the first library of reusable software.
&lt;/blockquote&gt;
&lt;blockquote&gt;
— Robert L. Glass, “Facts and Fallacies of Software Engineering”
&lt;/blockquote&gt;
&lt;blockquote&gt;
Out of this sense of sharing evolved several clearing houses such as SourceForge, Tigris, and eventually GitLab and GitHub. Unfortunately, even these clearing houses were subject to disruption.
&lt;/blockquote&gt;
&lt;p&gt;
In the highly competitive days just after Y2K, several organisations rose and fell in rapid succession and crises formed when code hosting platforms were simply turned off due to corporate takeover, sabotage, copyright infringement lawsuit, or even simple bankruptcy. Thousands of hours of work were lost to the simple issue of the centralised servers being turned off. Modern VCS (&lt;a href=&quot;https://en.wikipedia.org/wiki/Version_control_system&quot; target=&quot;_blank&quot;&gt;Version Control System&lt;/a&gt;) solutions (such as Git) evolved in this environment, allowing for each node to retain complete histories of work, allowing for projects to be completely retained through a single surviving distributed node.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/0-1aufxl2kalsutouw-1SyoUU1ZT6-__451__.avif 451w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/0-1aufxl2kalsutouw-1SyoUU1ZT6-__451__.webp&quot; alt=&quot;&quot; width=&quot;451&quot; height=&quot;388&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;CAP Theorem (&lt;a href=&quot;http://guide.couchdb.org/draft/consistency.html#cap&quot; target=&quot;_blank&quot;&gt;CouchDB: Definitive Guide&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
In more recent years, &lt;a href=&quot;https://en.wikipedia.org/wiki/CAP_theorem&quot; target=&quot;_blank&quot;&gt;CAP theorem&lt;/a&gt; has evolved to explain that high availability comes at certain costs, which, once the costs are accepted, can offer the benefits seen by the BBC newspaper during the Russo-Georgian conflict. During this period, communication lines were severed meaning that correspondents and readers could not communicate across the national boundary. Service continued to be delivered to each side of the boundary, allowing reporters to continue reporting, and commentors to continue to offer feedback during the entire conflict. Automated synchronisation of news reports, and on-the-ground reader comments, occurred during periods when alternate communication paths were established.(ERROR: I could have sworn this case was described in &lt;a href=&quot;http://guide.couchdb.org/editions/1/en/conflicts.html&quot; target=&quot;_blank&quot;&gt;CouchDB: The Definitive Guide&lt;/a&gt; … I can&#39;t seem to find the reference to this anymore)
&lt;/p&gt;
&lt;p&gt;
Each of these historic scenarios have common elements:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
contributors are forced to disconnect from communication and wait
&lt;/li&gt;
&lt;li&gt;
contributors wish to continue to prepare their communications
&lt;/li&gt;
&lt;li&gt;
“caching” is used to overcome communication latency, allowing people to continue working locally until the connection is reestablished.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
This “batching” or “caching”, offers means to mitigate connectivity issues with web development platforms.
&lt;/p&gt;
&lt;h3&gt;Web Based Publishing&lt;/h3&gt;
&lt;p&gt;
Regardless of the content being published, content development has many common elements through its progression. Whether this is dynamic content of Software, or the static content of News Videos, there is a common process in creating and distributing the content online.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/0-8chl9qm8it-tfhnn-UBTlistRV--__720__.avif 720w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/0-8chl9qm8it-tfhnn-UBTlistRV--__720__.webp&quot; alt=&quot;&quot; width=&quot;720&quot; height=&quot;441&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;
To use the example of an individual publishing an article to their newspaper (maybe their blog), they (the &lt;code class=&quot;markup--code markup--p-code&quot;&gt;Contributor&lt;/code&gt;) would connect to the internet (&lt;code class=&quot;markup--code markup--p-code&quot;&gt;NetProviderA&lt;/code&gt;), and type their article into Open Journal Service, or Wordpress, or Medium (the &lt;code class=&quot;markup--code markup--p-code&quot;&gt;Server&lt;/code&gt;). They can continue to type into the software on the server, perhaps running some spell and grammar checks, until they hit the “publish” button. At this point it is possible for the message to be retrieved by the consumer whenever the customer wants.
&lt;/p&gt;
&lt;p&gt;
Using web based development tools, the process for software development would be the same. The contributor would connect to the internet, edit their document on the server, and indicate readiness to “publish” which would make the application available to consumers of the application.
&lt;/p&gt;
&lt;h3&gt;Intermittent Connections&lt;/h3&gt;
&lt;p&gt;
Looking at the historic development of the internet, and the current issue at hand, we can see that there is a risk associated with the network not being available. We cannot consider the server in isolation, and must also include the effects of the network.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/0-rdvry0bc5omwhn0t-_qHRnhEal2-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/0-rdvry0bc5omwhn0t-_qHRnhEal2-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;579&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;
We can see that if the Contributor&#39;s network connection is terminated, they are unable to perform &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;any&lt;/strong&gt; work. Sticking to the newspaper article example, the author may have a wonderful idea in their mind, or may know of a flaw in the argument, or (frankly) just want to get some work toward the publishing dead-line; unfortunately, they are stopped.
&lt;/p&gt;
&lt;p&gt;
There is another layer that can be considered to overcome this issue, and that is the local computer which can be used to cache work: the Contributor can type their document on their local computer, and save to their local disk.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/1-tdlerwqcuqlvg6eth18poq-QSYVyhxeMl-__691__.avif 691w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/1-tdlerwqcuqlvg6eth18poq-QSYVyhxeMl-__691__.webp&quot; alt=&quot;&quot; width=&quot;691&quot; height=&quot;681&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;
Looking at the previously discussed internet history, we can look to VCS tools to assist us in solving this problem. SVN and Git (as well as their predecessors and competitors) were developed in an environment where work needed to be buffered against future connections. Specifically, work needs to be performed locally, and stored locally until such time as it is possible to transmit it.
&lt;/p&gt;
&lt;p&gt;
This has been an ongoing evolution, and Linus Torvalds specifically developed Git to resolve buffering issues he saw in SVN.
&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong class=&quot;markup--strong markup--blockquote-strong&quot;&gt;Tip&lt;/strong&gt;&lt;br&gt;&lt;br&gt;Git is not a simple upgrade of SVN, there are trade offs between the two products. Git stores &lt;strong class=&quot;markup--strong markup--blockquote-strong&quot;&gt;complete&lt;/strong&gt; copies of the database on &lt;strong class=&quot;markup--strong markup--blockquote-strong&quot;&gt;every&lt;/strong&gt; node, SVN stores one copy on each node. Git stores full copies of each state in its database, SVN stores a sequence of state changes in its database.
&lt;/blockquote&gt;
&lt;blockquote&gt;
The end result of these differences is that Git is always recoverable (any single node can rebuild the entire system), but SVN requires less storage space and works better when large binary files are involved.
&lt;/blockquote&gt;
&lt;blockquote&gt;
While Git is now dominant, many users of large binary files (Engineering Diagrams, Cartography) continue to prefer SVN.
&lt;/blockquote&gt;
&lt;h3&gt;Local or Web&lt;/h3&gt;
&lt;p&gt;
Using local development tools and synchronising periodic changes is a common practice to allow us to only communicate changes we are committed to, but this practice also offers the benefit of resolving latency issues. During 2020, lock-downs have resulted in many of us having to work from home, and being remotely positioned to our workspace. We are using networks that were established for scenarios that demand significantly less resilience (binge watching movies) for situations that demand significant resilience (earning income to pay for groceries). For those of us whose livelihoods have become tied to these networks for the first time, this can be surprising. In cases where internet has temporarily failed, and we are left unable to progress, it can be distressing to both our managers and ourselves.
&lt;/p&gt;
&lt;p&gt;
This does not mean I believe that local tools are better than web-based tools.
&lt;/p&gt;
&lt;p&gt;
For many years, my favourite platform was Cloud9, an online web based IDE that allowed for workstations to be stood up on demand. This allowed me to maintain several development environments that met various needs. The ability pick up work from anywhere in the world, allowed me to continue working on projects from a hotel courtyard in Ecuador, from an the old indestructible RCA Cambios. The ability for the vendor to supply me with powerful remote computer meant I could work from a $100 computer. This means I received software upgrades immediately, and could work from any cheap hardware I could scrounge up.
&lt;/p&gt;
&lt;p&gt;
There are trade-offs to be considered, and that is what this has been about. Be aware of the trade-offs before wholly committing to one solution or the other. IDE vendors want you to be tied to their tool, and this looses many of the benefits of distributed VCS platforms. On the other hand, we have computing networks, take advantage of them.
&lt;/p&gt;
&lt;p&gt;
In the end I recommend a balanced approach that takes the lessons from the rich history of information sharing that is the internet.
&lt;/p&gt;
&lt;p&gt;
Use Web based IDEs, but use generic ones. Do not depend on always having access to the vendor&#39;s editor. Instead maintain regular local pulls from your VCS repository, and use programming languages and data formats that are based on simple text. This allows you to switch to a local copy during network outages, as well as protecting you from vendor lock-in.
&lt;/p&gt;
&lt;h3&gt;Further Reading&lt;/h3&gt;
&lt;p&gt;
As ever, Wikipedia has become the place to start. I recommend reading the article on &lt;a href=&quot;https://en.wikipedia.org/wiki/Version_control&quot; target=&quot;_blank&quot;&gt;Version Control Systems&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
There are several generic, web-based, IDEs that I have enjoyed using:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://theia-ide.org/&quot; target=&quot;_blank&quot;&gt;Theia&lt;/a&gt; (Eclipse Foundation)
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.eclipse.org/che/&quot; target=&quot;_blank&quot;&gt;Eclipse Che&lt;/a&gt; (Eclipse Foundation)
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://aws.amazon.com/cloud9/&quot; target=&quot;_blank&quot;&gt;Cloud9&lt;/a&gt; (Amazon)
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Interestingly, each of these can be served on your corporate network (to protect your institution&#39;s intellectual property), or installed on your local computer to allow you to continue working when your network gets nuked.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/0-tnbpplo8wufexynl-422PNbvAQ1-__700__.avif 700w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2020/git-and-the-intermittent-network/0-tnbpplo8wufexynl-422PNbvAQ1-__700__.webp&quot; alt=&quot;&quot; width=&quot;700&quot; height=&quot;877&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Atomic Blast, Nevada, 1951 (&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Atomic_blast_Nevada_Yucca_1951_%28better_quality%29.png&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
</content>
        </entry>
        <entry>
            <title>Avoiding “Psychic”  Software Development</title>
            <link href="http://plaidsheep.ca/posts/2020/psychic-software/" />
            <updated>2020-10-24T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2020/psychic-software/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/0*AqfkmoGwNFXBYjDT.jpg&quot; alt=&quot;Avoiding “Psychic”  Software Development&quot;&gt;
                &lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2020/psychic-software/0-aqfkmogwnfxbyjdt-EKg4JM6vdX-__368__.avif 368w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2020/psychic-software/0-aqfkmogwnfxbyjdt-EKg4JM6vdX-__368__.webp&quot; alt=&quot;&quot; width=&quot;368&quot; height=&quot;516&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;
James Randi died October 20, 2020.
&lt;/p&gt;
&lt;p&gt;
The Amazing Randi was a successful stage magician, famously surpassing many of Harry Houdini&#39;s achievements. Later in his career, James Randi took his mastery of magic and used it to turn a critical and skeptical eye to claims of paranormal powers. Claims of divine healing powers, telekinesis, and psychic powers were all put to the test with what grew to a $1,000,000 reward to anyone who could prove their powers.
&lt;/p&gt;
&lt;p&gt;
The “Randi Prize” was &lt;a href=&quot;https://web.randi.org/home/jref-status&quot; target=&quot;_blank&quot;&gt;never claimed&lt;/a&gt; during its 50 years.
&lt;/p&gt;
&lt;h3&gt;Modern Product Development&lt;/h3&gt;
&lt;p&gt;
Edward Deming once said, “In God we trust, all others must bring data”.
&lt;/p&gt;
&lt;p&gt;
Over my career I have become infamous for citing Deming, but if this is a time for confessions, I must confess that my knowledge of Deming came much later than my attitudes toward evidence based decision making. Rather, I can attribute my personal distrust of claims and constant demands for evidence to people like James Randi showing me how, and when, to be critical of my own beliefs.
&lt;/p&gt;
&lt;p&gt;
Both Deming and Randi demand that we approach our observations critically and skeptically manage our own biases.
&lt;/p&gt;
&lt;p&gt;
This has impacted me professionally in two significant ways:
&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li&gt;
I am suspicious of software developers that claim they have finished a difficult task
&lt;/li&gt;
&lt;li&gt;
I am suspicious of consultants promising to make problems go away quickly and cheaply
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;
I say this as someone who has both development and consulting in his past.
&lt;/p&gt;
&lt;h3&gt;A Personal Confession&lt;/h3&gt;
&lt;p&gt;
In my youth, I was fascinated by psychic powers. I bought all the books on remote viewing, developing my psychic powers, and becoming a medium. I recognised that information was a powerful tool and was interested in any means to acquire more of it.
&lt;/p&gt;
&lt;p&gt;
The problem is that, like Fox Mulder, I&#39;ve always &lt;em&gt;wanted &lt;/em&gt;to believe. Lewellyn publishing can account for much of my allowance.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2020/psychic-software/0-0x7qhimbbrkjkor3-jcgohOKtRl-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2020/psychic-software/0-0x7qhimbbrkjkor3-jcgohOKtRl-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;531&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Scene from the X-Files with Mulder&#39;s famous “I want to believe” poster in the background&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
Unfortunately for my desire to believe, at some point I saw the now classic episode of What&#39;s My Line with guests &lt;a href=&quot;http://wafflesatnoon.com/james-hydrick-psychic/&quot; target=&quot;_blank&quot;&gt;James Hydric and James Randi&lt;/a&gt;. In the show, Randi is skeptical and flat out states that Hydric&#39;s telekinetic ability to turn book pages amounts to him blowing on the pages, and introduces some light weight Styrofoam around the book. He is firm and calm, and completely unrelenting in his stance, and the scene eventually gets uncomfortably awkward as Hydric begins a convoluted explanation of his ensuing failures.
&lt;/p&gt;
&lt;p&gt;
Hydric is a bit of a tragic character. While he was obviously a fraud, it appears he became so as an attention seeking behaviour. Watching his confessional interviews after the Randi event, I got the impression that he had come to believe his own hype; that he had misled even himself.
&lt;/p&gt;
&lt;p&gt;
Whatever the case, Randi&#39;s critical approach to assessing paranormal capabilities has haunted my ability to blindly believe with a shadow of skepticism.
&lt;/p&gt;
&lt;h3&gt;Software Developers&lt;/h3&gt;
&lt;p&gt;
In the process of developing software, developers are expected to produce solutions to problems that have not been solved. That is the nature of the craft. However there are corporate expectations that the problem be solved in a reasonable amount of time, because time is money. This places pressure on developers to “be finished”, and this creates the risk space: scared for our job, eager to please, wanting to appear skilled, we &lt;strong class=&quot;markup--strong markup--p-strong&quot;&gt;want&lt;/strong&gt; it to be done too.
&lt;/p&gt;
&lt;p&gt;
So we tell our managers that the job is complete.
&lt;/p&gt;
&lt;p&gt;
We are physically incapable of seeing that it does not completely solve the problem, or that it is too difficult to use in its current state. Like those that are healed by Faith, we want it to be true.
&lt;/p&gt;
&lt;figure&gt;
&lt;iframe src=&quot;https://www.youtube.com/embed/9cS6DjeBz7g?feature=oembed&quot; width=&quot;640&quot; height=&quot;480&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;&lt;figcaption&gt;James Randi and Bob Barker expose James Hydric as a fraud. Subsequent interviews with Hydric give the feel that this was the moment he stopped deceiving himself.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
This is where processes and philosophies around the SDLC come into play.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Issue boards keep us from reporting more progress than we have actually achieved
&lt;/li&gt;
&lt;li&gt;
Sprints and backlogs keep us focused on the most pressing issues
&lt;/li&gt;
&lt;li&gt;
Automated software build/test/deploy ensures that evaluation is unbiased and reproducible.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
While the debates continue about which controls are the best, there is no doubt that we need the controls. Like James Randi placing Styrofoam around a phone book, these controls ensure we are being honest … even to ourselves.
&lt;/p&gt;
&lt;h3&gt;Faith Healing&lt;/h3&gt;
&lt;p&gt;
Even assuming it had been real, Hydric&#39;s ability to turn phone book pages was little more than a novelty act. It may have sold a few books, but debunking it was not exactly an earth-shattering revelation. A more significant case can be found in that of Peter Popoff.
&lt;/p&gt;
&lt;p&gt;
Peter Popoff is an evangelical minister whose television broadcast became famous for his claims of divine knowledge and healing abilities. During his shows he would call arbitrary individuals from the audience by name and cite details of their life with no prior knowledge. As he approached them, and they began to stand, he would tell the gathered audience what terrible diseases the person had and that he would heal them. Both the knowledge and the healing were claimed to be directly imparted divine powers.
&lt;/p&gt;
&lt;figure&gt;
&lt;iframe src=&quot;https://www.youtube.com/embed/q7BQKu0YP8Y?feature=oembed&quot; width=&quot;640&quot; height=&quot;480&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
&lt;/figure&gt;
&lt;p&gt;
Rather than being divinely inspired, Randi discovered that Popoff&#39;s wife was the true source of knowledge. Prior to the show, she would gather information from attendees and was broadcasting their names and information via radio to an ear-piece he wore.
&lt;/p&gt;
&lt;p&gt;
Here lies a case of fraud that demonstrates true harm.
&lt;/p&gt;
&lt;p&gt;
People that sought out Popoff truly believed God spoke to him, and that his hands could remove illness from them. Believing that they had been cured meant that they would stop seeking treatment for arthritis, or epilepsy, or heart conditions. When one adds the $4 million a year in “donations” people made, Popoff has made it a little more difficult to replace the pills he told them to throw out.
&lt;/p&gt;
&lt;p&gt;
James Randi, did thousands of people a service when he exposed Popoff. Though we do need to ask why Popoff continues his “healing” to this day.
&lt;/p&gt;
&lt;h3&gt;Consultants and Vendors&lt;/h3&gt;
&lt;p&gt;
One way management can reduce costs is to hire outside expert consultants that understand the problem better than the internal staff, and nobody knows the solutions better than the vendors. Naturally, as experts they are to be paid more than internal staff, this is justified by their being more knowledgeable.
&lt;/p&gt;
&lt;p&gt;
Unfortunately, this is &lt;em&gt;often &lt;/em&gt;not the case.
&lt;/p&gt;
&lt;p&gt;
Due to the short time frame they are present for, consultants are not actually paid for measurable results. The measurable results of their suggestions and changes come after they have left. Their real rewards are tied to making the manager that hired them feel good about their decision. This does not necessarily mean they were successful at solving the problem.
&lt;/p&gt;
&lt;p&gt;
Like the Faith Healer, consultants can reap huge rewards for making promises of solving problems, and making their audience feel that the problem has been solved through some special conference from an authority. Unfortunately, like the Faith Healer, this can be, and often is, done as an act of faith.
&lt;/p&gt;
&lt;p&gt;
In fact, it is almost impossible for this to be &lt;em&gt;undone&lt;/em&gt; because the person that has paid out their life savings to be healed, or the manager that has spent a significant portion of their budget, cannot admit to themselves that they were swindled.
&lt;/p&gt;
&lt;p&gt;
The more we pay, the more we &lt;em&gt;want to believe&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
I have worked with some great consultants over the years, but I&#39;ve spent more working hours with bad ones. The reinforcement cycle is one in which the best rewards go to those who make management feel good. Unfortunately, they are also the ones that consume the most time in fixing and retrofitting good solutions around their popular one. Given they are paid by the hour, this means the feedback mechanism benefits the dishonest.
&lt;/p&gt;
&lt;p&gt;
There is no easy answer to this except to be skeptical of smooth talking salesmen that echo what you already want to believe. Often consultants are brought in because local staff have been asked to solve a problem and have given an undesirable response. Unfortunately, that is often the honest, but hard-to-hear, truth.
&lt;/p&gt;
&lt;p&gt;
Like James Randi upsetting a lot of Peter Popoff&#39;s believers, the truth can be hard to hear, but healthier for you in the long run.
&lt;/p&gt;
&lt;h3&gt;Faith Healing in Modern Times&lt;/h3&gt;
&lt;p&gt;
In reminiscing about the impact of James Randi, naturally I turned to Wikipedia to refresh my memory. It has been a long time since I have had need to know about Randi&#39;s work: a different time. We now rely on scientific reasoning, and no longer believe in psychics and faith healing. We no longer make business plans based on “gut feelings” but rather “&lt;a href=&quot;https://deming.org/data-are-not-taken-for-museum-purposes-they-are-taken-as-a-basis-for-doing-something/&quot; target=&quot;_blank&quot;&gt;collect data to provide a basis for action&lt;/a&gt;”.
&lt;/p&gt;
&lt;p&gt;
That was a different time, a simpler time.
&lt;/p&gt;
&lt;p&gt;
Imagine my shock to learn that frauds Randi has exposed continue to be active as recently as 2015, with terrifying consequences.
&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2020/psychic-software/1-tdta3icu5anp8ncv6ltvaa-3-dreu_sZd-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2020/psychic-software/1-tdta3icu5anp8ncv6ltvaa-3-dreu_sZd-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;486&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;The ADE-351 “bomb detector” [&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:ADE_651_at_QEDcon_2016_01.jpg&quot; target=&quot;_blank&quot;&gt;Wikipedia&lt;/a&gt;]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
According to Wikipedia, the &lt;a href=&quot;https://en.wikipedia.org/wiki/ADE_651#The_deception_and_its_discovery&quot; target=&quot;_blank&quot;&gt;ADE-651&lt;/a&gt; is an explosive detection device that is used internationally to keep people safe from terrorism. Naturally, people want to be safe from terrorist bombs and put their faith in technological devices to protect them. Randi first challenged the developers of the device in 2008, and it has since been demonstrated to be ineffective, to the point of containing no operating machinery at all. The FBI has repeatedly issued bulletins to law enforcement to stop using the device. In spite of this, &lt;a href=&quot;https://www.universal-dsg.com/product/hedd1-handheld-explosive-detection-device/&quot; target=&quot;_blank&quot;&gt;it continues to be used&lt;/a&gt;, &lt;a href=&quot;https://propakistani.pk/2019/12/13/shc-dismisses-petition-seeking-ban-on-fake-bomb-detectors-in-pakistan/&quot; target=&quot;_blank&quot;&gt;as a life saving device&lt;/a&gt;, by several countries and local law enforcement agencies.
&lt;/p&gt;
&lt;p&gt;
People are so desperate for it to be true, they just will not let it go, and people are dying as a result.
&lt;/p&gt;
&lt;blockquote&gt;
The false sense of security provided by the device had catastrophic effects for many Iraqi people, hundreds of whom were killed in bombings that the ADE 651 failed to prevent
&lt;/blockquote&gt;
&lt;blockquote&gt;
— Wikipedia: ADE-651, Investigations, Iraq
&lt;/blockquote&gt;
&lt;p&gt;
Perhaps people cling to it for hope, perhaps they cling to it for vanity, but in these modern times, people are paying millions of dollars for devices that end up getting them killed.
&lt;/p&gt;
&lt;p&gt;
Reading about the ADE-651 I am reminded that there is no quick cure for superstition. We still want to believe that we are finished, and still want to believe that we are clever, and we are still greedy when it comes to getting that promotion. Like Randi, all we can do is be eternally vigilant against our own fears, hopes, and biases.
&lt;/p&gt;
&lt;h3&gt;Thank-you Mr. Randi&lt;/h3&gt;
&lt;p&gt;
Randi has left a swath of fraudsters in his path: Uri Geller, James Hydric, Peter Popoff, James McCormick and many others. Each of them represents a swindler filling their pockets with millions by feeding on the hopes and fears of thousands of people. He showed the danger of blind faith, and importance of protecting ourselves from our own desires.
&lt;/p&gt;
&lt;p&gt;
From time-to-time I still blame software errors on “planetary alignment”, or “demonic possession”. Other times I amaze people with my psychic ability to know an error without ever having to have seen the problem they are experiencing. But these are done in jest, and always followed by (at least the offer of) a detailed investigation or explanation as to how the discovery was made.
&lt;/p&gt;
&lt;p&gt;
As individuals with a responsibility to achieve goals, and under pressure to deliver, its is sometimes hard to hold ourselves to account. Sometimes we feel tempted to give or accept false hope to preserve our own dignity. Randi&#39;s approach to debunking the paranormal did not make him friends with believers, but it cut directly to the heart of the matter. When &lt;a href=&quot;https://www.seattletimes.com/business/boeing-aerospace/boeing-whistleblower-alleges-systemic-problems-with-737-max/&quot; target=&quot;_blank&quot;&gt;lives &lt;/a&gt;and &lt;a href=&quot;https://www.nbcnews.com/tech/security/twitter-breach-exposes-one-tech-s-biggest-threats-its-own-n1234076&quot; target=&quot;_blank&quot;&gt;livelihoods&lt;/a&gt; are on &lt;a href=&quot;https://abcnews.go.com/Technology/wireStory/data-breach-software-provider-local-governments-73209257&quot; target=&quot;_blank&quot;&gt;the line&lt;/a&gt; that&#39;s &lt;a href=&quot;https://www.cigniti.com/blog/37-software-failures-inadequate-software-testing/&quot; target=&quot;_blank&quot;&gt;what&lt;/a&gt; &lt;a href=&quot;https://www.information-age.com/hsbc-suffers-it-outage-123464725/&quot; target=&quot;_blank&quot;&gt;really&lt;/a&gt; &lt;a href=&quot;https://www.huffingtonpost.ca/entry/hawaii-reaction-false-missile-alert_n_5a5a7329e4b03c4189662b37?ri18n=true&quot; target=&quot;_blank&quot;&gt;counts&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
So thank-you, James Randi. You did not make friends among the frauds and charlatans of the world, but you certainly inspired at least one developer to push beyond the illusion of success.
&lt;/p&gt;
&lt;blockquote&gt;
Allow people to make assumptions and they will come away absolutely convinced that assumption was correct and that it represents fact … It&#39;s not necessarily so.
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;a href=&quot;https://www.nytimes.com/2007/08/21/science/21magic.html&quot; target=&quot;_blank&quot;&gt;James Randi&lt;/a&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3&gt;Further Reading&lt;/h3&gt;
&lt;p&gt;
This has been a personal tribute to a great man and some of the things he inspired me to think about, and the way he caused me to see the world. Naturally, as I was writing this, I came across some articles on perception and how, as humans, we want to be deceived.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.nytimes.com/2007/08/21/science/21magic.html&quot; target=&quot;_blank&quot;&gt;New York Times: Sleights of Mind&lt;/a&gt;&lt;br&gt;An interesting discussion on the scientific description of how magic is based in cognitive perception
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://web.randi.org/swift/make-skeptically-themed-websites-your-daily-stop&quot; target=&quot;_blank&quot;&gt;Skeptic News&lt;/a&gt;&lt;br&gt;Randi&#39;s recommended daily reading of skeptic news sources that approach the world with a critical and scientific eye
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
If you are impressed with James Randi, you should also learn about
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Margaret_Hamilton_%28software_engineer%29#Businesses&quot; target=&quot;_blank&quot;&gt;Margaret Hamilton&lt;/a&gt;&lt;br&gt;who put quality control at the forefront of her teams software design, saving the Apollo Moon landing; but who&#39;s design of a error free programming language is largely forgotten
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/W._Edwards_Deming&quot; target=&quot;_blank&quot;&gt;Edwards Deming&lt;/a&gt;&lt;br&gt;the father of data driven decision making
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
… and in the interest of being skeptical of Randi, and because I still “want to believe”
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.dailygrail.com/2008/02/the-myth-of-the-million-dollar-challenge/&quot; target=&quot;_blank&quot;&gt;The Myth of James Randi&#39;s Million Dollar Challenge&lt;/a&gt;&lt;br&gt;an article critical of Randi&#39;s requirements for the prize, which indicates he may have used it as a vessel for suppressing legitimate evidence.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.nytimes.com/2014/11/09/magazine/the-unbelievable-skepticism-of-the-amazing-randi.html?_r=0&quot; target=&quot;_blank&quot;&gt;The Unbelievable Skepticism of James Randi&lt;/a&gt;&lt;br&gt;A slightly more critical look at Randi&#39;s life&#39;s work. Raises questions about Randi&#39;s personal bias and profit motive.
&lt;/li&gt;
&lt;/ul&gt;
</content>
        </entry>
        <entry>
            <title>Using WebGL to Solve a Practical Problem</title>
            <link href="http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/" />
            <updated>2019-10-28T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*rcQJwTza2yxMR9yiORtuYA.gif&quot; alt=&quot;Using WebGL to Solve a Practical Problem&quot;&gt;
                &lt;p&gt;Some time ago, I was teaching introductory Python, and basic browser programming. During this time, I wrote an application that compares pieces of software code and &lt;a href=&quot;https://jefferey-cave.gitlab.io/miss/&quot;&gt;presents their similarity&lt;/a&gt; in a force directed and tornado diagram. I ran this software semi-regularly (weekly) and, very early on, a significant problem appeared with my solution.&lt;/p&gt;
&lt;p&gt;It took a really long time to solve. A &lt;strong&gt;really&lt;/strong&gt; long time. A &lt;strong&gt;painfully&lt;/strong&gt; long time.&lt;/p&gt;
&lt;p&gt;I needed to find a way to speed up the processing.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;&quot; src=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-rcqjwtza2yxmr9yiortuya-z80Ko6xxB8-__246__.webp&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;246&quot; height=&quot;246&quot;&gt;
&lt;figcaption&gt;The points are computer programs, and the orange line represents the progress of a comparison. With 36 comparisons, this takes … long enough that I get bored.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;At the time I wrote the tool, GPU processing was hot and everyone was talking about how this was going to speed up &lt;em&gt;everything&lt;/em&gt;. No matter what the question, GPU was the answer. This was an obvious avenue of investigation. However, I had decided to write this tool in the browser (legal and ethical constraints), and browsers do not have direct access to the underlying hardware.&lt;/p&gt;
&lt;p&gt;So if all the cool kids are using GPUs, and this is written in the browser, and I&#39;m intensely curious …&lt;/p&gt;
&lt;p&gt;it looks like I&#39;m learning &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders&quot; data-href=&quot;https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;WebGL and GLSL&lt;/a&gt;.&lt;/p&gt;&lt;div name=&quot;0b06&quot; id=&quot;0b06&quot; class=&quot;graf graf--mixtapeEmbed graf-after--p&quot;&gt;&lt;a href=&quot;https://jefferey-cave.gitlab.io/miss/webgl.html&quot; data-href=&quot;https://jefferey-cave.gitlab.io/miss/webgl.html&quot; class=&quot;markup--anchor markup--mixtapeEmbed-anchor&quot; title=&quot;https://jefferey-cave.gitlab.io/miss/webgl.html&quot;&gt;&lt;strong&gt;WebGL Demo&lt;/strong&gt;&lt;br&gt;*Because WebGL handles 4 memory registers naturally (RGBA), we use those for (four?) memory locations. This also makes…*jefferey-cave.gitlab.io&lt;/a&gt;&lt;a href=&quot;https://jefferey-cave.gitlab.io/miss/webgl.html&quot; class=&quot;js-mixtapeImage mixtapeImage mixtapeImage--empty u-ignoreBlock&quot; data-media-id=&quot;6233a6dcfcd7fe0152c227de7c4abded&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;blockquote name=&quot;d167&quot; id=&quot;d167&quot; class=&quot;graf graf--blockquote graf-after--mixtapeEmbed&quot;&gt;Since writing &lt;a href=&quot;https://jefferey-cave.gitlab.io/miss/&quot; data-href=&quot;https://jefferey-cave.gitlab.io/miss/&quot; class=&quot;markup--anchor markup--blockquote-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;M.I.S.S.&lt;/a&gt;, Google has released tensorflow.js, also other tools like gpu.js and twgl.js are also available. While I chose to directly write in WebGL, the compute abstraction layer offered by these libraries is often useful.&lt;/blockquote&gt;&lt;blockquote name=&quot;9041&quot; id=&quot;9041&quot; class=&quot;graf graf--blockquote graf-after--blockquote&quot;&gt;WebGL is not a Compute language … but when has that stopped the adventurous.&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;####Pre-Requisites&lt;/p&gt;
&lt;p&gt;Before beginning you should be comfortable with programming. The demonstration is written in vanilla Browser JavaScript, so no particularly advanced techniques are used; however, using WebGL requires switching between two languages, and compiling of code. Web programming does not usually involve those things.&lt;/p&gt;
&lt;p&gt;The only programmatic technique you should be vaguely familiar with would be “cellular-automaton”: &lt;a href=&quot;https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life&quot; data-href=&quot;https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;Conway&#39;s Game of Life&lt;/a&gt; is the classic example of this. &lt;code&gt;GoL&lt;/code&gt; has been a staple of programming instructors for 50 years because the problem is relatively simple, the solution is complex enough to exercise student skills, and the output is kind of fun.&lt;/p&gt;
&lt;p&gt;In addition, I strongly recommend going to the local office supply store and buying a cheap pencil, eraser, and pad of grid paper. Nothing builds understanding like working through problems yourself.&lt;/p&gt;
&lt;h3&gt;How GPUs Speed Up Processing&lt;/h3&gt;
&lt;p&gt;GPUs are a completely distinct mechanism from CPUS. CPUs are designed in such a way as to offer many operations to people and allows you to run them one at a time. GPUs offer fewer operations but sets them up in a way that you can run a bunch of them simultaneously (parallel processing).&lt;/p&gt;
&lt;p&gt;This comes at a couple of different costs to us programmers.&lt;/p&gt;&lt;ol class=&quot;postList&quot;&gt;&lt;li name=&quot;3694&quot; id=&quot;3694&quot; class=&quot;graf graf--li graf-after--p&quot;&gt;It&#39;s like working on a different computer.&lt;/li&gt;&lt;li name=&quot;507e&quot; id=&quot;507e&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;The instructions we write for one, don&#39;t necessarily exist on the other.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;That&#39;s annoying, but … parallel processing: as long as they all run the same set of instructions, you can run a calculation a couple of thousand times, but simultaneously. Very simply put:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GPUs do parallel processing of a single function.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Technically, the function is called a “kernel”, in my code I referred to it as a “program”.&lt;/p&gt;
&lt;p&gt;Consider the following function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function MultiplicationTable(size=10){
  let table = Allocate2DArray(size);
  for(memLoc.x=0; memLoc.x &amp;amp;lt; list.length; memLoc.x++){
    for(memLoc.y=0; memLoc.y &amp;amp;lt; list.length; memLoc.y++){
      table[memLoc.x][memLoc.y] = memLoc.x * memLoc.y;
    }
  }
  return table;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Parallel processing on the GPU is about doing the same action simultaneously. In this case, the multiplication is a process that is consistently the same.&lt;/p&gt;
&lt;p&gt;I&#39;m going to just do some basic math: a &lt;code&gt;10 x 10&lt;/code&gt; array costs us 100 units of processing time.&lt;/p&gt;
&lt;p&gt;Now consider the processing using the GPU&lt;/p&gt;
&lt;pre&gt;function MultiplicationTable(size=10){&lt;br&gt;    let table = Allocate2DArray(size);&lt;br&gt;    table = gpu(table)&lt;br&gt;        .forEach((memLoc)=&amp;gt;{**return memLoc.x * memLoc.y;**});&lt;br&gt;    return table;&lt;br&gt;}&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;forEach&lt;/code&gt; costs one (1) unit of processing time, no matter whether it is &lt;code&gt;10x10&lt;/code&gt;, or &lt;code&gt;10000x10000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I made that code up, it won&#39;t work, but it does give you some idea of what we are trying to work toward. No matter how big we make &lt;code&gt;table&lt;/code&gt; it will take 1 unit of processing time.&lt;/p&gt;
&lt;h3&gt;Using GPUs&lt;/h3&gt;
&lt;p&gt;GPUs are mechanically different from CPUs.&lt;/p&gt;
&lt;p&gt;Because of this mechanical difference, it is useful to think of GPUs as a completely separate computer that you are attached to. Not only do you have separate processing unit (GPU instead of CPU), it also uses separate memory, and a separate instruction set.&lt;/p&gt;
&lt;p&gt;These three elements of separation mean there are three major phases that we need to go through to make use of them:&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-qh8h4jlz1opn-xbypeu6mg-pwK73KRLMI-__717__.avif 717w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-qh8h4jlz1opn-xbypeu6mg-pwK73KRLMI-__717__.webp&quot; alt=&quot;&quot; width=&quot;717&quot; height=&quot;418&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;Send the instructions in the GPU space (compilation)&lt;/li&gt;
&lt;li&gt;Exchange memory with the GPU space (transfer — read/write)&lt;/li&gt;
&lt;li&gt;Execute the instructions (execution)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Managing these three phases are the most difficult and complex part of using WebGL. There are a significant number of details that need to be handled just to exchange information with the other space.&lt;/p&gt;
&lt;p&gt;Part of the reason for this is that the &lt;code&gt;G&lt;/code&gt; in GPU and WebGL stands for “Graphics”. We are attempting to use something designed for manipulating images, to do general computation. The details that need to be managed revolve around defining elements of an image; this means we need to go through a process of describing our raw numbers in terms of an image.&lt;/p&gt;
&lt;p&gt;This is simplified by creating helper functions that will describe our data for us.&lt;/p&gt;
&lt;p&gt;####psGPU&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;WebGL.html&lt;/code&gt;, just such a helper class was setup, called &lt;code&gt;psGPU&lt;/code&gt; [&lt;a href=&quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L158-457&quot; data-href=&quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L158-457&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;webgl.html:158&lt;/a&gt;]. It has a few functions that abstract much of the configuration away:&lt;/p&gt;&lt;ol class=&quot;postList&quot;&gt;&lt;li name=&quot;2447&quot; id=&quot;2447&quot; class=&quot;graf graf--li graf-after--p&quot;&gt;&lt;code&gt;addProgram&lt;/code&gt;: Compiles and sends a block of GLSL code (as a string) to the GPU space. &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L384-456&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L384-456&amp;quot; class=&amp;quot;markup--anchor markup--li-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:384&amp;lt;/a&amp;gt;]&lt;/code&gt;&lt;/li&gt;&lt;li name=&quot;0eb4&quot; id=&quot;0eb4&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;&lt;code&gt;initMemory&lt;/code&gt;: Creates a hidden image that will act as our processing memory &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L289-346&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L289-346&amp;quot; class=&amp;quot;markup--anchor markup--li-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:289&amp;lt;/a&amp;gt;]&lt;/code&gt;&lt;/li&gt;&lt;li name=&quot;4784&quot; id=&quot;4784&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;&lt;code&gt;write&lt;/code&gt;: Transfers our memory (&lt;code&gt;UInt8Array&lt;/code&gt;) over to the GPU space &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L254-264&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L254-264&amp;quot; class=&amp;quot;markup--anchor markup--li-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:254&amp;lt;/a&amp;gt;]&lt;/code&gt;&lt;/li&gt;&lt;li name=&quot;f88c&quot; id=&quot;f88c&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;&lt;code&gt;read&lt;/code&gt;: Transfers our memory back from the GPU space &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L241-252&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L241-252&amp;quot; class=&amp;quot;markup--anchor markup--li-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:241&amp;lt;/a&amp;gt;]&lt;/code&gt;&lt;/li&gt;&lt;li name=&quot;f347&quot; id=&quot;f347&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;&lt;code&gt;run&lt;/code&gt;: executes the program we compiled &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L266-287&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L266-287&amp;quot; class=&amp;quot;markup--anchor markup--li-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:266&amp;lt;/a&amp;gt;]&lt;/code&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;As a novice, these functions became critical to setting up the GPU. I was strongly interested in implementing an algorithm, and the complexities of memory management were acting as a significant distraction to the complexity I wanted to focus on.&lt;/p&gt;
&lt;p&gt;####&lt;code&gt;pixel&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;At some point, a second helper class was created called &lt;code&gt;pixel&lt;/code&gt;. &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L96-156&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L96-156&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl:96&amp;lt;/a&amp;gt;]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The memory that is interchanged through read and write consists of a byte array. The interpretation of an image of as a byte array, calls for a few more helpers. In particular, each pixel of the image is interpreted as 4 bytes, representing the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#rgba%28%29&quot; data-href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#rgba()&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;Red, Green, Blue, and Alpha&lt;/a&gt; (&lt;code&gt;rgba&lt;/code&gt;) values of the pixel. Within the GPU, these values are represented by the type &lt;code&gt;vec4&lt;/code&gt; which is a collection of 4 values (&lt;code&gt;r&lt;/code&gt;,&lt;code&gt;g&lt;/code&gt;,&lt;code&gt;b&lt;/code&gt;,&lt;code&gt;a&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;To help maintain consistent naming across the CPU/GPU boundary, the pixel class was created. The pixel class is really just a convenience for mapping the returned &lt;code&gt;UInt8Array&lt;/code&gt; to the 4 bytes that represent a given pixel, allowing them to be referred to by the same &lt;code&gt;rgba&lt;/code&gt; notation.&lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L102-112&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L102-112&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl:102&amp;lt;/a&amp;gt;]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;####DrawGrid&lt;/p&gt;
&lt;p&gt;Probably the most interesting (maybe “useful” is a better word) utility function is &lt;code&gt;DrawGrid&lt;/code&gt;. &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L828-883&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L828-883&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl:828&amp;lt;/a&amp;gt;]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Because GPUs are designed to manage images, the only inspection of memory changes that is available is by looking at a picture. Since the purpose of this project has nothing to do with images, colour is not a meaningful representation. This makes debugging … trickier.&lt;/p&gt;
&lt;p&gt;To help, &lt;code&gt;DrawGrid&lt;/code&gt; does nothing more than render each pixel location as their underlying numeric values. It is roughly equivalent to JavaScript&#39;s &lt;code&gt;console.log&lt;/code&gt;, allowing the developer to dump a set of values to a visible location for inspection.&lt;/p&gt;
&lt;p&gt;It is most effectively used by placing it (and a break-point) immediately after a kernel &lt;code&gt;run&lt;/code&gt;. Don&#39;t forget to comment it out when measuring speed.&lt;/p&gt;
&lt;p&gt;####Actually Running&lt;/p&gt;
&lt;p&gt;Once the helpers are in place, it is a simple matter of defining our processing functions and calling them in the correct order.&lt;/p&gt;&lt;ol class=&quot;postList&quot;&gt;&lt;li name=&quot;3fd9&quot; id=&quot;3fd9&quot; class=&quot;graf graf--li graf-after--p&quot;&gt;Send the instructions in the GPU space&lt;/li&gt;&lt;li name=&quot;86af&quot; id=&quot;86af&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;Exchange memory with the GPU space&lt;/li&gt;&lt;li name=&quot;04ad&quot; id=&quot;04ad&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;Execute the instructions&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Given the amount of helper code in place, there is not a significant number of instructions left to perform in the actual code portion &lt;em&gt;on the CPU side&lt;/em&gt;. All of the really interesting logic should be moved to the GPU; all of the interesting processing should reside in the kernel definitions.&lt;/p&gt;
&lt;p&gt;On the CPU side, we send the instructions to the GPU (&lt;code&gt;addProgram&lt;/code&gt;), and then repeatedly send notifications to execute the function (&lt;code&gt;run&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;The General Problem&lt;/h3&gt;
&lt;p&gt;Comparing code for similarity is a well solved problem. “Comparing sequences of instructions, while taking into account small variations”, sounds very similar to DNA comparisons.&lt;/p&gt;
&lt;p&gt;In the world of genetics, &lt;a href=&quot;https://en.wikipedia.org/wiki/Sequence_alignment&quot; data-href=&quot;https://en.wikipedia.org/wiki/Sequence_alignment&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;Sequence Alignment&lt;/a&gt; algorithms have been around a long time (&lt;a href=&quot;https://en.wikipedia.org/wiki/Needleman%E2%80%93Wunsch_algorithm&quot; data-href=&quot;https://en.wikipedia.org/wiki/Needleman%E2%80%93Wunsch_algorithm&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;Needleman-Wunsch&lt;/a&gt; dating to about 1970). Comparing sequences of DNA for similarity, while taking into account small variations due to mutation or cross-over is a common goal, is the same problem we are trying to solve. Think of this as a &lt;a href=&quot;https://en.wikipedia.org/wiki/DNA_paternity_testing&quot; data-href=&quot;https://en.wikipedia.org/wiki/DNA_paternity_testing&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;paternity-test&lt;/a&gt; for software.&lt;/p&gt;
&lt;p&gt;In my case, I reached for a &lt;a href=&quot;https://en.wikipedia.org/wiki/Smith–Waterman_algorithm&quot; data-href=&quot;https://en.wikipedia.org/wiki/Smith–Waterman_algorithm&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;Smith-Waterman&lt;/a&gt; comparison.&lt;/p&gt;
&lt;p&gt;The best way to understand an algorithm is to solve it with pencil and paper. In this case, I spent a great deal of time with a pencil, eraser, and pad of grid paper from the local convenience store.&lt;/p&gt;
&lt;p&gt;Take the names of two animals: &lt;code&gt;&amp;lt;a href=&amp;quot;http://slideplayer.com/slide/5142106/16/images/21/Smith-Waterman-Algorithm.jpg&amp;quot; data-href=&amp;quot;http://slideplayer.com/slide/5142106/16/images/21/Smith-Waterman-Algorithm.jpg&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;coelecanth&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;a href=&quot;http://slideplayer.com/slide/5142106/16/images/21/Smith-Waterman-Algorithm.jpg&quot; data-href=&quot;http://slideplayer.com/slide/5142106/16/images/21/Smith-Waterman-Algorithm.jpg&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt; and &lt;/a&gt;&lt;code&gt;&amp;lt;a href=&amp;quot;http://slideplayer.com/slide/5142106/16/images/21/Smith-Waterman-Algorithm.jpg&amp;quot; data-href=&amp;quot;http://slideplayer.com/slide/5142106/16/images/21/Smith-Waterman-Algorithm.jpg&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;pellican&amp;lt;/a&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On the surface, they do not appear similar, however, closer inspection (surprisingly) shows they do align reasonably well:&lt;/p&gt;
&lt;p&gt;c o &lt;strong&gt;e . l e c a n&lt;/strong&gt; t h&lt;br&gt;p . &lt;strong&gt;e l l i c a n&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Programatically we can find this alignment by solving a Smith-Waterman matrix:&lt;/p&gt;&lt;figure&gt;&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-cqjrozyzxeypoj0-caipua-7pVNeBPGBg-__553__.avif 553w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-cqjrozyzxeypoj0-caipua-7pVNeBPGBg-__553__.webp&quot; alt=&quot;&quot; width=&quot;553&quot; height=&quot;359&quot;&gt;&lt;/picture&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;In order to understand how the above alignment can be solved mechanically, I recommend following the &lt;a href=&quot;https://en.wikipedia.org/wiki/Smith%E2%80%93Waterman_algorithm#Example&quot; data-href=&quot;https://en.wikipedia.org/wiki/Smith%E2%80%93Waterman_algorithm#Example&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;example given on Wikipedia&lt;/a&gt;. I don&#39;t mean go read the Wikipedia page, I mean pull out that grid paper, and reproduce the example for yourself. Solve each step, and verify it against the example. If you made a mistake, spend some time understanding your error, and start over.&lt;/p&gt;
&lt;p&gt;When you can solve a Smith-Waterman for yourself, you have proof that you understand it.&lt;/p&gt;
&lt;p&gt;While the implementation is discussed in detail below, it it worth having an intuitive understanding of one of the two processes involved; trying to learn both simultaneously is harder. If you are interested in how to implement algorithms on the GPU, already understanding the example algorithm is useful. If you are already a master of the GPU and are interested in how to implement Smith-Watermans, this very basic example may be a good stepping stone.&lt;/p&gt;
&lt;h3&gt;Put it together&lt;/h3&gt;
&lt;p&gt;It isn&#39;t easy. Using the GPU requires us to think in parallel, and this requires us think in ways that we aren&#39;t usually used to. Things we take for granted in linear processing aren&#39;t available to us; things we would normally avoid, we accept for the sake of being able to use the tool.&lt;/p&gt;
&lt;p&gt;The idea for how to do this actually came from one of my favourite college assignments: Conway&#39;s Game of Life (GoL). In GoL, each life-form location changes its state based on the state of it&#39;s nearest neighbours. This is normally solved in a grid represented with a table of values on the screen.&lt;/p&gt;
&lt;p&gt;The key to that statement is that that each automata-cell has a state that is resolved independently of all the others, and based on the values of its nearest neighbours. That pretty much describes the the Smith-Waterman as well. The only real change is that Smith-Watermans only consider the neighbouring cells in the upper-left corner (North, West, and North-West).&lt;/p&gt;
&lt;p&gt;These “parent” values need to be calculated prior to being able to calculate values, representing one of the challenges of parallelizing the algorithm: you cannot calculate dependant values in parallel. The first break through mentally was an animated GIF I found online that demonstrated a diagonal parallelization of values in an SW matrix. (the original reference is lost to the recesses of memory now … if you know it, leave a comment)&lt;/p&gt;
&lt;p&gt;This was compounded by the fact that I had originally optimised my algorithm for low memory consumption. To do this, I had maximised the early release of the memory associated with a given cell if it was not part of a chain. One of the challenges in using a GPU was determining a way to arrange the memory in such a way that calculations were only performed on elements that have a complete parent set.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-1p1o0n5nb66i4zsaau7jvq-Xsun3xytgK-__547__.avif 547w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-1p1o0n5nb66i4zsaau7jvq-Xsun3xytgK-__547__.webp&quot; alt=&quot;&quot; width=&quot;547&quot; height=&quot;353&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;Working diagonally, allows us to maintain sufficient parent cells (green) to calculate a number of child cells (yellow) in parallel. Processing this on the GPU means that all of the other cells (white) will be calculated for no reason. A reasonable trade-off.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This focus on optimisation blinded me to the fact that the cost of evaluating a matrix with a GPU is 1 regardless of the size of the matrix. The original speed using CPU calculations required that every cell be evaluated one at a time: &lt;code&gt;width * height&lt;/code&gt; processing time. Using the GPU, and calculating all of the elements each time felt like wasteful work, but at some point the realisation it was still only &lt;code&gt;width **+** height&lt;/code&gt; processing time dawned on my dummy programmer brain.&lt;/p&gt;
&lt;p&gt;Who cares about wasting a bunch of processing &lt;em&gt;effort&lt;/em&gt; when it saves that much &lt;em&gt;time&lt;/em&gt;!?&lt;/p&gt;
&lt;p&gt;While I&#39;m sure there are efficiencies to be gained, they are insignificant when compared to the speed increases of just evaluating cells needlessly until the entire matrix is solved.&lt;/p&gt;
&lt;p&gt;Once this situation is accepted, it becomes reasonable to create the GPU function (or “kernel”) &lt;code&gt;smithwaterman&lt;/code&gt; that can solve for an individual 2-D matrix cell &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L528-586&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L528-586&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl:528&amp;lt;/a&amp;gt;]&lt;/code&gt;. An initialisation routine was also created which calculates the initial match value &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L687-710&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L687-710&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl:687&amp;lt;/a&amp;gt;]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The first thing to note is the entire program is passed to &lt;code&gt;addProgram &lt;/code&gt;as a &lt;code&gt;string&lt;/code&gt;. This is to allow the GPU to compile the code, but unfortunately means no syntax highlighting. It also leads to very cryptic messages about invalid syntax. It is recommended that as you make changes, make them small to ensure you can identify where a syntax error was made.&lt;/p&gt;
&lt;p&gt;The algorithm itself is very small in its implementation…&lt;/p&gt;
&lt;h4&gt;Initialise the Memory&lt;/h4&gt;
&lt;p&gt;Before we can start acting on values, we need to transfer the values to the GPU. While &lt;code&gt;addProgram&lt;/code&gt; is used to write the code to the GPU, writing memory is performed by the &lt;code&gt;write&lt;/code&gt; function. We start by fetching an appropriately sized array, and then fill only the &lt;strong&gt;top&lt;/strong&gt;, and &lt;strong&gt;left&lt;/strong&gt; portions of the array &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L735-744&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L735-744&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:735&amp;lt;/a&amp;gt;]&lt;/code&gt;. This minimises CPU cycles by leaving the iterative portion to the GPU.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-t7bgjye2u4klebvg-jhq6q-zUR3yx7ikv-__170__.avif 170w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-t7bgjye2u4klebvg-jhq6q-zUR3yx7ikv-__170__.webp&quot; alt=&quot;&quot; width=&quot;170&quot; height=&quot;136&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;The initial Matrix score is done by comparing the extreme North and West values. Matches get a base score of 2 while everything else is set to 0.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Our first GPU task (&lt;code&gt;initializeSpace&lt;/code&gt;) is to compare the intersections of these values for matches &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L687-710&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L687-710&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:687&amp;lt;/a&amp;gt;]&lt;/code&gt;. For each cell, we lookup the value to the extreme west and north &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L700-701&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L700-701&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:700&amp;lt;/a&amp;gt;]&lt;/code&gt;, and assign &lt;code&gt;2&lt;/code&gt; points for a match, or &lt;code&gt;0&lt;/code&gt; points for a mismatch &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L707&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L707&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:707&amp;lt;/a&amp;gt;]&lt;/code&gt;. This score is stored on the &lt;code&gt;red&lt;/code&gt; channel.&lt;/p&gt;
&lt;p&gt;It is worth noting that the GPU does its processing in terms of fractional values (&lt;code&gt;float&lt;/code&gt;): everything is a portion of 1. So while the scores are intended to be the integer values &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;2&lt;/code&gt;, these must be consumed as some proportion. The values are actually passed to the function as &lt;code&gt;0.0/255.0&lt;/code&gt; and &lt;code&gt;2.0/255.0&lt;/code&gt;, making them easily convertible between an &lt;code&gt;UInt8&lt;/code&gt; and &lt;code&gt;float&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is an important thing to remember. For the purposes of this algorithm:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Everything GPU-side is treating the numbers as floats, but the returned memory is an integer.&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;Solve the Matrix&lt;/h4&gt;
&lt;p&gt;Smith-Waterman&#39;s construct a chain of values representing the “best” matches. In this case, “best” is defined as the neighbour with the highest running score.&lt;/p&gt;
&lt;p&gt;The first step is to look up the nearest neighbours, and this requires us to determine how close those neighbours are. The GPU thinks in terms of fractional values (&lt;code&gt;float&lt;/code&gt;), while we are thinking in terms of discrete values (&lt;code&gt;int&lt;/code&gt;). We need to calculate the fractional size of a memory location (a pixel) &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L544&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L544&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:544&amp;lt;/a&amp;gt;]&lt;/code&gt;. Once this is done, we can lookup the value of the current cell (&lt;code&gt;here&lt;/code&gt;), and its nearest neighbours (&lt;code&gt;nw&lt;/code&gt;, &lt;code&gt;n&lt;/code&gt;, &lt;code&gt;w&lt;/code&gt;) &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L548-551&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L548-551&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:548&amp;lt;/a&amp;gt;]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once we have identified the critical neighbours, we can begin to assess their values. This is done by checking all three to see which has the highest score, and temporarily storing it on the &lt;code&gt;blue&lt;/code&gt; channel &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L562-565&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L562-565&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:562&amp;lt;/a&amp;gt;]&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-qg-mse-w0exeoxruqcky5a-8QglnSLr5B-__292__.avif 292w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-qg-mse-w0exeoxruqcky5a-8QglnSLr5B-__292__.webp&quot; alt=&quot;&quot; width=&quot;292&quot; height=&quot;164&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Looking at the nearest neighbours (yellow) we can determine the direction of the match. In this case horizontal (127) is the best match. Values are stored as fractions of 255 (255/2 → 127).&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Knowing which score was highest, allows us to determine which direction forms the chain; each direction is tested to see if it forms the desired chain. In the event of a tied score, diagonal matches should be favoured due to, horizontal and vertical matches representing a skip; horizontal and vertical ties can be resolved arbitrarily. Directionality is represented by enumerations of &lt;code&gt;1-north&lt;/code&gt;, &lt;code&gt;2-west&lt;/code&gt;, &lt;code&gt;3-northwest&lt;/code&gt;, and is stored on the &lt;code&gt;blue&lt;/code&gt; channel &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L568-576&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L568-576&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:568&amp;lt;/a&amp;gt;]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Knowing the directional of the chain, we can now tally up the running total. The running score of the chain, is added to the local matching score; logically this is done after direction is recorded, however, due to only having 4 memory locations per cell, it is pulled from the temporary value stored on the &lt;code&gt;blue &lt;/code&gt;channel before we finalise direction &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L565&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L565&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:565&amp;lt;/a&amp;gt;]&lt;/code&gt;. We also apply a “skip” penalty (-1 points) to chains that had to perform a skip operation (non-diagonal direction) &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L579&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L579&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:579&amp;lt;/a&amp;gt;]&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-pd-i5ms9rwszwt-wra5hpa-87bnv55QUC-__223__.avif 223w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-pd-i5ms9rwszwt-wra5hpa-87bnv55QUC-__223__.webp&quot; alt=&quot;&quot; width=&quot;223&quot; height=&quot;118&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;Having identified the parent, we can add the running score, to the local score, and apply any skip penalties. In this case (`2+2-1`), which then gets stored on the current alpha channel.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Once the calculation is complete, the values are stored permanently to the current cell on the &lt;code&gt;alpha&lt;/code&gt; channel &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L584&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L584&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:584&amp;lt;/a&amp;gt;]&lt;/code&gt;. It is worth reviewing that there are 4 memory locations per cell, and how we have allocated them:&lt;/p&gt;
&lt;ul class=&quot;postList&quot;&gt;&lt;li name=&quot;52b9&quot; id=&quot;52b9&quot; class=&quot;graf graf--li graf-after--p&quot;&gt;`red`: local matching score&lt;/li&gt;&lt;li name=&quot;ec4d&quot; id=&quot;ec4d&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;`green`: unused (reserved for future use)&lt;/li&gt;&lt;li name=&quot;a484&quot; id=&quot;a484&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;`blue`: chain direction&lt;/li&gt;&lt;li name=&quot;0f21&quot; id=&quot;0f21&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;`alpha`: chain score&lt;/li&gt;&lt;/ul&gt;
&lt;figure&gt;&lt;img alt=&quot;&quot; src=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-xjm2wnbo0sqbpq-e7rrkha-PxCyE2BWau-__548__.webp&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;548&quot; height=&quot;369&quot;&gt;&lt;figcaption&gt;This diagram shows the calculation wave as it moves across the matrix. Green values represent cells with sufficient information to solve, yellow values represent values that have settled into their final state, and white values are ones that are indeterminate. Every cell is calculated on every cycle. Using this pattern, we can reduce the number of required cycles from ``x*y`` to ``x+y``.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;As noted earlier, it is not sufficient to execute this process once. While we are calculating every cell&#39;s chain score, there is insufficient information for the last cell to complete its chain calculation until it&#39;s neighbours have completed their calculation. To resolve this, we run the GPU processing multiple times: first calculating the worst-case number of cycles required &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L518&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L518&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:518&amp;lt;/a&amp;gt;]&lt;/code&gt;, and then sending processing signals to the GPU a loop &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L752-755&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L752-755&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:752&amp;lt;/a&amp;gt;]&lt;/code&gt;. Each iteration of GPU processing moves the “wave” of completed calculations forward one step.&lt;/p&gt;
&lt;p&gt;Once this loop completes, and the tip of this wave reaches the bottom-right of our matrix, the first phase of the calculation is complete. Most importantly it was completed in &lt;code&gt;x+y&lt;/code&gt; cycles, rather than &lt;code&gt;x*y&lt;/code&gt; cycles. While the test samples are too small to take accurate readings (1ms resolution in most browsers), the &lt;code&gt;animals&lt;/code&gt; sample went from 10ms to under a millisecond, while the &lt;code&gt;lorem&lt;/code&gt; sample went from 5.5 seconds to about 1 millisecond, no initial readings were taken for &lt;code&gt;identical&lt;/code&gt;, &lt;code&gt;longchain&lt;/code&gt;, or &lt;code&gt;gilbertsulivan&lt;/code&gt; &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L477-501&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L477-501&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:477&amp;lt;/a&amp;gt;]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Even accounting for significant measurement error, this is a significant improvement!&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;&quot; src=&quot;http://plaidsheep.ca/posts/2019/using-webgl-to-solve-a-practical-problem/1-hi4x81f5wmy-qm-gosmo9g-ql-A41LeVa-__628__.webp&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;628&quot; height=&quot;308&quot;&gt;&lt;figcaption&gt;We can see a significant improvement in speed in just the first 30 seconds of processing.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I can no longer find the source, but one of the best over views of the GPU on the web was from a video of a conference talk that ended with&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If this feels like a dirty hack to you, that&#39;s because it is.&lt;/p&gt;
&lt;p&gt;— if you know the video, please leave a comment&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Some of this definitely has the feel of a dirty hack, for example, working around all the references to &lt;code&gt;RGBA&lt;/code&gt; feels weird. WebGL was designed for graphics, not a computation. For the adventurous, this lends a sense of excitement and challenge.&lt;/p&gt;
&lt;p&gt;I find this takes me back to my (very brief) days of working in &lt;code&gt;C&lt;/code&gt;, where memory manipulation is a little closer at hand. Conforming to &lt;code&gt;vec4&lt;/code&gt; memory (&lt;code&gt;rgba&lt;/code&gt;) really encourages you to think of new ways of using (and abusing) the way you use memory, or reusing memory, or squeezing that extra &lt;code&gt;bit&lt;/code&gt; of information into an incompletely used &lt;code&gt;byte&lt;/code&gt; (most of which has been refactored out of the example).&lt;/p&gt;
&lt;p&gt;While I have loved Conway&#39;s Game of Life for decades, I never thought I would find a practical purpose for cellular automatons. Having noticed the similarity between this problem and GoL, I now want to revisit a personal attempt at a JavaScript implementation of aerosolized particulate dispersion models (mine has gone missing, &lt;a href=&quot;https://omrelli.ug/smoke.js/&quot; data-href=&quot;https://omrelli.ug/smoke.js/&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;this one is pretty cool&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Lastly, the most educational part of this for me was the value of pencil and grid paper. A lot of debugging revolved around solving the grid with a pencil and then comparing the resulting manually solved matrix to the program&#39;s solution.&lt;/p&gt;
&lt;p&gt;Solving Smith-Waterman&#39;s by hand is also like doing a giant Sudoku or Crossword… kind of fun.&lt;/p&gt;
&lt;h3&gt;Next Steps&lt;/h3&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li name=&quot;e30a&quot; id=&quot;e30a&quot; class=&quot;graf graf--li graf-after--h3&quot;&gt;Open in the browser&lt;/li&gt;&lt;li name=&quot;1cdb&quot; id=&quot;1cdb&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;Hit `F12`&lt;/li&gt;&lt;li name=&quot;7f82&quot; id=&quot;7f82&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;Insert a Break-point&lt;/li&gt;&lt;li name=&quot;9974&quot; id=&quot;9974&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;Start stepping through code&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;In this case, I&#39;d actually suggest saving a local copy first to allow you to make minor changes to see the effect.&lt;/p&gt;&lt;figure&gt;&lt;iframe src=&quot;https://codesandbox.io/embed/medium-webgl-i37mk?fontsize=14&quot; width=&quot;700&quot; height=&quot;350&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The performance gains I see by implementing the algorithm on the GPU were significant: approximately 5000 times in terms of speed. However, the more I ponder the problem, the more ways I see to improve it.&lt;/p&gt;
&lt;p&gt;On the other hand …&lt;/p&gt;
&lt;p&gt;This tool was written as a personal utility to meet personal needs. Until such time as there is more interest in &lt;a href=&quot;https://jefferey-cave.gitlab.io/miss/&quot; data-href=&quot;https://jefferey-cave.gitlab.io/miss/&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;M.I.S.S.&lt;/a&gt;, there is likely little reason to implement performance gains.&lt;/p&gt;
&lt;p&gt;Its fast enough… for now.&lt;/p&gt;
&lt;h4&gt;Looping&lt;/h4&gt;
&lt;p&gt;I suspect my calling of &lt;code&gt;run&lt;/code&gt; in a loop is inefficient. Likely, it would be better to implement the loop as part of the GPU code. However, there are two reasons I did not do this:&lt;/p&gt;&lt;ol class=&quot;postList&quot;&gt;&lt;li name=&quot;3a93&quot; id=&quot;3a93&quot; class=&quot;graf graf--li graf-after--p&quot;&gt;The current implementation allows for periodic reading of the data, allowing for progress bars in the visualisation (orange connectors).&lt;/li&gt;&lt;li name=&quot;b393&quot; id=&quot;b393&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;Input and Output memory is declared prior to execution. In the helper functions, input memory and output memory are swapped after every run. I don&#39;t know how to do this within the context of a single execution.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Neither of these issues seem insurmountable.&lt;/p&gt;
&lt;h4&gt;Chain Resolution&lt;/h4&gt;
&lt;p&gt;Smith-Waterman calls for two phases&lt;/p&gt;
&lt;ol class=&quot;postList&quot;&gt;&lt;li name=&quot;137d&quot; id=&quot;137d&quot; class=&quot;graf graf--li graf-after--p&quot;&gt;Build the chains&lt;/li&gt;&lt;li name=&quot;4d18&quot; id=&quot;4d18&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;Resolve the chains&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;This article intentionally ignores the second phase of a Smith-Waterman; the code that reads the chains back off the memory. The current implementation in M.I.S.S. uses pure CPU JavaScript to resolve the chains. However, a recent discussion in the office got me re-thinking about how that was done, and inspired me to rewrite the chain resolution function to use the GPU more.&lt;/p&gt;
&lt;p&gt;I encourage readers to have a look at the GPU function &lt;code&gt;chain&lt;/code&gt; &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L588-685&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L588-685&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:588&amp;lt;/a&amp;gt;]&lt;/code&gt; to see its implementation. It uses very similar techniques to those already discussed &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L763-768&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/webgl.html#L763-768&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;webgl.html:763&amp;lt;/a&amp;gt;]&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;Memory Consumption&lt;/h4&gt;
&lt;p&gt;Building a 2-D matrix in memory means &lt;code&gt;height&lt;/code&gt; &lt;strong&gt;times&lt;/strong&gt; &lt;code&gt;width&lt;/code&gt;. That is going to grow quickly depending on your inputs. Also, as the number of token grows, there is a risk that the numbers that represent them will exceed 65535 (2-bytes).&lt;/p&gt;
&lt;p&gt;One of the initial validation tests I had for the code was to do a comparison between the genomes of &lt;code&gt;&amp;lt;a href=&amp;quot;https://www.ncbi.nlm.nih.gov/nuccore/CU651637.1?report=fasta&amp;quot; data-href=&amp;quot;https://www.ncbi.nlm.nih.gov/nuccore/CU651637.1?report=fasta&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;E.Coli&amp;lt;/a&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;a href=&amp;quot;https://www.ncbi.nlm.nih.gov/nuccore/AE009952.1?report=fasta&amp;quot; data-href=&amp;quot;https://www.ncbi.nlm.nih.gov/nuccore/AE009952.1?report=fasta&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;Y.Pestarius&amp;lt;/a&amp;gt;&lt;/code&gt; &lt;code&gt;[&amp;lt;a href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/samples/bigcompares.zip&amp;quot; data-href=&amp;quot;https://gitlab.com/jefferey-cave/miss/blob/medium-practicalwebgl/www/samples/bigcompares.zip&amp;quot; class=&amp;quot;markup--anchor markup--p-anchor&amp;quot; rel=&amp;quot;noopener&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;bigcompares.zip&amp;lt;/a&amp;gt;]&lt;/code&gt;. Unfortunately, that eats up memory, and an 8TB array exceeds my laptop&#39;s capabilities.&lt;/p&gt;
&lt;p&gt;I would love to see an implementation that cuts the giant matrix into a &lt;a href=&quot;https://gitlab.com/jefferey-cave/miss/issues/6&quot; data-href=&quot;https://gitlab.com/jefferey-cave/miss/issues/6&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;series of smaller &lt;em&gt;‘tiles&#39;&lt;/em&gt;&lt;/a&gt; (&lt;code&gt;16000x16000&lt;/code&gt; for about &lt;code&gt;2GB&lt;/code&gt;?). This would ensure that they never consume more memory than is available, and never generate a token identifier greater than 2-bytes.&lt;/p&gt;
&lt;p&gt;Tokens could be mapped to an index not exceeding &lt;code&gt;UInt16&lt;/code&gt;, for the given tile. The tiles could be solved for independently (in parallel if you have more than one GPU), storing only their internal chains and edges. The edges could then be “stitched” together during chain-resolution.&lt;/p&gt;
&lt;p&gt;It&#39;s an interesting idea, and I&#39;d love to see someone run with it…&lt;/p&gt;
&lt;h3&gt;Further Reading&lt;/h3&gt;
&lt;p&gt;Unfortunately, most of this was done as a personal project almost 2 years ago, so many of the references and tutorials I used have been lost to the mists of time (“mists of time” is about 20 minutes, in my case)&lt;/p&gt;
&lt;h4&gt;The Helper Functions&lt;/h4&gt;
&lt;p&gt;If this is something you would like to expand on, I would strong recommend investigating my helper functions. There is a lot of … stuff … going on&lt;/p&gt;
&lt;h4&gt;Smith-Waterman&lt;/h4&gt;
&lt;p&gt;Originally, I simply attempted a port of a Java implementation. Unfortunately (or fortunately), my efforts to port, combined with my lack of understanding, resulted in a mash of non-functional code. The efforts to debug created my understanding, but resulted in something that looked nothing like what was started with.&lt;/p&gt;
&lt;ul class=&quot;postList&quot;&gt;&lt;li name=&quot;c286&quot; id=&quot;c286&quot; class=&quot;graf graf--li graf-after--p&quot;&gt;GitHub: &lt;a href=&quot;https://github.com/Checksims/checksims&quot; data-href=&quot;https://github.com/Checksims/checksims&quot; class=&quot;markup--anchor markup--li-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;CheckSims&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Much of my learning comes from simply stepping through the examples given on Wikipedia, and working through my own examples.&lt;/p&gt;
&lt;ul class=&quot;postList&quot;&gt;&lt;li name=&quot;fdd1&quot; id=&quot;fdd1&quot; class=&quot;graf graf--li graf-after--p&quot;&gt;Wikipedia: &lt;a href=&quot;https://en.wikipedia.org/wiki/Smith%E2%80%93Waterman_algorithm&quot; data-href=&quot;https://en.wikipedia.org/wiki/Smith%E2%80%93Waterman_algorithm&quot; class=&quot;markup--anchor markup--li-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;Smith-Waterman&lt;/a&gt;&lt;/li&gt;&lt;li name=&quot;dd3e&quot; id=&quot;dd3e&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;Wikipedia: &lt;a href=&quot;https://en.wikipedia.org/wiki/Cellular_automaton&quot; data-href=&quot;https://en.wikipedia.org/wiki/Cellular_automaton&quot; class=&quot;markup--anchor markup--li-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;Conway&#39;s Game of Life&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Understanding the algorithms came by writing code in PoJS (Plain old JavaScript), without adding the complexity of a GPU. Now that I understand GPUs better, I think it would have been the easier solution… hindsight is 20/20, so who really knows.&lt;/p&gt;
&lt;h4&gt;WebGL&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://webglfundamentals.org/&quot; data-href=&quot;https://webglfundamentals.org/&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;WebGL Fundamentals&lt;/a&gt;: was the primary set of tutorials I followed to figure out how to do things with WebGL&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL&quot; data-href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;Mozilla Developer Network&lt;/a&gt;: is the defacto-standard reference for all things browser related, and includes both tutorials and generic references for WebGL.&lt;/p&gt;
&lt;h4&gt;Libraries&lt;/h4&gt;
&lt;p&gt;WebGL is relatively new, and &lt;a href=&quot;https://www.khronos.org/webcl/&quot; data-href=&quot;https://www.khronos.org/webcl/&quot; class=&quot;markup--anchor markup--p-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;WebCL&lt;/a&gt; (Web Computational Language) is still a work in progress. Fortunately, quite a few libraries have been developed to make WebGL a little more computational friendly.&lt;/p&gt;&lt;ul class=&quot;postList&quot;&gt;&lt;li name=&quot;b604&quot; id=&quot;b604&quot; class=&quot;graf graf--li graf-after--p&quot;&gt;&lt;a href=&quot;https://www.tensorflow.org/js&quot; data-href=&quot;https://www.tensorflow.org/js&quot; class=&quot;markup--anchor markup--li-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;tensorflow.js&lt;/a&gt;: Google&#39;s famous library for machine learning… implemented in JavaScript&lt;/li&gt;&lt;li name=&quot;d722&quot; id=&quot;d722&quot; class=&quot;graf graf--li graf-after--li&quot;&gt;&lt;a href=&quot;https://github.com/greggman/twgl.js&quot; data-href=&quot;https://github.com/greggman/twgl.js&quot; class=&quot;markup--anchor markup--li-anchor&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;TWGL&lt;/a&gt;: &lt;code&gt;psGPU&lt;/code&gt; was meant to turn into what &lt;code&gt;TWGL&lt;/code&gt; is. If I were going to solve this problem again, I would use this library.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;
</content>
        </entry>
        <entry>
            <title>Build a Chromolabe, a Simple Pencil and Paper Computer</title>
            <link href="http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/" />
            <updated>2019-10-14T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                &lt;blockquote&gt;
&lt;p&gt;This article is based on an activity originally designed as a classroom micro-activity to introduce some concepts of Visualisation in Data Analysis, in a fun and interactive way. The activity is suitable for children aged 12 to 120.&lt;/p&gt;
&lt;p&gt;I would like to thank my wife, Sharon Cave, for her invaluable assistance in developing this activity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Most people rely on their natural human intuition for decision-making, and human intuition is based on interacting with the physical world around us. Computers, on the other hand, deal in abstract ideas; something humans just aren&#39;t well equipped to deal with.&lt;/p&gt;
&lt;p&gt;What goes on inside a computer&#39;s mind is a mystery to most people. In fact, its kind of scary: you can&#39;t see, touch, or taste it. This makes it hard to understand what it is that programmers are doing, and therefore can be intimidating for people.&lt;/p&gt;
&lt;p&gt;This means that programming is constrained to the realm of people with “&lt;a href=&quot;http://worrydream.com/KillMath/&quot;&gt;a freakish knack for manipulating abstract symbols&lt;/a&gt;”. This activity is designed to help bridge that gap. We are going to build a physical computer.&lt;/p&gt;
&lt;p&gt;Computer programmers are often faced with the daunting task of taking those abstract concepts and making them concrete for their audience; a tricky and detailed task.&lt;/p&gt;
&lt;p&gt;One of the tools used to do this is &lt;em&gt;colour&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
 &lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-1eltggnty13uzxws0zsb3g-DzTTWECwNp-__400__.avif 400w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-1eltggnty13uzxws0zsb3g-DzTTWECwNp-__400__.webp&quot; alt=&quot;&quot; width=&quot;400&quot; height=&quot;280&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-kml7xu1rmxqkuqarhuxnya-dy7kxPNmlu-__400__.avif 400w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-kml7xu1rmxqkuqarhuxnya-dy7kxPNmlu-__400__.webp&quot; alt=&quot;&quot; width=&quot;400&quot; height=&quot;264&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-kyuasrb3ppeyr0zn1vnfta-Qj51P2ErNc-__566__.avif 566w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-kyuasrb3ppeyr0zn1vnfta-Qj51P2ErNc-__566__.webp&quot; alt=&quot;&quot; width=&quot;566&quot; height=&quot;266&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-1nsg-ld4zrlm0ffw1wv5jq-eYGbTMhyov-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-1nsg-ld4zrlm0ffw1wv5jq-eYGbTMhyov-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;298&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-ggdbgywtdyhk6ajhtguqsq-1JQQq3cJQG-__600__.avif 600w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-ggdbgywtdyhk6ajhtguqsq-1JQQq3cJQG-__600__.webp&quot; alt=&quot;&quot; width=&quot;600&quot; height=&quot;476&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;
&lt;p&gt;Classes from my personal calendar, D3 map chloropleth, force directed graph, gantt style chart, or a knock off of a popular video game.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In each of the example cases, colour is used to visualise the categorisation of ideas: time blocks in a calendar, political parties, characters in a game, we use colour everywhere to group related things together.&lt;/p&gt;
&lt;p&gt;As a programmer, we have just come across our first problem: how do we choose the colour palette we will use in our program?&lt;/p&gt;
&lt;h2&gt;Good Palettes&lt;/h2&gt;
&lt;p&gt;There are three components to selecting a good colour palette for use in visualisations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High Differentiation&lt;/li&gt;
&lt;li&gt;Sufficient Colours&lt;/li&gt;
&lt;li&gt;Aesthetically Pleasing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every colour palette used in a visualisation should maintain these three basic elements&lt;/p&gt;
&lt;h3&gt;Sufficient Colours&lt;/h3&gt;
&lt;p&gt;There should be enough colours to meet the needs of the visualisation.&lt;/p&gt;
&lt;p&gt;In the early days of video games, it was often enough to have 4 different colours: one for each player. In cartography, the number of colours needed is dictated by the number of shared borders, the same colour should never touch.&lt;/p&gt;
&lt;p&gt;“Sufficient” is different depending on what you are trying to represent.&lt;/p&gt;
&lt;h3&gt;High Differentiation&lt;/h3&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-tq4j-pmhjlprv-wrwd4gg-cO5yCxk4hG-__265__.avif 265w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-tq4j-pmhjlprv-wrwd4gg-cO5yCxk4hG-__265__.webp&quot; alt=&quot;&quot; width=&quot;265&quot; height=&quot;240&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;
&lt;p&gt;
Finding food in the wilderness is a matter of survival for hunter/gatherer humans . The differentiation of colour is “built-in” in humans. [image: &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Strawberry_Plant_in_early_June.JPG&quot; target=&quot;_blank&quot;&gt;Wikimedia]&lt;/a&gt;
&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The colours used should be sufficiently different, that people can tell them apart. If you are trying to tell the difference between “stop” and “go” at a traffic intersection, there should be &lt;strong&gt;no&lt;/strong&gt; ambiguity in which colour you are showing your audience.&lt;/p&gt;
&lt;p&gt;This problem is complicated by internationalisation (colour means different things in different cultures), biologic issues (colour blindness), or other things.&lt;/p&gt;
&lt;h3&gt;Aesthetically Pleasing&lt;/h3&gt;
&lt;p&gt;At the end of the day, humans are the ones that will be looking at this visualisation, they should find it pleasing to look at.&lt;/p&gt;
&lt;p&gt;Choosing colours that are “complimentary” can often have bizarre visual consequences. We need to work with the way humans are built. There is also a fashionable element to this, colours that are popular today may not be popular tomorrow.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-onnwivjcjirp5mmk1btlea-pppcCaTseq-__300__.avif 300w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-onnwivjcjirp5mmk1btlea-pppcCaTseq-__300__.webp&quot; alt=&quot;&quot; width=&quot;300&quot; height=&quot;150&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;
&lt;p&gt;Complimentary colours have high contrast but, when placed side-by-side, can have negative consequences. Interestingly, the pastel variants do not suffer these consequences.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;Infinite Colours to Choose From&lt;/h2&gt;
&lt;p&gt;Choosing colours is difficult.&lt;/p&gt;
&lt;p&gt;There are an infinite number of colours to choose from, but we need to choose just 4–12 colours, and those colours need to meet our three requirements.&lt;/p&gt;
&lt;p&gt;Hard decision making is exactly what we build machines to help us with. To start building our information machine, we need to first organise everything we know, and all we know so far is a basic truth of the universe&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are a lot of colours to choose from&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To help us sort through the options we need some way of organising the them. That is a key thing programmers do, they organise stuff. By organising things, we can simplify our problem.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-myjtax0cugbdynr3ycvmbq-k9q0BnFpo0-__640__.avif 640w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-myjtax0cugbdynr3ycvmbq-k9q0BnFpo0-__640__.webp&quot; alt=&quot;&quot; width=&quot;640&quot; height=&quot;858&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;
Moses Harris, &lt;em&gt;The Natural System of Colours&lt;/em&gt; (1776) &lt;code&gt;[&lt;a href=&quot;https://en.wikipedia.org/wiki/Color_wheel&quot; target=&quot;_blank&quot;&gt;Wikipedia: Color Wheel&lt;/a&gt;]&lt;/code&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Colour wheels have long been a tool for grouping similar colours with one another.&lt;/p&gt;
&lt;p&gt;So we first need to narrow the set down, let&#39;s only pick from colours humans can see.&lt;/p&gt;
&lt;p&gt;This process can be represented with a Colour Wheel.&lt;/p&gt;
&lt;p&gt;Colour wheels represent the variations of colours that can be made by mixing different base colours together. The base colours are placed around the outside.&lt;/p&gt;
&lt;p&gt;While not representative of “every” colour possible, these colour wheels give us many colours to choose from. They also have the advantage of placing similar colours physically close together.&lt;/p&gt;
&lt;p&gt;This helps us satisfy two of our requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sufficient: If we need many, there are many.&lt;/li&gt;
&lt;li&gt;Differentiation: the further they are physically, the more different they are.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Mechanically Making Decisions&lt;/h2&gt;
&lt;p&gt;Narrowing down an infinite number of colours, to just the 4–6 we need, is difficult, and building a machine to choose for us is the point of the exercise. Unfortunately, describing the machine and how it works mathematically, sounds like a bunch of abstract symbol manipulation.&lt;/p&gt;
&lt;p&gt;Rather than trying to explain it, let the demonstration be its own proof. Besides, this machine is really simple...&lt;/p&gt;
&lt;p&gt;… like … &lt;em&gt;really&lt;/em&gt; simple.&lt;/p&gt;
&lt;p&gt;Building the machine is way easier than explaining it. Once you build it, it should become self evident how the colour picker works.&lt;/p&gt;
&lt;h3&gt;1. Gather your supplies&lt;/h3&gt;
&lt;p&gt;Get a pencil … sharper is better.&lt;/p&gt;
&lt;p&gt;Print the &lt;a href=&quot;https://docs.google.com/presentation/d/1apf0B0kM9jnF0AmjcxhZs28NrYbMWkiaGTLJxpw8ww8/edit?usp=sharing&quot;&gt;supplementary material&lt;/a&gt;: the key pages are the colour-wheel, and the blank circle.&lt;/p&gt;
&lt;figure&gt;
&lt;a href=&quot;https://docs.google.com/presentation/d/1apf0B0kM9jnF0AmjcxhZs28NrYbMWkiaGTLJxpw8ww8/edit?usp=sharing&quot; target=&quot;_blank&quot;&gt;&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-5k8vig8pp-1envkc2njkwq-X2xqTOw63T-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-5k8vig8pp-1envkc2njkwq-X2xqTOw63T-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;367&quot;&gt;&lt;/picture&gt;&lt;/a&gt;
&lt;figcaption&gt;&lt;p&gt;The two template images that will need to be printed from the slides.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;2. Draw a Spiral&lt;/h3&gt;
&lt;p&gt;Using the blank circle, starting anywhere along the outside, begin tracing it gently with your pencil. When you have a &lt;em&gt;feel&lt;/em&gt; for the circle, draw an evenly spaced spiral toward the middle.&lt;/p&gt;
&lt;h3&gt;3. Do the calculation&lt;/h3&gt;
&lt;p&gt;Place your pencil on the topmost tick-mark of the circle.&lt;/p&gt;
&lt;p&gt;Now, move your pencil three steps, and mark the position:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;all the way across the circle, to the opposite side&lt;/li&gt;
&lt;li&gt;over one tick-mark (either way)&lt;/li&gt;
&lt;li&gt;move in &lt;strong&gt;one&lt;/strong&gt; step toward the middle&lt;/li&gt;
&lt;li&gt;mark that position&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, starting from this new position,&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;&quot; src=&quot;http://plaidsheep.ca/posts/2019/Build-a-Chromolabe/1-2hiccdgshdbbgbdnhz-jtw--4HZtcrs1T-__261__.webp&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;261&quot; height=&quot;261&quot;&gt;
&lt;figcaption&gt;One of the reasons we like machines is they do repetitive tasks for us. Describing the repetitive tasks is often the real trick&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;move all the way across to the opposite side&lt;/li&gt;
&lt;li&gt;move over one tick-mark (same direction)&lt;/li&gt;
&lt;li&gt;move in &lt;strong&gt;two&lt;/strong&gt; steps toward the middle&lt;/li&gt;
&lt;li&gt;mark that position&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Did you see it? The small change in the repeating pattern?&lt;/p&gt;
&lt;p&gt;Keep moving around marking points on the chart until you work your way to the very middle.&lt;/p&gt;
&lt;h3&gt;4. Make it permanent&lt;/h3&gt;
&lt;p&gt;When you think you have all of your dots in the correct location, carefully…&lt;/p&gt;
&lt;p&gt;(&lt;em&gt;make sure you are careful, this is both delicate, and dangerous&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;place your pencil over the first dot and…&lt;/p&gt;
&lt;p&gt;(&lt;em&gt;don&#39;t mess it up: we are building computers here&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ram that pencil through!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Seriously, don&#39;t be gentle. Punch a good sized hole in the paper.&lt;/p&gt;
&lt;p&gt;(&lt;em&gt;just be careful not to put your hand in the way&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;Do that for each of the dots you made.&lt;/p&gt;
&lt;h2&gt;Reading the Results&lt;/h2&gt;
&lt;p&gt;Align your spiral graph with the colour wheel, and watch your colour palette reveal itself. Start from the first dot and copy down each of the colours your program chose.&lt;/p&gt;
&lt;p&gt;That is your individual colour palette to use in your visualisations.&lt;/p&gt;
&lt;p&gt;Remember when we started, we were looking for a colour palette that satisfied three criteria:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High Differentiation&lt;/li&gt;
&lt;li&gt;Sufficient Colours&lt;/li&gt;
&lt;li&gt;Aesthetically Pleasing&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;High Differentiation&lt;/h3&gt;
&lt;p&gt;By starting with a colour wheel, we locate similar colours physically close together. Our algorithm then travels far across to select colours that are physically far apart.&lt;/p&gt;
&lt;h3&gt;Sufficient Colours&lt;/h3&gt;
&lt;p&gt;The spiral pattern ensures that we never select the same colour twice. No matter how many colours we select, we will always have different colours.&lt;/p&gt;
&lt;p&gt;You may notice that the differentiation of colours decreases as you approach the middle: the more colours you pick, the more similar they get. This is a compromise we need to make. It is a balance between having a lot, and having them be different.&lt;/p&gt;
&lt;h3&gt;Aesthetically Pleasing&lt;/h3&gt;
&lt;p&gt;This is always a tough one, what is pleasing to me, may not be pleasing to you.&lt;/p&gt;
&lt;p&gt;There is no real way to have our simple computer decide whether the selected colour is pretty or not. That is something best left to humans. So take a good look at your colours, and decide if you like them or not. By luck, you may have gotten a good starting point, but maybe you didn&#39;t.&lt;/p&gt;
&lt;p&gt;There is one last thing you should try: poke a small hole right in the middle.&lt;/p&gt;
&lt;p&gt;Now you can rotate the selector wheel around the colour wheel to fine tune your selection process.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Don&#39;t give this as a written assignment, it was designed to be an interactive activity. Instead, deliver it as performance art. A lot of how to manipulate the paper and pencil is very obvious when demonstrated, but is very difficult to describe.&lt;/p&gt;
&lt;p&gt;This was originally designed with a target audience of first year programming students in mind, but can easily be scaled to the elementary level (by leaving out all the theory), or scaled up to the undergrad level by asking students to mathematically define the formulas involved, or write software that implements the process.&lt;/p&gt;
&lt;p&gt;Don&#39;t forget that the “&lt;em&gt;reveal&lt;/em&gt;” is part of the “&lt;em&gt;fun&lt;/em&gt;”.&lt;/p&gt;
&lt;h3&gt;Variations&lt;/h3&gt;
&lt;p&gt;I would recommend explaining a colour wheel and colour mixing as a separate, precursor, exercise. For elementary students, have them mix paint. For high school students, have them physically make their own colour wheel with pencil crayons or water colour. For undergrad programming students, have them create a colour wheel programmatically.&lt;/p&gt;
&lt;p&gt;The perfect combination activities (in my fanatasy world) would be for students to build a colour wheel in their art or physics class, construct the selector wheel in maths class, and use the resulting colours in charts constructed for their civics class. All of it should be hand drawn to maximise comprehension.&lt;/p&gt;
&lt;p&gt;A natural extension of this activity is to write a function (&lt;em&gt;programmatic&lt;/em&gt; or &lt;em&gt;algebraic&lt;/em&gt;) to pick the colour. This would require students to define the colour wheel space, as well as traverse the space in a spiral. HTML colour codes based on &lt;code&gt;RGB&lt;/code&gt; make a good return value.&lt;/p&gt;
&lt;p&gt;If you really wanted a challenge, move into a more complete 3 dimensional colour-space. &lt;code&gt;RGB&lt;/code&gt; defines three dimensions: define a function that selects from a &lt;em&gt;colour sphere&lt;/em&gt;, with a 3 dimensional spiral search of the space…&lt;/p&gt;
&lt;p&gt;hmm…&lt;/p&gt;
&lt;p&gt;Even I&#39;m not sure how to do that.&lt;/p&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;p&gt;While this represents one way to select a colour palette, it is not necessarily the best. Have a look at some other considerations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Color Wheel Pro: &lt;a href=&quot;http://www.color-wheel-pro.com/color-theory-basics.html&quot; target=&quot;_blank&quot;&gt;Color Theory Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Graphiq: &lt;a href=&quot;https://blog.graphiq.com/finding-the-right-color-palettes-for-data-visualizations-fcd4e707a283&quot; target=&quot;_blank&quot;&gt;Finding the Right Color Palettes for Data Visualizations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/&quot; target=&quot;_blank&quot;&gt;A color palette optimized for data visualization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I came up with the idea for constructing this contraption, I had two other works in mind&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Astrolabe&quot; target=&quot;_blank&quot;&gt;Astrolabe&lt;/a&gt;: a computer from the first century, developed in Alexandria&lt;/li&gt;
&lt;li&gt;There is an article I can no longer find. It used a phi based spiral to select colours from a colour map for use in web based video games. Please leave a comment if you know of the article.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are looking for a digital variation of this tool&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adobe: &lt;a href=&quot;https://color.adobe.com/create/color-wheel/&quot; target=&quot;_blank&quot;&gt;Color Wheel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Though I suspect building it yourself would be more fun…&lt;/p&gt;
&lt;p&gt;Finally, I would like to thank &lt;a href=&quot;http://sharoncave.ca/&quot; target=&quot;_blank&quot;&gt;Sharon Cave&lt;/a&gt; for her help with this activity. I am grateful for her effort both in helping with my teaching and as my wife.&lt;/p&gt;
&lt;h2&gt;Tips For Teachers&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;As an introduction, promise students that in the next 30 minutes they are going build their first computer program.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Don&#39;t have students cut-out the circles. By keeping them on the page, you imply “no rotation”. This delays discovery long enough for you to suggest it as “intentional thought”.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Students will have different “tightness” of their spirals. This is a hint as to the trade-off between high differentiation, and more colours.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There will be the student that is tentative about punching a hole in their paper. The hole needs to be big enough that the colour shows through. Encouraging the student to be a little rough with it gets a couple of chuckles.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Have the students (as a group) describe how their computer meets the three criteria that were laid out.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ignore the aesthetics until the very end. Inevitably a student will bring up aesthetics. Act surprised… like you forgot about it. Then hold up your demo and rotate it. If you don&#39;t like the palette you got, turn it to select a different palette.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Like any activity, leave this to the end of the class. The excitement caused by comparing colour schemes brings the class to an end. Expect to do no more than have students take a written handout on the way out the door.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;At the beginning, you drew a spiral. This was not strictly necessary. Really, you should do this using Radial Coordinate graphs (on paper), or a continuous formula. The act of counting across, and in, was the real spiral algorithm.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Activity Plan&lt;/h2&gt;
&lt;h3&gt;Skills&lt;/h3&gt;
&lt;p&gt;Algorithmic Thinking&lt;/p&gt;
&lt;p&gt;Problem definition&lt;/p&gt;
&lt;h3&gt;Learning Objectives&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Describe the use of colour in categorisation&lt;/li&gt;
&lt;li&gt;Describe the components of a good colour palette&lt;/li&gt;
&lt;li&gt;Apply the components of a good colour palette&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Audience&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Grades 7 — Undergraduate&lt;/li&gt;
&lt;li&gt;Introductory programming students&lt;/li&gt;
&lt;li&gt;Introductory data analysis students&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Materials&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Colour Wheel: Make one, or print the templates&lt;/li&gt;
&lt;li&gt;Radial graph: must be same size as colour wheel&lt;/li&gt;
&lt;li&gt;Pencil: must be sharp&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Progression&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Learn the three components of a good colour palette (~5 min)&lt;/li&gt;
&lt;li&gt;Construct a colour selector (~5 min)&lt;/li&gt;
&lt;li&gt;Reflect&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Steps&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Gather Supplies (pencil/printouts)&lt;/li&gt;
&lt;li&gt;All the way across, 1 over, step in, Repeat until full&lt;/li&gt;
&lt;li&gt;Punch Holes&lt;/li&gt;
&lt;li&gt;Select colors, record by colouring in grid&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Reflection/Assessment&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;What changes could you make to get even more colours?&lt;/li&gt;
&lt;li&gt;What happens to differentiation as you get more colours?&lt;/li&gt;
&lt;li&gt;What decision making inputs can you control on the machine?&lt;/li&gt;
&lt;li&gt;How can you change the colours if they aren&#39;t aesthetically pleasing?&lt;/li&gt;
&lt;/ul&gt;
</content>
        </entry>
        <entry>
            <title>Facts and Fallacies of Software Engineering</title>
            <link href="http://plaidsheep.ca/posts/2018/Facts-and-Fallacies-of-Software-Engineering/" />
            <updated>2018-06-02T21:57:37Z</updated>
            <id>http://plaidsheep.ca/posts/2018/Facts-and-Fallacies-of-Software-Engineering/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                &lt;p&gt;This content is taken from the Table of Contents of the book. I can think of
no more important step to professional and product development than to
(at minimum) be aware that these are enduring myths of software.&lt;/p&gt;
&lt;p&gt;Studying and pondering the implications is the next step.&lt;/p&gt;
&lt;p&gt;This listing was created to use as a searchable index. Since I am currently
reviewing one a week, it is useful to know there are 65 items total (55
facts, and 10 fallacies).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://amzn.to/2HTry7V&quot;&gt;Facts and Fallacies of Software Engineering&lt;/a&gt;,
Robert L. Glass
(2003)&lt;/p&gt;
&lt;h2&gt;Facts&lt;/h2&gt;
&lt;h3&gt;Management&lt;/h3&gt;
&lt;h4&gt;People&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The most important factor in software work is the quality of the programmers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The best programmers are up to 28 times better than the worst programmers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adding people to a late project makes it later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The working environment has a profound impact on productivity and quality.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Tools and Techniques&lt;/h4&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;
&lt;p&gt;Hype (about tools and techniques) is the plague on the house of software.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;New tools and techniques cause an initial &lt;em&gt;loss&lt;/em&gt; of productivity/quality.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Software developers talk a lot about tools. They evaluate quite a few,
buy a fair number, and use practically none.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Estimation&lt;/h4&gt;
&lt;ol start=&quot;8&quot;&gt;
&lt;li&gt;
&lt;p&gt;One of the two most common causes of runaway projects is poor estimation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Most software estimates are performed at the beginning of the life
cycle. This makes sense until we realize that estimates are obtained
before theh requirments are defined and thus before the problem is
understood. Estimation, therefore, usually occurs at the wrong time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Software estimation is usually done by the wrong people.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Software estimates are rarely corrected as the project proceeds.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Since estimates are so faulty, there is little reason to be concerned
when software projects do not meet estimated targets. But everyone
is concerned anyway.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There is a disconnect between software management and their programmers.
In one research study of a project that failed to meet its estimate
and was seen by its management as a failure, the technical participants
saw it as the most successful project they had ever worked on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The answer to a feasibility study is almost always &amp;quot;yes&amp;quot;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Reuse&lt;/h4&gt;
&lt;ol start=&quot;15&quot;&gt;
&lt;li&gt;
&lt;p&gt;Reuse-in-the-small is a solved problem.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reuse-in-the-large remains a mostly unsolved problem.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reuse-in-the-large works best in families of related systems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are two &lt;code&gt;rules of three&lt;/code&gt; in resuse:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;It is three times as difficult to build reusable components as
single use components, and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a resuable component should be tried out in three different
applications before it will be sufficiently general to accept
into a reuse library.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Modification of reused code is particularly error-prone. If more than
20 to 25 percent of a component is to be revised, it is more efficient
and effective to rewrite it from scratch.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Corollary: It is almost always a mistake to modify packaged, vendor-produced
software systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Design pattern reuse is one solution to the problems inherent in code reuse.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Corollary: Design patterns emerge from practice, not from theory&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Complexity&lt;/h4&gt;
&lt;ol start=&quot;21&quot;&gt;
&lt;li&gt;
&lt;p&gt;For every 25 percent increase in problem complexity, there is a 100
percent increase in solution complexity. That&#39;s not a condition to
try to change (even though reducing complexity is always a desirable
thing to do); that just the way it is.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Eighty percent of software work is intellectual. A fair amount of
it is creative. Little of it is clerical.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;About the Life Cycle&lt;/h3&gt;
&lt;h4&gt;Requirements&lt;/h4&gt;
&lt;ol start=&quot;23&quot;&gt;
&lt;li&gt;
&lt;p&gt;One of the two most common causes of runaway projects is unstable
requirements.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Requirements errors are the most expensive to fix during production.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Missing requirements are the hardest requirements errors to correct.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Design&lt;/h4&gt;
&lt;ol start=&quot;26&quot;&gt;
&lt;li&gt;
&lt;p&gt;Explicit requirements &#39;explode&#39; as implicit requirements for a solution evolve.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There is seldom one best design solution to a software problem.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Design is a complex, iterative process. Initial design solutions are
usually wrong and certainly not optimal.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Coding&lt;/h4&gt;
&lt;ol start=&quot;29&quot;&gt;
&lt;li&gt;
&lt;p&gt;Designer &#39;primitives&#39; rarely match programmer &#39;primitives&#39;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;COBOL is a very bad language, but all the others are so much worse.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Error removal&lt;/h4&gt;
&lt;ol start=&quot;31&quot;&gt;
&lt;li&gt;Error removal is the most time-consuming phase of the lifecycle.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Testing&lt;/h4&gt;
&lt;ol start=&quot;32&quot;&gt;
&lt;li&gt;
&lt;p&gt;Software is usually tested at best to the 55 to 60 percent coverage
level.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;100 percent test coverage is still far from enough.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test tools are essential, but rarely used.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test automation rarely is. Most testing activities cannot be automated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Programmer-created, built-in debug code is an important supplement
to testing tools.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Reviews and Inspections&lt;/h4&gt;
&lt;ol start=&quot;37&quot;&gt;
&lt;li&gt;
&lt;p&gt;Rigorous inspections can remove up to 90 percent of errors before
the first test case is run.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rigorous inspections should not replace testing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Post-delivery reviews, postmortems, and retrospectives are important
and seldom performed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reviews are both technical and sociological, and both factors must
be accommodated.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Maintenance&lt;/h4&gt;
&lt;ol start=&quot;41&quot;&gt;
&lt;li&gt;
&lt;p&gt;Maintenance typically consumes 40 to 80 percent (average, 60 percent)
of software costs. Therefore, it is probably the most important software
lifecycle phase of software.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Corollary&lt;/strong&gt;: Old hardware becomes obsolete; old software goes
into production every night.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enhancements represent roughly 60 percent of maintenance costs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maintenance is a solution -- not a problem.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Understanding the existing product is the most difficult maintenance
task.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Better methods lead to more maintenance, not less.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Quality&lt;/h3&gt;
&lt;h4&gt;Quality&lt;/h4&gt;
&lt;ol start=&quot;46&quot;&gt;
&lt;li&gt;
&lt;p&gt;Quality &lt;em&gt;is&lt;/em&gt; a collection of attributes. (ED NOTE: Glass lists seven)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Quality &lt;em&gt;is not&lt;/em&gt; user satisfaction, meeting requirements, achieving
cost and schedule, or reliability.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Reliability&lt;/h4&gt;
&lt;ol start=&quot;48&quot;&gt;
&lt;li&gt;
&lt;p&gt;There are errors that most programmers tend to make.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Errors tend to cluster.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There is no single best approach to software error removal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Residual errors will always persist. The goal should be to minimize
or eliminate severe errors.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Efficiency&lt;/h4&gt;
&lt;ol start=&quot;52&quot;&gt;
&lt;li&gt;
&lt;p&gt;Efficiency stems more from good design than good coding.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;High-order language code can be about 90 percent as efficient as comparable
assembler code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are tradeoffs between optimizing for time and optimizing for space.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Research&lt;/h4&gt;
&lt;ol start=&quot;55&quot;&gt;
&lt;li&gt;
&lt;p&gt;Many researchers advocate rather than investigate. As a result,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;some advocated concepts are worth far less than their advocates
believe, and&lt;/li&gt;
&lt;li&gt;there is a shorage of evaluative resarch to help determine what
the value of such concepts really is.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Fallacies&lt;/h2&gt;
&lt;h3&gt;About Management&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;(x) You can&#39;t manage what you can&#39;t measure&lt;/li&gt;
&lt;li&gt;(x) You can manage quality into a software product&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;People&lt;/h4&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;(x) Programming can and should be egoless&lt;/li&gt;
&lt;li&gt;(x) Tools and techinques: one size fits all&lt;/li&gt;
&lt;li&gt;(x) Software needs more methodologies&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Estimation&lt;/h4&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;(x) To estimate cost and schedule, first estimate lines of code.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;About the Life Cycle&lt;/h3&gt;
&lt;h4&gt;Testing&lt;/h4&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;(x) Random test input is a good way to optimize testing.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Reviews&lt;/h4&gt;
&lt;ol start=&quot;8&quot;&gt;
&lt;li&gt;(x) &amp;quot;Given enough eyeballs, all bugs are shallow&amp;quot;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Maintenance&lt;/h4&gt;
&lt;ol start=&quot;9&quot;&gt;
&lt;li&gt;(x) The way to predict future maintenance costs and to make product
replacement descisions is to look at past cost data.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;About Education&lt;/h4&gt;
&lt;ol start=&quot;10&quot;&gt;
&lt;li&gt;(x) You teach people how to program by showing them how to &lt;em&gt;write&lt;/em&gt;
programs.&lt;/li&gt;
&lt;/ol&gt;
</content>
        </entry>
        <entry>
            <title>Shit Disturbers Reinvent the Wheel</title>
            <link href="http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/" />
            <updated>2010-08-12T00:00:00Z</updated>
            <id>http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/</id>
            <author>
                <name>11ty LibDoc</name>
            </author>
            <content type="html">
                
                    &lt;img src=&quot;https://cdn-images-1.medium.com/max/800/0*irBdav88esVQ-ozd.jpg&quot; alt=&quot;Shit Disturbers Reinvent the Wheel&quot;&gt;
                &lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/0-irbdav88esvq-ozd-vqx-FwgQjJ-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/0-irbdav88esvq-ozd-vqx-FwgQjJ-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;547&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;[&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Australian_cart.jpg&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/User:Fir0002&quot; target=&quot;_blank&quot;&gt;Fir0002/Flagstaffotos&lt;/a&gt;, &lt;a href=&quot;https://commons.wikimedia.org/wiki/Commons:GNU_Free_Documentation_License,_version_1.2&quot; target=&quot;_blank&quot;&gt;GFDL v1.2&lt;/a&gt;]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Automated testing of systems is my pet peeve: I think every computer system
should have a series of tests that get run by another computer that tests
every problem ever thought of. My current customer has asked me to start
developing a system just like this for their record-keeping and delivery
system.&lt;/p&gt;
&lt;p&gt;Currently, the customer has purchased a third-party tool for automating control of the software, unfortunately, the tool sucks. It is very difficult for non-programmers to understand its roundabout logic (it&#39;s using screen-scraper-triggered events), and has no mechanism for managing large numbers of scripts (each one is managed in and of itself). When building testing systems, the tests themselves tend to be easy to create, it is managing and tracking large numbers of tests becomes the problem.&lt;/p&gt;
&lt;p&gt;Being a diligent consultant (alright, a diligent problem solver), I suggested it was possible to build a custom tool that wrapped the objects, and was better able to be understood by non-programmers, allowing us to more easily manage a large number of tests.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/0-aotxlfwj6gvomsze-_zceHM-iK6-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/0-aotxlfwj6gvomsze-_zceHM-iK6-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;547&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;Steam Locomotive wheel [&lt;a href=&quot;https://en.wikipedia.org/wiki/File:Steam_locomotive_driving_wheel.jpg&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;, CC-SA]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;Reinventing the Wheel&lt;/h2&gt;
&lt;p&gt;That&#39;s when I heard it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There&#39;s no point reinventing the wheel.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I take exception to this, I am encouraged to solve problems in the office and invention is the key to doing this. I recognize that all problems have been solved; we already have wheels. The only problem that ever exists is the need to refine the general solution to the particular instance of the problem; we need wheels suited to the current task.&lt;/p&gt;
&lt;p&gt;If we had never reinvented the wheel we would still be driving around on Wagon Wheels. And …&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I myself like having soft rubber tires on my car.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the end, we reinvent the wheel regularly, not every wheel is perfect for every vehicle. Similarly, when solving problems at the workplace designing systems, it is sometimes necessary to build a custom component that suits the needs of the problem.&lt;/p&gt;
&lt;p&gt;While not a total reinvention, they are a design better suited to the problem at hand. To work around the foibles of the existing technology, just because the technology already exists, is the kind of short-sightedness that leads to &lt;a href=&quot;https://en.wikipedia.org/wiki/Boeing_737_MAX&quot; target=&quot;_blank&quot;&gt;planes falling out of the sky&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/0-aqg74r8qiluk-xkr-wRd4Kvbo4a-__464__.avif 464w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/0-aqg74r8qiluk-xkr-wRd4Kvbo4a-__464__.webp&quot; alt=&quot;&quot; width=&quot;464&quot; height=&quot;348&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;Automotive Tire [&lt;a href=&quot;https://en.wikipedia.org/wiki/File:Topazwheel.jpg&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;, Public Domain]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;Shit Disturber&lt;/h2&gt;
&lt;p&gt;Naturally, the moment I suggest all of this, I am accused of being a Shit Disturber. But …&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When someone accuses me of being a Shit Disturber, I know I&#39;m on the right track.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Shit Disturber. Let&#39;s break that term down; “&lt;strong&gt;shit&lt;/strong&gt;” and “disturber”; or a “disturber of shit”. For this to be true, there must be &lt;strong&gt;shit&lt;/strong&gt; to be disturbed.&lt;/p&gt;
&lt;p&gt;That I am being accused of being a Shit Disturber forces my audience to acknowledge that there is (in fact) &lt;strong&gt;shit&lt;/strong&gt; present.&lt;/p&gt;
&lt;p&gt;If there is &lt;strong&gt;shit&lt;/strong&gt; present that has been ignored and avoided; it may be more important to ask questions like, “When does somebody intend to do something about this &lt;strong&gt;shit&lt;/strong&gt;?” (This is usually the hardest part of convincing people to change: getting them to acknowledge that there is a problem which requires fixing.)&lt;/p&gt;
&lt;p&gt;Being a &lt;strong&gt;Disturber of Shit&lt;/strong&gt; is not a bad thing.&lt;/p&gt;
&lt;p&gt;Just because you are disturbing the &lt;strong&gt;shit&lt;/strong&gt;, does not mean you put it there.&lt;/p&gt;
&lt;p&gt;If the &lt;strong&gt;shit&lt;/strong&gt; is in the middle of the road, we can either ignore the &lt;strong&gt;shit&lt;/strong&gt; or do something about it.&lt;/p&gt;
&lt;p&gt;Naturally, this causes some discomfort: people have got used to their path around the &lt;strong&gt;shit&lt;/strong&gt;; while it is being moved, the &lt;strong&gt;shit&lt;/strong&gt; tends to stink; people have a hard enough time cleaning their own &lt;strong&gt;shit&lt;/strong&gt; (let alone someone else&#39;s); and the person that put the &lt;strong&gt;shit&lt;/strong&gt; there probably feels like &lt;strong&gt;shit&lt;/strong&gt; for not cleaning it up in the first place.&lt;/p&gt;
&lt;p&gt;The Disturber is just the person willing to do something about the problem. The fact of the matter is, we can ignore problems for a long time, or put up with the temporary discomfort of fixing them.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/0-e9phcpmmlmvdk-51-SgJctejhid-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/0-e9phcpmmlmvdk-51-SgJctejhid-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;973&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;Michelin&amp;#39;s new tire for the Future [&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:MICHELIN_X_Tweel_SSL.JPG&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;, CC-SA]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Shit stinks and The Wheel turns; these are two truths of the world. Ignoring them does not make them go away.&lt;/p&gt;
&lt;p&gt;In life, we need to identify problems (shit), find solutions (reinvent the wheel), and make the changes to enact those solutions (disturb the shit).&lt;/p&gt;
&lt;p&gt;In the past, I have been both punished and praised for taking drastic action to solve drastic problems (often regarding the same problem and by the same person). While we may find change uncomfortable, we should never turn away from those solutions.&lt;/p&gt;
&lt;p&gt;So a “tip of the hat” to all those Shit Disturbers out there; may you always keep finding ways to reinvent the wheel.&lt;/p&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/0-r35xbpafcmqs-ldj-2_lqSVv2n8-__88__.avif 88w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/0-r35xbpafcmqs-ldj-2_lqSVv2n8-__88__.webp&quot; alt=&quot;&quot; width=&quot;88&quot; height=&quot;31&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;This work is licensed under a &lt;a href=&quot;http://creativecommons.org/licenses/by-nc-sa/4.0/&quot; target=&quot;_blank&quot;&gt;Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License&lt;/a&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;picture class=&quot;eleventy-image&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/0-heexgtmh8i-cblqq-BAgHSdiveb-__730__.avif 730w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;http://plaidsheep.ca/posts/2010/Shit-Disturbers-Reinvent-the-Wheel/0-heexgtmh8i-cblqq-BAgHSdiveb-__730__.webp&quot; alt=&quot;&quot; width=&quot;730&quot; height=&quot;479&quot;&gt;&lt;/picture&gt;
&lt;figcaption&gt;The John Deer Low-Down, wheel-driven, Manure Spreader [&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Soil_culture_and_modern_farm_methods_%281916%29_%2814802522493%29.jpg&quot; target=&quot;_blank&quot;&gt;Wikimedia&lt;/a&gt;, Public Domain]&lt;/figcaption&gt;
&lt;/figure&gt;
s</content>
        </entry>
</feed>