<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: KMJ Tire Calgary</title>
    <description>The latest articles on DEV Community by KMJ Tire Calgary (@kmjtirecalgary).</description>
    <link>https://dev.to/kmjtirecalgary</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3939055%2Fb354733e-d11b-4d15-a4b5-a14590cce058.png</url>
      <title>DEV Community: KMJ Tire Calgary</title>
      <link>https://dev.to/kmjtirecalgary</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kmjtirecalgary"/>
    <language>en</language>
    <item>
      <title>What Did It Cost on March 3rd, and When Did We Know? Bitemporal Modeling for a Service Price Catalog</title>
      <dc:creator>KMJ Tire Calgary</dc:creator>
      <pubDate>Wed, 19 Aug 2026 16:22:21 +0000</pubDate>
      <link>https://dev.to/kmjtirecalgary/what-did-it-cost-on-march-3rd-and-when-did-we-know-bitemporal-modeling-for-a-service-price-catalog-4no0</link>
      <guid>https://dev.to/kmjtirecalgary/what-did-it-cost-on-march-3rd-and-when-did-we-know-bitemporal-modeling-for-a-service-price-catalog-4no0</guid>
      <description>&lt;h1&gt;
  
  
  What Did It Cost on March 3rd, and When Did We Know? Bitemporal Modeling for a Service Price Catalog
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Two Questions Wearing One Costume
&lt;/h2&gt;

&lt;p&gt;Someone shows up at the counter in mid-March holding a printed quote from the previous week — the sort of figure &lt;a href="https://calgaryrimandtire.ca/buying-tires-in-calgary" rel="noopener noreferrer"&gt;anyone comparing prices around town&lt;/a&gt; collects before deciding. The number on the paper is not the number on the screen. Two explanations exist, and only two. Either the price genuinely changed between then and now, or the number on the paper was wrong when it was printed and has since been corrected. Those are different situations with different remedies, and the person standing there deserves to know which one they are in.&lt;/p&gt;

&lt;p&gt;A schema with a single &lt;code&gt;updated_at&lt;/code&gt; column cannot tell them. It cannot tell you either. &lt;code&gt;updated_at&lt;/code&gt; records that a row was touched. It says nothing about the period during which the value it holds was the truth, and once it has been overwritten it says nothing about what the value used to be. You have thrown away both axes and kept a timestamp that answers neither question.&lt;/p&gt;

&lt;p&gt;Here are the two questions, stated precisely, because the whole design falls out of taking them literally:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What was the price on March 3rd?&lt;/strong&gt; This is a question about the world. It has an answer independent of what any database contained.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What did our system say the price on March 3rd was, as of March 10th?&lt;/strong&gt; This is a question about our beliefs. It has a different answer, and on the day of a dispute it is usually the more useful one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first axis is called &lt;strong&gt;valid time&lt;/strong&gt;: the period during which a fact was true of reality. The second is &lt;strong&gt;transaction time&lt;/strong&gt;: the period during which our system asserted that fact. Model both and every version of the question above becomes a &lt;code&gt;SELECT&lt;/code&gt;. Model one, and you are guessing. Model neither, and you are reconstructing history from Slack messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framing: What Follows Is a Design Exercise
&lt;/h2&gt;

&lt;p&gt;I work with a Calgary tire and oil-change business, &lt;a href="https://calgaryrimandtire.ca/calgarys-local-tire-shop" rel="noopener noreferrer"&gt;a local independent operation&lt;/a&gt; with the ordinary pressures of that trade: a violent twice-yearly changeover rush, seasonal price movement, quotes issued days before the work happens, and fleet customers who reconcile invoices at month-end against numbers they were given weeks earlier. Those constraints are real and they are why this modeling problem is interesting to me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The system described below is a design exercise.&lt;/strong&gt; It is how I would model a service and price catalogue for a business shaped like that one. There is no deployment behind it, no incident report, no migration, no rollout, no measured adoption. Nothing here is running production infrastructure. The SQL is written against PostgreSQL 14-and-later semantics — multiranges, &lt;code&gt;range_agg&lt;/code&gt;, &lt;code&gt;INCLUDE&lt;/code&gt; columns on partial indexes — and it is a design sketch to adapt and test in your own instance, not a migration to paste. The scenario around it is a scenario, not a log.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every dollar figure in this article is illustrative.&lt;/strong&gt; I picked round numbers that make the interval arithmetic legible on a page. They are not anyone's prices, they are not derived from anyone's prices, and you should not read them as such. The engineering claims are checkable and I checked them. The money is pedagogy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Valid Time and Transaction Time, Without Hand-Waving
&lt;/h2&gt;

&lt;p&gt;Valid time is a property of the fact. "Mounting and balancing a 17-inch wheel is $45" was true from the first of October until the first of March. That statement is about the world. It remains true whether or not anybody ever wrote it down, and it stays true after the database is dropped.&lt;/p&gt;

&lt;p&gt;Transaction time is a property of the record. "Our catalogue asserted that mounting a 17-inch wheel is $45" was true from 14:22 on September 28th until 09:05 on November 2nd, when someone replaced the row. That statement is about us. It is not falsifiable by the world — either we asserted it or we did not — and it is the only axis that gives you an audit trail.&lt;/p&gt;

&lt;p&gt;The two come apart constantly, and the gap between them is where all the interesting behaviour lives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Future-dated change.&lt;/strong&gt; On September 28th we record that from October 1st the &lt;a href="https://calgaryrimandtire.ca/seasonal-tire-changes" rel="noopener noreferrer"&gt;seasonal changeover&lt;/a&gt; rate goes up. Valid time starts after transaction time. For three days the system holds a fact about a future it has not reached.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backdated correction.&lt;/strong&gt; On March 12th we learn the figure we published on March 1st was wrong. Valid time starts &lt;em&gt;before&lt;/em&gt; transaction time. For eleven days we asserted something false, and — this is the part that matters — we need to keep asserting that we asserted it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retroactive restatement by a third party.&lt;/strong&gt; A supplier says a line was mispriced from the first. We did not err; we were fed bad input. Valid time again precedes transaction time, but the story and the remedy both differ.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any one of these is expressible with a single axis if you squint. All three together are not. The moment you need "what did we believe last Tuesday about last Monday," one axis is provably insufficient — you are asking for a value indexed by two independent coordinates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Soft Deletes and a Touched-At Column Are Not Version History
&lt;/h2&gt;

&lt;p&gt;The standard evolution of a price table goes like this, and I have watched it happen more than once.&lt;/p&gt;

&lt;p&gt;Version one is &lt;code&gt;price&lt;/code&gt; with an &lt;code&gt;updated_at&lt;/code&gt;. Someone asks what a rate was last month. Nobody can answer.&lt;/p&gt;

&lt;p&gt;Version two adds &lt;code&gt;price_history&lt;/code&gt;, populated by a trigger. That answers "what did the row look like before the last edit," which is a different question. History tables record edits, not periods. Three edits in one afternoon, two of them typos, produce three rows and no way to tell which two were wrong.&lt;/p&gt;

&lt;p&gt;Version three adds &lt;code&gt;effective_date&lt;/code&gt;. This is real progress — it is a valid-time lower bound — but there is no upper bound, so "which row applies on March 3rd" becomes &lt;code&gt;ORDER BY effective_date DESC LIMIT 1&lt;/code&gt; with a &lt;code&gt;&amp;lt;=&lt;/code&gt; predicate, and that query is a lie the moment somebody inserts a correction with an &lt;code&gt;effective_date&lt;/code&gt; in the past. The correction silently changes the answer to historical questions, with no record that the answer used to be different.&lt;/p&gt;

&lt;p&gt;Version four adds &lt;code&gt;deleted_at&lt;/code&gt; and calls it versioning. It is not. A soft delete is a tombstone with a timestamp; it tells you a row stopped being used, not when the fact stopped being true, and not what replaced it. Two rows both soft-deleted five minutes apart give you no ordering of beliefs, only an ordering of clicks.&lt;/p&gt;

&lt;p&gt;The tell that you are in this hole is a support conversation containing the phrase "let me check the audit log." If the audit log is a separate artifact from the data, the data has no history and you are reconstructing it from a side channel nobody tested. History is not a side channel. It is the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shape of the Table
&lt;/h2&gt;

&lt;p&gt;Here is the core object. It is append-only in the sense that facts are never erased; the only in-place mutation permitted is closing a transaction-time upper bound, and I will argue later that even that should go through one function and nothing else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;btree_gist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;offering_state&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;ENUM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'priced'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'not_offered'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'on_request'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;service_price&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;row_id&lt;/span&gt;        &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;catalog_key&lt;/span&gt;   &lt;span class="nb"&gt;text&lt;/span&gt;          &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;state&lt;/span&gt;         &lt;span class="n"&gt;offering_state&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'priced'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;        &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;currency&lt;/span&gt;      &lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'CAD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;valid_span&lt;/span&gt;    &lt;span class="n"&gt;daterange&lt;/span&gt;     &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;txn_span&lt;/span&gt;      &lt;span class="n"&gt;tstzrange&lt;/span&gt;     &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;authored_by&lt;/span&gt;   &lt;span class="nb"&gt;text&lt;/span&gt;          &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;intent&lt;/span&gt;        &lt;span class="nb"&gt;text&lt;/span&gt;          &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;note&lt;/span&gt;          &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;amount_matches_state&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'priced'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'priced'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;valid_span_has_start&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;lower_inf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;txn_span_has_start&lt;/span&gt;   &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;lower_inf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;spans_nonempty&lt;/span&gt;       &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;isempty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;isempty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five decisions in there are worth defending.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;valid_span&lt;/code&gt; is a &lt;code&gt;daterange&lt;/code&gt;, not a &lt;code&gt;tstzrange&lt;/code&gt;. Prices in this domain change on business dates, not at 14:07:33. Using a date range removes an entire category of questions — what instant does "starting October 1st" mean, in which zone, and what about the row that starts at midnight — by making them unrepresentable. If you genuinely need intra-day valid time, use &lt;code&gt;tstzrange&lt;/code&gt; and read the section on local dates below twice.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;txn_span&lt;/code&gt; is a &lt;code&gt;tstzrange&lt;/code&gt; because transaction time is an instant, always, and it is assigned by the system. Users may specify any valid time they like. Users may never specify transaction time. The moment you let an operator backdate the transaction axis, the audit property is gone and you have an expensive table with none of the benefits.&lt;/p&gt;

&lt;p&gt;Catalogue keys deserve a word too. &lt;code&gt;MOUNT_BALANCE:17&lt;/code&gt; bundles two operations — mounting and &lt;a href="https://calgaryrimandtire.ca/wheel-balancing" rel="noopener noreferrer"&gt;wheel balancing&lt;/a&gt; — under one priced unit, keyed on a rim diameter that comes straight off &lt;a href="https://calgaryrimandtire.ca/tire-sidewall-information" rel="noopener noreferrer"&gt;the markings on the sidewall&lt;/a&gt;. Keys should be stable strings that never encode a price, a season, or a supplier, because anything encoded in the key cannot be corrected by the mechanism this whole article is about.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;state&lt;/code&gt; exists so that absence never has to be interpreted. A missing row and a row saying "we do not price this" are different facts, and I will come back to why.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;intent&lt;/code&gt; is free text with a house vocabulary — &lt;code&gt;initial&lt;/code&gt;, &lt;code&gt;scheduled&lt;/code&gt;, &lt;code&gt;correction&lt;/code&gt;, &lt;code&gt;supplier_restatement&lt;/code&gt;, &lt;code&gt;withdrawal&lt;/code&gt;. Some of it is derivable from the two lower bounds, but derivation cannot separate "we typed it wrong" from "we were told wrong," and those land very differently on anyone quoted under the old figure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Half-Open Intervals and the Tax Closed Ones Charge
&lt;/h2&gt;

&lt;p&gt;Both spans are half-open: &lt;code&gt;[lower, upper)&lt;/code&gt;. Lower bound included, upper bound excluded. This is not a stylistic preference, it is the only convention that composes.&lt;/p&gt;

&lt;p&gt;With half-open intervals, adjacency is equality: the span that ends on April 1st and the span that begins on April 1st are adjacent with no gap and no overlap, and you can test that with &lt;code&gt;upper(a) = lower(b)&lt;/code&gt;. With closed intervals you write the previous span as ending March 31st, and now adjacency requires you to know the granularity of the type in order to add one to it. That knowledge leaks everywhere. It leaks into application code, into reporting queries, into the CSV export, and into whatever service consumes the CSV export.&lt;/p&gt;

&lt;p&gt;The failure mode is specific and I have seen its fingerprints in more than one schema: someone widens a column from &lt;code&gt;date&lt;/code&gt; to &lt;code&gt;timestamp&lt;/code&gt;, and every "minus one day" in the codebase is now wrong by 86,399 seconds. A hole opens in the history on every boundary day, and the daily revenue query does not error — it returns nothing for that key on that date, and the number is quietly a little low forever.&lt;/p&gt;

&lt;p&gt;PostgreSQL helps on one axis and not the other, and the asymmetry catches people. The behaviour below is documented rather than surprising, which somehow makes it easier to be bitten by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;daterange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2026-03-01'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-03-31'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;canonicalized&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;--  canonicalized&lt;/span&gt;
&lt;span class="c1"&gt;-- -------------------------&lt;/span&gt;
&lt;span class="c1"&gt;--  [2026-03-01,2026-04-01)&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;tstzrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2026-04-01 00:00-06'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-01 00:00-06'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;untouched&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;--  untouched&lt;/span&gt;
&lt;span class="c1"&gt;-- ----------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;--  ["2026-04-01 00:00:00-06","2026-05-01 00:00:00-06"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;daterange&lt;/code&gt; is a range over a discrete type, so Postgres canonicalizes every literal to &lt;code&gt;[)&lt;/code&gt; regardless of what you wrote. &lt;code&gt;tstzrange&lt;/code&gt; is continuous and cannot be canonicalized, so an inclusive upper bound survives — and two "adjacent" tstzranges written with &lt;code&gt;[]&lt;/code&gt; will overlap at exactly one instant, which is precisely enough to make an exclusion constraint reject an insert that looks obviously fine to the person writing it. Construct every &lt;code&gt;tstzrange&lt;/code&gt; with an explicit &lt;code&gt;'[)'&lt;/code&gt; third argument. Every one. Make it a lint rule if you have to.&lt;/p&gt;

&lt;p&gt;Unbounded upper bounds are the other half of the convention. A currently-believed row has &lt;code&gt;txn_span&lt;/code&gt; with an infinite upper bound, written &lt;code&gt;tstzrange(now(), NULL, '[)')&lt;/code&gt;, and a price with no scheduled end has &lt;code&gt;valid_span&lt;/code&gt; unbounded above. &lt;code&gt;upper_inf(txn_span)&lt;/code&gt; then becomes the definition of "this is a row we currently assert," which is a cheap and very indexable predicate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Truths at One Instant Are a Bug, So Make Them Impossible
&lt;/h2&gt;

&lt;p&gt;The invariant that makes the whole model trustworthy is this: for any catalogue key, any valid date, and any transaction instant, at most one row applies. Enforce it in the database, because enforcing it in the application means enforcing it in every code path anyone will ever add.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;service_price&lt;/span&gt;
  &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;service_price_single_truth&lt;/span&gt;
  &lt;span class="n"&gt;EXCLUDE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;gist&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;catalog_key&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;valid_span&lt;/span&gt;  &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;txn_span&lt;/span&gt;    &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read what that actually forbids, because it is subtler than it looks. It rejects a pair of rows for the same key whose valid spans overlap &lt;strong&gt;and&lt;/strong&gt; whose transaction spans overlap. Overlap on one axis alone is not merely permitted, it is the entire point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A correction produces two rows with overlapping — often identical — valid spans. Their transaction spans are disjoint, because the old row's belief window was closed at the instant the new one opened. Allowed.&lt;/li&gt;
&lt;li&gt;A scheduled price change produces two rows with overlapping transaction spans, both currently believed. Their valid spans are disjoint, one ending where the next begins. Allowed.&lt;/li&gt;
&lt;li&gt;Two rows both claiming to be the truth for the same key on the same day at the same instant. Rejected, at the storage layer, with a constraint violation and a row identifier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;catalog_key WITH =&lt;/code&gt; element requires &lt;code&gt;btree_gist&lt;/code&gt;, which teaches GiST equality on scalar types. Without the extension the constraint will not build.&lt;/p&gt;

&lt;p&gt;One caveat that bites during backfills: the constraint is checked immediately, row by row, as each insert lands. A multi-row rewrite that passes through an intermediate overlapping state fails even when the final state would have been clean. You can declare the constraint &lt;code&gt;DEFERRABLE INITIALLY DEFERRED&lt;/code&gt; and push the check to commit, and occasionally that is the pragmatic answer, but I would rather not — a deferred check turns a precise error at the offending statement into a vague one at commit time, and it invites write paths that are only correct in aggregate. Order the writes instead: close before you open, inside one transaction. The intermediate state is then a &lt;em&gt;gap&lt;/em&gt; rather than an &lt;em&gt;overlap&lt;/em&gt;, which the constraint permits and which no other session observes, because nothing has committed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Query Nobody Argues About: Current Truth
&lt;/h2&gt;

&lt;p&gt;Start with the boring one, because it establishes the shape.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;catalog_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;service_price&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;catalog_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'MOUNT_BALANCE:17'&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="s1"&gt;'America/Edmonton'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;upper_inf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two predicates instead of one: the permanent cost of the model. Every read grows a valid-time containment test and a transaction-time test, and no version of bitemporality makes that go away. The only real question is whether it buys you something.&lt;/p&gt;

&lt;p&gt;Note &lt;code&gt;upper_inf(sp.txn_span)&lt;/code&gt; rather than &lt;code&gt;sp.txn_span @&amp;gt; now()&lt;/code&gt;. Same rows, but the first is a clock-independent boolean over a single row, so it can back a partial index and cannot change answer mid-statement. Save containment for when you genuinely need to move along the transaction axis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rewinding the World: As Of a Valid Date
&lt;/h2&gt;

&lt;p&gt;Now ask what was true on the third of March, according to everything we believe today.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;probe&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2026-03-03'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;on_day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;took_effect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;superseded&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;service_price&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;probe&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;catalog_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'MOUNT_BALANCE:17'&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;probe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_day&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;upper_inf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the query most teams think they want, and for reporting it usually is. It reflects corrections: if we discovered on the twelfth that the third was mispriced, this returns the corrected figure. That is right for a revenue restatement and wrong for a dispute, which is the distinction the next two queries exist to draw.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rewinding the Record: As Of a Belief Instant
&lt;/h2&gt;

&lt;p&gt;Same key, same day, but now pinned to what the system asserted at a particular moment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;asserted_at&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;service_price&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;catalog_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'MOUNT_BALANCE:17'&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2026-03-03'&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;   &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="s1"&gt;'2026-03-10 11:00-06'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a correction landed on the twelfth, this returns the &lt;em&gt;old&lt;/em&gt; figure — the one that was on the screen when someone printed the quote on the tenth. That is the answer to "why does my paper say something different," and it is not reachable from any single-axis schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full As-Of-As-Of, Which Is What Settles Arguments
&lt;/h2&gt;

&lt;p&gt;Parameterize both coordinates and you have one query that subsumes the previous three.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;price_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;p_key&lt;/span&gt;   &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_day&lt;/span&gt;   &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_asof&lt;/span&gt;  &lt;span class="n"&gt;timestamptz&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="n"&gt;offering_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;note&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="k"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;STABLE&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;
      &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;service_price&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
     &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;catalog_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p_key&lt;/span&gt;
       &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p_day&lt;/span&gt;
       &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;   &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p_asof&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;price_at('MOUNT_BALANCE:17', '2026-03-03', now())&lt;/code&gt; is current belief about the past. &lt;code&gt;price_at('MOUNT_BALANCE:17', '2026-03-03', '2026-03-10 11:00-06')&lt;/code&gt; is past belief about the past. &lt;code&gt;price_at('MOUNT_BALANCE:17', '2026-10-01', now())&lt;/code&gt; is current belief about the future, which is exactly how you answer "what will the &lt;a href="https://calgaryrimandtire.ca/winter-tires-calgary" rel="noopener noreferrer"&gt;winter tire&lt;/a&gt; changeover rate be in October" without a separate scheduling table.&lt;/p&gt;

&lt;p&gt;The function returns at most one row, guaranteed by the exclusion constraint rather than by hope. That guarantee is why I am comfortable putting a &lt;code&gt;TABLE&lt;/code&gt; return type on it instead of defensively aggregating.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recording a Correction Without Erasing What You Believed
&lt;/h2&gt;

&lt;p&gt;Here is where naive implementations break. Somebody discovers that the figure published for March was wrong. The temptation is &lt;code&gt;UPDATE service_price SET amount = ... WHERE ...&lt;/code&gt;. Do that and you have destroyed the only copy of what the quote on the tenth was based on.&lt;/p&gt;

&lt;p&gt;The correct operation has three parts, in one transaction, at one instant:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Close the transaction span of every currently-believed row whose valid span intersects the interval being corrected.&lt;/li&gt;
&lt;li&gt;Insert the corrected row or rows covering the interval.&lt;/li&gt;
&lt;li&gt;Re-insert the &lt;strong&gt;remainders&lt;/strong&gt; — the parts of the closed rows' valid spans that fall outside the corrected interval — as fresh rows with the new transaction lower bound.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step three is the one people forget, and forgetting it silently deletes history at the edges. If the old row was valid from January 1st onward and the correction covers only March, you must reassert January-through-February and April-onward, or those periods now have no applicable row at all.&lt;/p&gt;

&lt;p&gt;PostgreSQL 14 and later make the remainder computation pleasant, because multiranges support difference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;restate_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;p_key&lt;/span&gt;    &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_span&lt;/span&gt;   &lt;span class="n"&gt;daterange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_state&lt;/span&gt;  &lt;span class="n"&gt;offering_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_amount&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_intent&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_who&lt;/span&gt;    &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_note&lt;/span&gt;   &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
    &lt;span class="n"&gt;t_now&lt;/span&gt;   &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transaction_timestamp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;victim&lt;/span&gt;  &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;leftover&lt;/span&gt; &lt;span class="n"&gt;daterange&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;victim&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;service_price&lt;/span&gt;
         &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;catalog_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p_key&lt;/span&gt;
           &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;upper_inf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;valid_span&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p_span&lt;/span&gt;
         &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;
    &lt;span class="n"&gt;LOOP&lt;/span&gt;
        &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;service_price&lt;/span&gt;
           &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;txn_span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tstzrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;t_now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;row_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;victim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;row_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;leftover&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt;
            &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;unnest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datemultirange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;victim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;datemultirange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_span&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;LOOP&lt;/span&gt;
            &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;service_price&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catalog_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authored_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;VALUES&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;victim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;catalog_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;victim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;victim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;leftover&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;tstzrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t_now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[)'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;p_who&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'carried_forward'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;victim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;service_price&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catalog_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authored_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tstzrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t_now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[)'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;p_who&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_note&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;transaction_timestamp()&lt;/code&gt; is doing critical work here. Close the old span at one instant and open the new one at a slightly later instant — which is exactly what &lt;code&gt;clock_timestamp()&lt;/code&gt; hands you — and there exists a real, microseconds-wide instant at which this key has no applicable row. A transaction-time probe landing in that window returns nothing. It will land there roughly never, until the once it does and you lose an afternoon. A single instant for the whole transaction closes the seam by construction.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FOR UPDATE&lt;/code&gt; matters too. Two concurrent restatements over overlapping intervals would otherwise read the same open rows, close them, and insert; the exclusion constraint catches the overlap and aborts one, which is safe but produces a baffling error. Locking the victims first turns that into an ordinary serialization wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing the Past Versus Scheduling the Future
&lt;/h2&gt;

&lt;p&gt;Both a backdated correction and a future-dated change have &lt;code&gt;lower(valid_span) &amp;lt;&amp;gt; lower(txn_span)&lt;/code&gt;. The sign of the difference separates them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;catalog_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;effective_from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;entered_on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;CASE&lt;/span&gt;
         &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'ahead of record'&lt;/span&gt;
         &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'behind record'&lt;/span&gt;
         &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'same day'&lt;/span&gt;
       &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;posture&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;service_price&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;upper_inf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That derivation is fine for a dashboard and insufficient for anything else, which is why &lt;code&gt;intent&lt;/code&gt; is stored rather than computed. Take two rows both sitting "behind record" by eleven days. In one, a colleague fat-fingered a figure and we fixed it. In the other, a supplier restated a line and we passed it through. The database sees identical geometry; the business sees two very different conversations with whoever was quoted in between. Geometry does not encode responsibility. Store the reason at write time — you will never recover it later.&lt;/p&gt;

&lt;p&gt;Future-dated rows have their own hazard, which is that they are &lt;em&gt;visible&lt;/em&gt;. A row with &lt;code&gt;valid_span&lt;/code&gt; starting October 1st is in the table in late September, currently believed, and any query that forgets the valid-time predicate will find it. It gets worse with more than one reader. A counter terminal and &lt;a href="https://calgaryrimandtire.ca/online-bookings" rel="noopener noreferrer"&gt;an online reservation form&lt;/a&gt; consume the same rows and will not be updated on the same day. This is the likeliest leak in a bitemporal catalogue: not the exotic queries, but a reporting script that joins on &lt;code&gt;catalog_key&lt;/code&gt; and filters only on &lt;code&gt;upper_inf(txn_span)&lt;/code&gt; because the author was thinking about "current rows." Current in transaction time is not current in valid time. They are orthogonal, and the vocabulary does not help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restatement, and the Quotes Already Out the Door
&lt;/h2&gt;

&lt;p&gt;The supplier says a line was mispriced since the first. You restate. Now: what about the quotes you handed out in between?&lt;/p&gt;

&lt;p&gt;This is not a database question, but the database determines whether it is answerable. The rule is that any document you emit which contains a price must persist the coordinates it was priced at, not just the number. Beyond its own identity, a quote line needs three fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;quote_line&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;quote_line_id&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;quote_id&lt;/span&gt;      &lt;span class="nb"&gt;bigint&lt;/span&gt;      &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;catalog_key&lt;/span&gt;   &lt;span class="nb"&gt;text&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;service_day&lt;/span&gt;   &lt;span class="nb"&gt;date&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;priced_asof&lt;/span&gt;   &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount_shown&lt;/span&gt;  &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;service_day&lt;/code&gt; is the valid-time coordinate, &lt;code&gt;priced_asof&lt;/code&gt; is the transaction-time coordinate, and &lt;code&gt;amount_shown&lt;/code&gt; is a denormalized copy so that the document renders identically forever even if someone drops the whole catalogue. With those, "why did we quote this figure" is reconstructible exactly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quote_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount_shown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;reconstructed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;believed_then&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;current_view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;  &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;believed_now&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;quote_line&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;
  &lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;LATERAL&lt;/span&gt; &lt;span class="n"&gt;price_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;catalog_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;priced_asof&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;reconstructed&lt;/span&gt;
  &lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;LATERAL&lt;/span&gt; &lt;span class="n"&gt;price_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;catalog_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;         &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;current_view&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quote_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4711&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;amount_shown&lt;/code&gt; and &lt;code&gt;believed_then&lt;/code&gt; disagree, you have a bug in the quoting path and you can prove it. If &lt;code&gt;believed_then&lt;/code&gt; and &lt;code&gt;believed_now&lt;/code&gt; disagree, you have a restatement and you can list every affected document with one query rather than by memory. For a &lt;a href="https://calgaryrimandtire.ca/fleet-management" rel="noopener noreferrer"&gt;fleet account&lt;/a&gt; reconciling thirty vehicles at month-end, that list is the difference between a five-minute conversation and a spreadsheet argument.&lt;/p&gt;

&lt;p&gt;What you then &lt;em&gt;do&lt;/em&gt; about those quotes is policy, and the schema should stay out of it. Honour the old figure, reissue, absorb the difference — all defensible. But none of the three is even available if the quote stored a bare number with no coordinates, because you cannot identify which documents were affected.&lt;/p&gt;

&lt;h2&gt;
  
  
  We Stopped Offering It Versus We Never Offered It
&lt;/h2&gt;

&lt;p&gt;Absence is the most under-modeled concept in catalogues, and bitemporality makes the distinctions crisp instead of ambiguous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We never offered it.&lt;/strong&gt; No rows for that key at any coordinate. &lt;code&gt;price_at&lt;/code&gt; returns zero rows for every probe. This is the only case where genuine absence is correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We stopped offering it as of a date.&lt;/strong&gt; The world changed. Close the valid span of the priced row at that date and insert a &lt;code&gt;not_offered&lt;/code&gt; row valid from that date onward. Historical valid-time queries still return the old price, which is correct — it &lt;em&gt;was&lt;/em&gt; offered then. A seasonal &lt;a href="https://calgaryrimandtire.ca/mobile-tire-service" rel="noopener noreferrer"&gt;mobile service&lt;/a&gt; line is exactly this shape — available, then not, then available again — and if it is also priced differently across &lt;a href="https://calgaryrimandtire.ca/service-areas" rel="noopener noreferrer"&gt;the areas it covers&lt;/a&gt;, each of those spans has its own history. All of them should remain readable rather than being overwritten by whatever is true today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We never should have listed it.&lt;/strong&gt; Our belief was wrong, not the world. Close the transaction span with no replacement row. Now current queries find nothing, and — crucially — as-of-valid-time queries also find nothing, because there is no currently-believed row covering that date. But as-of-transaction-time queries still show what we wrongly asserted, which is what you need when someone was quoted from a listing that should never have existed.&lt;/p&gt;

&lt;p&gt;Those three collapse into one indistinguishable state under soft deletes. Under two time axes they are three distinct geometries, and you can tell them apart with a query rather than an interview.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;state&lt;/code&gt; enum earns its keep here. A &lt;code&gt;not_offered&lt;/code&gt; row is a positive assertion of unavailability with an author and a timestamp on it, not a hole for some future reader to interpret. &lt;code&gt;on_request&lt;/code&gt; covers the case that always turns up eventually — &lt;a href="https://calgaryrimandtire.ca/commercial-tire-services" rel="noopener noreferrer"&gt;commercial and oversize work&lt;/a&gt; where the figure depends on the vehicle and no catalogue entry would be honest. "We have no price" and "priced individually" are different facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Worked Example, With Deliberately Fake Numbers
&lt;/h2&gt;

&lt;p&gt;Every figure below is invented for the example. Say the key is &lt;code&gt;MOUNT_BALANCE:17&lt;/code&gt; and the timeline runs as follows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;September 28th, 2025, 14:22.&lt;/strong&gt; We record that from October 1st the rate is $45, unbounded above. One row: valid &lt;code&gt;[2025-10-01, ∞)&lt;/code&gt;, transaction &lt;code&gt;[2025-09-28 14:22, ∞)&lt;/code&gt;, intent &lt;code&gt;scheduled&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;October 1st, 2025.&lt;/strong&gt; Nothing happens in the database. The row that was future-dated is now current, purely because the calendar moved. No write, no trigger, no job. This is one of the quiet pleasures of valid-time modeling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;March 1st, 2026, 08:00.&lt;/strong&gt; We record a spring rate of $39, valid from March 1st. &lt;code&gt;restate_price&lt;/code&gt; closes the October row's transaction span, inserts the remainder &lt;code&gt;[2025-10-01, 2026-03-01)&lt;/code&gt; at $45, and inserts &lt;code&gt;[2026-03-01, ∞)&lt;/code&gt; at $39. Three rows now, two of them currently believed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;March 10th, 11:00.&lt;/strong&gt; A fleet account asks for a written figure covering a service visit that already happened on March 3rd. The document is generated that morning showing $39, and the line it writes stores &lt;code&gt;service_day = 2026-03-03&lt;/code&gt;, &lt;code&gt;priced_asof = 2026-03-10 11:00-06&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;March 12th, 16:40.&lt;/strong&gt; We discover the spring figure should have been $41 all along. &lt;code&gt;restate_price&lt;/code&gt; runs again with valid span &lt;code&gt;[2026-03-01, ∞)&lt;/code&gt;, intent &lt;code&gt;correction&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The table now looks like this. Transaction upper bounds are shown as &lt;code&gt;∞&lt;/code&gt; where the row is still believed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;row&lt;/th&gt;
&lt;th&gt;valid span&lt;/th&gt;
&lt;th&gt;transaction span&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;th&gt;intent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[2025-10-01, ∞)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[2025-09-28 14:22, 2026-03-01 08:00)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;45.00&lt;/td&gt;
&lt;td&gt;scheduled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[2025-10-01, 2026-03-01)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[2026-03-01 08:00, ∞)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;45.00&lt;/td&gt;
&lt;td&gt;carried_forward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[2026-03-01, ∞)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[2026-03-01 08:00, 2026-03-12 16:40)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;39.00&lt;/td&gt;
&lt;td&gt;scheduled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[2026-03-01, ∞)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[2026-03-12 16:40, ∞)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;41.00&lt;/td&gt;
&lt;td&gt;correction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four rows where a single-axis schema would have one row and a support conversation. Now run the probes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;price_at(key, '2026-03-03', now())&lt;/code&gt; hits row 4. Answer: 41.00. What we now believe was true that day.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;price_at(key, '2026-03-03', '2026-03-10 11:00-06')&lt;/code&gt; hits row 3. Answer: 39.00. What the printed quote was based on, recoverable two years later.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;price_at(key, '2025-11-15', now())&lt;/code&gt; hits row 2. Answer: 45.00, carried forward untouched through two restatements.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;price_at(key, '2025-11-15', '2026-02-01 09:00-07')&lt;/code&gt; hits row 1. Same amount, different row, because in February we believed it via a different assertion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last pair is the one that convinces people. The answer is the same but the provenance is not, and only one of the two rows can be cited in a conversation about what the system said in February.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a Business Date Meets an Absolute Instant
&lt;/h2&gt;

&lt;p&gt;A brief hazard, because it is a genuine trap rather than the subject of this piece.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;valid_span&lt;/code&gt; is a &lt;code&gt;daterange&lt;/code&gt; in the local business calendar. &lt;code&gt;txn_span&lt;/code&gt; is a &lt;code&gt;tstzrange&lt;/code&gt;, an absolute instant. Mixing them carelessly is how you get a price that takes effect at 5 p.m. the previous afternoon.&lt;/p&gt;

&lt;p&gt;The specific failure: &lt;code&gt;CURRENT_DATE&lt;/code&gt; in PostgreSQL is evaluated in the session's &lt;code&gt;TimeZone&lt;/code&gt; setting. A connection pool that leaves &lt;code&gt;TimeZone&lt;/code&gt; at UTC will, every evening after 6 p.m. Mountain Daylight Time, compute tomorrow's date. Your October 1st price goes live on the evening of September 30th for every query issued from that pool. Write the conversion explicitly — &lt;code&gt;(now() AT TIME ZONE 'America/Edmonton')::date&lt;/code&gt; — and never rely on session configuration for a business-meaningful boundary.&lt;/p&gt;

&lt;p&gt;The write-side rule follows: "effective October 1st" is a local business date and belongs in the table as the date &lt;code&gt;2026-10-01&lt;/code&gt;, not as an instant computed at write time. Conversion to an instant is a rendering concern for the edge, where the asking zone is known. Store the business date; derive the instant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Indexes, Range Types, and What the Planner Will Actually Do
&lt;/h2&gt;

&lt;p&gt;The exclusion constraint builds a GiST index over &lt;code&gt;(catalog_key, valid_span, txn_span)&lt;/code&gt;. It will service &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;@&amp;gt;&lt;/code&gt; queries, and for genuine time-travel it is the right structure. It is not, however, the right structure for the hot path, and assuming it is will make your catalogue reads slower than the single-axis table they replaced.&lt;/p&gt;

&lt;p&gt;The hot path is "current price for one key," which is extremely selective on &lt;code&gt;catalog_key&lt;/code&gt; and then wants exactly the rows with an infinite transaction upper bound. In a table that only ever grows, currently-believed rows become an ever-shrinking fraction of the whole. That is a textbook partial index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;service_price_live&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;service_price&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catalog_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;upper_inf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;valid_span&lt;/code&gt; sits in the &lt;code&gt;INCLUDE&lt;/code&gt; payload, not in the key. A B-tree cannot answer containment, so putting a range in a key column buys nothing; carrying it as payload lets the containment filter run without a heap fetch. The index stays roughly the size of the live catalogue rather than the size of its entire history, and it does not grow when you restate. Pair it with the &lt;code&gt;upper_inf()&lt;/code&gt; form of the predicate in the query — this is why I wrote the current-truth query that way rather than with &lt;code&gt;txn_span @&amp;gt; now()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Three further planner notes worth knowing before you profile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;now()&lt;/code&gt; is &lt;code&gt;STABLE&lt;/code&gt;, not &lt;code&gt;VOLATILE&lt;/code&gt;, so Postgres evaluates it once per statement and can use it as an index-scan bound. &lt;code&gt;clock_timestamp()&lt;/code&gt; is volatile and cannot be used that way. If you sprinkle &lt;code&gt;clock_timestamp()&lt;/code&gt; through predicates for "accuracy," you will silently lose index usage on every one of them.&lt;/li&gt;
&lt;li&gt;GiST gives the planner worse selectivity estimates for &lt;code&gt;@&amp;gt;&lt;/code&gt; than B-tree does for equality. A query combining a very selective key equality with a range containment is usually better planned as a B-tree lookup plus a filter, which is exactly what the partial index encourages.&lt;/li&gt;
&lt;li&gt;SP-GiST supports range types and often beats GiST on pure containment, but cannot back an exclusion constraint. If time-travel reporting becomes a real workload, a second SP-GiST index on &lt;code&gt;valid_span&lt;/code&gt; is worth measuring. Measure it; do not assume it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One sentence of realism about growth: a few thousand keys restated a handful of times a year stays trivially small for a decade. If your price catalogue needs partitioning, it is probably not a price catalogue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Snapshot Views That Cannot Quietly Drift
&lt;/h2&gt;

&lt;p&gt;Application developers should not be writing two temporal predicates on every read. Give them a view.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;catalog_today&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;catalog_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;in_effect_since&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;in_effect_until&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;service_price&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;upper_inf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txn_span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_span&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="s1"&gt;'America/Edmonton'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A plain view is honest by construction: it is the query, so it cannot disagree with the query. Reach for a materialized view only when profiling says so, and when you do, make it self-describing. Carry the instant it was computed from as a column, so any consumer can verify the snapshot rather than trust it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;MATERIALIZED&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;catalog_snapshot&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;derived_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;catalog_today&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a scheduled job can assert equality between the materialized rows and a live recomputation, and fail loudly on mismatch. The failure mode you are protecting against is not staleness in the abstract — it is a materialized view that has silently stopped refreshing while every dashboard built on it continues to render confidently. A snapshot that carries its own derivation coordinate can be checked. One that does not, cannot.&lt;/p&gt;

&lt;p&gt;One more trap: a "current price" cache keyed only on &lt;code&gt;catalog_key&lt;/code&gt; throws away both axes at the cache layer and reintroduces the original problem a tier up. Cache the result of a coordinate pair, or do not cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Property Tests Over Generated Interval Sets
&lt;/h2&gt;

&lt;p&gt;Example-based tests will not find the bugs in this model. The bugs live at boundaries — spans that touch, spans that nest, corrections that exactly cover a previous correction — and hand-written cases cluster around the shapes you already thought of.&lt;/p&gt;

&lt;p&gt;Generate operation sequences instead: a random series of asserts, restatements, scheduled changes, and withdrawals against a small key space, with invariants checked after every step.&lt;/p&gt;

&lt;p&gt;One craft note matters more than the framework you pick — &lt;strong&gt;generate dates from a small pool, not uniformly at random.&lt;/strong&gt; Draw dates uniformly from a decade and adjacent or identical boundaries essentially never occur, so the boundary bugs this model exists to prevent go unexercised. Twenty or thirty candidate dates makes collisions common. Same for keys: three, not three thousand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hypothesis&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategies&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;

&lt;span class="n"&gt;DAYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;KEYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MOUNT_BALANCE:17&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ROTATE:STD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REPAIR:PATCH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;spans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sampled_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DAYS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;min_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tuples&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sampled_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sampled_from&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priced&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not_offered&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_sequence_preserves_invariants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sequence&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;fresh_schema&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sequence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;assert_invariants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three keys differ structurally rather than cosmetically: a bundled operation, a flat-rate service, and &lt;a href="https://calgaryrimandtire.ca/tire-repair-in-calgary" rel="noopener noreferrer"&gt;a puncture repair&lt;/a&gt; — the entry most likely to sit in &lt;code&gt;on_request&lt;/code&gt;, because whether a given injury is repairable at all gets decided at the vehicle, not in a price list. Homogeneous keys make for a lazy generator.&lt;/p&gt;

&lt;p&gt;Shrinking is the reason to use a property framework rather than a loop with a random seed. When an invariant breaks after nineteen operations, the framework hands you back the minimal three-operation sequence that reproduces it, and that sequence is almost always immediately legible as a design flaw rather than a coding slip.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Invariants I Would Assert
&lt;/h2&gt;

&lt;p&gt;These are the assertions worth having, roughly in order of how often I would expect each to catch something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One truth per coordinate.&lt;/strong&gt; For every key, every date in a probe set, and every distinct transaction lower bound in the table, the count of applicable rows is zero or one. This duplicates the exclusion constraint deliberately — constraints get dropped during migrations and not always restored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transaction time is append-only.&lt;/strong&gt; No row's &lt;code&gt;lower(txn_span)&lt;/code&gt; ever changes, and &lt;code&gt;upper(txn_span)&lt;/code&gt; goes from infinite to finite exactly once, never back. Diff a table snapshot across each generated operation; any other mutation is a write-path bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;History is stable.&lt;/strong&gt; For any instant &lt;code&gt;t&lt;/code&gt; and date &lt;code&gt;d&lt;/code&gt;, &lt;code&gt;price_at(key, d, t)&lt;/code&gt; computed now equals the same probe computed later. This is the property that makes the model worth its cost and it is trivially easy to lose — one stray &lt;code&gt;UPDATE ... SET amount&lt;/code&gt; does it. Record every answer as you go, re-check them all at the end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Valid-time coverage is contiguous.&lt;/strong&gt; Aggregate the valid spans of all currently-believed rows for a key: &lt;code&gt;SELECT range_agg(valid_span) FROM service_price WHERE catalog_key = $1 AND upper_inf(txn_span)&lt;/code&gt;. The result must be a multirange with exactly one component. More than one component means a restatement dropped a remainder — that is the step-three bug, and this assertion catches it immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reversal is not erasure.&lt;/strong&gt; Apply a correction, then apply a second correction restoring the original figure. Current-truth queries must return the original answer; the row count must have strictly increased; and the intermediate belief must still be reachable by a transaction-time probe. Systems that fail this one usually have an "undo" feature that deletes rows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nothing before the beginning.&lt;/strong&gt; For every key, a probe at a date before the earliest &lt;code&gt;lower(valid_span)&lt;/code&gt; returns zero rows in every transaction coordinate. Coverage should be total forward from the first assertion and empty before it — never accidentally unbounded below, which is what happens when someone constructs a range with a &lt;code&gt;NULL&lt;/code&gt; lower bound by mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  When This Is Too Much Machinery
&lt;/h2&gt;

&lt;p&gt;I would not put this in every schema, and the honest version of this article has to say where the line is.&lt;/p&gt;

&lt;p&gt;It is overkill when the data is naturally immutable — an events table already carries transaction time and has no valid time worth modeling. It is overkill when nobody outside the team ever sees a derived figure, since the payoff is defending a number to someone who did not compute it. And it is overkill when corrections are rare enough that a spreadsheet annotation is genuinely fine; I would rather a team admit that than get a temporal model subtly wrong.&lt;/p&gt;

&lt;p&gt;The costs compound:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every read grows two predicates, and every developer who joins has to learn why.&lt;/li&gt;
&lt;li&gt;ORMs fight you. Most assume a row is a thing rather than an assertion about a thing, and the workarounds are ugly in every framework I have used.&lt;/li&gt;
&lt;li&gt;"Current" becomes ambiguous in conversation. Current in valid time and current in transaction time are different, and people will conflate them verbally even when the code does not.&lt;/li&gt;
&lt;li&gt;Backfilling a single-axis table is hard, because historical transaction time is exactly what the old schema discarded. Seed everything at one instant and be honest that pre-cutover history is unknown; you cannot manufacture it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider uni-temporal first, because it is often enough. Valid time alone handles scheduled changes and gives you the &lt;a href="https://calgaryrimandtire.ca/all-season-tires-calgary" rel="noopener noreferrer"&gt;seasonal rate movement&lt;/a&gt; story cleanly. Transaction time alone gives you audit. Reach for both only when &lt;em&gt;corrections to the past&lt;/em&gt; are a normal part of operations rather than an emergency — and in any business where a supplier can restate a figure after the fact, they are.&lt;/p&gt;

&lt;p&gt;Three questions I would use as the test. Can an external party dispute a number you produced? Is a figure ever restated retroactively by someone who is not you? Must you be able to reproduce a decision months later? Two yeses and I would build this. Three and there is no real alternative — a service catalogue where customers receive written quotes, suppliers revise prices after the fact, and &lt;a href="https://calgaryrimandtire.ca/commercial-tire-services" rel="noopener noreferrer"&gt;commercial invoices&lt;/a&gt; get reconciled at month-end hits all three without trying.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Actually Build First
&lt;/h2&gt;

&lt;p&gt;If I were starting this tomorrow for a catalogue of tire and oil-change services, I would not begin with the full model. I would begin with the &lt;code&gt;daterange&lt;/code&gt; valid-time axis and the exclusion constraint, because scheduled seasonal changes are the immediate operational need and half-open intervals are the thing you cannot retrofit cheaply. Getting the interval convention right on day one costs nothing; changing it later means rewriting every boundary in the table.&lt;/p&gt;

&lt;p&gt;Then I would add the transaction axis the first time somebody asks a question beginning "what did it say when." That question always arrives, usually within a season. Where a customer is quoted for &lt;a href="https://calgaryrimandtire.ca/shop-all-tires-in-calgary" rel="noopener noreferrer"&gt;a set of winter rubber&lt;/a&gt; on a Tuesday and arrives the following Monday, or an &lt;a href="https://calgaryrimandtire.ca/all-weather-tires-calgary" rel="noopener noreferrer"&gt;all-weather&lt;/a&gt; line is restated by a supplier mid-month, the gap between what was true and what we said is not an edge case. It is the ordinary weather.&lt;/p&gt;

&lt;p&gt;The failure I would most want to avoid is the one that looks like success: a table with &lt;code&gt;valid_from&lt;/code&gt;, &lt;code&gt;valid_to&lt;/code&gt;, and an &lt;code&gt;updated_at&lt;/code&gt;, which appears to be temporal and is not. It answers the world question and silently overwrites the record question, and it does so without ever producing an error. You find out it was wrong when someone is standing there with a printout, which is the worst possible moment to discover that your history is a single mutable column.&lt;/p&gt;

&lt;p&gt;Two axes. Half-open intervals on both. One exclusion constraint. One function that writes, and nothing else that does. That is the whole design, and everything above is just the consequences of taking it seriously.&lt;/p&gt;

</description>
      <category>automotive</category>
      <category>calgary</category>
      <category>maintenance</category>
      <category>tires</category>
    </item>
    <item>
      <title>Open Is Not Available: Modeling Service Hours, DST Boundaries, and Interval Algebra</title>
      <dc:creator>KMJ Tire Calgary</dc:creator>
      <pubDate>Tue, 18 Aug 2026 16:28:31 +0000</pubDate>
      <link>https://dev.to/kmjtirecalgary/open-is-not-available-modeling-service-hours-dst-boundaries-and-interval-algebra-1hf2</link>
      <guid>https://dev.to/kmjtirecalgary/open-is-not-available-modeling-service-hours-dst-boundaries-and-interval-algebra-1hf2</guid>
      <description>&lt;h1&gt;
  
  
  Open Is Not Available: Modeling Service Hours, DST Boundaries, and Interval Algebra
&lt;/h1&gt;

&lt;h2&gt;
  
  
  A Six-Hour Window That Holds Five Hours of Work
&lt;/h2&gt;

&lt;p&gt;Ask a naive hours model how much working time sits between 00:00 and 06:00 local on 8 March 2026 in &lt;code&gt;America/Edmonton&lt;/code&gt;, and it will confidently answer six hours. The right answer is five. Here is the REPL transcript that produced it, unedited:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;zoneinfo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ZoneInfo&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;yyc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;America/Edmonton&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yyc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yyc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;21600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;18000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twenty-one thousand six hundred seconds of wall clock. Eighteen thousand seconds of reality. An hour vanished into the spring-forward transition, and a system that promised customers six hours of throughput that morning had five hours of bay time to deliver it with. Nobody typed a wrong number anywhere. The arithmetic was performed on the wrong kind of quantity.&lt;/p&gt;

&lt;p&gt;Let me be exact about what this article is, because that matters more than the code. KMJ Tire is a real tire and oil-change operation in Calgary, working in the &lt;code&gt;America/Edmonton&lt;/code&gt; zone, with a violent seasonal changeover rush, walk-in traffic, and jobs whose durations vary by an order of magnitude. Those operational constraints are real and they are the reason the problem is interesting. &lt;strong&gt;The scheduling system described below is a design exercise.&lt;/strong&gt; It is a model I reasoned through on paper and exercised in a Python REPL, not live production infrastructure that the business runs today. There is no deployment, no incident, no migration and no rollout behind any of this.&lt;/p&gt;

&lt;p&gt;Every figure in this article — durations, bay counts, technician counts, grid sizes, the shape of a working day — is &lt;strong&gt;illustrative&lt;/strong&gt;. I picked numbers that make the edge cases legible. Do not read them as published hours, as staffing levels, or as measured performance. The engineering claims are testable and I have tested them; the business numbers are pedagogy.&lt;/p&gt;

&lt;p&gt;With that settled: the thesis is that "when are we open, and can this job fit" decomposes into three separate problems that most codebases fuse into one. There is a &lt;strong&gt;representation&lt;/strong&gt; problem (what you persist), a &lt;strong&gt;resolution&lt;/strong&gt; problem (how layered rules produce a concrete set of instants for a given date), and an &lt;strong&gt;algebra&lt;/strong&gt; problem (how you combine open windows with busy windows and job durations to get a truthful answer). Fusing them is what produces the six-hour window that holds five hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wall Clock Plus Zone Id, Never a Stored Offset
&lt;/h2&gt;

&lt;p&gt;The first decision determines whether the rest of the system is possible. Opening hours are not instants. They are a &lt;strong&gt;rule that generates instants&lt;/strong&gt; when applied to a date. "We open at seven" is a statement about a local clock face, and it stays true across the transition even though the underlying UTC instant moves by an hour twice a year.&lt;/p&gt;

&lt;p&gt;So the storage shape is: a local wall-clock time with no offset attached, plus an IANA zone identifier that names the ruleset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rule_set_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hours-v7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"loc-yyc-main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iana_zone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/Edmonton"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"weekly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"weekday"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MON"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"windows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"07:30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"17:30"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"weekday"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TUE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"windows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"07:30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"17:30"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"weekday"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"windows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"07:30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"17:30"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"weekday"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"THU"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"windows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"07:30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"17:30"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"weekday"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FRI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"windows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"07:30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"17:00"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"weekday"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SAT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"windows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"09:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"14:00"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"weekday"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SUN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"windows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Illustrative rule set. The hour values are chosen to exercise the model, not published hours.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three properties earn their keep here. The times are strings on a 24-hour clock, so they survive JSON round-trips without a numeric timezone sneaking in. The zone is an identifier, not an offset, so &lt;code&gt;tzdata&lt;/code&gt; updates flow through automatically. And the whole document has an id, &lt;code&gt;hours-v7&lt;/code&gt;, which is the hook that later lets you explain why a quote issued three weeks ago said what it said.&lt;/p&gt;

&lt;p&gt;Note what is &lt;em&gt;absent&lt;/em&gt;: no &lt;code&gt;-07:00&lt;/code&gt;, no &lt;code&gt;-06:00&lt;/code&gt;, no &lt;code&gt;"MST"&lt;/code&gt;, no &lt;code&gt;utc_offset_minutes: -420&lt;/code&gt;. Abbreviations like MST and MDT are display artifacts. They are not unique across the world, they are not stable over time, and no library should be asked to parse them back into a zone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Rots When You Persist an Offset
&lt;/h2&gt;

&lt;p&gt;Storing &lt;code&gt;07:30-07:00&lt;/code&gt; looks harmless in March and becomes wrong in April. The failure is not that the value is malformed; it is that the value was correct and then quietly stopped being correct, with no write to the row.&lt;/p&gt;

&lt;p&gt;Walk the decay:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Day one.&lt;/strong&gt; Someone records winter hours in January. &lt;code&gt;-07:00&lt;/code&gt; is accurate. Every query agrees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Second Sunday in March.&lt;/strong&gt; The zone shifts to &lt;code&gt;-06:00&lt;/code&gt;. The stored row still says &lt;code&gt;-07:00&lt;/code&gt;. Every generated instant is now an hour late in real terms, so the location appears to open at 08:30 to anyone rendering from the stored offset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Someone notices.&lt;/strong&gt; A patch job updates the offsets in spring. Now the November transition breaks them the other way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A rule change lands.&lt;/strong&gt; A jurisdiction changes its transition dates or abandons the change entirely. With a zone id, you consume a &lt;code&gt;tzdata&lt;/code&gt; release and you are done. With stored offsets, you own a data migration and a backfill for every future-dated record you already emitted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is a live design pressure in Alberta specifically. The province has revisited the twice-yearly change more than once, including a 2021 referendum on permanent daylight time that was defeated. Nothing has changed, and I am not going to pretend otherwise — clocks in Calgary still move. But the &lt;em&gt;possibility&lt;/em&gt; of a legislative change is exactly the argument for storing &lt;code&gt;America/Edmonton&lt;/code&gt; rather than &lt;code&gt;-07:00&lt;/code&gt;. A zone id makes a future policy shift somebody else's release note. A stored offset makes it your outage.&lt;/p&gt;

&lt;p&gt;A subtler variant of the same mistake is persisting the &lt;em&gt;instant&lt;/em&gt; instead of the rule. Materializing "opens at 2026-11-04T14:30:00Z" for every future date bakes today's &lt;code&gt;tzdata&lt;/code&gt; predictions into your database, and those predictions are forecasts of current legislation rather than physical constants. Materialized instants are a cache, and like every cache they need an invalidation story tied to the &lt;code&gt;tzdata&lt;/code&gt; version that produced them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Local Time That Never Happens
&lt;/h2&gt;

&lt;p&gt;Between 02:00:00 and 02:59:59 local on 8 March 2026, &lt;code&gt;America/Edmonton&lt;/code&gt; has no valid local time. The clock jumps from 01:59:59 MST straight to 03:00:00 MDT. Here is the transition at second granularity, printed from UTC so nothing is hidden:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yyc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2026-03-08T01:59:59-07:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yyc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2026-03-08T03:00:00-06:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the dangerous part. Python's &lt;code&gt;zoneinfo&lt;/code&gt; does not raise when you construct a local time inside that hole. It returns something, and what it returns depends on the &lt;code&gt;fold&lt;/code&gt; attribute in a way that surprises nearly everyone the first time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ghost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yyc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcoffset&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tzname&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;61200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;MST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2026-03-08T09:15:00+00:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yyc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2026-03-08T03:15:00-06:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that last line again. You constructed 02:15, converted to UTC, converted straight back, and got 03:15. The round-trip is not the identity. With &lt;code&gt;fold=1&lt;/code&gt; the same wall time resolves an hour the &lt;em&gt;other&lt;/em&gt; way, to 01:15 local. PEP 495 defines this precisely — in a gap, &lt;code&gt;fold=0&lt;/code&gt; uses the offset in effect &lt;em&gt;before&lt;/em&gt; the transition and &lt;code&gt;fold=1&lt;/code&gt; uses the offset &lt;em&gt;after&lt;/em&gt; — but "defined" and "what you wanted" are different things, and a silent 60-minute swing in either direction is not a good default for a system that quotes finish times.&lt;/p&gt;

&lt;p&gt;Different ecosystems handle the same hole differently, which is worth knowing if your stack is polyglot. Java's &lt;code&gt;java.time&lt;/code&gt; shifts a gap time &lt;em&gt;forward&lt;/em&gt; by the length of the gap, so 02:15 becomes 03:15 and it is documented. The Temporal proposal in JavaScript makes you choose with a &lt;code&gt;disambiguation&lt;/code&gt; option and defaults to &lt;code&gt;'compatible'&lt;/code&gt;, which also shifts forward. Python hands you a value and a &lt;code&gt;fold&lt;/code&gt; flag and trusts you to have read the PEP. None of these are wrong. They are simply different policies, and if you do not pick one deliberately, you have inherited three.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Local Time That Happens Twice
&lt;/h2&gt;

&lt;p&gt;The autumn transition is the mirror image and it is worse, because nothing looks anomalous. On 2 November 2025, the local time 01:30 occurred twice in &lt;code&gt;America/Edmonton&lt;/code&gt;, sixty minutes apart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;early&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yyc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;late&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yyc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;early&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tzname&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;early&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;MDT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2025-11-02T07:30:00+00:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;late&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tzname&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;late&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;MST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2025-11-02T08:30:00+00:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;late&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;early&lt;/span&gt;
&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are legitimate instants. Both render identically in a local-time UI. If two rows in a work-order table say &lt;code&gt;01:30&lt;/code&gt; and one carries &lt;code&gt;fold=0&lt;/code&gt; while the other carries &lt;code&gt;fold=1&lt;/code&gt;, they are an hour apart and every naive equality check will insist they are the same moment. If your ORM drops the &lt;code&gt;fold&lt;/code&gt; bit on the way to the database — and plenty of serialization paths do, because &lt;code&gt;fold&lt;/code&gt; is not part of ISO 8601 — you have silently collapsed two distinct instants into one.&lt;/p&gt;

&lt;p&gt;I used a 2025 date deliberately. It is historical, so it is stable in every &lt;code&gt;tzdata&lt;/code&gt; release you are likely to have, which makes it a safe fixture. Future transitions are predictions, and pinning a test to a prediction is how a green suite turns red on a routine dependency bump.&lt;/p&gt;

&lt;p&gt;Note also that the elapsed-time arithmetic runs the other way in autumn. The same 00:00-to-06:00 local window that shrank to five hours in March expands to seven hours on a fall-back date. A capacity planner that assumes 24-hour days is wrong twice a year in opposite directions, and the autumn error is the friendlier one only because it errs toward having more time than you promised.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolving an Edge on Purpose Instead of by Accident
&lt;/h2&gt;

&lt;p&gt;The fix is to stop treating local-to-instant conversion as a cast and start treating it as a function with a policy argument. Classify first, then resolve according to what the value &lt;em&gt;means&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;zoneinfo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ZoneInfo&lt;/span&gt;

&lt;span class="n"&gt;UTC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocalKind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;UNIQUE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unique&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;AMBIGUOUS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ambiguous&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;NONEXISTENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nonexistent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;local_kind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LocalKind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Classify a naive wall-clock time against a zone&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s transition table.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;early&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;late&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;early&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcoffset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;late&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcoffset&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;LocalKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UNIQUE&lt;/span&gt;
    &lt;span class="n"&gt;round_trip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;early&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;LocalKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AMBIGUOUS&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;round_trip&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;LocalKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NONEXISTENT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The classifier leans on one asymmetry: in an overlap, the wall time survives a UTC round-trip; in a gap, it does not. That is a three-line test with no hard-coded transition dates, and it works for any zone the database knows about, including the half-hour and forty-five-minute offsets that break assumptions elsewhere.&lt;/p&gt;

&lt;p&gt;Resolution then depends on the role the value plays. An opening edge and a closing edge want opposite treatment during an overlap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;OPENING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;opening&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;CLOSING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;closing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_instant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Edge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;local_kind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;LocalKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;LocalKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AMBIGUOUS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;fold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OPENING&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;early&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;late&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;shifted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;late&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcoffset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;early&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcoffset&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;local_kind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shifted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;LocalKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unresolvable wall time &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;naive&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;shifted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Openings take the earliest candidate and closings take the latest, which widens the window across an ambiguous hour. That is the correct bias for &lt;em&gt;describing&lt;/em&gt; hours — you do not want to tell someone the doors are shut while the doors are open. It is the wrong bias for &lt;em&gt;committing&lt;/em&gt; to a finish time, where you want the pessimistic edge. If the same conversion serves both purposes in your codebase, split it. The policy belongs at the boundary between the hours model and whatever is asking.&lt;/p&gt;

&lt;p&gt;The gap branch shifts forward by the size of the jump, matching &lt;code&gt;java.time&lt;/code&gt; and Temporal's compatible mode. A stricter variant clamps to the transition instant itself, so a 02:15 opening resolves to exactly 03:00 rather than 03:15. Both are defensible; the guard clause exists because a jurisdiction with a two-hour jump would land the shifted value in a second anomaly, and I would rather see an exception than a plausible-looking wrong answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked Boundary: Concrete Instants for a Seventy-Five Minute Job
&lt;/h2&gt;

&lt;p&gt;Here is the whole failure mode in one worked example, with every instant written out. Suppose an illustrative seasonal drop-off window runs 01:30 to 04:00 local on 8 March 2026, and a job needs 75 minutes of productive time. Naive wall-clock arithmetic says: start 01:30, finish 02:45, comfortably inside the window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yyc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2026-03-08T08:30:00+00:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;naive_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;naive_end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2026-03-08T03:30:00-06:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;naive_end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding two hours of &lt;code&gt;timedelta&lt;/code&gt; to an aware datetime moved the wall clock two hours and moved the &lt;em&gt;instant&lt;/em&gt; one hour. Python's arithmetic on aware datetimes is wall-clock arithmetic; the offset is re-derived after the addition. So a job the model believed had 120 minutes of runway actually had 60.&lt;/p&gt;

&lt;p&gt;The correct sequence is: resolve to an instant, do the arithmetic on the instant, then render back to local for display.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;true_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yyc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;true_end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2026-03-08T04:30:00-06:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two hours of real work that begins at 01:30 finishes at 04:30 on the clock face, not 03:30. Every customer-visible time in the system is a &lt;em&gt;rendering&lt;/em&gt; of an instant, and every calculation is done on instants. Concretely, for this window:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Naive wall-clock answer&lt;/th&gt;
&lt;th&gt;Instant-correct answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Window 01:30 → 04:00 local&lt;/td&gt;
&lt;td&gt;150 minutes&lt;/td&gt;
&lt;td&gt;90 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latest start for 75 min of work&lt;/td&gt;
&lt;td&gt;02:45 local&lt;/td&gt;
&lt;td&gt;01:45 local (&lt;code&gt;08:45Z&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finish if started 01:30&lt;/td&gt;
&lt;td&gt;02:45 local&lt;/td&gt;
&lt;td&gt;03:45 local (&lt;code&gt;09:45Z&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finish if started 02:00&lt;/td&gt;
&lt;td&gt;03:15 local&lt;/td&gt;
&lt;td&gt;resolution error — 02:00 does not exist&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is the one that catches people. A 30-minute grid over that window generates 02:00 and 02:30 as candidate start times, and both are wall times that never occur. If your grid generator iterates in local time by adding &lt;code&gt;timedelta(minutes=30)&lt;/code&gt; to a naive datetime, it will emit them happily. Generate candidate starts on the &lt;em&gt;instant&lt;/em&gt; timeline and render them to local afterwards, and the ghosts simply never appear, because there is no instant that maps to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layered Rules: Weekly Patterns, Overrides, and Statutory Days
&lt;/h2&gt;

&lt;p&gt;Real opening hours are not one table. They are a stack of assertions made by different people at different times with different authority, and the interesting engineering is in how the stack collapses to an answer.&lt;/p&gt;

&lt;p&gt;At minimum you need these layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Base weekly pattern.&lt;/strong&gt; The default cadence. Lowest authority, always present.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seasonal pattern.&lt;/strong&gt; Extended hours through changeover season, effective over a date range. Overrides the base for the dates it covers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recurring intraday closure.&lt;/strong&gt; A midday break, a shift handover, a weekly stock-count hour. Removes time rather than replacing a day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Statutory holiday set.&lt;/strong&gt; Jurisdiction-scoped, with its own effective range and provenance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-off date override.&lt;/strong&gt; "Closing at noon that Friday." Applies to a single date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emergency closure.&lt;/strong&gt; Highest authority. A furnace failure, a water main, a blizzard that keeps staff off Deerfoot. Wins over everything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two distinct &lt;em&gt;kinds&lt;/em&gt; of layer live in that list, and conflating them is the usual bug. Some layers &lt;strong&gt;replace&lt;/strong&gt; the day's windows outright. Others &lt;strong&gt;cut&lt;/strong&gt; time out of whatever the day already had. A holiday replaces; a lunch break cuts. Modeling both as "a list of windows" and last-write-wins gives you a resolver whose behaviour depends on insertion order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;span_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;opens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;closes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;to_instant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opens&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OPENING&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;to_instant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;closes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLOSING&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve_day&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Collapse a layer stack into concrete open spans for one calendar date.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;ranked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;applies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;spans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;span_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;windows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="n"&gt;cuts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;span_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cut&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;applies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;windows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cuts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cuts&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That helper assumes both edges land on the same calendar date, which is true for the windows above and false for an overnight window; a window whose closing time is less than its opening time needs the closing edge combined with the following day. Handle it in &lt;code&gt;span_for&lt;/code&gt; or forbid it in validation, but do not let it default to a span with &lt;code&gt;end &amp;lt;= start&lt;/code&gt;, because the constructor will reject it at a point far from the rule that caused it.&lt;/p&gt;

&lt;p&gt;The algorithm has exactly two rules and both are stated in the code: the highest-priority applicable &lt;code&gt;replace&lt;/code&gt; layer establishes the day's shape, and every &lt;code&gt;cut&lt;/code&gt; layer &lt;em&gt;at or above that priority&lt;/em&gt; removes time from it. Cuts below the winning base are suppressed. That is a real semantic choice, not an accident, and the next section shows why it bites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Precedence Is a Product Decision, Not an Implementation Detail
&lt;/h2&gt;

&lt;p&gt;Run the resolver over an illustrative stack and watch the consequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;layers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base weekly&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;applies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;windows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;08:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;17:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seasonal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;applies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;windows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;07:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;18:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;midday closure&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cut&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;applies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;windows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12:45&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statutory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;applies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;windows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resolving three dates produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-03-10  [('07:00', '18:00')]
2026-04-03  []
2026-06-10  [('08:00', '12:00'), ('12:45', '17:00')]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On 10 March the midday closure &lt;strong&gt;disappears&lt;/strong&gt;. Its priority of 15 sits below the seasonal layer's 20, so the rule suppresses it. In June, with the seasonal layer inactive and the base weekly layer winning at priority 10, the same closure applies normally.&lt;/p&gt;

&lt;p&gt;Is that right? It depends entirely on what the business meant. If the midday break is a staffing reality that holds regardless of season, its priority is wrong and it should sit at 25. If the seasonal pattern exists precisely because the location runs straight through the middle of the day during changeover, then priority 15 encodes the intent correctly. There is no neutral default. What the code buys you is that the question becomes answerable by reading two integers instead of tracing insertion order through a merge function.&lt;/p&gt;

&lt;p&gt;I would add one more thing to that stack in anything long-lived: every layer carries an &lt;code&gt;author&lt;/code&gt;, an &lt;code&gt;effective_from&lt;/code&gt;, and a free-text &lt;code&gt;reason&lt;/code&gt;. When a resolved day looks wrong six months later, the fastest path to an answer is a resolver that can emit the winning layer's name alongside the spans it produced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alberta's Holiday List Belongs in Data, Not in an If-Statement
&lt;/h2&gt;

&lt;p&gt;Statutory holidays are the layer most likely to be hard-coded, and they are the least suitable for it, because the list is a jurisdictional policy artifact rather than a fact about calendars.&lt;/p&gt;

&lt;p&gt;Alberta's general-holiday list is not the same as Ontario's or British Columbia's. Alberta has Family Day on the third Monday in February. Heritage Day in August is an optional holiday here rather than a general one, which means different employers treat it differently and a single boolean cannot represent the situation. The 30 September observance is a federal holiday and its provincial treatment differs across the country. Several dates are computed rather than fixed — Good Friday moves with the ecclesiastical calendar, and 3 April 2026 falls on it, which is why the resolver above returned an empty span list for that date.&lt;/p&gt;

&lt;p&gt;The design consequences:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Holidays are &lt;strong&gt;rows&lt;/strong&gt;, keyed by jurisdiction, with an effective date range and a source citation. Not a set literal in a module.&lt;/li&gt;
&lt;li&gt;A holiday row asserts &lt;em&gt;that a date is a holiday&lt;/em&gt;, not &lt;em&gt;what the hours are&lt;/em&gt;. Whether the location closes fully, opens short, or ignores the date entirely is a separate policy join. Plenty of service businesses work statutory days.&lt;/li&gt;
&lt;li&gt;Computed dates get materialized into rows by a job with a horizon, and the materialization records which algorithm produced it.&lt;/li&gt;
&lt;li&gt;"Observed on" is its own column. When a fixed-date holiday lands on a weekend, the observed day may shift, and payroll, hours and government offices do not always shift it the same way.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this is exotic. It is the same discipline you would apply to tax rates: the values change by jurisdiction and over time, so they live in versioned data with provenance, and the code reads them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Half-Open Intervals and the Boundary Overlap Bug
&lt;/h2&gt;

&lt;p&gt;Every interval in the system is half-open: &lt;code&gt;[start, end)&lt;/code&gt;. The start instant is included, the end instant is not. This is not stylistic.&lt;/p&gt;

&lt;p&gt;Consider two jobs in one bay, the first ending at 10:00 and the second beginning at 10:00. With closed intervals &lt;code&gt;[start, end]&lt;/code&gt;, both contain the instant 10:00:00.000, so an overlap check reports a conflict on a perfectly legal back-to-back sequence. Loosen the check to fix that, and now genuinely overlapping intervals slip through at the boundary. Teams oscillate between these two bugs for years, usually by sprinkling &lt;code&gt;+ 1 second&lt;/code&gt; into comparisons, which merely relocates the problem to a finer resolution and makes it depend on the precision of the clock and the column type.&lt;/p&gt;

&lt;p&gt;Half-open intervals dissolve it. The overlap predicate becomes a strict two-way inequality with no epsilon anywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
    &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__post_init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Span requires aware datetimes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;empty or inverted span: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; .. &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;overlaps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Span&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;moment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;moment&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two consequences fall out immediately. Adjacent spans do not overlap, so &lt;code&gt;[08:00, 10:00)&lt;/code&gt; and &lt;code&gt;[10:00, 11:00)&lt;/code&gt; coexist. And the durations of a partition sum exactly to the whole, with no double-counted boundary instants — which is what makes utilization arithmetic add up instead of drifting by a second per split.&lt;/p&gt;

&lt;p&gt;The constructor rejects zero-length spans on purpose. A zero-length span is almost always a symptom: a date override that collapsed, a subtraction that consumed a window entirely, a parse that produced &lt;code&gt;17:00&lt;/code&gt; for both edges. Letting it exist means it propagates into a &lt;code&gt;merge&lt;/code&gt; and produces output that is hard to reason about. Rejecting it at the boundary turns a silent data problem into a loud one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Merging, Subtracting, Intersecting
&lt;/h2&gt;

&lt;p&gt;Three operations cover essentially every availability question. Merge normalizes an unordered pile of spans into a canonical disjoint set. Subtract removes busy time from open time. Intersect finds where two independently-constrained resources are simultaneously free.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;cuts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pieces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cut&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cuts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;nxt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pieces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;overlaps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cut&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="n"&gt;nxt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;nxt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;nxt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;pieces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nxt&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pieces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;=&lt;/code&gt; in &lt;code&gt;merge&lt;/code&gt; is deliberate and it is the opposite of the &lt;code&gt;&amp;lt;&lt;/code&gt; in &lt;code&gt;overlaps&lt;/code&gt;. Adjacent spans do not &lt;em&gt;overlap&lt;/em&gt;, but they should &lt;em&gt;coalesce&lt;/em&gt;: &lt;code&gt;[08:00, 10:00)&lt;/code&gt; and &lt;code&gt;[10:00, 11:00)&lt;/code&gt; merge into &lt;code&gt;[08:00, 11:00)&lt;/code&gt; because the union is contiguous. Getting these two comparisons backwards produces a system that either reports phantom conflicts or emits a fragmented free list where every 15-minute increment is its own span.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;subtract&lt;/code&gt; is where the half-open discipline pays off visibly. When a cut lands strictly inside a span you get two pieces and the boundary instants land in exactly one of them. When a cut consumes a span entirely, both &lt;code&gt;if&lt;/code&gt; branches fail and the piece vanishes without producing an empty &lt;code&gt;Span&lt;/code&gt; that the constructor would reject.&lt;/p&gt;

&lt;p&gt;Intersection composes the same primitives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;intersect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quadratic, and completely fine at the scale of one location for one day. If you ever run it across a year of half-hour slots for a fleet of locations, a sweep-line over sorted boundaries gets you to linearithmic, but reach for that when a profiler asks you to and not before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work Duration Versus Elapsed Duration
&lt;/h2&gt;

&lt;p&gt;A job has at least four durations and they are all different numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Work duration&lt;/strong&gt; is hands-on time. &lt;strong&gt;Buffer&lt;/strong&gt; is the pull-in, paperwork, torque check and pull-out around it. &lt;strong&gt;Calendar duration&lt;/strong&gt; is how much wall time the job occupies, which exceeds work plus buffer whenever the job pauses. &lt;strong&gt;Customer-perceived duration&lt;/strong&gt; is arrival to keys-back, which includes the wait before anyone touches the vehicle.&lt;/p&gt;

&lt;p&gt;A model that stores one number called &lt;code&gt;duration&lt;/code&gt; will eventually be asked to be all four, and the resulting bugs hide well because every individual answer looks plausible.&lt;/p&gt;

&lt;p&gt;Concretely: a seasonal changeover with a rebalance is a different shape of job from a puncture repair, which is a different shape again from an oil change. The &lt;a href="https://calgaryrimandtire.ca/seasonal-tire-changes" rel="noopener noreferrer"&gt;seasonal changeover&lt;/a&gt; work involves four wheel positions with torque and pressure steps; a &lt;a href="https://calgaryrimandtire.ca/wheel-balancing" rel="noopener noreferrer"&gt;balance&lt;/a&gt; adds spin-up time per wheel that scales with how far out the assembly is; a &lt;a href="https://calgaryrimandtire.ca/tire-repair-in-calgary" rel="noopener noreferrer"&gt;puncture repair&lt;/a&gt; has an inspection step that can terminate the job early with a "not repairable" verdict. These are the tire and oil-change services the location performs, and each one has a different variance profile. A scheduler that treats them as interchangeable 60-minute blocks will be wrong in a different direction for each.&lt;/p&gt;

&lt;p&gt;The modeling advice is unglamorous: store &lt;code&gt;work_minutes&lt;/code&gt; and &lt;code&gt;buffer_minutes&lt;/code&gt; as separate fields, derive calendar duration from the resolved open spans at query time, and never persist calendar duration as an attribute of the service type, because it is a function of &lt;em&gt;when&lt;/em&gt; the job runs and not of &lt;em&gt;what&lt;/em&gt; the job is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Last Viable Start Is Not Closing Time
&lt;/h2&gt;

&lt;p&gt;This is the single most common off-by-a-job bug in service scheduling, and it survives in production systems for years because it only manifests near the end of the day.&lt;/p&gt;

&lt;p&gt;If the location closes at 17:00 and a job needs 75 minutes of work plus 10 minutes of buffer, the last instant at which that job can start is 15:35. Not 17:00, and not 15:45 either — the buffer counts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cutoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;buffer&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cutoff&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cutoff&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;None&lt;/code&gt; return matters as much as the arithmetic. A window shorter than the job produces no viable start at all, and that is a normal condition rather than an error. A 30-minute gap between two committed jobs is real open time and it is unusable for a 75-minute job. Systems that return the window's start anyway, on the theory that "the location is open then," produce reservations that cannot finish.&lt;/p&gt;

&lt;p&gt;Running the primitives over an illustrative day makes it concrete. Take a resolved day of &lt;code&gt;[07:00, 18:00)&lt;/code&gt; with a midday cut of &lt;code&gt;[12:00, 12:45)&lt;/code&gt;, and a job of 75 minutes of work plus 10 minutes of buffer on a 15-minute grid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;free windows      : 07:00–12:00, 12:45–18:00
latest start (am) : 10:35
latest start (pm) : 16:35
grid starts       : 07:00, 07:15, ... 10:30  |  12:45, 13:00, ... 16:30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The grid truncates below the true cutoff, because 10:35 is not on a 15-minute boundary. That is the correct behaviour for an offered set of start times, but be aware that you are discarding five usable minutes in each window. Whether that matters depends on how tight capacity is; during a changeover rush it might, and a scheduler that offers off-grid start times when the grid would otherwise return nothing is a legitimate refinement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jobs That May Pause and Jobs That May Not
&lt;/h2&gt;

&lt;p&gt;Some work can straddle a midday closure. A vehicle sitting on a hoist while staff are away is not necessarily a problem. Other work cannot: a job holding a shared resource, or one with a chemical or thermal step that has to run continuously, has to finish before the pause begins.&lt;/p&gt;

&lt;p&gt;Two predicates, therefore, not one. Non-pausable jobs must fit entirely inside a single free span. Pausable jobs may consume time across several spans, and their calendar end is computed by walking productive time forward while skipping pauses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;finish_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pauses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Advance `work` of productive time from `start`, stepping over pauses.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pause&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pauses&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="n"&gt;productive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;productive&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;productive&lt;/span&gt;
        &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Started at 11:30 with 75 minutes of work and a &lt;code&gt;[12:00, 12:45)&lt;/code&gt; pause, this returns 13:30 — a calendar span of two hours for 75 minutes of work. That gap between 75 and 120 is exactly the number a customer-facing estimate needs and a technician-facing estimate does not.&lt;/p&gt;

&lt;p&gt;The pausable flag belongs on the service type, and its default should be &lt;code&gt;False&lt;/code&gt;. Wrongly marking a job pausable produces a promise the location cannot keep; wrongly marking it non-pausable costs you some scheduling density. Those are not symmetric costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capacity as Constrained Resources: Bays, Technicians, a Hoist
&lt;/h2&gt;

&lt;p&gt;Here is where "are we open" stops being sufficient. The location can be open, have an empty free window, and still be unable to take the job, because open hours and capacity are different predicates over different objects.&lt;/p&gt;

&lt;p&gt;Model each physical or human constraint — every bay, every technician, the hoist — as its own resource with its own busy list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bay-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bay"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"changeover"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repair"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"oil"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bay-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bay"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"changeover"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repair"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hoist-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hoist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"oil"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tech-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tech"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"changeover"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repair"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"oil"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tech-b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tech"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"changeover"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requirements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"changeover"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"bay"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tech"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"oil"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"bay"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tech"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hoist"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Illustrative resource inventory — invented for the model, not a description of any real floor plan or staffing level.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Availability for a job type is then the union, over every valid assignment of resources, of the intersection of those resources' free time with the open windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;joint_windows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;open_spans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;busy_by_resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;need&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;open_spans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;busy_by_resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;need&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;nxt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;intersect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nxt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nxt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With an open day of &lt;code&gt;[07:00, 18:00)&lt;/code&gt;, &lt;code&gt;bay-1&lt;/code&gt; busy 09:00–11:00, &lt;code&gt;bay-2&lt;/code&gt; busy 07:00–10:00, and &lt;code&gt;tech-a&lt;/code&gt; busy 07:00–09:30, the joint availability is &lt;code&gt;[10:00, 18:00)&lt;/code&gt;. Neither bay alone explains that answer and neither does the technician. It falls out of the combination, which is the whole point.&lt;/p&gt;

&lt;p&gt;Three refinements this sketch omits and a production model would want. Resources have their own hours — a part-timer's shift is a smaller window than the day's. Some resources are shared &lt;em&gt;across&lt;/em&gt; concurrent jobs rather than exclusive to one. And the assignment search is a matching problem that grows unpleasant with resource count, so past a certain size you stop enumerating and start solving. Even the naive version, though, gets you the crucial distinction: the difference between "open" and "available" is a join, and code that skips the join over-promises.&lt;/p&gt;

&lt;p&gt;Real constraints leak in here too, and they are the reason a model like this earns its complexity. Work that happens at a customer's site through a &lt;a href="https://calgaryrimandtire.ca/mobile-tire-service" rel="noopener noreferrer"&gt;mobile service&lt;/a&gt; route consumes a technician without consuming a bay, and adds travel time that belongs in the buffer. &lt;a href="https://calgaryrimandtire.ca/fleet-management" rel="noopener noreferrer"&gt;Fleet work&lt;/a&gt; arrives in batches that need several resources at once and behaves nothing like a single-vehicle reservation. The &lt;a href="https://calgaryrimandtire.ca/service-areas" rel="noopener noreferrer"&gt;geographic areas&lt;/a&gt; a location serves determine how much travel buffer a mobile job needs. Any of these can break a model that assumed one job means one bay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shape of the Availability Response
&lt;/h2&gt;

&lt;p&gt;The API is where all this either stays honest or leaks. The rule I would enforce: &lt;strong&gt;the server does the zone math, always, and the client is never asked to reconstruct an instant from a local string.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"location_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"loc-yyc-main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"service_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"changeover"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date_local"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-08"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rule_set_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hours-v7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tzdata_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026c"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"zone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/Edmonton"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"open_spans"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-08T14:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-08T20:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"local_start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"07:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"local_end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"13:00"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"instant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-08T14:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"local"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"07:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-07:00"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"instant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-08T14:30:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"local"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"07:30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-07:00"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"notes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dst_gap_skipped"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local 02:00–03:00 does not exist on this date"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Illustrative payload. Values are constructed to show the shape.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Four properties are doing work. Every time appears as an unambiguous instant &lt;em&gt;and&lt;/em&gt; as a pre-rendered local string, so the client displays a string and reasons with an instant, and never converts. The &lt;code&gt;offset&lt;/code&gt; field is present for display and debugging but is never the source of truth. The &lt;code&gt;rule_set_id&lt;/code&gt; and &lt;code&gt;tzdata_version&lt;/code&gt; make the response reproducible. And &lt;code&gt;notes&lt;/code&gt; gives the resolver somewhere to explain itself, which is the difference between a support conversation that takes two minutes and one that takes a week.&lt;/p&gt;

&lt;p&gt;Return open spans &lt;em&gt;and&lt;/em&gt; startable instants as separate fields. They answer different questions — one is "are the doors open," the other is "can this specific job begin here" — and a client that has both can render a sensible UI without re-deriving the second from the first and getting the buffer wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versioning the Rule Set So an Old Quote Can Be Explained
&lt;/h2&gt;

&lt;p&gt;Hours change. Seasonal patterns turn on and off, statutory rows get corrected, a one-off override gets added and later removed. If the rule set is mutable in place, then every quote you issued before the change becomes unexplainable, because the only way to justify it is to reconstruct rules that no longer exist.&lt;/p&gt;

&lt;p&gt;Make the rule set immutable and content-addressed. Changing hours writes a new version and flips a pointer. A quote records the &lt;code&gt;rule_set_id&lt;/code&gt; and the &lt;code&gt;tzdata_version&lt;/code&gt; it resolved against. Then "why did the system offer 16:45 on that Thursday" becomes a query rather than an argument.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;tzdata&lt;/code&gt; version deserves its own field. Resolve a date six months out, let the zone database change that transition, and your stored instant will disagree with a freshly-resolved one without either being a bug. Recording the version turns that from a mystery into a diff — and in containerized deployments it also explains why two replicas built from different base images resolved the same future date differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Edge-Case Table Worth Pinning to the Wall
&lt;/h2&gt;

&lt;p&gt;These are the inputs I would put in a fixture file before writing a line of resolver code.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Case&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Expected behaviour&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Spring gap, opening edge&lt;/td&gt;
&lt;td&gt;wall 02:15 on a gap date&lt;/td&gt;
&lt;td&gt;shift forward, never silently pick an offset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring gap, grid generation&lt;/td&gt;
&lt;td&gt;30-min grid across 01:30–04:00&lt;/td&gt;
&lt;td&gt;02:00 and 02:30 never emitted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fall overlap, opening edge&lt;/td&gt;
&lt;td&gt;wall 01:30, ambiguous&lt;/td&gt;
&lt;td&gt;earliest instant (&lt;code&gt;fold=0&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fall overlap, closing edge&lt;/td&gt;
&lt;td&gt;wall 01:30, ambiguous&lt;/td&gt;
&lt;td&gt;latest instant (&lt;code&gt;fold=1&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overnight window&lt;/td&gt;
&lt;td&gt;22:00 → 02:00 next day&lt;/td&gt;
&lt;td&gt;one span crossing midnight, not two&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero-length window&lt;/td&gt;
&lt;td&gt;17:00 → 17:00&lt;/td&gt;
&lt;td&gt;rejected at construction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inverted window&lt;/td&gt;
&lt;td&gt;17:00 → 08:00 same day&lt;/td&gt;
&lt;td&gt;rejected, or explicitly overnight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cut consumes window&lt;/td&gt;
&lt;td&gt;closure covers the whole day&lt;/td&gt;
&lt;td&gt;empty free list, not an empty span&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adjacent jobs&lt;/td&gt;
&lt;td&gt;10:00 end, 10:00 start&lt;/td&gt;
&lt;td&gt;no conflict&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Job longer than window&lt;/td&gt;
&lt;td&gt;90 min into a 45 min gap&lt;/td&gt;
&lt;td&gt;no viable start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Holiday plus override&lt;/td&gt;
&lt;td&gt;both apply to one date&lt;/td&gt;
&lt;td&gt;highest priority wins, deterministically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leap day&lt;/td&gt;
&lt;td&gt;29 Feb on a weekly rule&lt;/td&gt;
&lt;td&gt;resolves like any other date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two cuts overlapping&lt;/td&gt;
&lt;td&gt;12:00–12:45 and 12:30–13:00&lt;/td&gt;
&lt;td&gt;merged before subtraction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most of these are one assertion each. All of them are cheap to write and expensive to discover in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Property Tests Across a Transition
&lt;/h2&gt;

&lt;p&gt;Example-based tests catch the cases you thought of. Properties catch the ones you did not. Four hold for any correct implementation of this model, in any zone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Round-trip stability.&lt;/strong&gt; For any instant &lt;code&gt;t&lt;/code&gt;, rendering to local and resolving back yields &lt;code&gt;t&lt;/code&gt; — except in an overlap, where the resolved value must be one of the two candidates and the choice must match the declared edge policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monotonicity.&lt;/strong&gt; If &lt;code&gt;t1 &amp;lt; t2&lt;/code&gt; as instants, then resolving both through the same pipeline preserves the ordering. This is the property that catches offset-based comparison bugs, because a naive implementation that compares wall-clock strings will invert the order across a fall-back hour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Duration conservation.&lt;/strong&gt; Subtracting a set of cuts from a set of spans and summing the durations of the result equals the total base duration minus the duration of the intersection of base and cuts. Off-by-one boundary handling breaks this immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotence.&lt;/strong&gt; &lt;code&gt;merge(merge(xs)) == merge(xs)&lt;/code&gt;, and subtracting the same cut twice equals subtracting it once.&lt;/p&gt;

&lt;p&gt;A generator that concentrates on the interesting region is worth more than one that samples a decade uniformly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hypothesis&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategies&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;

&lt;span class="n"&gt;TRANSITION_DATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="n"&gt;near_transition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sampled_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRANSITION_DATES&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;near_transition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_merge_is_idempotent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;spans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_day_spans&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uniform sampling across a year hits a DST boundary in roughly one draw in four thousand at minute granularity. Sampling the six hours around known transitions hits it constantly. Bias the generator toward the cliff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Golden Files, Pinned tzdata, and Frozen Clocks
&lt;/h2&gt;

&lt;p&gt;Three testing disciplines, each catching a different class of failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Golden files.&lt;/strong&gt; For a fixed rule set and a fixed list of dates, serialize the resolved spans to a checked-in file, so any resolver change that alters output arrives as a reviewable diff. Keep one per zone, and include at least one zone that is &lt;em&gt;not&lt;/em&gt; your primary — something on a southern-hemisphere schedule, or with a half-hour offset, catches assumptions that &lt;code&gt;America/Edmonton&lt;/code&gt; alone never exercises.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pinned &lt;code&gt;tzdata&lt;/code&gt;.&lt;/strong&gt; Add the &lt;code&gt;tzdata&lt;/code&gt; package as an explicit dependency and pin it, or your suite's correctness depends on whatever zone database the base image shipped, and a routine rebuild changes behaviour with no commit to blame. Assert the version in a test so an unintended bump fails loudly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frozen clocks.&lt;/strong&gt; Every function here should take "now" as a parameter rather than reading it. Once &lt;code&gt;now&lt;/code&gt; is an argument, "what does availability return at 16:50 on a Friday in changeover season" is a unit test rather than a stakeout. Where an ambient clock is unavoidable, freeze it at an &lt;em&gt;interesting&lt;/em&gt; instant — one inside an ambiguous hour — not at a comfortable Tuesday afternoon.&lt;/p&gt;

&lt;p&gt;One more habit: write tests that assert the &lt;em&gt;instant&lt;/em&gt;, not the local rendering. A test that asserts &lt;code&gt;'07:00'&lt;/code&gt; passes in both the correct and incorrect implementations across a transition. A test that asserts &lt;code&gt;'2026-03-08T14:00:00Z'&lt;/code&gt; does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Modes, Named
&lt;/h2&gt;

&lt;p&gt;Naming a bug class makes it easier to spot in review. These are the ones I would put on a checklist.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Offset fossilization.&lt;/strong&gt; A stored offset that was correct at write time and silently wrong after the next transition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ghost slot emission.&lt;/strong&gt; Candidate start times generated by adding to naive local datetimes, producing wall times inside a gap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fold amnesia.&lt;/strong&gt; Serialization that discards &lt;code&gt;fold&lt;/code&gt;, collapsing two distinct instants during an overlap into one indistinguishable value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wall-clock arithmetic.&lt;/strong&gt; Adding &lt;code&gt;timedelta&lt;/code&gt; to an aware datetime and treating the result as elapsed time, as in the two-hour addition that advanced the instant by one hour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boundary conflict.&lt;/strong&gt; Closed intervals reporting a conflict between a job ending at 10:00 and one starting at 10:00.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closing-time promise.&lt;/strong&gt; Offering a start time that equals the last open instant minus nothing, so the job cannot finish before the doors shut.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layer order dependence.&lt;/strong&gt; A resolver whose output depends on the order rules were inserted rather than a declared precedence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Holiday hard-coding.&lt;/strong&gt; A jurisdiction's statutory list embedded in code, so a correction requires a deploy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Availability conflated with openness.&lt;/strong&gt; Returning open windows as though they were bookable capacity, ignoring resource constraints entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these produces answers that look right in a screenshot. That is what makes them expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Abstraction Meets the Bay Floor
&lt;/h2&gt;

&lt;p&gt;The reason this domain rewards careful modeling is that the physical operation it describes is genuinely irregular, and the irregularity is not noise you can average away.&lt;/p&gt;

&lt;p&gt;Demand is not uniform. Changeover season concentrates an enormous share of the year's &lt;a href="https://calgaryrimandtire.ca/winter-tires-calgary" rel="noopener noreferrer"&gt;winter tire&lt;/a&gt; work into a few weeks, driven by weather that does not consult a calendar. A model whose capacity assumptions are calibrated on a quiet July week will be catastrophically wrong in late October, and it will be wrong in the direction of over-promising. That is the direction that costs trust.&lt;/p&gt;

&lt;p&gt;Job durations are not uniform either. A four-wheel changeover on a passenger vehicle and a wheel-off inspection on a light truck consume different amounts of the same resources. Whether a customer runs &lt;a href="https://calgaryrimandtire.ca/all-weather-tires-calgary" rel="noopener noreferrer"&gt;all-weather tires&lt;/a&gt; year-round or swaps twice annually changes how often they appear in the schedule at all. Understanding &lt;a href="https://calgaryrimandtire.ca/be-tire-smart" rel="noopener noreferrer"&gt;the basics of what a tire is telling you&lt;/a&gt; is what lets a service writer estimate a duration accurately, and an estimate is the input the whole model depends on. Garbage duration estimates produce a beautifully correct interval algebra over meaningless numbers.&lt;/p&gt;

&lt;p&gt;Walk-ins break the closed-world assumption outright. Any model that treats the schedule as fully known is describing a system that does not exist, which is why real deployments reserve capacity rather than filling it — and why the &lt;a href="https://calgaryrimandtire.ca/online-bookings" rel="noopener noreferrer"&gt;online reservation form&lt;/a&gt; and the counter queue are two producers writing to one resource pool. Sizing that reservation is a forecasting problem sitting on top of the availability model, and it needs the availability model to be correct before it can be tuned at all.&lt;/p&gt;

&lt;p&gt;There is a documentation angle too. A service writer who knows why the system refused a 16:45 start can explain it in one sentence instead of apologizing for software. A technician who can explain a &lt;a href="https://calgaryrimandtire.ca/tire-load-index-explained" rel="noopener noreferrer"&gt;load index&lt;/a&gt; or why a &lt;a href="https://calgaryrimandtire.ca/commercial-tire-services" rel="noopener noreferrer"&gt;commercial job&lt;/a&gt; needs different resourcing is doing exactly what the &lt;code&gt;notes&lt;/code&gt; field does in the API response: turning a refusal into a reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Build First
&lt;/h2&gt;

&lt;p&gt;If I were starting this model tomorrow, the order would be: &lt;code&gt;Span&lt;/code&gt; with half-open semantics and a validating constructor; &lt;code&gt;merge&lt;/code&gt;, &lt;code&gt;subtract&lt;/code&gt; and &lt;code&gt;intersect&lt;/code&gt; with property tests; &lt;code&gt;local_kind&lt;/code&gt; and a policy-carrying &lt;code&gt;to_instant&lt;/code&gt;; then the layered resolver; then resources; then the API surface. Availability queries come last, because they are a composition of everything below them and they are the hardest thing to debug if any layer beneath is subtly wrong.&lt;/p&gt;

&lt;p&gt;The single highest-leverage decision is the first one. Store wall-clock times with an IANA zone identifier, never an offset, and treat local-to-instant conversion as a function that takes a policy argument and can fail. Systems built that way have DST bugs that are annoying. Systems built the other way have DST bugs that are archaeological.&lt;/p&gt;

&lt;p&gt;And keep the six-hour window that holds five hours somewhere visible. It is a two-line REPL session that disproves the intuition the entire naive design rests on, and it costs nothing to re-run whenever someone proposes storing an offset "just for the cache."&lt;/p&gt;

</description>
      <category>automotive</category>
      <category>calgary</category>
      <category>maintenance</category>
      <category>tires</category>
    </item>
    <item>
      <title>Staleness Budgets: Designing Freshness Guarantees for Supplier Price and Stock Feeds</title>
      <dc:creator>KMJ Tire Calgary</dc:creator>
      <pubDate>Mon, 17 Aug 2026 16:18:17 +0000</pubDate>
      <link>https://dev.to/kmjtirecalgary/staleness-budgets-designing-freshness-guarantees-for-supplier-price-and-stock-feeds-g5</link>
      <guid>https://dev.to/kmjtirecalgary/staleness-budgets-designing-freshness-guarantees-for-supplier-price-and-stock-feeds-g5</guid>
      <description>&lt;h1&gt;
  
  
  Staleness Budgets: Designing Freshness Guarantees for Supplier Price and Stock Feeds
&lt;/h1&gt;

&lt;p&gt;Every integration that reads another company's inventory is a claim about the past dressed up as a claim about the present. You cached a number at 06:04. A customer reads it at 14:52. Somewhere in between, a warehouse in Edmonton shipped the last four casings of that size to somebody else, and nobody told you.&lt;/p&gt;

&lt;p&gt;The usual reaction is to shorten the refresh interval and move on. That reaction is wrong in an interesting way: it treats freshness as a knob rather than as a property you can specify, measure, and hold yourself accountable to. What follows is a design for treating it as the latter — a &lt;em&gt;staleness budget&lt;/em&gt; per field class, derived from measured change rates and from the cost of being wrong at a specific decision point.&lt;/p&gt;

&lt;p&gt;Concrete setting, because abstractions about "entities" get slippery fast: a tire retailer pulls sizes, brands, availability and landed cost from several distributor feeds. Someone asks whether a 225/45R17 can go on the vehicle Thursday. Answering requires three facts that belong to other companies and one that belongs to you. Everything below is a design exercise. All numbers in code, tables and worked examples are &lt;strong&gt;illustrative and synthetic&lt;/strong&gt; — placeholders chosen to make the arithmetic legible, never real supplier data, real cost, or real availability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The only honest question about somebody else's inventory
&lt;/h2&gt;

&lt;p&gt;"Is it available?" is not answerable. It is a question about the current state of a system you cannot observe, and the observation you &lt;em&gt;can&lt;/em&gt; make takes 200 milliseconds to travel and arrives describing a moment that has already passed.&lt;/p&gt;

&lt;p&gt;The answerable version is: &lt;em&gt;given that I last observed this value N seconds ago, what is the probability it has since changed, and what does being wrong cost me here?&lt;/em&gt; Both halves matter. A landed cost that drifted a little on a browse page costs mild embarrassment. The same drift on a confirmed reservation costs either margin or a phone conversation nobody enjoys. Same data, same age, completely different tolerance.&lt;/p&gt;

&lt;p&gt;So the unit of design is not the cache. It is the pair (field class, decision). A staleness budget is the maximum age at which a value may still be used for a &lt;em&gt;particular&lt;/em&gt; decision, chosen so the probability of being wrong stays under a target you picked on purpose. Write that budget down. Enforce it in code. Alarm when you violate it. Everything else in this article is machinery for making that sentence true.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model freshness as data, not as a boolean
&lt;/h2&gt;

&lt;p&gt;The first structural mistake is storing &lt;code&gt;available: true&lt;/code&gt;. The second is storing &lt;code&gt;available: true, updated_at: &amp;lt;timestamp&amp;gt;&lt;/code&gt; and treating those two columns as unrelated. They are one value. The timestamp is not metadata; it is half the meaning.&lt;/p&gt;

&lt;p&gt;Three distinct instants deserve separate columns, and collapsing them will cost you a debugging afternoon eventually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;observed_at&lt;/code&gt; — when &lt;em&gt;your&lt;/em&gt; process received the bytes. This is about your pipeline. It is the only one of the three you can fully trust, because you generated it from your own clock.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;source_as_of&lt;/code&gt; — the instant the supplier asserts the value was true. Present in good feeds, absent in most. Untrusted input: clamp it, never let it exceed &lt;code&gt;observed_at&lt;/code&gt; by more than a small skew allowance, and record when you had to clamp it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;valid_until&lt;/code&gt; — a supplier-asserted expiry, when they publish one. Rare, and worth honouring when it appears, because it is the only case where somebody else has told you their own budget.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why not one column? Because retries, queue backlogs and replays decouple them. A delta message produced at 06:04 that sat in a stalled consumer until 09:30 has &lt;code&gt;source_as_of = 06:04&lt;/code&gt; and &lt;code&gt;observed_at = 09:30&lt;/code&gt;. If you kept only one, you would either believe the data is three hours fresher than it is, or you would discard a perfectly good historical fact. You need both to compute age &lt;em&gt;and&lt;/em&gt; to detect pipeline lag, which are different alarms with different owners.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Generic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TypeVar&lt;/span&gt;

&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TypeVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FieldClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;PRICE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;AVAILABILITY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;availability&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;LEAD_TIME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lead_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;DESCRIPTIVE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;descriptive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Observed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Generic&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;A value plus everything needed to reason about how much to trust it.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;
    &lt;span class="n"&gt;field_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FieldClass&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;observed_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
    &lt;span class="n"&gt;source_as_of&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;valid_until&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;effective_as_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Prefer the supplier&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s assertion, but never let it run ahead of us.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_as_of&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observed_at&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_as_of&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observed_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_as_of&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;expired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_until&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_until&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the wire, make "we do not know" representable. A boolean plus an optional timestamp lets an unknown masquerade as a fresh &lt;code&gt;false&lt;/code&gt;, and a discriminated union does not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FieldClass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;availability&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;leadTime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;descriptive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Observed&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;fieldClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FieldClass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;observedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// RFC 3339, always UTC, generated by us&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;sourceAsOf&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// supplier's own assertion, when published&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;validUntil&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// supplier-asserted expiry, when published&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Freshness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;ageSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;ageSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;budgetSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;never-observed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;upstream-down&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Answer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observed&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;freshness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Freshness&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;unknown&lt;/code&gt; variant is the whole point. A downstream renderer that has to destructure it cannot accidentally paint a confident green badge over a supplier outage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field classes decay at very different rates
&lt;/h2&gt;

&lt;p&gt;Lump every attribute of a product into one record and you have implicitly declared that they all rot at the same speed. They do not, and the spread is enormous.&lt;/p&gt;

&lt;p&gt;A tire's section width, aspect ratio and rim diameter never change for a given part number — those are identity, not state. The &lt;a href="https://calgaryrimandtire.ca/tire-sidewall-information" rel="noopener noreferrer"&gt;markings moulded into the sidewall&lt;/a&gt; are fixed at manufacture. A &lt;a href="https://calgaryrimandtire.ca/tire-load-index-explained" rel="noopener noreferrer"&gt;load index&lt;/a&gt; is a property of the design. If you are polling a supplier every fifteen minutes to re-learn that a tire is still 225 millimetres wide, you are spending your rate limit on the least interesting bytes in the payload.&lt;/p&gt;

&lt;p&gt;Availability, by contrast, is a counter that other people decrement all day. Landed cost changes when a price file lands, when freight terms shift, or when a distributor's own pricing cycle turns over — bursty, not smooth. Lead time is a derived estimate that moves with warehouse routing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field class&lt;/th&gt;
&lt;th&gt;Example fields&lt;/th&gt;
&lt;th&gt;What drives change&lt;/th&gt;
&lt;th&gt;Illustrative budget&lt;/th&gt;
&lt;th&gt;Failure if stale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Identity&lt;/td&gt;
&lt;td&gt;part number, size, construction&lt;/td&gt;
&lt;td&gt;never (new SKU instead)&lt;/td&gt;
&lt;td&gt;effectively unbounded&lt;/td&gt;
&lt;td&gt;wrong product entirely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Descriptive&lt;/td&gt;
&lt;td&gt;tread pattern name, speed rating, load index&lt;/td&gt;
&lt;td&gt;catalogue revisions&lt;/td&gt;
&lt;td&gt;30 days&lt;/td&gt;
&lt;td&gt;mildly wrong copy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lead time&lt;/td&gt;
&lt;td&gt;days to depot, transfer route&lt;/td&gt;
&lt;td&gt;routing and stocking policy&lt;/td&gt;
&lt;td&gt;24 hours&lt;/td&gt;
&lt;td&gt;a missed promise date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;td&gt;landed cost, freight adder&lt;/td&gt;
&lt;td&gt;price-file batches, freight terms&lt;/td&gt;
&lt;td&gt;6 hours&lt;/td&gt;
&lt;td&gt;margin error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability&lt;/td&gt;
&lt;td&gt;units at each depot&lt;/td&gt;
&lt;td&gt;every order anyone places&lt;/td&gt;
&lt;td&gt;20 minutes&lt;/td&gt;
&lt;td&gt;a commitment you cannot honour&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those budgets are placeholders for the shape of the argument, not measured production values. The point is the four orders of magnitude between the top row and the bottom one. Any design that cannot express that spread is throwing away information you already have.&lt;/p&gt;

&lt;p&gt;Field classes also let you split storage sensibly. Descriptive attributes belong in a slowly-changing catalogue table refreshed by the nightly snapshot. Availability belongs in a hot table with a narrow row and an index built for age queries. Mixing them means every availability tick rewrites a row containing a 400-character marketing description, which is a fine way to melt your write amplification.&lt;/p&gt;

&lt;h2&gt;
  
  
  One global TTL is the wrong abstraction
&lt;/h2&gt;

&lt;p&gt;A single TTL forces one number to satisfy every consumer, so it converges on whichever consumer is loudest. Set it to fifteen minutes and 95% of your request budget goes to re-fetching data that changes twice a year. Set it to six hours and you will confidently offer a size that went to zero before lunch.&lt;/p&gt;

&lt;p&gt;There is a subtler problem. A TTL is a property of a &lt;em&gt;record&lt;/em&gt;, but staleness tolerance is a property of an &lt;em&gt;edge&lt;/em&gt; — the specific read, by a specific consumer, for a specific purpose. The same availability row is simultaneously fresh enough for a category listing page and far too old for a confirmation screen. Attaching the number to the row erases that distinction and forces the strictest consumer's requirement onto everyone.&lt;/p&gt;

&lt;p&gt;The third problem is that TTLs are uniform in time while demand is not. In Calgary a chinook can move the temperature twenty degrees in an afternoon, and the volume of people suddenly interested in &lt;a href="https://calgaryrimandtire.ca/seasonal-tire-changes" rel="noopener noreferrer"&gt;changeover work&lt;/a&gt; follows the thermometer with a lag of about a day. During the October ramp, availability on popular winter sizes churns hard; in July, the same rows sit untouched for weeks. A fixed interval is either wasteful in summer or dangerously slack in autumn, and it is usually both, at different times, for different SKUs.&lt;/p&gt;

&lt;p&gt;So: budgets attach to (field class × decision), and refresh &lt;em&gt;scheduling&lt;/em&gt; is a separate concern driven by observed volatility and observed demand. Conflating policy with scheduling is the root error that a global TTL encodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring decay from your own history
&lt;/h2&gt;

&lt;p&gt;You cannot derive a budget from first principles. You derive it from what your own data did last month.&lt;/p&gt;

&lt;p&gt;Keep an append-only change log: every time a poll produces a value different from the stored one, write a row with the supplier, the SKU, the field class, the previous value, the new value, and both timestamps. This log is cheap — it only grows on change — and it is the input to everything else.&lt;/p&gt;

&lt;p&gt;The naive estimator is &lt;code&gt;changes_detected / total_observation_hours&lt;/code&gt;. It is biased low, and knowing &lt;em&gt;why&lt;/em&gt; matters. If you poll every 20 minutes and a value bounces from 4 to 0 to 4 between polls, you record zero changes. Polling gives you interval-censored data: you learn only whether the value moved during a window, never how many times.&lt;/p&gt;

&lt;p&gt;Under a constant-hazard assumption, the correction is one line of algebra. If changes arrive as a Poisson process with rate λ, the probability that at least one change occurs in a window of length &lt;code&gt;T&lt;/code&gt; is &lt;code&gt;1 - exp(-λT)&lt;/code&gt;. Estimate that probability as the fraction of polls that detected a difference, then invert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hazard_from_polls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;polls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changes_detected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Interval-censored estimate of change rate, in changes per second.

    We observe only whether a value differed between two consecutive reads, so a
    raw change count understates reality. Inverting the Poisson survival function
    recovers the underlying rate from the detection fraction.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;polls&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;changes_detected&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;interval_seconds&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="n"&gt;detection_fraction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;changes_detected&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;polls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.999&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;detection_fraction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;interval_seconds&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ttl_for_error_budget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rate_per_second&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Oldest age at which P(value already moved) stays under target_error.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rate_per_second&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inf&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;target_error&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target_error must be strictly between 0 and 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target_error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;rate_per_second&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bucket the estimate. Per-SKU rates over-fit wildly on long-tail parts you have observed nine times; a global rate hides the fact that a common winter size behaves nothing like a niche commercial fitment. Something like (supplier × field class × demand decile) is usually the right granularity, with shrinkage toward the bucket mean for sparse cells.&lt;/p&gt;

&lt;p&gt;And then the honest caveat: constant hazard is a lie you adopt deliberately. Price changes are not memoryless — they cluster at whatever hour the distributor's batch job runs. Availability hazard tracks order flow, which has a weekday shape and a weather shape. Treat the exponential model as a floor for reasoning, then override it with the two things you actually know: known publication times, and known demand spikes.&lt;/p&gt;

&lt;h2&gt;
  
  
  From hazard rate to a number you can defend
&lt;/h2&gt;

&lt;p&gt;With a rate in hand, the budget falls out of a target error probability you choose per decision.&lt;/p&gt;

&lt;p&gt;Survival under constant hazard is &lt;code&gt;P(unchanged after age t) = exp(-λt)&lt;/code&gt;, so the age at which you first exceed an error tolerance ε is &lt;code&gt;t = -ln(1 - ε) / λ&lt;/code&gt;. Small ε makes the numerator approximately ε itself, which gives a useful mental shortcut: &lt;em&gt;the budget is roughly your error tolerance divided by the change rate.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Illustrative arithmetic, synthetic figures throughout. Suppose a popular winter size at one distributor shows a detected-change fraction of 0.08 per 20-minute poll during the autumn ramp. Then λ ≈ -ln(0.92) / 1200 s ≈ 6.95 × 10⁻⁵ per second. For a browse page where you accept a 10% chance of being wrong, the budget is -ln(0.90) / 6.95e-5 ≈ 1,516 seconds, roughly 25 minutes. For a firm commitment where you accept 1%, it collapses to -ln(0.99) / 6.95e-5 ≈ 145 seconds. Same data, same rate, an order of magnitude apart, and the second number is short enough that the only sane implementation is to revalidate at the moment of commitment rather than to poll harder.&lt;/p&gt;

&lt;p&gt;Two refinements that earn their complexity:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asymmetry.&lt;/strong&gt; Not all changes hurt equally. Availability falling to zero is expensive; availability rising from 4 to 9 is free. Landed cost rising costs you margin; falling costs you nothing you will notice today. Estimate one-sided rates — the hazard of a &lt;em&gt;harmful&lt;/em&gt; transition — and set budgets against those. In practice this roughly doubles usable budgets, because harmful moves are typically a minority of moves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision-weighted ε.&lt;/strong&gt; Publish a small table of tolerances rather than scattering magic numbers: listing pages 10%, quote generation 3%, reservation confirmation 1%, fleet commitments 0.5%. Whether those are the right values is a business argument, and the table is where you have it — once, in the open, instead of implicitly in seventeen different cache decorators.&lt;/p&gt;

&lt;h2&gt;
  
  
  A policy evaluator you can unit-test
&lt;/h2&gt;

&lt;p&gt;Budgets are worthless as documentation. They need to be a function that returns a verdict, so that the decision to serve stale data is a recorded event rather than an accident.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;BUDGET_SECONDS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FieldClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;FieldClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PRICE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FieldClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AVAILABILITY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FieldClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LEAD_TIME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FieldClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DESCRIPTIVE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;DEMAND_TIER_MULTIPLIER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;warm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;SERVE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;serve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;SERVE_LABELLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;serve_labelled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;REVALIDATE_FIRST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;revalidate_first&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;REFUSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refuse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;
    &lt;span class="n"&gt;age_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;budget_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;budget_violated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age_seconds&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;budget_seconds&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;budget_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FieldClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;demand_tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BUDGET_SECONDS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field_class&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;DEMAND_TIER_MULTIPLIER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;demand_tier&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Observed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;field_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FieldClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;demand_tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;warm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Decide what may be done with a cached value.

    `binding` marks decisions that create an obligation to the customer. Those
    never accept stale input; everything else may degrade gracefully.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;budget_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;demand_tier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REVALIDATE_FIRST&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REFUSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REVALIDATE_FIRST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SERVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REVALIDATE_FIRST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SERVE_LABELLED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REFUSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four verdicts, not two. The interesting one is &lt;code&gt;SERVE_LABELLED&lt;/code&gt;: past budget, not yet absurd, safe to show provided the interface admits its age. The &lt;code&gt;budget * 4.0&lt;/code&gt; cliff is a policy choice — beyond some multiple, a number stops being approximate and starts being fiction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull, push, and the hybrid that survives contact
&lt;/h2&gt;

&lt;p&gt;Every real integration ends up with three mechanisms, and each one exists because the other two have a specific hole.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;nightly full snapshot&lt;/strong&gt; is your ground truth reset. It is the only mechanism that catches deletions, because deltas tell you what changed and are silent about what vanished. It bounds worst-case drift at 24 hours regardless of what else broke. Run it against a staging table, compare row counts against the previous run, and refuse to promote a snapshot whose count moved more than a threshold — a truncated file that parses cleanly is the classic way to zero out a catalogue at 03:00.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Intraday deltas&lt;/strong&gt; are how you get minutes instead of hours. Webhooks, a message queue, an incremental endpoint keyed on a cursor. The rule with deltas is simple and universally violated: never assume completeness. Demand a monotonic sequence number per stream, track the highest one you have processed, and alarm on gaps. If the supplier will not give you a sequence, you cannot detect loss, and you must treat the delta stream as a latency optimisation rather than a correctness mechanism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On-demand revalidation&lt;/strong&gt; at the decision point is the only thing that makes commitments defensible. When someone is about to be told "yes, Thursday works," you spend one synchronous request against the supplier and you spend it right then. The budget arithmetic above showed why: a 1% tolerance on a volatile field yields a budget measured in low minutes, and no polling schedule you can afford will keep every SKU that fresh. You do not need every SKU that fresh. You need the one that is about to become a promise.&lt;/p&gt;

&lt;p&gt;Hybrid, therefore, is not indecision. It is three different guarantees composed: bounded worst case from the snapshot, good typical case from deltas, and exactness where it counts from revalidation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conditional requests make aggressive polling cheap
&lt;/h2&gt;

&lt;p&gt;Here is the mechanism people skip, and it is the one that changes the economics.&lt;/p&gt;

&lt;p&gt;An HTTP &lt;code&gt;304 Not Modified&lt;/code&gt; costs a round trip and a few hundred bytes, and it tells you something enormously valuable: the value you already hold is still current &lt;em&gt;as of now&lt;/em&gt;. That resets &lt;code&gt;observed_at&lt;/code&gt; without transferring, parsing, or writing anything. Freshness without payload. Once you internalise that, polling frequency stops being a bandwidth question and becomes a request-count question, and request counts are far cheaper to buy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;replace&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CacheEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;etag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;last_modified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;observed_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CacheEntry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CacheEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Refresh an entry, transferring bytes only when the resource moved.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;etag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;If-None-Match&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;etag&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_modified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;If-Modified-Since&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_modified&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;304&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed_at&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;CacheEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;etag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ETag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;last_modified&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Last-Modified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;observed_at&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Details that bite:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Last-Modified&lt;/code&gt; has one-second granularity, so two changes inside the same second are indistinguishable and you can pin a stale body indefinitely. Prefer &lt;code&gt;ETag&lt;/code&gt; when both are offered. Weak validators (&lt;code&gt;W/"abc"&lt;/code&gt;) mean semantic equivalence, not byte equality — fine for freshness, useless if you are checksumming.&lt;/p&gt;

&lt;p&gt;Many distributor APIs support neither. Two fallbacks: ask for a lightweight digest endpoint returning a version stamp per SKU group, or hash the normalised payload yourself. Self-hashing does not save bandwidth, but it saves parsing, writes, downstream invalidation, and — importantly — it prevents a re-serialised-but-identical payload from creating a spurious "change" that corrupts your rate estimates. Normalise before hashing: sort keys, drop server-generated request IDs, drop the response timestamp.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling: priority, jitter, and a shared bucket
&lt;/h2&gt;

&lt;p&gt;Fixed-interval polling spends uniform effort on non-uniform value. Replace it with a priority score and a work queue.&lt;/p&gt;

&lt;p&gt;A serviceable score multiplies three factors: how much demand the SKU sees, how volatile its field class is at that supplier, and how far through its budget the current value has aged. Something like &lt;code&gt;demand_weight × λ̂ × (age / budget)&lt;/code&gt;, recomputed lazily when an item is popped. Items whose age exceeds budget dominate naturally, items nobody looks at sink, and a SKU that becomes popular climbs within one cycle.&lt;/p&gt;

&lt;p&gt;Demand weight is where local knowledge pays. Sizes that fit the vehicles common on Deerfoot in December deserve tighter refresh than a fitment you have quoted twice this year, and the popularity of a given &lt;a href="https://calgaryrimandtire.ca/winter-tires-calgary" rel="noopener noreferrer"&gt;winter fitment&lt;/a&gt; versus an &lt;a href="https://calgaryrimandtire.ca/all-weather-tires-calgary" rel="noopener noreferrer"&gt;all-weather option&lt;/a&gt; shifts with the forecast, not with the calendar. Feed the score from actual read counts over a trailing window and it tracks reality without anyone maintaining a list.&lt;/p&gt;

&lt;p&gt;Then jitter, because synchronised timers are a self-inflicted denial of service. Anything scheduled with &lt;code&gt;interval&lt;/code&gt; and no spread will converge into a convoy — a nightly job at midnight, 40,000 SKUs whose timers were all seeded at deploy, a supplier that starts returning 429s at exactly 00:00:03.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;next_poll_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spread&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fixed cadence with proportional jitter so thousands of items never align.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;interval_seconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;spread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;spread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;backoff_delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;60.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Full-jitter exponential backoff: growing ceiling, uniform draw beneath it.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;ceiling&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ceiling&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TokenBucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Per-supplier rate limit shared by every worker.

    This in-process version is the reference model; production state lives in a
    single store so N workers draw from one pool rather than N pools.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rate_per_second&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;burst&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rate_per_second&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;burst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;burst&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;burst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_checked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Seconds to wait before proceeding; 0.0 means go immediately.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;burst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_checked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_checked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tokens&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shared-bucket point deserves emphasis. Per-process limits are not limits. Six workers each politely capped at two requests per second produce twelve, and when autoscaling adds four more during the autumn ramp you are at twenty and the supplier's abuse detection has opinions. One bucket per supplier, in one place, with &lt;code&gt;cost&lt;/code&gt; weighted so a full snapshot pull consumes more tokens than a conditional probe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failing well: breakers, backoff, and negative caching
&lt;/h2&gt;

&lt;p&gt;Retry policy for feeds is mercifully simple because reads are idempotent. Retry &lt;code&gt;GET&lt;/code&gt; freely; budget the retries per logical request rather than per attempt, so a request with a 3-second deadline does not spend 40 seconds heroically retrying into a supplier that is plainly down.&lt;/p&gt;

&lt;p&gt;Circuit breakers matter more here than in most integrations, because the failure mode is not just latency — it is that a hung supplier holds workers hostage while every SKU in the queue ages past its budget. Trip on error &lt;em&gt;rate&lt;/em&gt; over a rolling window rather than on a raw count. Half-open with a single probe, and choose the probe deliberately: a known-stable descriptive endpoint, not the heaviest availability query. When the breaker is open, every read for that supplier returns &lt;code&gt;unknown&lt;/code&gt; immediately, which is a far better answer than a 30-second timeout.&lt;/p&gt;

&lt;p&gt;Negative caching is the underrated half. A SKU that returns 404 will keep returning 404, and re-asking 3,000 times an hour helps nobody. Cache the negative — with its own, shorter budget, because "does not exist" reverses more often than you would think when a distributor onboards a line. The hard rule: &lt;strong&gt;never negative-cache a 5xx or a timeout.&lt;/strong&gt; Those mean "I could not ask," not "the answer is no," and conflating them turns a ninety-second blip into an hour of phantom unavailability. That distinction is worth a separate cache state, not a shared one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serve stale with a label, or fail closed
&lt;/h2&gt;

&lt;p&gt;This is the decision that people get wrong most often, and the reason is that they ask "how stale is it?" when the question is "what happens if it is wrong?"&lt;/p&gt;

&lt;p&gt;Two consequence classes, and you should be able to name which one every read belongs to:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Wasted click.&lt;/em&gt; A listing shows a size as available; the customer clicks through; the detail view revalidates and corrects. Cost: mild annoyance. Serve stale, label it, move on. Refusing to render anything because a feed is 40 minutes old produces a blank page, which is strictly worse than an approximate one.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Wrong commitment.&lt;/em&gt; Someone is told a specific set can go on Thursday. That sentence creates an obligation. If the casings are not there, the cost is a rescheduled reservation, a wasted bay hour, and a driver who took an afternoon off work. Fail closed: revalidate synchronously, and if the supplier will not answer, say "let me confirm and get back to you" rather than guessing.&lt;/p&gt;

&lt;p&gt;The boundary between those worlds should be a type, not a convention. Make the commitment path take a &lt;code&gt;Confirmed&amp;lt;T&amp;gt;&lt;/code&gt; that can only be constructed from a fresh revalidation, and the compiler will enforce what a code review will eventually miss. In practice this means the reservation flow — the one behind the &lt;a href="https://calgaryrimandtire.ca/online-bookings" rel="noopener noreferrer"&gt;scheduling page&lt;/a&gt; — physically cannot accept a value that came out of the cache without a successful probe attached.&lt;/p&gt;

&lt;p&gt;Same logic applies to work that travels. A &lt;a href="https://calgaryrimandtire.ca/mobile-tire-service" rel="noopener noreferrer"&gt;van going out to a customer's site&lt;/a&gt; carries a fixed load of inventory, and being wrong about what is on it is not a wasted click but a wasted trip across the city. Anything dispatched to a location in &lt;a href="https://calgaryrimandtire.ca/service-areas" rel="noopener noreferrer"&gt;our coverage map&lt;/a&gt; gets the fail-closed treatment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Out-of-order updates and idempotent upserts
&lt;/h2&gt;

&lt;p&gt;Distributed delivery reorders things. A retried webhook from 06:04 arrives after a fresh one from 06:09 and, if your upsert is unconditional, quietly resurrects a five-minute-old price. This bug is invisible in testing and produces exactly the kind of intermittent wrongness that costs a week to trace.&lt;/p&gt;

&lt;p&gt;The fix is a monotonic version per source and a conditional write.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;supplier_offer&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;supplier_id&lt;/span&gt;      &lt;span class="nb"&gt;text&lt;/span&gt;        &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sku&lt;/span&gt;              &lt;span class="nb"&gt;text&lt;/span&gt;        &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;price_cents&lt;/span&gt;      &lt;span class="nb"&gt;integer&lt;/span&gt;     &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;currency&lt;/span&gt;         &lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="s1"&gt;'CAD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;available_units&lt;/span&gt;  &lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;lead_time_days&lt;/span&gt;   &lt;span class="nb"&gt;smallint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;source_version&lt;/span&gt;   &lt;span class="nb"&gt;bigint&lt;/span&gt;      &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;source_as_of&lt;/span&gt;     &lt;span class="n"&gt;timestamptz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;observed_at&lt;/span&gt;      &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;valid_until&lt;/span&gt;      &lt;span class="n"&gt;timestamptz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tombstoned&lt;/span&gt;       &lt;span class="nb"&gt;boolean&lt;/span&gt;     &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;supplier_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;supplier_offer_age_idx&lt;/span&gt;
  &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;supplier_offer&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;supplier_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed_at&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;tombstoned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the write, which refuses to move backwards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;supplier_offer&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;existing&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;supplier_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_cents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;available_units&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;lead_time_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source_as_of&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed_at&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;conflict&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;supplier_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="k"&gt;update&lt;/span&gt;
   &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;price_cents&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;excluded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price_cents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;available_units&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;excluded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available_units&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;lead_time_days&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;excluded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_time_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;source_version&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;excluded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;source_as_of&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;excluded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_as_of&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;observed_at&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;excluded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observed_at&lt;/span&gt;
 &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;excluded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_version&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_version&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;where&lt;/code&gt; on the &lt;code&gt;do update&lt;/code&gt; branch is the entire mechanism. Replays become no-ops, out-of-order arrivals are dropped, and the statement is idempotent under any delivery order. Count the dropped ones — a rising rate of rejected-as-older writes is a good early signal that a consumer is lagging.&lt;/p&gt;

&lt;p&gt;When the supplier publishes no version, synthesise one. Ranked preference: their own sequence number; failing that, &lt;code&gt;source_as_of&lt;/code&gt; converted to microseconds; failing that, a per-batch counter combined with the batch's start time. Falling back to &lt;code&gt;observed_at&lt;/code&gt; is legal but weak, because it makes the retry itself look newer than the original — precisely the case you were defending against.&lt;/p&gt;

&lt;p&gt;Multi-source is where it gets genuinely awkward. If two distributors both stock a SKU, there is no single ordering, and a version vector keyed by source is the honest structure: keep each source's latest observation separately, and resolve at read time with an explicit rule (cheapest currently-available, or preferred-partner-first, or fastest to depot). Merging them into one row destroys the ability to answer "which supplier said that, and when?" — which is the first question anyone asks when a number looks wrong.&lt;/p&gt;

&lt;p&gt;Tombstones instead of deletes, for the same reason. A row that disappears from a snapshot has been discontinued, and "we knew about this until Tuesday" is a much more useful state than a missing row that looks identical to a SKU you never ingested.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reconciliation loop and the drift metric
&lt;/h2&gt;

&lt;p&gt;Everything above is inference. Reconciliation is the part where you actually check.&lt;/p&gt;

&lt;p&gt;Sample records on a schedule, fetch them fresh and unconditionally, and compare against what you were serving. The comparison yields the single most valuable number in the system: &lt;strong&gt;drift rate&lt;/strong&gt;, the fraction of sampled records whose cached value was wrong &lt;em&gt;while inside its budget&lt;/em&gt;. If that number exceeds the ε you designed for, your budgets are too generous and the arithmetic is not opinion any more.&lt;/p&gt;

&lt;p&gt;Three design notes that make the difference between a useful loop and a decorative one:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stratify the sample.&lt;/strong&gt; Uniform sampling across 40,000 SKUs spends almost all its effort on the long tail, where nothing changes and nobody looks. Weight by read volume so the sample resembles the traffic. Report drift per stratum, because a 0.4% aggregate hiding 6% on your top fifty sizes is worse than useless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate drift from loss.&lt;/strong&gt; A record can be wrong because the value moved and the budget was too long (a budget problem), or because a delta message was never delivered (a pipeline problem). Distinguish them by checking whether the correct value's source version was one you had ever received. Different alarms, different owners, and conflating them sends the wrong team looking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sample the writes, not just the reads.&lt;/strong&gt; Reconcile a slice of records that your delta stream claims to have updated recently. If a delta said "4 units" and the truth is 9, you have a transformation bug, not a freshness bug, and no amount of polling faster will fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Surfacing age to humans and to downstream systems
&lt;/h2&gt;

&lt;p&gt;Internally, freshness is a typed field on the response body. Not a header — headers get stripped by proxies, ignored by clients, and lost the moment somebody wraps your response in an envelope. Put it in the payload where the type system can see it, and make it non-optional so nobody can forget to handle it.&lt;/p&gt;

&lt;p&gt;For humans, the design goal is to convert an engineering fact into a calibration cue without inducing anxiety. "Checked 6 minutes ago" does that. A green dot does not — it communicates certainty you have not earned. Rounding rules worth stealing: under 60 seconds say "just now"; under an hour, whole minutes; under a day, whole hours; beyond that, the date. Never round &lt;em&gt;up&lt;/em&gt; into a friendlier bucket, because the entire value of the label is that it is conservative.&lt;/p&gt;

&lt;p&gt;Context changes the wording more than the number does. Someone comparing &lt;a href="https://calgaryrimandtire.ca/all-season-tires-calgary" rel="noopener noreferrer"&gt;all-season&lt;/a&gt; against &lt;a href="https://calgaryrimandtire.ca/buying-tires-in-calgary" rel="noopener noreferrer"&gt;winter-specific&lt;/a&gt; options on a browse page does not need a timestamp on every tile; a single "availability checked within the last 20 minutes" line at the top of the results is enough calibration. Someone on a confirmation screen deserves the exact age of the specific fact they are relying on, because that is the moment where the number becomes a promise.&lt;/p&gt;

&lt;p&gt;For fleet customers the requirement is different again. A &lt;a href="https://calgaryrimandtire.ca/fleet-management" rel="noopener noreferrer"&gt;fleet operations&lt;/a&gt; integration consuming your API needs machine-readable freshness so their own planner can decide whether to trust a figure, and it needs the age of the &lt;em&gt;oldest&lt;/em&gt; input to a derived number, not the newest. Composite freshness is a minimum, never an average.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability: histograms, violations, and the silence alarm
&lt;/h2&gt;

&lt;p&gt;Instrument age at &lt;em&gt;read&lt;/em&gt; time. Age at write time is a measure of your poller's diligence; age at read time is a measure of what customers actually experienced, and the two diverge badly whenever your scheduling priorities are misaligned with demand.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;th&gt;Why it earns its cardinality&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Age at read&lt;/td&gt;
&lt;td&gt;histogram by field class&lt;/td&gt;
&lt;td&gt;p50 tells you the typical experience; p99 tells you the tail that generates complaints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Budget violation rate&lt;/td&gt;
&lt;td&gt;ratio by field class × decision&lt;/td&gt;
&lt;td&gt;the direct SLO; alarm here first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drift rate&lt;/td&gt;
&lt;td&gt;ratio from reconciliation&lt;/td&gt;
&lt;td&gt;validates that the budgets themselves are honest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revalidation latency&lt;/td&gt;
&lt;td&gt;histogram at the commitment path&lt;/td&gt;
&lt;td&gt;this one is on the synchronous path and shows up as user-visible delay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;304 ratio&lt;/td&gt;
&lt;td&gt;ratio per supplier&lt;/td&gt;
&lt;td&gt;efficiency, and a collapse means validators broke&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rejected-as-older writes&lt;/td&gt;
&lt;td&gt;counter&lt;/td&gt;
&lt;td&gt;early warning of consumer lag or clock trouble&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then the alarm nobody builds until after it has hurt them: &lt;strong&gt;feed silence&lt;/strong&gt;. An unchanged feed is almost always a broken feed, not a stable one. If a supplier's availability stream normally produces four thousand changes a day and produced eleven yesterday, something upstream is stuck — a credential expired, a cursor got pinned, a filter clause started matching nothing. Alarm on the &lt;em&gt;absence&lt;/em&gt; of change, using a floor derived from that stream's own trailing history rather than a hand-picked constant.&lt;/p&gt;

&lt;p&gt;Related pathologies worth their own checks: a 304 ratio that reaches exactly 100% for a whole day (a validator that stopped changing rather than content that stopped moving); record counts landing on suspiciously round numbers like 10,000 (a page limit silently truncating); and a snapshot whose row count drops by more than a few percent (a partial file). Each of these has produced a quiet catalogue corruption somewhere, and each is a five-line check.&lt;/p&gt;

&lt;p&gt;Here is a read-log query that produces the top half of that table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt;
    &lt;span class="n"&gt;field_class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                                  &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;reads&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;percentile_disc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;within&lt;/span&gt; &lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;age_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p50_age_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;percentile_disc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;within&lt;/span&gt; &lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;age_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p99_age_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;age_ms&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;budget_ms&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;violation_rate&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;freshness_read_log&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;read_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'7 days'&lt;/span&gt;
&lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;field_class&lt;/span&gt;
&lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;violation_rate&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sample the read log rather than writing every read — one in a hundred is plenty for percentiles at any real volume, and the write amplification of logging every cache hit will outweigh the insight.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example, entirely synthetic
&lt;/h2&gt;

&lt;p&gt;Numbers below are &lt;strong&gt;illustrative placeholders&lt;/strong&gt; invented for the arithmetic. They are not KMJ figures, not supplier figures, and not measured production values.&lt;/p&gt;

&lt;p&gt;Setting: three distributors, 40,000 SKU-supplier pairs, an API allowance of 2 requests per second per supplier, so about 518,000 requests per supplier per day.&lt;/p&gt;

&lt;p&gt;Naive uniform polling at 20 minutes needs &lt;code&gt;40,000 × 72 = 2.88M&lt;/code&gt; requests per day against each supplier. Over budget by 5.5×. This is where most teams give up and stretch the interval to two hours, quietly accepting a drift rate they never measure.&lt;/p&gt;

&lt;p&gt;Now apply the structure. Split by field class: descriptive attributes ride the nightly snapshot and cost nothing intraday. Only availability and price need intraday attention, and only the demand-weighted head of the catalogue needs it often.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;SKU-supplier pairs&lt;/th&gt;
&lt;th&gt;Interval&lt;/th&gt;
&lt;th&gt;Requests/day&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hot (top 3% by reads)&lt;/td&gt;
&lt;td&gt;1,200&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;345,600&lt;/td&gt;
&lt;td&gt;conditional; ~85% return 304&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warm&lt;/td&gt;
&lt;td&gt;6,800&lt;/td&gt;
&lt;td&gt;45 min&lt;/td&gt;
&lt;td&gt;217,600&lt;/td&gt;
&lt;td&gt;conditional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold&lt;/td&gt;
&lt;td&gt;32,000&lt;/td&gt;
&lt;td&gt;nightly only&lt;/td&gt;
&lt;td&gt;32,000&lt;/td&gt;
&lt;td&gt;snapshot rows, not individual requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revalidation at commitment&lt;/td&gt;
&lt;td&gt;~400 events/day&lt;/td&gt;
&lt;td&gt;on demand&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;unconditional, synchronous&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Total intraday across all three suppliers is roughly 595,600 requests, about 199,000 per supplier — comfortably inside the 518,000 allowance, with headroom for the autumn ramp when hot-tier membership swells. Bytes transferred are dominated by the 304s, so actual bandwidth is a fraction of the naive plan even though request volume is only modestly lower.&lt;/p&gt;

&lt;p&gt;Freshness outcome, using the illustrative λ from earlier: hot-tier availability carries a p99 read age near 5 minutes against a 5-minute budget (hot tier multiplier 0.25 × 20 min), warm tier sits near 45 minutes against 20, which sounds like a failure until you notice warm-tier reads are browse traffic where the tolerance is 10% and the labelled-stale path is fine. Commitments bypass the cache entirely. Three different guarantees, one system, and every one of them is written down and measured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes worth designing against explicitly
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The timestamp that means nothing.&lt;/strong&gt; &lt;code&gt;updated_at&lt;/code&gt; set by an ORM &lt;code&gt;onUpdate&lt;/code&gt; hook, so it records when a row was written rather than when the value was true. Every freshness computation downstream is then measuring your own write traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clock skew as data corruption.&lt;/strong&gt; A supplier's &lt;code&gt;as_of&lt;/code&gt; five minutes in the future makes age negative, which makes everything look eternally fresh. Clamp, count the clamps, and alarm if the count grows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry storms disguised as demand.&lt;/strong&gt; A supplier slows down, your retries multiply, their rate limiter trips, everything becomes retries. Full jitter and a shared bucket, or this happens on your worst day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The delta stream that silently stopped.&lt;/strong&gt; No errors, no alerts, just a cursor that stopped advancing two days ago. Only a silence alarm catches this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Negative caching a timeout.&lt;/strong&gt; "I could not ask" gets stored as "no," and a brief outage turns into an hour of phantom unavailability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The unconditional upsert.&lt;/strong&gt; A replayed message reinstates old state. Conditional writes on a monotonic version, always.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uniform reconciliation sampling.&lt;/strong&gt; Drift measured almost entirely on parts nobody reads, producing a reassuring aggregate that says nothing about the head of the distribution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freshness in a header.&lt;/strong&gt; Stripped by a proxy, dropped by a client wrapper, and now every consumer treats absence as fresh.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A composite that averages.&lt;/strong&gt; A quote assembled from four supplier facts is exactly as fresh as its oldest input, and reporting the mean makes stale data look acceptable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Descriptive fields on the hot path.&lt;/strong&gt; Re-polling immutable specifications every fifteen minutes, then wondering where the rate limit went.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A compressed incident, and what it teaches
&lt;/h2&gt;

&lt;p&gt;Sketch of the shape these things take, offered as an illustrative scenario rather than a report of a specific event.&lt;/p&gt;

&lt;p&gt;Mid-October, a chinook collapses overnight and the forecast flips. Search volume for winter sizes triples inside twelve hours, which is the demand-side load spike anyone doing &lt;a href="https://calgaryrimandtire.ca/calgarys-local-tire-shop" rel="noopener noreferrer"&gt;seasonal changeover work&lt;/a&gt; plans for. The hot tier of the polling scheduler swells accordingly, the shared token bucket starts throttling, and warm-tier items begin aging past budget.&lt;/p&gt;

&lt;p&gt;Meanwhile — and independently — one distributor's delta stream stalls at 04:00. The nightly snapshot had run at 02:00, so every record looks recent enough. Availability continues to be served from a snapshot that is now nine hours old on the most volatile field in the system, during the busiest demand hour of the season.&lt;/p&gt;

&lt;p&gt;What surfaces the problem is not the polling metrics, which look healthy. It is two things. The silence alarm fires because a stream that normally emits thousands of changes emitted forty. And the reconciliation loop's drift rate on hot-tier availability jumps from a fraction of a percent to double digits — records wrong &lt;em&gt;while inside budget&lt;/em&gt;, which is precisely the signal that says "your inference is broken, stop trusting it."&lt;/p&gt;

&lt;p&gt;The lesson is not "add more polling." It is that the two mechanisms that caught it were both mechanisms that check the system against reality instead of against itself. Poller dashboards measure whether your code ran. Drift and silence measure whether your code was right. Only one of those pairs is worth waking up for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to build first
&lt;/h2&gt;

&lt;p&gt;If you are retrofitting this into an existing integration, the order matters, because each step makes the next one measurable.&lt;/p&gt;

&lt;p&gt;Start with the three timestamps and a read log. You cannot argue about budgets without knowing your current age distribution, and most teams are startled by their own p99. Add the change log next; two weeks of it gives you real rates. Then write budgets down — badly at first, refined by the drift signal later — and put them behind a policy function rather than scattering constants.&lt;/p&gt;

&lt;p&gt;After that, the commitment path: make binding decisions revalidate synchronously, and make the type system enforce it. That single change removes the most expensive category of error before you have optimised anything. Conditional requests come next, because they make everything else affordable. Reconciliation and the silence alarm after that. Adaptive scheduling last — it is the most fun to build and the least valuable until the measurement layer exists to tune it.&lt;/p&gt;

&lt;p&gt;Underneath all of it is one commitment worth restating plainly. You are never going to know the true state of somebody else's warehouse. You &lt;em&gt;can&lt;/em&gt; know how old your information is, how fast that kind of information decays, and what you are willing to risk at each decision point. A system that knows those three things and says so out loud is more trustworthy than one that hides an unknown behind a confident boolean — whether the consumer is a rendering engine, a fleet planner, or a person at a counter asking whether their &lt;a href="https://calgaryrimandtire.ca/tire-repair-in-calgary" rel="noopener noreferrer"&gt;existing set can be repaired&lt;/a&gt; or whether new rubber needs to be sourced and &lt;a href="https://calgaryrimandtire.ca/wheel-balancing" rel="noopener noreferrer"&gt;balanced&lt;/a&gt; this week.&lt;/p&gt;

&lt;p&gt;Freshness is not a feature you add. It is a number you owe your users, and the only way to owe it honestly is to write it down first.&lt;/p&gt;

</description>
      <category>automotive</category>
      <category>calgary</category>
      <category>maintenance</category>
      <category>tires</category>
    </item>
    <item>
      <title>Money Math for a Tire Quote Engine: Integer Cents, One Rounding Policy, and a Paper Trail You Can Replay</title>
      <dc:creator>KMJ Tire Calgary</dc:creator>
      <pubDate>Sun, 16 Aug 2026 16:16:04 +0000</pubDate>
      <link>https://dev.to/kmjtirecalgary/money-math-for-a-tire-quote-engine-integer-cents-one-rounding-policy-and-a-paper-trail-you-can-13e3</link>
      <guid>https://dev.to/kmjtirecalgary/money-math-for-a-tire-quote-engine-integer-cents-one-rounding-policy-and-a-paper-trail-you-can-13e3</guid>
      <description>&lt;h1&gt;
  
  
  Money Math for a Tire Quote Engine: Integer Cents, One Rounding Policy, and a Paper Trail You Can Replay
&lt;/h1&gt;

&lt;p&gt;Quoting four winter tires sounds like arithmetic a ten-year-old could do: unit price times four, add the install labour, add the per-tire fees, add tax, done. Then you try to build it as software, and the ten-year-old's version starts disagreeing with the accountant's version by a cent. Sometimes two cents. Sometimes it disagrees with &lt;em&gt;itself&lt;/em&gt; depending on which order the line items were entered. This article is about making that impossible by construction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ground rules: real constraints, illustrative system
&lt;/h2&gt;

&lt;p&gt;Before anything else, the framing, stated plainly so nobody mistakes design for deployment. KMJ Tire is a real, working tire business in Calgary — the operational constraints in this article (tire quantities sold in twos and fours, per-tire environmental fees, 5% GST, "out-the-door" totals quoted to customers before any work happens) are its genuine daily reality. The quoting system described here, however, is a &lt;strong&gt;design exercise&lt;/strong&gt;: a worked-through architecture for how such a system &lt;em&gt;should&lt;/em&gt; handle money, not a description of live production infrastructure. Every dollar figure, every tire price, every fee amount in this article is &lt;strong&gt;illustrative&lt;/strong&gt; — invented for the worked examples, not pulled from a price list. There are no incident reports here, no production metrics, no adoption numbers, because none exist. What you get instead is the interesting part: the reasoning.&lt;/p&gt;

&lt;p&gt;Why is a tire quote a good vehicle for this topic? Because it is small enough to hold in your head and gnarly enough to exercise every classic money-handling failure. A single quote for &lt;a href="https://calgaryrimandtire.ca/winter-tires-calgary" rel="noopener noreferrer"&gt;winter tires&lt;/a&gt; composes at least five kinds of amounts — a unit price, a quantity multiplier, a flat per-unit levy, a labour charge, and a percentage tax — and the customer expects one number at the end that will still be the same number when they arrive to pay. That expectation, "the total you quoted is the total I pay," is the entire correctness contract. Everything below serves it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Floating point is the wrong tool, and here is the receipt
&lt;/h2&gt;

&lt;p&gt;Start with the demonstration everyone has seen and half of us have shipped anyway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;
&lt;span class="mf"&gt;0.30000000000000004&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;
&lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IEEE 754 doubles represent numbers in binary fractions. One tenth has no finite binary expansion, the same way one third has no finite decimal expansion. So &lt;code&gt;0.1&lt;/code&gt; is actually the nearest representable double, which is &lt;code&gt;0.1000000000000000055511151231257827…&lt;/code&gt;. Each arithmetic operation rounds its result to the nearest representable value, and those tiny errors are not random noise that cancels out — they accumulate directionally depending on the magnitudes and order of your operations.&lt;/p&gt;

&lt;p&gt;For a quote engine the failure mode is subtler than the meme. You will not see &lt;code&gt;$1,203.30000000000004&lt;/code&gt; on an invoice, because everyone formats output to two decimals. The formatting &lt;em&gt;hides&lt;/em&gt; the drift until two independently correct code paths disagree. Consider an illustrative quote line: a tire at &lt;code&gt;$247.99&lt;/code&gt; times 4 is &lt;code&gt;991.96&lt;/code&gt;. In doubles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;247.99&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="mf"&gt;991.9599999999999&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;247.99&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mf"&gt;991.96&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fine so far — &lt;code&gt;round&lt;/code&gt; rescued it. Now let a percentage enter the picture. Apply an illustrative 3.5% adjustment to that extended amount, then 5% GST, and compare against computing the same thing in a different but algebraically equivalent order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;247.99&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.965&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;247.99&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.965&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1005.11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1005.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One path says &lt;code&gt;$1,005.11&lt;/code&gt;, the other &lt;code&gt;$1,005.10&lt;/code&gt;. Both are "correct" implementations of an underspecified calculation. Neither is buggy in the compiler's eyes. The bug is that the &lt;em&gt;specification never said where rounding happens&lt;/em&gt;, so two developers made two defensible choices and now the emailed quote disagrees with the point-of-sale screen by one cent. A cent sounds trivial until the accounting export refuses to reconcile, or until a customer notices the number changed between the estimate and the invoice — for a small business whose whole pitch is straight-dealing, that cent is reputational, not financial.&lt;/p&gt;

&lt;p&gt;Two lessons fall out of this before we write a single type:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Binary floating point cannot faithfully represent decimal money.&lt;/strong&gt; This is not a precision problem you can out-round; it is a representation mismatch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Even with exact representation, the &lt;em&gt;order and location&lt;/em&gt; of rounding is a policy decision&lt;/strong&gt; that must be written down once and enforced everywhere, or equivalent formulas will diverge.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rest of the article is those two lessons, taken seriously.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integer cents: the representation that cannot lie
&lt;/h2&gt;

&lt;p&gt;The boring, bulletproof fix: store money as an integer count of the smallest currency unit. In Canada that is the cent. &lt;code&gt;$247.99&lt;/code&gt; becomes &lt;code&gt;24799&lt;/code&gt;. Integer addition and multiplication are exact — a 64-bit signed integer holds about 92 quadrillion cents, which comfortably exceeds the gross domestic product of tires.&lt;/p&gt;

&lt;p&gt;But a naked &lt;code&gt;number&lt;/code&gt; or &lt;code&gt;int&lt;/code&gt; labelled "cents" in a comment is an invitation to disaster, because someone will eventually add a cents value to a dollars value, or multiply two money values together (dollars-squared is not a currency). The representation wants a &lt;strong&gt;type&lt;/strong&gt;. In TypeScript, a branded type plus a tiny algebra:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A branded integer type: structurally a number, nominally Money.&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;__brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;cents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isSafeInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RangeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Money must be an integer cent count, got &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;cents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Multiplying money by a scalar quantity is legal…&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isSafeInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RangeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quantity must be integral&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;cents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// …but note there is deliberately no `multiply(Money, Money)`.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point of the brand is that &lt;code&gt;add(unitPriceCents, taxRateBasisPoints)&lt;/code&gt; fails to type-check. The compiler now enforces dimensional analysis: money plus money is money; money times a dimensionless integer is money; money times money is a compile error. Rates get their own representation — and here is a second trap worth naming. If you store 5% as the float &lt;code&gt;0.05&lt;/code&gt;, you have reintroduced binary fractions through the back door. Store rates as &lt;strong&gt;integer basis points&lt;/strong&gt; (5% = 500 bp) or as an integer numerator over a fixed denominator, and define exactly one function that applies a rate to a money value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BasisPoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;__brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BasisPoints&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/** The ONLY place a rate touches money. Rounding policy lives here. */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyRate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BasisPoints&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// exact: integer * integer&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;roundDiv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// one documented rounding, see below&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;amount * rate&lt;/code&gt; is exact integer arithmetic; the &lt;em&gt;only&lt;/em&gt; inexact step is the division by 10,000, and that step is centralized in one function whose rounding behaviour is documented and tested. This is the structural move that makes the whole design work: &lt;strong&gt;push every rounding decision into a single choke point.&lt;/strong&gt; When rounding can only happen in one function, "which rounding mode do we use?" has exactly one answer, and "where does rounding happen?" is answerable by grepping for one name.&lt;/p&gt;

&lt;p&gt;A note on alternatives, because they deserve a fair hearing. Python's &lt;code&gt;decimal.Decimal&lt;/code&gt; and Java's &lt;code&gt;BigDecimal&lt;/code&gt; are decimal floating point: they represent &lt;code&gt;0.1&lt;/code&gt; exactly and let you set rounding modes explicitly. They are excellent, and for a system doing currency conversion or fractional-cent unit pricing (fuel at 154.9 cents/litre, say) they may be the better call-site ergonomics. For a single-currency quote engine where every externally visible amount is whole cents, integer cents win on three counts: they serialize trivially (JSON, SQL &lt;code&gt;BIGINT&lt;/code&gt;, wire protocols — no locale or precision ambiguity), they are impossible to construct inexactly, and they make the rounding choke point &lt;em&gt;obvious&lt;/em&gt; because division is the only operation that can lose information. &lt;code&gt;Decimal&lt;/code&gt; makes rounding configurable per-context; integers make it impossible except where you built the door.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy of an out-the-door tire quote
&lt;/h2&gt;

&lt;p&gt;To design the calculation, first inventory what actually goes into the number a Calgary tire counter gives a customer. When a shopper is &lt;a href="https://calgaryrimandtire.ca/buying-tires-in-calgary" rel="noopener noreferrer"&gt;comparing tires before a purchase&lt;/a&gt;, the meaningful figure is never the sticker price of one tire — it is the out-the-door total: everything, installed, taxes in. Illustratively, a four-tire quote composes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product lines.&lt;/strong&gt; Unit price × quantity for each tire model. Usually one line of quantity 4, but a staggered fitment (different sizes front and rear) produces two lines of quantity 2 — which is exactly why per-line behaviour matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-unit environmental fees.&lt;/strong&gt; Alberta operates a tire recycling program funded by a levy collected on new tire sales; the mechanics that matter to the engine are that the fee is &lt;em&gt;per tire&lt;/em&gt;, is a flat amount rather than a percentage, and is itemized. (The actual current rate is set by the program, varies by tire class, and changes over time — the amounts used in this article's examples, like &lt;code&gt;$5.00&lt;/code&gt; per passenger tire, are illustrative placeholders, not a statement of the official rate.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Labour and service lines.&lt;/strong&gt; Mounting and &lt;a href="https://calgaryrimandtire.ca/wheel-balancing" rel="noopener noreferrer"&gt;wheel balancing&lt;/a&gt;, valve stems or TPMS service kits, possibly a disposal charge for the old set. Some are per-tire, some are flat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adjustments.&lt;/strong&gt; A percentage or fixed-amount change to some subset of lines — the most policy-sensitive component, covered in its own section.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GST.&lt;/strong&gt; Alberta has no provincial sales tax, so the only sales tax on a quote like this is the federal GST at 5%. One rate applied to a defined base — but &lt;em&gt;which&lt;/em&gt; base, computed &lt;em&gt;when&lt;/em&gt;, is precisely the kind of thing that must be specified rather than assumed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice what is absent: nothing here requires fractional cents as an &lt;em&gt;output&lt;/em&gt;. Intermediate math produces fractions (5% of &lt;code&gt;$1,151.96&lt;/code&gt; is &lt;code&gt;$57.598&lt;/code&gt;), but every line a customer sees, and every number that posts to a ledger, is whole cents. That asymmetry — exact integers at the boundaries, controlled rounding in the middle — is the shape of the whole design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rounding is a policy, not an implementation detail
&lt;/h2&gt;

&lt;p&gt;Here is the uncomfortable truth about rounding money: for amounts this size there is rarely a single legally mandated answer, but there is always a mandated &lt;em&gt;consistency&lt;/em&gt;. Canada's GST guidance permits normal commercial rounding practices; what gets a business into trouble is not choosing half-up over half-even, it is being unable to say which one it uses, or using different ones in different code paths. So the deliverable is not "the correct rounding mode." The deliverable is &lt;strong&gt;one written sentence, enforced by one function, verified by tests.&lt;/strong&gt; Something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Policy (illustrative): all rounding is round-half-up on the absolute value (half-away-from-zero), applied per tax-calculation and per-adjustment at the moment that value is produced; line extensions (integer unit price × integer quantity) never round because they are exact; the invoice total is the exact integer sum of already-rounded parts and is itself never rounded.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every clause in that sentence forecloses a category of divergence. "Half-away-from-zero" pins the tie-breaking rule and handles negative adjustments symmetrically (a &lt;code&gt;-$0.125&lt;/code&gt; adjustment rounds to &lt;code&gt;-$0.13&lt;/code&gt;, mirroring the positive case). "Per-calculation at the moment produced" pins &lt;em&gt;where&lt;/em&gt;. "Extensions never round" documents that multiplication of integers needs no policy. "The total is a sum of rounded parts" means the bottom line is derived, never independently computed — so it cannot disagree with its own line items.&lt;/p&gt;

&lt;p&gt;The implementation of the choke point, continuing the TypeScript sketch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** Round-half-away-from-zero integer division. The system's ONLY rounding site. */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;roundDiv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numerator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;denominator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;denominator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;denominator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;denominator&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numerator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;cents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;bump&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ten lines, and every cent of ambiguity in the system routes through them. If the policy ever changes — a jurisdiction mandates something, an auditor prefers something — it changes in one place, with a version stamp (more on that in the audit section, because "we changed rounding modes on date X" is exactly the sort of fact a replayable history must capture).&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-line or per-invoice: pick where the tax rounds
&lt;/h2&gt;

&lt;p&gt;The classic composition question: do you compute tax on each line and sum the rounded results, or sum the untaxed lines and compute tax once on the subtotal? Both are legitimate; they can differ by a cent or two per invoice; the difference is structural, not moral.&lt;/p&gt;

&lt;p&gt;Watch them diverge on an illustrative staggered fitment — two front tires at &lt;code&gt;$189.99&lt;/code&gt;, two rears at &lt;code&gt;$204.49&lt;/code&gt;, GST 5%:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line&lt;/th&gt;
&lt;th&gt;Extension&lt;/th&gt;
&lt;th&gt;5% of line&lt;/th&gt;
&lt;th&gt;Rounded per line&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Front pair&lt;/td&gt;
&lt;td&gt;$379.98&lt;/td&gt;
&lt;td&gt;$18.999&lt;/td&gt;
&lt;td&gt;$19.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rear pair&lt;/td&gt;
&lt;td&gt;$408.98&lt;/td&gt;
&lt;td&gt;$20.449&lt;/td&gt;
&lt;td&gt;$20.45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sum of line taxes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$39.45&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Per-invoice instead: subtotal &lt;code&gt;$788.96&lt;/code&gt;, times 5% is &lt;code&gt;$39.448&lt;/code&gt;, rounds to &lt;strong&gt;$39.44&lt;/strong&gt;. One cent apart, both defensible, and a system that computes per-line in the quote screen but per-invoice in the export will disagree with itself forever, one reconciliation ticket at a time.&lt;/p&gt;

&lt;p&gt;Considerations that actually decide it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-invoice&lt;/strong&gt; minimizes accumulated rounding steps (one rounding event per rate per invoice) and matches how a person with a calculator checks the math: subtotal, times 1.05, done. For a single-rate, single-jurisdiction quote engine it is the simpler mental model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-line&lt;/strong&gt; is what you want when lines can carry &lt;em&gt;different&lt;/em&gt; tax treatments — a taxable product line next to an exempt one — or when lines must be independently exportable to systems that expect each row to carry its own tax. It also localizes the effect of voiding one line: remove the line, remove exactly its tax.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid reality:&lt;/strong&gt; many engines compute per &lt;em&gt;tax group&lt;/em&gt; — sum the lines sharing a tax treatment, apply the rate once per group. This gets per-invoice's minimal rounding with per-line's ability to handle mixed treatments, and it is the choice the worked example below uses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whichever you choose, encode it in the calculation-order document and in the property tests, because this is the single most common source of the off-by-a-cent class of bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Half-up, half-even, and why the tie-break matters less than you think
&lt;/h2&gt;

&lt;p&gt;Round-half-up (2.5 → 3) is what humans expect from school. Round-half-to-even, "banker's rounding" (2.5 → 2, 3.5 → 4), exists because always rounding ties upward introduces a systematic upward bias: over many rounding events, half-up drifts totals up by a fraction of a cent per event on average, while half-even's ties alternate direction and cancel in aggregate. IEEE 754 defaults to half-even for exactly this reason, and Python's built-in &lt;code&gt;round&lt;/code&gt; follows it — a genuine gotcha if you assumed schoolbook behaviour:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3.5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# half-even, and 0.125 isn't even exactly 0.125 in binary
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(That last value is a compound gotcha: the literal &lt;code&gt;0.125&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; exact in binary, but &lt;code&gt;2.675&lt;/code&gt; famously is not — &lt;code&gt;round(2.675, 2)&lt;/code&gt; gives &lt;code&gt;2.67&lt;/code&gt;, not because of the tie rule but because the stored value is fractionally below the tie. Binary representation and tie-breaking failures interleave, which is one more argument for integers, where the tie is genuinely a tie.)&lt;/p&gt;

&lt;p&gt;For a quote engine, here is the honest assessment: at retail scale the statistical bias between half-up and half-even is economically irrelevant — fractions of a cent across the number of rounding events a small operation generates. What is &lt;em&gt;not&lt;/em&gt; irrelevant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explainability.&lt;/strong&gt; Half-up is explainable at a counter in one sentence. Half-even requires explaining why $0.125 became $0.12 this time and $0.135 became $0.14. When a human will re-check the math on paper, matching human arithmetic has real value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-system agreement.&lt;/strong&gt; If the accounting package, the payment terminal, and the tax authority's examples all use half-up (most retail-facing tooling does), choosing half-even buys you a permanent trickle of one-cent explanations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symmetry under negation.&lt;/strong&gt; Whatever you choose, specify behaviour for negative amounts (refund lines, negative adjustments). "Half away from zero" makes a refund the exact mirror of the sale, which is what reversal logic wants.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So: half-away-from-zero for this design, chosen for legibility, documented in the policy sentence, enforced in &lt;code&gt;roundDiv&lt;/code&gt;, and — critically — &lt;em&gt;testable&lt;/em&gt;, because the tie cases (&lt;code&gt;…5&lt;/code&gt; at the last digit) are enumerable and belong in the unit suite as table-driven cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quote as a pure function with a declared order
&lt;/h2&gt;

&lt;p&gt;Everything so far becomes real in one place: a &lt;strong&gt;pure function&lt;/strong&gt; from a quote's inputs to its computed totals. Pure meaning: no clock reads, no database reads, no config lookups inside — every input that affects the output arrives as an argument, including the tax rate, the fee schedule, and the policy version. Same inputs, same cents, forever. That property is what later makes auditability and idempotent recalculation nearly free.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;QuoteInput&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProductLine&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;        &lt;span class="c1"&gt;// unit cents, integer qty, fee class, tax group&lt;/span&gt;
  &lt;span class="nl"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ServiceLine&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;     &lt;span class="c1"&gt;// flat or per-unit labour, in cents&lt;/span&gt;
  &lt;span class="nl"&gt;adjustments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Adjustment&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;   &lt;span class="c1"&gt;// see the discount section&lt;/span&gt;
  &lt;span class="nl"&gt;feeScheduleVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// e.g. "fees-2026-illustrative-v1"&lt;/span&gt;
  &lt;span class="nl"&gt;gstBasisPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BasisPoints&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 500 — passed in, never read from globals&lt;/span&gt;
  &lt;span class="nl"&gt;policyVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rounding-half-away-v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;QuoteResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;lineExtensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;   &lt;span class="c1"&gt;// exact, unrounded-because-exact&lt;/span&gt;
  &lt;span class="nl"&gt;perUnitFees&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// Σ (feePerTire × qty), exact&lt;/span&gt;
  &lt;span class="nl"&gt;serviceTotal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// exact sum of service lines&lt;/span&gt;
  &lt;span class="nl"&gt;adjustmentApplied&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// rounded once at computation, signed&lt;/span&gt;
  &lt;span class="nl"&gt;taxableBase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// exact sum of taxable parts&lt;/span&gt;
  &lt;span class="nl"&gt;gst&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                &lt;span class="c1"&gt;// applyRate(taxableBase, 500) — rounds once&lt;/span&gt;
  &lt;span class="nl"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;// exact sum of the above — NEVER rounded&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;computeQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QuoteInput&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;QuoteResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The declared calculation order — the second half of the specification, ranking with the rounding policy itself:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extend each product line&lt;/strong&gt;: &lt;code&gt;unitCents × qty&lt;/code&gt;. Exact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extend per-unit fees&lt;/strong&gt;: for each line, &lt;code&gt;feeCents(feeClass) × qty&lt;/code&gt;; sum. Exact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sum service lines.&lt;/strong&gt; Exact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply adjustments&lt;/strong&gt; to their declared targets (defined below), each producing a signed cents amount, rounded at the moment of computation. This is rounding site #1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assemble the taxable base&lt;/strong&gt;: product extensions + fees + services + adjustments, per tax group. Exact sum of integers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply GST per tax group&lt;/strong&gt;: &lt;code&gt;applyRate(base, 500)&lt;/code&gt;. Rounding site #2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total&lt;/strong&gt;: exact integer sum. No rounding exists at this step &lt;em&gt;by construction&lt;/em&gt; — there is nothing left to round.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two structural notes. First, the order is data, not folklore: &lt;code&gt;policyVersion&lt;/code&gt; names it, and changing the order means minting a new version string. Second, purity is load-bearing. Because &lt;code&gt;computeQuote&lt;/code&gt; cannot observe the world, the quote a customer received last month can be recomputed today by replaying last month's inputs — the fee schedule &lt;em&gt;as versioned then&lt;/em&gt;, the rate &lt;em&gt;as passed then&lt;/em&gt; — and it must produce identical cents. A function that secretly read "current GST rate" from config would make historical quotes unreproducible the moment anything changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  One quote, end to end, in exact integers
&lt;/h2&gt;

&lt;p&gt;Concreteness time. All figures illustrative. A customer wants four all-weather tires installed — the sort of no-swap-twice-a-year setup covered on the &lt;a href="https://calgaryrimandtire.ca/all-weather-tires-calgary" rel="noopener noreferrer"&gt;all-weather tires overview&lt;/a&gt; — with an install package and a seasonal-changeover-style mount-and-balance labour line.&lt;/p&gt;

&lt;p&gt;Inputs, in cents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product:  4 × 21,499  (tire @ $214.99, tax group: GST)
Fee:      4 ×    500  (illustrative per-tire recycling levy, GST group)
Service:  1 ×  8,000  (install labour package, $80.00, GST group)
Adjust:   −250 bp on product lines only (illustrative 2.5% off tires)
GST:      500 bp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Following the declared order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Product extension: &lt;code&gt;21_499 × 4 = 85_996&lt;/code&gt;. Exact.&lt;/li&gt;
&lt;li&gt;Fees: &lt;code&gt;500 × 4 = 2_000&lt;/code&gt;. Exact.&lt;/li&gt;
&lt;li&gt;Services: &lt;code&gt;8_000&lt;/code&gt;. Exact.&lt;/li&gt;
&lt;li&gt;Adjustment: targets product extension only. &lt;code&gt;85_996 × (−250) = −21_499_000&lt;/code&gt;; divide by 10,000 → &lt;code&gt;−2_149.9&lt;/code&gt; → rounds away from zero → &lt;code&gt;−2_150&lt;/code&gt;. (This is &lt;code&gt;−$21.50&lt;/code&gt;.) One rounding event, recorded.&lt;/li&gt;
&lt;li&gt;Taxable base: &lt;code&gt;85_996 + 2_000 + 8_000 − 2_150 = 93_846&lt;/code&gt; → &lt;code&gt;$938.46&lt;/code&gt;. Exact.&lt;/li&gt;
&lt;li&gt;GST: &lt;code&gt;93_846 × 500 = 46_923_000&lt;/code&gt;; over 10,000 → &lt;code&gt;4_692.3&lt;/code&gt; → &lt;code&gt;4_692&lt;/code&gt; (no tie, plain truncation-side case, &lt;code&gt;.3&lt;/code&gt; rounds down). &lt;code&gt;$46.92&lt;/code&gt;. Second and final rounding event.&lt;/li&gt;
&lt;li&gt;Total: &lt;code&gt;93_846 + 4_692 = 98_538&lt;/code&gt; → &lt;strong&gt;$985.38 out the door.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Exactly two rounding events, both at named sites, both reconstructible. Re-enter the same lines in any order, on any machine, in any decade: &lt;code&gt;98_538&lt;/code&gt;. Compare that determinism with the floating-point version from earlier, where algebraically equivalent orderings produced different formatted totals — the difference is not diligence, it is architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discounts: percentages and absolutes are different animals
&lt;/h2&gt;

&lt;p&gt;Adjustments deserve their own type discipline because they are where well-meaning flexibility destroys invariants. The safe representation is a tagged union that makes the dangerous cases unrepresentable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Adjustment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;percentage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;basisPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BasisPoints&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// e.g. −250 = 2.5% off&lt;/span&gt;
      &lt;span class="nl"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product-lines&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;services&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;lineId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                                &lt;span class="c1"&gt;// mandatory, audited&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;absolute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                &lt;span class="c1"&gt;// e.g. −1_500 = $15.00 off&lt;/span&gt;
      &lt;span class="nl"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quote-pre-tax&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;lineId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rules this encoding enforces, and why each exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A percentage must name its base.&lt;/strong&gt; "2.5% off" is meaningless until you say off &lt;em&gt;what&lt;/em&gt; — the tire lines? Including levies? Including labour? The &lt;code&gt;target&lt;/code&gt; field makes the base explicit, so the computed cents are a function of declared data. (Per-tire environmental levies are typically &lt;em&gt;not&lt;/em&gt; discountable — the amount is remitted onward regardless of what the seller charges — which is an excellent illustration of why "percentage off everything" must not be the only representable adjustment.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Percentages compute once, into cents, at step 4, and the cents are what's stored on the result.&lt;/strong&gt; Never store "2.5% off" and re-derive it at display time from whatever the lines happen to be &lt;em&gt;now&lt;/em&gt; — that re-derivation is how a later line edit silently changes an already-communicated figure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Absolutes are already cents and never round&lt;/strong&gt;, but they must declare whether they apply pre-tax (reducing the GST base — the normal case for a seller-funded price reduction) or post-tax (a payment against the total, which is not a price change at all and arguably belongs in a different structure entirely). Pre-tax is the default; post-tax should be rare and loudly typed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stacking order is policy.&lt;/strong&gt; Two percentage adjustments: do they compose on the original base (additive: 10% + 5% = 15% off) or sequentially (compound: 14.5% off)? Neither is wrong; unspecified is wrong. The design picks sequential-in-list-order and the property tests pin it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;reason&lt;/code&gt; is mandatory.&lt;/strong&gt; Not for the math — for the humans and the audit trail. An adjustment with no recorded rationale is the line item every future reader distrusts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One more guard worth its weight: clamp. An absolute adjustment larger than its target line, or stacked percentages exceeding 100%, should fail validation rather than produce a negative line. Negative totals from over-discounting are a classic silent-corruption path into every downstream system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composing GST and per-tire levies without double-taxing or under-taxing
&lt;/h2&gt;

&lt;p&gt;Tax composition questions are really "what is in the base?" questions, and the tire case has a genuinely instructive one: &lt;strong&gt;is the recycling levy itself subject to GST?&lt;/strong&gt; In Canada, environmental handling fees of this style are generally treated as part of the consideration for the sale — meaning GST applies on top of the levy — but the correct move for a design article is not to bake a legal conclusion into code, it is to make the question a &lt;em&gt;data&lt;/em&gt; question. Hence tax groups: every line, including fee lines, carries a tax-group tag, and the engine taxes whatever the group says to tax. If the treatment ever needs to change, it changes in the versioned fee schedule, not in the arithmetic.&lt;/p&gt;

&lt;p&gt;The fee schedule as data, versioned like the policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;FeeSchedule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;// "fees-2026-illustrative-v1"&lt;/span&gt;
  &lt;span class="nl"&gt;effectiveFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// ISO date&lt;/span&gt;
  &lt;span class="nl"&gt;perUnit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FeeClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;amountCents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;taxGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gst&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exempt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// e.g. passenger: { amountCents: 500, taxGroup: "gst" }  ← illustrative&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shape handles the realities gracefully: levy amounts differ by tire class (a passenger tire and the tires on the trucks served by a &lt;a href="https://calgaryrimandtire.ca/commercial-tire-services" rel="noopener noreferrer"&gt;commercial tire program&lt;/a&gt; carry different fee classes), amounts change when the program changes them, and historical quotes must keep pointing at the schedule version that priced them. GST composition then follows mechanically from the tax-group partition built in step 5 of the calculation order: sum each group exactly, apply each group's rate once, sum the results. No line is ever taxed twice because it belongs to exactly one group; no taxable line escapes because group membership is mandatory, not defaulted.&lt;/p&gt;

&lt;p&gt;A brief word on the 5% itself: Alberta's lack of a provincial sales tax makes this the &lt;em&gt;easy&lt;/em&gt; version of the problem — one rate, one group in the common case. The design still carries the tax-group machinery, because "we only have one tax" is exactly the assumption that hurts when a quote engine meets a second jurisdiction, an exempt fleet account, or a zero-rated line. The machinery costs one field and one loop; the assumption costs a rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Property-based tests: state the invariants, let the machine hunt
&lt;/h2&gt;

&lt;p&gt;Example-based tests check the arithmetic you thought of. Property-based tests check the algebra you &lt;em&gt;claimed&lt;/em&gt;. For a money engine the claims are crisp enough to be executable, which makes this one of the best property-testing domains there is. Using Python and Hypothesis for the sketches (fast-check is the TypeScript equivalent):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hypothesis&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategies&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;

&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tuples&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100_000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# unit cents ($0.01–$1,000)
&lt;/span&gt;              &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;       &lt;span class="c1"&gt;# quantity
&lt;/span&gt;    &lt;span class="n"&gt;min_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randoms&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_total_invariant_under_reordering&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;shuffled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;[:];&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shuffled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;compute_quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;compute_quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shuffled&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;

&lt;span class="nd"&gt;@given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_total_equals_sum_of_parts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line_extensions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;per_unit_fees&lt;/span&gt; \
                    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service_total&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;adjustment_applied&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gst&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The properties worth encoding for this engine, roughly in order of bug-finding yield:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Permutation invariance.&lt;/strong&gt; Reordering lines never changes any total. This property is &lt;em&gt;false&lt;/em&gt; in naive floating-point implementations (float addition is not associative) and true by construction here — which makes it the perfect canary: if it ever fails, someone reintroduced a float.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parts-sum identity.&lt;/strong&gt; The displayed total always equals the exact sum of displayed parts. This is the "invoice adds up on paper" guarantee, and it holds because the total is defined as that sum — the test guards against anyone "helpfully" recomputing it another way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No-drift round-trip.&lt;/strong&gt; Serialize a quote result to JSON and back, or write and re-read it through the persistence layer: every cents field is bit-identical. Trivial with integers, historically violated by anything that transits a &lt;code&gt;float&lt;/code&gt; column or a locale-formatting layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recompute stability.&lt;/strong&gt; &lt;code&gt;compute_quote(x) == compute_quote(x)&lt;/code&gt;, and more usefully: recomputing from the stored &lt;em&gt;inputs&lt;/em&gt; of a historical quote reproduces the stored &lt;em&gt;outputs&lt;/em&gt;. This is the executable form of purity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bounded rounding error.&lt;/strong&gt; Per-group tax differs from the unrounded real-number tax by strictly less than one cent, and the count of rounding events equals (number of adjustments + number of tax groups). Pinning the &lt;em&gt;count&lt;/em&gt; is underrated: it catches a refactor that sneaks a third rounding site in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adjustment symmetry.&lt;/strong&gt; Applying an adjustment and its exact negation yields the original totals — the refund-mirrors-sale property that half-away-from-zero was chosen to provide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monotonicity sanity.&lt;/strong&gt; Adding a line with positive amounts never decreases the total; a clamped discount never produces a negative line. Cheap to state, embarrassing to fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hypothesis will find the tie-break edges (amounts ending in exactly half a cent after rate application), the qty-zero and single-line degenerates, and adjacency effects between stacked adjustments faster than any hand-written table. Keep the hand-written table too — the worked example above belongs in it verbatim, as an executable specification.&lt;/p&gt;

&lt;h2&gt;
  
  
  The audit trail: append-only events, replayable prices
&lt;/h2&gt;

&lt;p&gt;Now the operational half. A quoted price is a small promise, and promises need provenance: what was quoted, when, under which fee schedule, and &lt;em&gt;why&lt;/em&gt; did revision 3 differ from revision 2? The design answer is an &lt;strong&gt;append-only event log&lt;/strong&gt; — quote state is never updated in place; every change is a new immutable record, and "the quote" is a fold over its events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;quote_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;event_id&lt;/span&gt;      &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;quote_id&lt;/span&gt;      &lt;span class="n"&gt;UUID&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;revision&lt;/span&gt;      &lt;span class="nb"&gt;INT&lt;/span&gt;         &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;-- 1, 2, 3… per quote&lt;/span&gt;
  &lt;span class="n"&gt;event_type&lt;/span&gt;    &lt;span class="nb"&gt;TEXT&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;-- 'created' | 'line_added'&lt;/span&gt;
                                                  &lt;span class="c1"&gt;-- | 'line_removed' | 'adjustment_applied'&lt;/span&gt;
                                                  &lt;span class="c1"&gt;-- | 'reprice_requested' | 'superseded'&lt;/span&gt;
  &lt;span class="n"&gt;payload&lt;/span&gt;       &lt;span class="n"&gt;JSONB&lt;/span&gt;       &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;-- full input delta, typed&lt;/span&gt;
  &lt;span class="n"&gt;policy_version&lt;/span&gt;   &lt;span class="nb"&gt;TEXT&lt;/span&gt;     &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;-- 'rounding-half-away-v1'&lt;/span&gt;
  &lt;span class="n"&gt;fee_schedule_ver&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;     &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;-- 'fees-2026-illustrative-v1'&lt;/span&gt;
  &lt;span class="n"&gt;computed_total&lt;/span&gt;   &lt;span class="nb"&gt;BIGINT&lt;/span&gt;   &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;-- cents, result AFTER this event&lt;/span&gt;
  &lt;span class="n"&gt;actor&lt;/span&gt;         &lt;span class="nb"&gt;TEXT&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;-- who or what made the change&lt;/span&gt;
  &lt;span class="n"&gt;reason&lt;/span&gt;        &lt;span class="nb"&gt;TEXT&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;-- human-entered rationale&lt;/span&gt;
  &lt;span class="n"&gt;occurred_at&lt;/span&gt;   &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quote_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- No UPDATE, no DELETE. Enforce with permissions, not discipline:&lt;/span&gt;
&lt;span class="c1"&gt;-- REVOKE UPDATE, DELETE ON quote_events FROM app_role;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this structure buys, concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reconstruction.&lt;/strong&gt; To answer "why was this customer quoted &lt;code&gt;$985.38&lt;/code&gt; on the 3rd?", replay events 1..n through the pure &lt;code&gt;computeQuote&lt;/code&gt; with the recorded schedule and policy versions. Because the function is pure and the inputs are fully captured, the replay &lt;em&gt;must&lt;/em&gt; land on &lt;code&gt;computed_total&lt;/code&gt; — and storing that expected total in the event row turns every replay into a checksum. A mismatch means either data corruption or an unversioned behaviour change, both of which you urgently want to know about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diffable revisions.&lt;/strong&gt; Revision 2 versus revision 3 is a structured diff of payloads: "rear line quantity 2 → 4, actor: counter-staff, reason: customer switched from pair to full set." The &lt;em&gt;why&lt;/em&gt; travels with the &lt;em&gt;what&lt;/em&gt; — this is the difference between an audit trail and a mere backup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supersession, not mutation.&lt;/strong&gt; When a quote is re-priced because a new fee schedule took effect, the old revision remains fully intact and reproducible under its original schedule; a &lt;code&gt;reprice_requested&lt;/code&gt; event creates the next revision under the new one. Nothing historical ever becomes false.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest timestamps.&lt;/strong&gt; &lt;code&gt;occurred_at&lt;/code&gt; is when the event was recorded, and it is the only clock in the system. The pure function takes no clock, so "what would this quote be if entered today" versus "what was it then" are both answerable, distinctly, without ambiguity about which question you asked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same discipline scales down and up. A single retail quote for a &lt;a href="https://calgaryrimandtire.ca/tire-repair-in-calgary" rel="noopener noreferrer"&gt;flat repair&lt;/a&gt; might have two events in its whole life; the multi-vehicle estimates a &lt;a href="https://calgaryrimandtire.ca/fleet-management" rel="noopener noreferrer"&gt;fleet account&lt;/a&gt; generates might accumulate dozens across weeks of back-and-forth. The event model does not care — the fold is the fold, and cheap immutable rows are the least expensive insurance a money system can buy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotent recalculation: same request, same cents, no surprises
&lt;/h2&gt;

&lt;p&gt;"Idempotent" gets used loosely; here it means something specific and testable: &lt;strong&gt;recalculating a quote any number of times, from any starting point, converges on identical output, and requesting the same mutation twice produces one event, not two.&lt;/strong&gt; Both halves matter and they are different mechanisms.&lt;/p&gt;

&lt;p&gt;The first half is purity again, now with teeth. Because &lt;code&gt;computeQuote&lt;/code&gt; is a function of (lines, services, adjustments, schedule version, rate, policy version) and nothing else, recalculation is safe to run anywhere, any time: on every event append, in a nightly verification sweep that replays recent quotes and compares checksums, in a migration dry-run. There is no "recalculate" button that might change the number — recalculation is &lt;em&gt;verification&lt;/em&gt;, and if it would produce a different total, that is definitionally a new revision requiring a new event with a recorded cause, never a silent overwrite.&lt;/p&gt;

&lt;p&gt;The second half is transport-level: the counter's browser retries a save; a webhook fires twice; a queued job redelivers. Standard fix, worth writing down because money systems are where duplicate mutations hurt most: every mutating request carries a client-generated &lt;strong&gt;idempotency key&lt;/strong&gt;, and the event append is conditional on that key being unseen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;quote_events&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;idempotency_key&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;quote_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- Retry with the same key → unique violation → return the ORIGINAL result, HTTP 200.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The retry does not error out from the client's perspective — it receives the same response the first attempt earned, which is the whole point: the caller cannot tell (and need not care) whether it was the first or the fourth delivery. Combined with the &lt;code&gt;UNIQUE (quote_id, revision)&lt;/code&gt; constraint doing optimistic concurrency (two staff editing the same quote race to claim revision n+1; the loser re-reads and re-applies), the event log stays linear, duplicate-free, and calm under exactly the flaky-network conditions a real counter with a tablet on retail Wi-Fi will produce.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schema sketches: the whole design on one page
&lt;/h2&gt;

&lt;p&gt;Pulling the persistent shapes together, trimmed to essentials. Money columns are &lt;code&gt;BIGINT&lt;/code&gt; cents everywhere — no &lt;code&gt;NUMERIC(10,2)&lt;/code&gt; (which invites application-layer float conversion at the boundary) and absolutely no &lt;code&gt;FLOAT&lt;/code&gt;/&lt;code&gt;REAL&lt;/code&gt; (which is the original sin the whole article exists to prevent):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;fee_schedules&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;version&lt;/span&gt;        &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;effective_from&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;   &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;body&lt;/span&gt;           &lt;span class="n"&gt;JSONB&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;     &lt;span class="c1"&gt;-- {class: {amount_cents, tax_group}}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;quotes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;quote_id&lt;/span&gt;         &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;current_revision&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt;           &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt;
                     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'draft'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'issued'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'accepted'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'superseded'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'expired'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- quotes is a THIN pointer; all substance lives in quote_events.&lt;/span&gt;
&lt;span class="c1"&gt;-- Rebuilding `quotes` from the event log must always be possible.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the TypeScript view of a materialized line, the shape the counter UI renders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RenderedLine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;unitCents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;extensionCents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// exact product&lt;/span&gt;
  &lt;span class="nl"&gt;feeClass&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;FeeClass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// levy attaches per unit if present&lt;/span&gt;
  &lt;span class="nl"&gt;taxGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gst&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exempt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three deliberate omissions, each a design statement. There is no &lt;code&gt;discount_percent&lt;/code&gt; column on any persistent line — percentages exist only inside adjustment events, computed to cents at application time. There is no &lt;code&gt;total&lt;/code&gt; column on &lt;code&gt;quotes&lt;/code&gt; — totals live on events, where they were computed, with the versions that computed them. And there is no nullable &lt;code&gt;tax_rate&lt;/code&gt; defaulting to "current" — rates are always explicit arguments, because a default that tracks the present is a time bomb for the past.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the design meets the driveway
&lt;/h2&gt;

&lt;p&gt;Strip away the tire-and-levy particulars and the design is five commitments, each small, each load-bearing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Represent money as typed integer cents&lt;/strong&gt;, with rates as integer basis points, so representation error is impossible rather than managed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Round in exactly one function&lt;/strong&gt;, under exactly one written policy, at named sites in a declared calculation order — and version both the policy and the fee data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute quotes with a pure function&lt;/strong&gt; whose every input is explicit, so any historical figure is reproducible from its recorded inputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Record changes as append-only events&lt;/strong&gt; carrying actor, reason, versions, and the computed result — provenance and checksum in one row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make recalculation idempotent&lt;/strong&gt; at both the math layer (purity) and the transport layer (idempotency keys), so retries and verification sweeps are boring.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this is exotic. All of it is the difference between a quoting tool that a one-location tire business could actually trust and a spreadsheet with delusions. The pattern generalizes far past tires — it applies to any system that promises a person a number and expects them to still believe it later — but the grounding matters: constraints like whole-cent totals, per-unit levies, single-rate GST, and quantities of two and four are what make the design tractable enough to get &lt;em&gt;provably&lt;/em&gt; right. If the domain itself interests you — what actually distinguishes an all-season from an all-weather compound, or how &lt;a href="https://calgaryrimandtire.ca/seasonal-tire-changes" rel="noopener noreferrer"&gt;seasonal changeovers&lt;/a&gt; are scheduled around the first snowfall rush, or how a storefront like &lt;a href="https://calgaryrimandtire.ca/calgarys-local-tire-shop" rel="noopener noreferrer"&gt;this Calgary tire operation&lt;/a&gt; organizes the services these quotes describe — the operational side has its own depth, from &lt;a href="https://calgaryrimandtire.ca/tire-sidewall-information" rel="noopener noreferrer"&gt;reading a sidewall&lt;/a&gt; to &lt;a href="https://calgaryrimandtire.ca/tire-load-index-explained" rel="noopener noreferrer"&gt;decoding load indexes&lt;/a&gt;, and even the humble &lt;a href="https://calgaryrimandtire.ca/online-bookings" rel="noopener noreferrer"&gt;online scheduling flow&lt;/a&gt; is downstream of exactly one requirement: the number quoted is the number paid.&lt;/p&gt;

&lt;p&gt;One final restatement of the frame, because it belongs at both ends of an article like this: the operational constraints above are real, the illustrative figures are not real prices, and the system is a design on paper — a target worth aiming at, described precisely enough to build, tested in the only way that counts before production exists: by construction.&lt;/p&gt;

</description>
      <category>automotive</category>
      <category>calgary</category>
      <category>maintenance</category>
      <category>tires</category>
    </item>
    <item>
      <title>Two Records, One Driver: Entity Resolution for People and Vehicles in a Service Backend</title>
      <dc:creator>KMJ Tire Calgary</dc:creator>
      <pubDate>Wed, 12 Aug 2026 16:22:17 +0000</pubDate>
      <link>https://dev.to/kmjtirecalgary/two-records-one-driver-entity-resolution-for-people-and-vehicles-in-a-service-backend-10j1</link>
      <guid>https://dev.to/kmjtirecalgary/two-records-one-driver-entity-resolution-for-people-and-vehicles-in-a-service-backend-10j1</guid>
      <description>&lt;h2&gt;
  
  
  The Tuesday Where One Driver Became Four Rows
&lt;/h2&gt;

&lt;p&gt;A customer walked in with a set of winter wheels he had stored with us the previous spring. The counter staffer searched his surname, found nothing useful, and created a new customer row. His wheels were in the rack under a tag that referenced a VIN. His spring visit had been entered from a web form under "Jonathan." The autumn visit before that had been entered by phone under "Jon." A fourth row existed because someone had typed his mobile number with the last digit transposed.&lt;/p&gt;

&lt;p&gt;Four rows. One person. One vehicle. The storage tag was the only thing that saved us, and the storage tag only worked because a technician had photographed the VIN plate through the windshield eighteen months earlier.&lt;/p&gt;

&lt;p&gt;That is entity resolution, and it is not an exotic problem reserved for banks and hospitals. Any operation that accepts identity from humans under time pressure will manufacture duplicates. Ours does roughly fifteen thousand vehicle interactions a year across tire and oil work, which is small enough that people assume the problem does not exist and large enough that it absolutely does. This is a walkthrough of how we model it: what we normalize, how we generate candidates, how we score them, and — the part most write-ups skip — how we undo a bad decision six months later without corrupting history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Duplicates Are Manufactured, Not Discovered
&lt;/h2&gt;

&lt;p&gt;It helps to stop thinking of duplicates as data-quality accidents and start thinking of them as the predictable output of specific mechanisms. We catalogued ours. Each one needs a different countermeasure, which is why a single "fuzzy match on name" heuristic never works.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Transcription noise under time pressure.&lt;/strong&gt; During the two-week crunch around a &lt;a href="https://calgaryrimandtire.ca/seasonal-tire-changes" rel="noopener noreferrer"&gt;seasonal changeover&lt;/a&gt;, intake volume triples and the person entering data is standing, holding a tablet, with three people behind the first. Digit transposition in phone numbers spikes measurably in those weeks. So do truncated surnames.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provenance divergence.&lt;/strong&gt; A record born in the &lt;a href="https://calgaryrimandtire.ca/online-bookings" rel="noopener noreferrer"&gt;self-serve web form&lt;/a&gt; has a validated email and a legal-form name. A record born at the counter has a nickname and no email. A record born from a voice conversation has whatever the staffer heard. These three describe the same human and share almost no exact tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared household numbers.&lt;/strong&gt; Two adults, one landline or one mobile listed for both. Phone agreement is strong evidence for a household and weak evidence for a person, and conflating those two is the single most common way a naive matcher fuses a married couple into one entity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Number recycling.&lt;/strong&gt; Carriers reissue numbers. A number that identified one customer in 2021 can identify an unrelated person in 2026. Any rule that treats phone equality as identity equality without a temporal guard will eventually merge strangers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plate transfer.&lt;/strong&gt; In Alberta the plate follows the registrant, not the vehicle. Someone sells a car, keeps the plate, puts it on the replacement. Plate is therefore a pointer that gets reassigned, and treating it as a vehicle key silently glues two vehicles' histories together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VIN captured from a photograph.&lt;/strong&gt; The dash plate is behind glass at an angle, often dirty, often in a dark bay. Optical character recognition and human eyes both confuse S with 5, B with 8, and 0 with D. One wrong character produces a vehicle that has never existed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fleet versus retail confusion.&lt;/strong&gt; A &lt;a href="https://calgaryrimandtire.ca/fleet-management" rel="noopener noreferrer"&gt;fleet program&lt;/a&gt; account has a dispatcher whose phone appears on forty vehicles. If the dispatcher is modelled as the customer, forty vehicles collapse into one relationship and the per-unit history becomes unusable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name variants that are not typos.&lt;/strong&gt; Diacritics dropped, hyphenated surnames split, given names anglicized, married names adopted mid-history. These are legitimately different strings for the same person, and a strict comparison will never join them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deliberate separation.&lt;/strong&gt; Occasionally a person wants two profiles — a personal vehicle and a business vehicle — and the "duplicate" is intentional. A resolution system with no way to record "these are known to be distinct" will re-merge them every night.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last one matters more than it seems. Any deduplication pipeline that cannot store a negative assertion will thrash forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identifier Stability Beats Identifier Convenience
&lt;/h2&gt;

&lt;p&gt;Before writing any comparison logic, rank your identifiers by how stable they are over time and how strongly they bind to the thing you care about. Convenience and stability are almost inversely correlated, which is why systems drift toward the wrong keys.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Identifier&lt;/th&gt;
&lt;th&gt;Binds to&lt;/th&gt;
&lt;th&gt;Stability over time&lt;/th&gt;
&lt;th&gt;Availability at intake&lt;/th&gt;
&lt;th&gt;Typical corruption&lt;/th&gt;
&lt;th&gt;Practical role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VIN (17 char)&lt;/td&gt;
&lt;td&gt;The physical vehicle&lt;/td&gt;
&lt;td&gt;Permanent for the life of the unit&lt;/td&gt;
&lt;td&gt;Poor — often skipped when busy&lt;/td&gt;
&lt;td&gt;Single-character misreads from photos or glare&lt;/td&gt;
&lt;td&gt;Strongest vehicle anchor; verify the check digit before trusting it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Licence plate&lt;/td&gt;
&lt;td&gt;The registrant's entitlement&lt;/td&gt;
&lt;td&gt;Low — transferable and reassigned&lt;/td&gt;
&lt;td&gt;Excellent, visible on arrival&lt;/td&gt;
&lt;td&gt;Ambiguous glyphs, spacing, stale after transfer&lt;/td&gt;
&lt;td&gt;Good candidate generator, poor persistent key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phone (E.164)&lt;/td&gt;
&lt;td&gt;A household or a dispatcher&lt;/td&gt;
&lt;td&gt;Medium — recycled every few years&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Transposition, missing area code, extensions&lt;/td&gt;
&lt;td&gt;Strong blocking key, medium person evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;An account, not a person&lt;/td&gt;
&lt;td&gt;Medium-high, but frequently absent&lt;/td&gt;
&lt;td&gt;Poor for walk-in traffic&lt;/td&gt;
&lt;td&gt;Shared family addresses, typo domains&lt;/td&gt;
&lt;td&gt;High weight when present, low coverage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;A person, loosely&lt;/td&gt;
&lt;td&gt;High in essence, low in spelling&lt;/td&gt;
&lt;td&gt;Always present&lt;/td&gt;
&lt;td&gt;Everything imaginable&lt;/td&gt;
&lt;td&gt;Never sufficient alone; useful in combination&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Postal code&lt;/td&gt;
&lt;td&gt;A residence&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Format variants, stale after moving&lt;/td&gt;
&lt;td&gt;Weak corroboration only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wheel storage tag&lt;/td&gt;
&lt;td&gt;A physical set of wheels&lt;/td&gt;
&lt;td&gt;High while stored&lt;/td&gt;
&lt;td&gt;Internal, always present&lt;/td&gt;
&lt;td&gt;Mislabelling at the rack&lt;/td&gt;
&lt;td&gt;Excellent internal cross-check&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice that the two identifiers a staffer can grab fastest — plate and phone — are both pointers rather than identities. That mismatch is the core tension. You need the fast ones to find candidates and the slow ones to confirm them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normalize First, Or Everything Downstream Is Noise
&lt;/h2&gt;

&lt;p&gt;Everything that follows assumes a normalization layer that is deterministic, versioned, and stored separately from the raw input. Three properties matter.&lt;/p&gt;

&lt;p&gt;Normalization must be pure and reproducible: given the same raw string and the same normalizer version, you get the same output forever. Store the version alongside the output so you can tell which records were processed by which logic. When you fix a bug in surname folding, you need to know exactly which derived values are now stale.&lt;/p&gt;

&lt;p&gt;Normalization must never destroy the original. Persist the raw string the human typed. You will need it during a review, and you will need it when a regulator or a customer asks what you actually recorded. The normalized value is a derived index, not a replacement.&lt;/p&gt;

&lt;p&gt;Normalization must fail loudly rather than guess. If a phone string cannot be resolved to a valid North American number, record it as unusable with a reason code instead of coercing it into something plausible. Silent coercion is how you end up with three hundred customers whose phone is &lt;code&gt;+10000000000&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  E.164 Is The Only Phone Format Worth Persisting
&lt;/h2&gt;

&lt;p&gt;Every phone comparison in the system operates on E.164 strings and nothing else. The normalizer below handles the input shapes we actually see: parenthesized area codes, dotted separators, a leading long-distance &lt;code&gt;1&lt;/code&gt;, trailing extensions, and pasted international strings that begin with the North American escape prefix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NamedTuple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NamedTuple&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;e164&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


&lt;span class="n"&gt;_EXTENSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(?:ext|extn|x|poste)\.?\s*\d{1,6}\s*$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;_NON_DIGIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[^0-9]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalize_nanp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Reduce a North American phone string to E.164, or explain why not.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_EXTENSION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_NON_DIGIT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;011&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;international_escape_prefix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wrong_length:{}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;npa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nxx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;npa&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;nxx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid_npa_or_nxx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;npa&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;npa&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n11_service_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running it over a handful of representative inputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;(403) 555-0142&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;           &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e164&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+14035550142&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1-403-555-0142&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;           &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e164&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+14035550142&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+1 403 555 0142 ext 12&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;   &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e164&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+14035550142&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;403.555.014&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;              &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e164&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wrong_length:9&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;011 44 20 7946 0000&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e164&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;international_escape_prefix&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;211-555-0142&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;             &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;PhoneResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e164&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;n11_service_code&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two deliberate choices. Extensions are stripped rather than preserved in the match key, because an extension identifies a desk inside an organization and two people at the same organization should not fuse. We keep the extension in the raw record and in a separate column; it just does not participate in comparison. And the N11 guard exists because someone will eventually paste a service number into the field. The &lt;code&gt;555-01xx&lt;/code&gt; numbers used throughout this article are from the range reserved for fictional use, which is why they appear in sample data without hesitation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing The VIN Check Digit Without Getting It Wrong
&lt;/h2&gt;

&lt;p&gt;The VIN is the only identifier in this domain with built-in error detection, and it is astonishing how many systems store VINs without ever using it. North American VINs assigned since 1981 carry a check digit in position nine. Getting the algorithm right is not optional: a subtly wrong implementation is worse than none, because it rejects good data and passes bad data.&lt;/p&gt;

&lt;p&gt;Three details trip people up. First, the letters I, O, and Q are excluded from the VIN alphabet entirely, precisely because they are confusable with 1 and 0. Any VIN containing them is malformed, full stop. Second, the transliteration table is not "position in the alphabet" — it wraps, so J maps to 1 and S maps to 2. Third, position nine contributes weight zero to its own sum, and a remainder of 10 is written as the letter X.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;VIN_TRANSLITERATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;D&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;E&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;F&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;G&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;H&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;J&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;K&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;L&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;P&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;U&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;V&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;W&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;VIN_WEIGHTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;VIN_ALPHABET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0123456789&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VIN_TRANSLITERATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VinError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;vin_check_digit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return the character that belongs in position 9 of a 17-character VIN.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;vin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vin&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;VinError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;length {} is not 17&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;VIN_ALPHABET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;VinError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;character {!r} is illegal at position {}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;VIN_TRANSLITERATION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;VIN_WEIGHTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remainder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;vin_is_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;vin_check_digit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;VinError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exercising it against a synthetic VIN — &lt;code&gt;ZZZ&lt;/code&gt; is not an assigned world manufacturer identifier, so this string cannot collide with a real vehicle — plus the widely published test vector &lt;code&gt;1M8GDM9AXKP042788&lt;/code&gt;, whose check digit is X:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;check digit of ZZZTESTV9N9SB4207 is 9 | valid: True
S read as 5: False
B read as 8: False
digits swapped: False
illegal letter Q: False
known reference 1M8GDM9AXKP042788 -&amp;gt; X
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one of those failure cases is a real transcription error we have seen when a VIN is keyed in from a photograph taken through a windshield.&lt;/p&gt;

&lt;h2&gt;
  
  
  What The Check Digit Cannot Save You From
&lt;/h2&gt;

&lt;p&gt;Modulo-11 with those weights catches all single-character substitutions and all transpositions of adjacent characters. It does not catch everything. Roughly one in eleven random 17-character strings will pass by coincidence, so validity is evidence, not proof. More importantly, a VIN can be perfectly valid and still be the wrong vehicle — someone reads the VIN off the paperwork of the car parked in the next bay, or a fleet coordinator pastes the wrong row from a spreadsheet.&lt;/p&gt;

&lt;p&gt;So we treat check-digit validity as a gate on &lt;em&gt;storage confidence&lt;/em&gt;, not on truth. A VIN that fails validation is retained in the raw record, flagged, and excluded from blocking and scoring. A VIN that passes gets a confidence tier based on how it was captured: scanned barcode outranks manual entry, which outranks a value derived from a photograph. When two records disagree on a valid VIN, that disagreement is a hard veto rather than a negative weight, because a confirmed VIN conflict means you are looking at two different vehicles regardless of how much else matches.&lt;/p&gt;

&lt;p&gt;There is also a population wrinkle worth knowing. Vehicles built before the 1981 standardization, some imported units, and a scattering of trailers and equipment carry VINs that are shorter than 17 characters or that do not follow the check-digit convention at all. If you serve any &lt;a href="https://calgaryrimandtire.ca/commercial-tire-services" rel="noopener noreferrer"&gt;commercial customers&lt;/a&gt;, you will meet these. Model the field as "vehicle identifier with a scheme tag" rather than "17-character VIN," and your data layer stops lying to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plates Are Leases, Not Primary Keys
&lt;/h2&gt;

&lt;p&gt;Plate handling needs two different canonical forms, and conflating them causes a specific class of bug.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;comparison&lt;/em&gt; form strips whitespace and punctuation and uppercases. Nothing more. &lt;code&gt;BXK 4471&lt;/code&gt; and &lt;code&gt;bxk-4471&lt;/code&gt; are the same plate; &lt;code&gt;BXK4471&lt;/code&gt; and &lt;code&gt;8XK4471&lt;/code&gt; are not, because on a real plate the glyphs are distinct and the registry issued exactly one of them.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;blocking&lt;/em&gt; form is deliberately lossier. It folds visually confusable glyph pairs together so that a mis-keyed plate still lands in the same bucket as the correct one. This form exists only to generate candidates. It must never be used to decide anything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;

&lt;span class="n"&gt;NAME_SUFFIXES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ii&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iii&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;_NAME_JUNK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[^a-z ]+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;_PLATE_JUNK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[^A-Z0-9]+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;_GLYPH_FOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maketrans&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;O&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;canonical_plate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Exact-comparison form: case and separators removed, glyphs preserved.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_PLATE_JUNK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;plate_block_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Recall-oriented form: confusable glyphs collapsed. Candidates only.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;canonical_plate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_GLYPH_FOLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fold_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;decomposed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NFKD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ascii_only&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;decomposed&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;combining&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;lowered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ascii_only&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_NAME_JUNK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lowered&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;NAME_SUFFIXES&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;_SOUNDEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bfpv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cgjkqsxz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;l&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;letter&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_SOUNDEX&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;soundex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fold_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_SOUNDEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]:&lt;/span&gt;
        &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_SOUNDEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beyond canonicalization, plates need a validity window. We store plate observations as time-bounded facts attached to a vehicle, not as a column on the vehicle row. When a customer arrives with a plate that we last saw on a different VIN, that is not a data error to reconcile — it is a transfer event to record. Losing that distinction is how a returning customer's &lt;a href="https://calgaryrimandtire.ca/wheel-balancing" rel="noopener noreferrer"&gt;wheel balancing&lt;/a&gt; history from a sold vehicle ends up attached to the car they bought last month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Folding Names Without Flattening People
&lt;/h2&gt;

&lt;p&gt;Name folding is the step where enthusiasm does the most damage. Aggressive folding boosts recall and destroys precision, and in a small population the precision loss is what people notice, because merging two real customers is visible and embarrassing while missing a duplicate is merely inefficient.&lt;/p&gt;

&lt;p&gt;Our folding does four things and stops: Unicode decomposition with combining marks removed, lowercasing, hyphen and apostrophe normalization to spaces or nothing, and removal of generational suffixes. &lt;code&gt;D'Angelo-Rossi, Jr&lt;/code&gt; becomes &lt;code&gt;dangelo rossi&lt;/code&gt;. We do not stem, we do not apply nickname dictionaries at the folding stage, and we do not reorder tokens.&lt;/p&gt;

&lt;p&gt;Nickname expansion happens later, as a scoring signal rather than a normalization step, and it is directional. "Jon" being a prefix of "Jonathan" earns partial credit. A curated table maps "Bill" toward "William" and "Sasha" toward "Alexander" — but as evidence with a weight, never as a rewrite. The moment you rewrite the stored value, you have lost the ability to tell a reviewer why the system thought two records matched.&lt;/p&gt;

&lt;p&gt;Soundex is the phonetic key we use for blocking. It is old, crude, and heavily biased toward English orthography, which is a genuine weakness in a city as linguistically varied as this one. Its saving grace is that it is cheap and its failure mode is predictable: it over-groups, which is exactly what you want from a candidate generator. &lt;code&gt;Okonkwo&lt;/code&gt; and &lt;code&gt;Okonquo&lt;/code&gt; both fold to &lt;code&gt;O252&lt;/code&gt;. It is not doing the deciding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Candidate Generation, Or How To Avoid n² Comparisons
&lt;/h2&gt;

&lt;p&gt;With a hundred thousand source records, all-pairs comparison is five billion evaluations. Even at a microsecond each that is over an hour of CPU for a nightly job, and the comparison function is not going to be a microsecond. Blocking is the standard escape: emit one or more keys per record, and only compare records that share a key.&lt;/p&gt;

&lt;p&gt;The tradeoff is explicit. A narrow key produces small blocks and fast runs but misses pairs whose linking evidence lives in a field the key ignores. A broad key produces high recall and blocks large enough to reintroduce the quadratic problem inside a single bucket. The answer is several independent narrow keys rather than one clever broad one, with a hard size cap that skips pathological blocks and reports them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;blocking_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;vin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin8:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
        &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin6:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
    &lt;span class="n"&gt;plate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;plate_block_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plt:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;plate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone_e164&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ph7:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
    &lt;span class="n"&gt;surname_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;soundex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;surname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;given&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fold_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;given_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;surname_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nm:{}{}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surname_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nmph:{}{}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surname_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:]))&lt;/span&gt;
        &lt;span class="n"&gt;fsa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fsa&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nmfsa:{}{}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surname_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fsa&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;


&lt;span class="n"&gt;MAX_BLOCK_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;candidate_pairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
    &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;blocking_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MAX_BLOCK_SIZE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;skipped&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
                &lt;span class="n"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]))))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skipped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the synthetic fixture used for the rest of this article. Invented people, invented plates, one synthetic VIN.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# synthetic sample data: invented people, invented plates, synthetic VIN
&lt;/span&gt;&lt;span class="n"&gt;SAMPLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;given_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Jonathan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;surname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Okonkwo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone_e164&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+14035550142&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BXK4471&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ZZZTESTV9N9SB4207&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T2N 1N4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web_form&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;given_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Jon&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;surname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Okonkwo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone_e164&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+14035550142&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bxk 4471&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T2N1N4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;front_counter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;given_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Marie&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;surname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Okonkwo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone_e164&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+14035550142&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DHR2210&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T2N1N4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;front_counter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;given_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Jonathan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;surname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Okonkwo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone_e164&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+14035550143&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T2N1N4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;voice_intake&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;given_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Jonathan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;surname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Okonkwo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone_e164&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ZZZTESTV9N9SB4207&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;storage_tag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Picking Block Keys That Recall Without Exploding
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;MAX_BLOCK_SIZE&lt;/code&gt; cap deserves a paragraph of its own, because it is where blocking meets reality. A key like "soundex of surname plus first initial" behaves beautifully for &lt;code&gt;O252j&lt;/code&gt; and catastrophically for the local equivalent of &lt;code&gt;S530j&lt;/code&gt;. One oversized bucket can dominate an entire run.&lt;/p&gt;

&lt;p&gt;We cap at sixty and emit skipped keys to a metric rather than silently dropping them. If a key is skipped, the pairs inside it were never evaluated, and pretending otherwise makes your recall measurement a fiction. In practice the skipped list is short and dominated by two things: the surname keys for very common names, and the phone key belonging to a dispatcher whose number is attached to an entire &lt;a href="https://calgaryrimandtire.ca/fleet-management" rel="noopener noreferrer"&gt;commercial vehicle fleet&lt;/a&gt;. The second case gets handled structurally rather than by tuning — dispatcher numbers are tagged as organizational and excluded from person-level blocking entirely.&lt;/p&gt;

&lt;p&gt;Two more habits earn their keep. Suffix keys beat prefix keys for VINs, because the last eight characters are the sequential serial and carry far more entropy than the manufacturer prefix; blocking on the first eight would put every vehicle from one marque in one bucket. And compound keys beat single-field keys when both components are individually weak: surname phonetic plus last four digits of phone is far more selective than either half, and it survives an area-code error that a full-phone key would not.&lt;/p&gt;

&lt;p&gt;Running the generator over the fixture yields nine candidate pairs from five records — a reminder that even tiny populations produce a lot of comparisons once several keys are in play.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weighting Field Agreement With Numbers You Can Defend
&lt;/h2&gt;

&lt;p&gt;Once you have pairs, you need a score. The Fellegi–Sunter framework is fifty years old and still the right starting point because it forces you to state two probabilities per field and then does the arithmetic honestly.&lt;/p&gt;

&lt;p&gt;For each field you estimate &lt;code&gt;m&lt;/code&gt;, the probability that the field agrees given the pair is genuinely the same entity, and &lt;code&gt;u&lt;/code&gt;, the probability that it agrees given the pair is not. Agreement contributes &lt;code&gt;log2(m/u)&lt;/code&gt; bits; disagreement contributes &lt;code&gt;log2((1-m)/(1-u))&lt;/code&gt;, which is negative. Missing data contributes zero, which is the property that makes this approach so much better than an ad-hoc percentage: an absent VIN neither helps nor hurts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FieldModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;   &lt;span class="c1"&gt;# P(field agrees | same entity)
&lt;/span&gt;    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;   &lt;span class="c1"&gt;# P(field agrees | different entities)
&lt;/span&gt;
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;agree_weight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;disagree_weight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log2&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="n"&gt;MODELS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="nc"&gt;FieldModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.94&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0000005&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nc"&gt;FieldModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0000020&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;household&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FieldModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0035000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;surname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nc"&gt;FieldModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0030000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;given&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nc"&gt;FieldModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0120000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nc"&gt;FieldModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0002000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;PARTIAL_CREDIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.35&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;weight_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MODELS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agree_weight&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agree_weight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PARTIAL_CREDIT&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disagree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disagree_weight&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which produces this weight table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vin        agree +20.84  disagree  -4.06
plate      agree +18.75  disagree  -3.06
household  agree  +8.01  disagree  -3.32
surname    agree  +8.26  disagree  -3.64
given      agree  +6.15  disagree  -2.72
email      agree +11.43  disagree  -1.15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where do the numbers come from? The &lt;code&gt;u&lt;/code&gt; values are estimated from the data itself: sample random pairs from your population, measure how often each field agrees by chance, and use that frequency. For VIN the chance-agreement rate is essentially the reciprocal of the distinct-VIN count, hence the tiny value and the enormous weight. The &lt;code&gt;m&lt;/code&gt; values are harder and require a labelled sample; start with a defensible guess, then refine against clerical review outcomes. Expectation-maximization can estimate both without labels, but on a population this size the estimates are unstable and I would rather label eight hundred pairs by hand and know what I have.&lt;/p&gt;

&lt;p&gt;One refinement that matters: &lt;code&gt;u&lt;/code&gt; should be frequency-adjusted for the specific value, not just the field. Agreement on a surname held by 0.4% of your customers is far weaker evidence than agreement on one held by 0.004%. The full treatment computes per-value weights; a cheap approximation is to halve the surname weight whenever the folded value appears in the top fifty most frequent surnames in your own table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conditional Independence Is A Lie In A Household
&lt;/h2&gt;

&lt;p&gt;The naive Fellegi–Sunter sum assumes fields are conditionally independent given match status. Phone and postal code are not remotely independent — people who share an address usually share a phone. Summing both as separate evidence double-counts the same underlying fact and inflates scores for exactly the pairs you most need to keep apart: family members.&lt;/p&gt;

&lt;p&gt;The pragmatic fix is to collapse correlated fields into a single composite comparison. Our &lt;code&gt;household&lt;/code&gt; field takes phone agreement if both phones exist, and falls back to forward sortation area agreement otherwise, capped at partial credit. One field, one weight, no double counting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compare_levels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;lv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lv&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="nf"&gt;else &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;levenshtein&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disagree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;lp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonical_plate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nf"&gt;canonical_plate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rp&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disagree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lp&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;rp&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;lph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone_e164&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone_e164&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;lfsa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;rfsa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lph&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;rph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lph&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;household&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;levenshtein&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;household&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;household&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disagree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;lfsa&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;rfsa&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;household&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lfsa&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rfsa&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disagree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;household&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fold_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;surname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nf"&gt;fold_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;surname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;near&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;soundex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;soundex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;edit_ratio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.80&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;surname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="nf"&gt;else &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;near&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disagree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;surname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fold_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;given_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nf"&gt;fold_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;given_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lg&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;clipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;given&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lg&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rg&lt;/span&gt; &lt;span class="nf"&gt;else &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;clipped&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;edit_ratio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disagree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;given&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;le&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;le&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rr&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disagree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;le&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;rr&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two string helpers it leans on are the ordinary dynamic-programming edit distance and its length-normalized complement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;levenshtein&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ca&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ca&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;edit_ratio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;levenshtein&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that a phone one edit away earns partial credit rather than full — that is the transposition case from intake — while a phone two or more edits away scores as disagreement. The same distance powers the near-miss VIN level, where two characters of slack absorbs a photograph misread without accepting an unrelated unit. Anything looser and you start joining vehicles that differ only in trim code, which the &lt;a href="https://calgaryrimandtire.ca/shop-all-tires-in-calgary" rel="noopener noreferrer"&gt;tire fitment catalogue&lt;/a&gt; will happily tell you are genuinely different machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Worked Comparison, Bit By Bit
&lt;/h2&gt;

&lt;p&gt;Take &lt;code&gt;r1&lt;/code&gt; (web form, full given name, plate and VIN present) against &lt;code&gt;r3&lt;/code&gt; (counter entry, different given name, same household phone, different plate). Field by field:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;vin&lt;/code&gt;: one side is empty, so &lt;code&gt;unknown&lt;/code&gt; → 0.00&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;plate&lt;/code&gt;: &lt;code&gt;BXK4471&lt;/code&gt; versus &lt;code&gt;DHR2210&lt;/code&gt;, &lt;code&gt;disagree&lt;/code&gt; → −3.06&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;household&lt;/code&gt;: identical E.164 strings, &lt;code&gt;agree&lt;/code&gt; → +8.01&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;surname&lt;/code&gt;: both fold to &lt;code&gt;okonkwo&lt;/code&gt;, &lt;code&gt;agree&lt;/code&gt; → +8.26&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;given&lt;/code&gt;: &lt;code&gt;jonathan&lt;/code&gt; versus &lt;code&gt;marie&lt;/code&gt;, neither prefix nor near, &lt;code&gt;disagree&lt;/code&gt; → −2.72&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;email&lt;/code&gt;: absent on both sides, &lt;code&gt;unknown&lt;/code&gt; → 0.00&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total: &lt;strong&gt;10.49 bits&lt;/strong&gt;. With an auto-link threshold of 12.0, that lands in the review band — which is the correct answer. These two records probably describe two adults in one household. Without the composite household field, phone and postal would have contributed separately and the pair would have crossed the threshold and merged a couple.&lt;/p&gt;

&lt;p&gt;Contrast with &lt;code&gt;r1&lt;/code&gt; against &lt;code&gt;r5&lt;/code&gt;, the wheel-storage tag that carries a VIN and a name but no phone or address. VIN agrees (+20.84), surname agrees (+8.26), given name agrees (+6.15), everything else unknown. Total 35.25 bits, comfortably a link, driven almost entirely by one strong identifier. That asymmetry is the whole point: a single high-quality field beats a pile of weak corroboration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deterministic Vetoes Over Probabilistic Enthusiasm
&lt;/h2&gt;

&lt;p&gt;Probabilistic scoring is the right default, but there are facts about this domain that no weight should be allowed to outvote. Those go in a veto layer that runs after scoring and can only demote a decision, never promote one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;AUTO_LINK_AT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;12.0&lt;/span&gt;
&lt;span class="n"&gt;REVIEW_FLOOR_AT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hard_block_reason&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;vehicle_anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disagree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vin_conflict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;given&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disagree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;vehicle_anchor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;given_name_conflict_without_vehicle_anchor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lvl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;weight_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lvl&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lvl&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;compare_levels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;levels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;lvl&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lvl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hard_block_reason&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;AUTO_LINK_AT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;REVIEW_FLOOR_AT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;distinct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verdict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;veto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full run over the fixture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r1~r2   37.17 link     -
        vin=unknown plate=agree household=agree surname=agree given=partial email=unknown
r1~r3   10.49 review   given_name_conflict_without_vehicle_anchor
        vin=unknown plate=disagree household=agree surname=agree given=disagree email=unknown
r1~r4   17.21 link     -
        vin=unknown plate=unknown household=partial surname=agree given=agree email=unknown
r1~r5   35.25 link     -
        vin=agree plate=unknown household=unknown surname=agree given=agree email=unknown
r3~r4    8.34 review   given_name_conflict_without_vehicle_anchor
        vin=unknown plate=unknown household=partial surname=agree given=disagree email=unknown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When does each approach win? Here is the honest division of labour:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Deterministic rule&lt;/th&gt;
&lt;th&gt;Probabilistic score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A verified strong identifier agrees exactly&lt;/td&gt;
&lt;td&gt;Wins — cheap, explainable, no tuning&lt;/td&gt;
&lt;td&gt;Redundant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A verified strong identifier conflicts&lt;/td&gt;
&lt;td&gt;Wins as a veto&lt;/td&gt;
&lt;td&gt;Dangerous; weights can be outvoted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Several weak fields partially agree&lt;/td&gt;
&lt;td&gt;Cannot express degree&lt;/td&gt;
&lt;td&gt;Wins clearly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regulatory or policy constraint&lt;/td&gt;
&lt;td&gt;Wins — must be auditable&lt;/td&gt;
&lt;td&gt;Unacceptable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Novel error pattern nobody anticipated&lt;/td&gt;
&lt;td&gt;Fails silently&lt;/td&gt;
&lt;td&gt;Degrades gracefully into review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explaining a decision to a customer&lt;/td&gt;
&lt;td&gt;Wins — one sentence&lt;/td&gt;
&lt;td&gt;Needs a rendered weight breakdown&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our production pipeline is deterministic first, probabilistic second, veto third. Exact agreement on a validated VIN links immediately without scoring. Everything else goes through weights. Vetoes then pull suspicious links down into review.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Review Band Is Deliberate Engineering
&lt;/h2&gt;

&lt;p&gt;The gap between &lt;code&gt;REVIEW_FLOOR_AT&lt;/code&gt; and &lt;code&gt;AUTO_LINK_AT&lt;/code&gt; is not an admission that the model is weak. It is a budget. Fellegi–Sunter's original formulation sets the two thresholds from your tolerated false-match and false-non-match rates, and everything in between is routed to a human. Widen the band and you buy accuracy with labour; narrow it and you buy throughput with errors.&lt;/p&gt;

&lt;p&gt;Sizing it is an operations question, not a statistics question. We measured how many pairs land in the band per week and set the thresholds so that the queue stays under what one person can clear in twenty minutes a day, with headroom for the &lt;a href="https://calgaryrimandtire.ca/winter-tires-calgary" rel="noopener noreferrer"&gt;changeover crunch&lt;/a&gt; when volume triples. If the queue grows faster than it drains, the band is wrong or the upstream capture is degrading; either way it is a signal, not a chore.&lt;/p&gt;

&lt;p&gt;The review interface matters more than the model. A reviewer needs the raw values side by side — not the normalized ones — the per-field weight breakdown, the provenance of each record, and the service history of both. Three outcomes are available: link, distinct, or defer with a note. "Distinct" writes a negative assertion that suppresses the pair permanently, which is what stops the nightly job from re-proposing the same married couple every week for a year.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persist Links, Never Overwrite Rows
&lt;/h2&gt;

&lt;p&gt;Here is the structural decision that separates systems you can live with from systems you cannot: never destructively merge. Keep every source record exactly as captured, immutable, and express identity as a separate layer of assertions on top.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Immutable capture. One row per identity assertion made by a human or an import.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;source_record&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;source_record_id&lt;/span&gt;   &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;origin&lt;/span&gt;             &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- web_form | front_counter | voice_intake | fleet_import | storage_tag&lt;/span&gt;
    &lt;span class="n"&gt;captured_at&lt;/span&gt;        &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;location_id&lt;/span&gt;        &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;raw_payload&lt;/span&gt;        &lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;-- exactly what was typed, never edited&lt;/span&gt;
    &lt;span class="n"&gt;normalizer_version&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;given_name_folded&lt;/span&gt;  &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;surname_folded&lt;/span&gt;     &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;surname_phonetic&lt;/span&gt;   &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;phone_e164&lt;/span&gt;         &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;phone_reject_code&lt;/span&gt;  &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email_normalized&lt;/span&gt;   &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;plate_canonical&lt;/span&gt;    &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vin_upper&lt;/span&gt;          &lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;vin_check_ok&lt;/span&gt;       &lt;span class="nb"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;postal_fsa&lt;/span&gt;         &lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;source_record_phone_idx&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;source_record&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone_e164&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;phone_e164&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;source_record_vin_idx&lt;/span&gt;   &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;source_record&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vin_upper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;vin_check_ok&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;source_record_plate_idx&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;source_record&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plate_canonical&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Pairwise assertions. Positive and negative, both first class.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;identity_link&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;identity_link_id&lt;/span&gt;  &lt;span class="n"&gt;bigserial&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;entity_kind&lt;/span&gt;       &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity_kind&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'person'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'vehicle'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;left_record_id&lt;/span&gt;    &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;right_record_id&lt;/span&gt;   &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;assertion&lt;/span&gt;         &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;assertion&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'same'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'distinct'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;score_bits&lt;/span&gt;        &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;veto_reason&lt;/span&gt;       &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;decided_by&lt;/span&gt;        &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- 'auto:v7' or a staff identifier&lt;/span&gt;
    &lt;span class="n"&gt;decided_at&lt;/span&gt;        &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;retired_at&lt;/span&gt;        &lt;span class="n"&gt;timestamptz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;retired_reason&lt;/span&gt;    &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left_record_id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;right_record_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left_record_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;source_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;right_record_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;source_record&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;identity_link_live_idx&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;identity_link&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity_kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left_record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right_record_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;retired_at&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Materialized closure. Cheap to rebuild, never a source of truth.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;identity_cluster&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cluster_id&lt;/span&gt;     &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;entity_kind&lt;/span&gt;    &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;generation&lt;/span&gt;     &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;computed_at&lt;/span&gt;    &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;cluster_member&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cluster_id&lt;/span&gt;       &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;identity_cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;source_record_id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;source_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source_record_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three properties fall out of this shape. The raw capture is never mutated, so an audit can always reconstruct what a human actually entered. Identity is a set of retractable assertions, so reversing a decision means retiring a row rather than reconstructing deleted data. And the cluster table is derived, disposable, and versioned by &lt;code&gt;generation&lt;/code&gt;, so you can rebuild the whole closure after a weight change and diff the result before promoting it.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;CHECK (left_record_id &amp;lt; right_record_id)&lt;/code&gt; constraint plus the partial unique index means one live assertion per unordered pair, enforced by the database rather than by hope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Materializing Clusters With Union-Find
&lt;/h2&gt;

&lt;p&gt;Turning pairwise assertions into groups is a connected-components problem, and disjoint-set union is the obvious tool. You will write this whether you plan to or not, so write it well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DisjointSet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rank&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# path compression
&lt;/span&gt;            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;union&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;ra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ra&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ra&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;ra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ra&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ra&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ra&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ra&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_clusters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active_links&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;dsu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DisjointSet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;record_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;dsu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;active_links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;dsu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;union&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;record_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dsu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feeding it the three auto-links from the fixture gives &lt;code&gt;[['r1', 'r2', 'r4', 'r5'], ['r3']]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Transitive closure is where entity resolution goes wrong at scale. Union is unconditional: if A links to B and B links to C, then A, B, and C are one cluster even if A and C are wildly dissimilar. One bad link can chain two large groups together, and the failure is invisible until someone notices a customer with forty vehicles.&lt;/p&gt;

&lt;p&gt;Two guards are worth the effort. First, run a post-closure sanity pass: for every cluster above a size threshold, evaluate the &lt;em&gt;lowest&lt;/em&gt;-scoring internal pair and flag the cluster if that score falls below the review floor. That catches chains held together by a single weak edge. Second, respect negative assertions during closure — if any pair inside a proposed cluster carries a live &lt;code&gt;distinct&lt;/code&gt; assertion, refuse the merge and route the whole component to review rather than silently overriding a human.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unmerge Is A First-Class Operation
&lt;/h2&gt;

&lt;p&gt;You will merge two people wrongly. Plan the reversal before you ship the merge.&lt;/p&gt;

&lt;p&gt;Because assertions are retractable and source records are immutable, unmerge is mechanically simple: retire the offending &lt;code&gt;identity_link&lt;/code&gt; row with a reason, bump the generation, recompute the closure for the affected component. The cluster splits. No data was destroyed, so nothing needs reconstructing.&lt;/p&gt;

&lt;p&gt;What is not simple is everything downstream that captured the merged identity. Consider the artifacts a service visit produces: a work order, an invoice, a wheel-storage tag, a tire-and-rim inspection note, a communication log. If those reference &lt;code&gt;cluster_id&lt;/code&gt; directly, an unmerge orphans them or silently reassigns them.&lt;/p&gt;

&lt;p&gt;The rule we settled on: transactional records reference &lt;code&gt;source_record_id&lt;/code&gt;, never &lt;code&gt;cluster_id&lt;/code&gt;. The cluster is a read-time lens, resolved through &lt;code&gt;cluster_member&lt;/code&gt; at query time or through a materialized view refreshed after each generation. Any record that must survive an identity change — anything with legal, financial, or safety weight — points at immutable capture. A generated invoice is a document; it names whoever it named when it was issued, and it does not retroactively change owners because a clustering job changed its mind.&lt;/p&gt;

&lt;p&gt;External systems need a separate escape hatch. Anything you have already exported — an accounting ledger, a message log, a warranty registration filed with a manufacturer — cannot be unmerged by you. Log every identity change as an event with before and after cluster identifiers so that downstream consumers can reconcile at their own pace. We keep that event stream for seven years, which matches the retention on the financial records it explains.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ownership Has A Start Date And An End Date
&lt;/h2&gt;

&lt;p&gt;Here is the modelling error I see most often: a &lt;code&gt;vehicle&lt;/code&gt; table with an &lt;code&gt;owner_id&lt;/code&gt; column. That column asserts a fact with no time bounds, and vehicle ownership is one of the most reliably temporary relationships in this domain. Cars get sold. Fleets rotate units between drivers. A leased truck returns to the lessor.&lt;/p&gt;

&lt;p&gt;The relationship needs its own table with validity intervals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;vehicle&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vehicle_id&lt;/span&gt;       &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;identifier_scheme&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;identifier_scheme&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'vin17'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pre1981'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'serial'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'internal'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;vin_upper&lt;/span&gt;        &lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;first_seen_at&lt;/span&gt;    &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;identifier_scheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vin_upper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;vehicle_custody&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;custody_id&lt;/span&gt;     &lt;span class="n"&gt;bigserial&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vehicle_id&lt;/span&gt;     &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;vehicle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;person_cluster&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;valid_period&lt;/span&gt;   &lt;span class="n"&gt;tstzrange&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;evidence&lt;/span&gt;       &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;-- observed_at_visit | stated_by_customer | fleet_roster_import&lt;/span&gt;
    &lt;span class="n"&gt;EXCLUDE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;gist&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vehicle_id&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_period&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;plate_observation&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;observation_id&lt;/span&gt;  &lt;span class="n"&gt;bigserial&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vehicle_id&lt;/span&gt;      &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;vehicle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;plate_canonical&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;observed_at&lt;/span&gt;     &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;observed_by&lt;/span&gt;     &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;EXCLUDE USING gist&lt;/code&gt; constraint is the important line. It makes overlapping custody intervals for one vehicle physically impossible at the storage layer, which means the "two owners at once" bug cannot be introduced by an application bug, a race, or a careless backfill.&lt;/p&gt;

&lt;p&gt;Plate observations are events, not state. The current plate for a vehicle is the most recent observation; the plate history is the full series. When the same canonical plate appears against a second &lt;code&gt;vehicle_id&lt;/code&gt;, the system emits a transfer candidate rather than a conflict, and a reviewer decides whether it was a genuine transfer, a typo, or a vehicle record that was created twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading Service History Through A Temporal Lens
&lt;/h2&gt;

&lt;p&gt;Once custody is temporal, history questions split into two genuinely different queries, and conflating them produces answers that feel wrong to everybody.&lt;/p&gt;

&lt;p&gt;"What has been done to this vehicle?" is a question about the machine. It spans owners. If a set of &lt;a href="https://calgaryrimandtire.ca/all-weather-tires-calgary" rel="noopener noreferrer"&gt;all-weather tires&lt;/a&gt; was fitted two owners ago, that is still the tread on the vehicle today and still relevant when the current driver arrives. The same holds for an &lt;a href="https://calgaryrimandtire.ca/all-season-tires-calgary" rel="noopener noreferrer"&gt;all-season fitment&lt;/a&gt; with three winters on it, or a spare that was never rotated in.&lt;/p&gt;

&lt;p&gt;"What has this person had done?" is a question about the human, and it spans vehicles. It should include only the intervals during which they actually held each vehicle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Everything ever done to one vehicle, annotated with who held it at the time.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;performed_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service_kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person_cluster&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;holder_at_time&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;service_visit&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;   &lt;span class="n"&gt;vehicle_custody&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt;   &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vehicle_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vehicle_id&lt;/span&gt;
 &lt;span class="k"&gt;AND&lt;/span&gt;   &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_period&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;performed_at&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vehicle_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;performed_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Everything one person had done, across every vehicle they actually held.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;performed_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service_kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;veh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vin_upper&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;service_visit&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;   &lt;span class="n"&gt;vehicle_custody&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt;   &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vehicle_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vehicle_id&lt;/span&gt;
 &lt;span class="k"&gt;AND&lt;/span&gt;   &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_period&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;performed_at&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;   &lt;span class="n"&gt;vehicle&lt;/span&gt; &lt;span class="n"&gt;veh&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;veh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vehicle_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vehicle_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person_cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;performed_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;service_kind&lt;/code&gt; enum is deliberately narrow, because our work is narrow: &lt;code&gt;tire_installation&lt;/code&gt;, &lt;code&gt;seasonal_changeover&lt;/code&gt;, &lt;code&gt;rotation&lt;/code&gt;, &lt;code&gt;wheel_balance&lt;/code&gt;, &lt;code&gt;flat_repair&lt;/code&gt;, &lt;code&gt;tpms_service&lt;/code&gt;, &lt;code&gt;wheel_storage_in&lt;/code&gt;, &lt;code&gt;wheel_storage_out&lt;/code&gt;, &lt;code&gt;oil_change&lt;/code&gt;. Nine values, all of them things we actually do. A constrained enum is also a quiet integrity check — if a row shows up that does not fit, something upstream is wrong.&lt;/p&gt;

&lt;p&gt;There is a third question the temporal model answers well: "is the tread on this vehicle ours, and how old is it?" That drives a genuinely useful prompt when a vehicle arrives for &lt;a href="https://calgaryrimandtire.ca/tire-repair-in-calgary" rel="noopener noreferrer"&gt;a puncture repair&lt;/a&gt; on rubber we fitted four winters ago, and it only works if the fitment event stayed attached to the vehicle across an ownership change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fleet Records Violate Every Retail Assumption
&lt;/h2&gt;

&lt;p&gt;Retail intuitions collapse on the commercial side, and they collapse in ways that are worth enumerating because each one breaks a different part of the pipeline.&lt;/p&gt;

&lt;p&gt;The phone number belongs to a dispatcher, not a driver, and appears on every unit. Any person-level rule keyed on phone will fuse the entire roster. We tag organizational numbers explicitly and exclude them from person blocking and from the &lt;code&gt;household&lt;/code&gt; comparison entirely.&lt;/p&gt;

&lt;p&gt;The name attached to a visit is the driver on shift, which changes weekly. Driver names are attributes of the visit, not identity evidence about the account holder. Feeding them into person resolution generates noise proportional to staff turnover.&lt;/p&gt;

&lt;p&gt;Vehicles arrive in batches from a roster import, so provenance is a spreadsheet rather than a human at the counter. Roster imports have their own failure mode: duplicated rows, VINs with a stray leading apostrophe from a spreadsheet export, and units that were sold months ago but never removed. We validate check digits on import and quarantine the failures rather than creating vehicles.&lt;/p&gt;

&lt;p&gt;Custody is organizational and the individual driver is irrelevant to it. The &lt;code&gt;person_cluster&lt;/code&gt; on a fleet vehicle points at an organization entity, and the driver appears only on the visit. This is also why &lt;a href="https://calgaryrimandtire.ca/mobile-tire-service" rel="noopener noreferrer"&gt;mobile service&lt;/a&gt; visits need the vehicle anchor to be rock solid — there is often nobody present who can confirm anything about the account, just a unit number stencilled on a door.&lt;/p&gt;

&lt;p&gt;Finally, unit numbers are a fleet's own internal identifier and they are frequently more reliable in practice than anything else available, because the fleet manager maintains them. Treat a customer-supplied internal identifier as a first-class alias with a namespace: &lt;code&gt;fleet:acme-hauling:unit-114&lt;/code&gt;. Namespaced aliases are exact-match evidence with a very small &lt;code&gt;u&lt;/code&gt;, which makes them powerful and safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retention, Consent, And The Cost Of Remembering
&lt;/h2&gt;

&lt;p&gt;Identity resolution is a system for remembering people harder than they expect, which puts it squarely inside privacy obligations rather than adjacent to them.&lt;/p&gt;

&lt;p&gt;Under Canadian private-sector privacy law the relevant principle is purpose limitation: you collect and retain personal information for identified purposes and no longer than needed to fulfil them. Linking records to avoid duplicate profiles is a defensible purpose — it directly serves service quality and safety. Retaining a decade of phone numbers to build behavioural profiles is a different purpose, and you should not quietly acquire it just because your schema makes it possible.&lt;/p&gt;

&lt;p&gt;Practical consequences we implemented. Reason codes on every retention decision, so a stored value can always explain why it is still there. Automatic expiry of unusable capture artifacts: a rejected phone string with a &lt;code&gt;wrong_length&lt;/code&gt; code has no ongoing value and is purged after ninety days. Separation of identity data from marketing consent, so that resolving two records into one person never silently transfers a communication preference — consent attaches to the channel and the assertion, not to the cluster.&lt;/p&gt;

&lt;p&gt;Deletion is where immutability gets interesting. "Source records are never mutated" cannot mean "we ignore erasure requests." The resolution is tombstoning: the row survives with its identifiers replaced by a redaction marker, the cluster membership survives, and the aggregate history survives in a form that no longer identifies anyone. You lose the ability to re-resolve that record later, which is the correct tradeoff.&lt;/p&gt;

&lt;p&gt;One more thing worth stating plainly: every merge you make increases the blast radius of a breach, because a resolved cluster is a richer profile than any of its parts. That is not an argument against resolution. It is an argument for encrypting identifier columns at rest, restricting the review interface to named staff, and logging every access to a cluster view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Precision, Recall, And The Size Of The Queue
&lt;/h2&gt;

&lt;p&gt;You cannot tune what you do not measure, and "the duplicates seem better" is not a measurement.&lt;/p&gt;

&lt;p&gt;Build a labelled sample. Draw a few hundred candidate pairs stratified across the score range — deliberately oversampling near the thresholds, because that is where the decision boundary lives — and have someone adjudicate each one with all available evidence. Several hundred labels is enough to estimate precision and recall with useful confidence at this population size, and it is a day of work, not a project.&lt;/p&gt;

&lt;p&gt;From that sample: precision is the fraction of auto-linked pairs that were genuinely the same entity, and recall is the fraction of genuine matches the system linked without human help. Both matter, and they trade off against each other along the threshold. Report them together with the threshold values, always, because a precision number without its operating point is meaningless.&lt;/p&gt;

&lt;p&gt;Beyond the pair-level metrics, four population-level indicators earn dashboard space.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Duplicate rate: clusters holding more than one source record, by capture channel.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;sr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;nullif&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;multi_record_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;mean_cluster_size&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;cluster_member&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;   &lt;span class="n"&gt;source_record&lt;/span&gt; &lt;span class="n"&gt;sr&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_record_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_record_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;cluster_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cluster_member&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;sizes&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt;   &lt;span class="n"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cluster_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cluster_id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Review queue depth and age, which is a staffing signal, not a quality signal.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;percentile_cont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;WITHIN&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;decided_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;median_age&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;identity_link&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="k"&gt;assertion&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;retired_at&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Reviewer agreement with the model: how often a human overturns an auto-link.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;date_trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'week'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decided_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;wk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;retired_reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'reviewer_overturned'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;
         &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;nullif&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;overturn_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;identity_link&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;decided_by&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'auto:%'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The overturn rate is the metric I watch most closely, because it is the only one that updates continuously without new labelling effort. A rising overturn rate means the model and reality have diverged, and it usually shows up weeks before anybody complains about duplicates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drift Signals Worth Waking Someone For
&lt;/h2&gt;

&lt;p&gt;Entity resolution degrades quietly. The pipeline keeps running, the job keeps succeeding, and the output keeps getting worse. These are the monitors that have actually caught something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Score distribution shift.&lt;/strong&gt; Histogram the scores of all evaluated pairs weekly and compare against the trailing baseline. A shift in mass toward the review band means input quality changed — usually because someone modified an intake form. When we added a field to the &lt;a href="https://calgaryrimandtire.ca/online-bookings" rel="noopener noreferrer"&gt;self-serve reservation flow&lt;/a&gt;, phone completion dropped four points and the histogram showed it before anyone noticed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Field null-rate by origin.&lt;/strong&gt; VIN capture rate at the counter is a leading indicator of everything else. During the busiest changeover weeks it falls, and the review queue swells about ten days later. Knowing that lets you staff for it instead of being surprised.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocking-key explosion.&lt;/strong&gt; The count of blocks exceeding &lt;code&gt;MAX_BLOCK_SIZE&lt;/code&gt;, and the number of records inside them. A sudden jump almost always means a normalizer regression collapsed a field to a constant — the classic being a plate parser that starts returning empty strings and puts everyone in one bucket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cluster size outliers.&lt;/strong&gt; Alert on any person cluster exceeding a threshold that is generous for a household and impossible for an individual. Fifteen is our line for retail clusters. Anything above it is either a fleet account misfiled as retail or a transitive-closure chain, and both need eyes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unmerge frequency by cohort.&lt;/strong&gt; Group unmerges by the model version that created the link. If a version's unmerge rate is climbing relative to its predecessors, you have a regression with a clear owner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normalizer version skew.&lt;/strong&gt; The proportion of source records whose &lt;code&gt;normalizer_version&lt;/code&gt; lags current. If it stops falling after a deploy, your backfill is stuck, and half your comparisons are running against stale derived values.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Change On A Second Pass
&lt;/h2&gt;

&lt;p&gt;Some of this design has held up for two years and some of it I regret.&lt;/p&gt;

&lt;p&gt;I would introduce the vehicle-versus-person split on day one instead of month nine. We started with a single "customer" entity that carried a plate, and untangling that later cost more than building it properly would have. Vehicles and people have different identifiers, different stability, and different lifecycles; they were never the same problem.&lt;/p&gt;

&lt;p&gt;I would put the review interface in front of staff earlier. The model improved far faster once the people who create the records could see the consequences of a hurried entry. Nothing sharpened VIN capture like a technician watching a merge decision hinge on a missing one.&lt;/p&gt;

&lt;p&gt;I would resist per-value frequency weighting for longer than I did. It is theoretically correct and it made the system noticeably harder to explain to a reviewer, for a precision gain that was real but small. On a hundred thousand records, a well-chosen composite field bought more than sophisticated weighting did.&lt;/p&gt;

&lt;p&gt;And I would treat the negative assertion as the primitive, not the afterthought. Half the operational value of this system comes from its ability to remember that two similar-looking records are definitively not the same, and we built that capability third. Positive matching is the interesting problem; recording what is known to be false is the one that stops the queue from filling with the same five pairs forever.&lt;/p&gt;

&lt;p&gt;The counter still moves fast during changeover season, staff still type in a hurry, and people still arrive with a phone they share with a spouse and a plate they moved off a car they sold. None of that is fixable at the point of capture. What is fixable is the system's willingness to say "I am not sure," write that uncertainty down, and let a human settle it — for further reading on the identifiers themselves, the &lt;a href="https://calgaryrimandtire.ca/tire-sidewall-information" rel="noopener noreferrer"&gt;sidewall marking reference&lt;/a&gt; and the &lt;a href="https://calgaryrimandtire.ca/tire-load-index-explained" rel="noopener noreferrer"&gt;load index explainer&lt;/a&gt; cover the vehicle-side data we key off, and the &lt;a href="https://calgaryrimandtire.ca/service-areas" rel="noopener noreferrer"&gt;service area map&lt;/a&gt; shows the geography that makes postal codes such weak evidence here.&lt;/p&gt;

</description>
      <category>automotive</category>
      <category>calgary</category>
      <category>maintenance</category>
      <category>tires</category>
    </item>
    <item>
      <title>Idempotent Reservations and Exclusion Constraints: Designing a Four-Bay Scheduler That Cannot Double-Book</title>
      <dc:creator>KMJ Tire Calgary</dc:creator>
      <pubDate>Tue, 11 Aug 2026 16:26:29 +0000</pubDate>
      <link>https://dev.to/kmjtirecalgary/idempotent-reservations-and-exclusion-constraints-designing-a-four-bay-scheduler-that-cannot-jea</link>
      <guid>https://dev.to/kmjtirecalgary/idempotent-reservations-and-exclusion-constraints-designing-a-four-bay-scheduler-that-cannot-jea</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;About this article.&lt;/strong&gt; We are a tire and oil change shop in Calgary, and the operational constraints described here are real: a small number of physical bays, jobs of tightly clustered durations, and a seasonal changeover rush that concentrates demand into a few weeks. The scheduling system itself is a &lt;strong&gt;design exercise&lt;/strong&gt; written up for other developers, not a description of live production infrastructure. Every schema, query, timestamp, metric and figure below is &lt;strong&gt;illustrative&lt;/strong&gt; — invented to make the reasoning concrete. Treat it as a worked design, and adapt it to your own stack.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Duplicate That Shows Up on a Tuesday in October
&lt;/h2&gt;

&lt;p&gt;Start with the domain, because it is unusually easy to reason about. A tire shop in Calgary has a handful of physical bays, jobs that run in fairly predictable blocks, and one weekend in October where half the city decides simultaneously that it is time. When a scheduler is wrong, the cost is concrete: a technician standing in a bay with nothing to do, or a customer who drove across the city for a slot that had already been consumed.&lt;/p&gt;

&lt;p&gt;The failure worth designing against looks like this in the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; reservation_id                        | bay_id | service          | lower(during)             | created_at
--------------------------------------+--------+------------------+---------------------------+---------------------------
 6f2a1c9e-...-a41b                    |      3 | tire_changeover  | 2026-10-20 08:00:00-06    | 2026-10-19 21:14:02.881-06
 c0d7be34-...-9f52                    |      3 | tire_changeover  | 2026-10-20 08:00:00-06    | 2026-10-19 21:14:03.774-06
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two rows. Same bay, same start time, same customer, 893 milliseconds apart. Nobody wanted two of them. The customer was sitting in a parking lot somewhere along Deerfoot with one bar of LTE, the request took long enough that the spinner felt broken, and they tapped the submit control a second time. The mobile client had no memory of the first attempt. Our server had no reason to think the second attempt was the same intention as the first.&lt;/p&gt;

&lt;p&gt;The result was a bay that looked occupied for two hours when it was occupied for one, and a second customer who was told there was no room. That is a revenue problem and a trust problem, and it is caused by a bug that has been solved thousands of times in other people's systems. It is worth writing down properly, because most of the write-ups I found either stop at "use an idempotency key" or drift into distributed-consensus territory that a single Postgres instance does not need.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Network Actually Promises You
&lt;/h2&gt;

&lt;p&gt;Here is the uncomfortable primitive at the bottom of all of this. A client that issues a POST and then times out cannot distinguish between two worlds:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The request never reached the server. Nothing happened.&lt;/li&gt;
&lt;li&gt;The request reached the server, the server did the work and committed it, and the response was lost on the way home.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is no protocol trick that collapses those two worlds into one. TCP will not tell you. HTTP will not tell you. A load balancer that returns 504 after 30 seconds is guessing exactly as hard as the client is. This is the ordinary at-least-once delivery problem, and it has exactly one honest resolution: the server must make repetition harmless, and the client must be told precisely how to repeat.&lt;/p&gt;

&lt;p&gt;That is what idempotency means here. Not "the operation has no side effects" — creating a reservation obviously has side effects. It means "executing this request N times produces the same observable outcome as executing it once, and returns the same answer every time."&lt;/p&gt;

&lt;p&gt;Everything below is the machinery for that sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Work We Actually Schedule
&lt;/h2&gt;

&lt;p&gt;Before the schemas, the domain, because the domain constrains the design far more than the framework does.&lt;/p&gt;

&lt;p&gt;We do tire work and oil changes. That is the whole catalogue. It matters for the scheduler because the durations cluster tightly and the resource is coarse: a small number of physical bays, each of which can hold exactly one vehicle at a time. The service types we encode are the ones described on the public pages — &lt;a href="https://calgaryrimandtire.ca/seasonal-tire-changes" rel="noopener noreferrer"&gt;seasonal changeover work&lt;/a&gt;, &lt;a href="https://calgaryrimandtire.ca/tire-repair-in-calgary" rel="noopener noreferrer"&gt;puncture repair&lt;/a&gt;, &lt;a href="https://calgaryrimandtire.ca/wheel-balancing" rel="noopener noreferrer"&gt;balancing&lt;/a&gt;, fresh installation, and oil service. Nothing else lands in the enum, because nothing else happens in the bays.&lt;/p&gt;

&lt;p&gt;Illustrative durations, which is what our seed data uses in tests:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service code&lt;/th&gt;
&lt;th&gt;Typical bay time&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tire_changeover&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;45 min&lt;/td&gt;
&lt;td&gt;Faster when the second set is already mounted on its own rims&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tire_installation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;75 min&lt;/td&gt;
&lt;td&gt;Mount, balance, torque, valve stems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wheel_balancing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;40 min&lt;/td&gt;
&lt;td&gt;Sometimes a follow-on to a road-force complaint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tire_repair&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;30 min&lt;/td&gt;
&lt;td&gt;Dismount, inspect, patch-plug from inside, remount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;oil_change&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;30 min&lt;/td&gt;
&lt;td&gt;Only bay 1 and bay 4 are plumbed for it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those numbers are example figures for the model, not a price list and not a promise. The point is the shape: durations vary by more than a factor of two, they are not multiples of a single grid unit, and one service type is restricted to a subset of bays. Any design that assumes uniform fixed-size slots will start lying to you within a month.&lt;/p&gt;

&lt;p&gt;The seasonal rhythm matters too. Calgary gets a first real snowfall warning somewhere in October, and the request volume on the &lt;a href="https://calgaryrimandtire.ca/winter-tires-calgary" rel="noopener noreferrer"&gt;changeover pages&lt;/a&gt; goes up by an order of magnitude in about six hours. That is the week the concurrency bugs surface, because that is the week two people genuinely are competing for the same 8:00 AM slot in the same bay, and the week a cell tower on the Trail is congested enough to produce ambiguous timeouts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Different Problems Wearing One Costume
&lt;/h2&gt;

&lt;p&gt;The failure I opened with is really two failures that happen to look identical from the customer's side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem one is request duplication.&lt;/strong&gt; The same intention arrives twice. This is a transport-layer artifact. The fix lives at the edge of your API and is about recognising that two byte streams represent one decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem two is resource contention.&lt;/strong&gt; Two genuinely different intentions arrive and both want the last available bay. This is a data-integrity problem. The fix lives in the storage engine and is about making an invalid state unrepresentable.&lt;/p&gt;

&lt;p&gt;Neither mechanism solves the other. An idempotency key will not stop two different customers from overbooking bay 3. An exclusion constraint will not stop one customer from creating two reservations in two different bays because their phone retried. If you build only one, you will spend a quarter chasing the symptoms of the other. Build both, and know which one you are relying on for which failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idempotency Record, In Full
&lt;/h2&gt;

&lt;p&gt;Start with the table, because the columns encode most of the decisions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;idempotency_records&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;requester_id&lt;/span&gt;     &lt;span class="n"&gt;uuid&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;endpoint&lt;/span&gt;         &lt;span class="nb"&gt;text&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;idem_key&lt;/span&gt;         &lt;span class="nb"&gt;text&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;request_digest&lt;/span&gt;   &lt;span class="n"&gt;bytea&lt;/span&gt;       &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;state&lt;/span&gt;            &lt;span class="nb"&gt;text&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
                   &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'in_flight'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'failed'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;response_status&lt;/span&gt;  &lt;span class="nb"&gt;smallint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;response_body&lt;/span&gt;    &lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;result_ref&lt;/span&gt;       &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;lease_until&lt;/span&gt;      &lt;span class="n"&gt;timestamptz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;finished_at&lt;/span&gt;      &lt;span class="n"&gt;timestamptz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;expires_at&lt;/span&gt;       &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;first_seen_at&lt;/span&gt;    &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requester_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idem_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idempotency_records_expiry&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;idempotency_records&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'in_flight'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things about that primary key.&lt;/p&gt;

&lt;p&gt;It is scoped by &lt;code&gt;requester_id&lt;/code&gt;. Keys are client-generated, and clients are not coordinated with each other. If two different mobile installs both use a UUIDv4 you are statistically fine, but if some integration partner uses an incrementing counter you will get collisions between tenants and start replaying one customer's response to another. Scoping by requester makes that impossible rather than unlikely.&lt;/p&gt;

&lt;p&gt;It is scoped by &lt;code&gt;endpoint&lt;/code&gt;. The same key reused against a different route is almost certainly a client bug, and treating the two as unrelated records is far kinder than replaying a reservation payload to a cancellation route.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;request_digest&lt;/code&gt; is a hash, not the body. Storing the full body is tempting for debugging and terrible for retention. A SHA-256 over a canonical serialisation is 32 bytes, tells you everything you need for the equality check, and carries no plate numbers or phone numbers into a table you will eventually forget to encrypt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fingerprinting a Request Without Lying to Yourself
&lt;/h2&gt;

&lt;p&gt;The digest is where most implementations quietly break. If you hash the raw bytes, a client that reorders JSON keys between attempts — which many HTTP libraries will happily do — produces a different digest for an identical intention, and your server rejects a legitimate retry.&lt;/p&gt;

&lt;p&gt;So canonicalise first, and be explicit about what is excluded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;VOLATILE_FIELDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;frozenset&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# regenerated on every attempt
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;device_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# not part of the intention
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trace_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;# observability plumbing
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;# the client's own retry counter
&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;request_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;stable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;VOLATILE_FIELDS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;canonical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;separators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                           &lt;span class="n"&gt;ensure_ascii&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two rules I would enforce in review. First, the exclusion list is an allowlist inverted, and it must be small and deliberate; every field you drop is a field where a genuine change slips past the equality check. Second, nested structures need the same treatment recursively if your serialiser does not sort deeply — &lt;code&gt;json.dumps(sort_keys=True)&lt;/code&gt; does, but a Protobuf-to-dict conversion might not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same Key, Different Body
&lt;/h2&gt;

&lt;p&gt;Now the interesting case. A request arrives with a key we have already seen, but the digest does not match what we stored.&lt;/p&gt;

&lt;p&gt;Return &lt;code&gt;409 Conflict&lt;/code&gt;. Do not process it. Do not overwrite the stored record. Do not silently replay the old response either.&lt;/p&gt;

&lt;p&gt;The reasoning is that you are being asked to do something incoherent, and every alternative is worse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Process it as new.&lt;/strong&gt; You have just created a second reservation under a key that was supposed to guarantee at most one. The key is now decorative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replay the old response.&lt;/strong&gt; The client asked for a 9:30 slot and you hand back a receipt for 8:00. It will believe you. Somebody drives to the wrong time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update the record and process it.&lt;/strong&gt; Same as processing it as new, with extra steps and a corrupted audit trail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;409 is the only answer that preserves the contract, and the body should say why in machine-readable form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"idempotency_key_reuse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This key was already used for a request with a different body."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01J9XW7Q4S6R8T2V3M5N7P9Q1R"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"first_seen_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-19T21:14:02.881-06:00"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That error code matters because it separates two 409s that mean entirely different things. &lt;code&gt;idempotency_key_reuse&lt;/code&gt; is a client defect and must never be retried. &lt;code&gt;slot_unavailable&lt;/code&gt;, which we will get to, is a legitimate business outcome and should be surfaced to a human, not to a retry loop. If both return a bare 409 with prose, some well-meaning SDK will retry the first one forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lifecycle of a Record
&lt;/h2&gt;

&lt;p&gt;A record is a tiny state machine with three terminal-ish states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 claim (INSERT ... ON CONFLICT DO NOTHING)
                          |
                          v
                    +-----------+
                    | in_flight |
                    +-----+-----+
                          |
          success         |          deterministic failure
      +-------------------+-------------------+
      |                                       |
      v                                       v
+-----------+                           +----------+
| completed |                           |  failed  |
+-----------+                           +----------+
      |                                       |
      +---------------+-----------------------+
                      |
                  expires_at passes, row is reaped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Transient failures — a lost connection to the database, a 500 from a downstream service, a deadlock — do &lt;em&gt;not&lt;/em&gt; move the record to &lt;code&gt;failed&lt;/code&gt;. They delete the claim entirely, or let its lease lapse, so the client's retry gets a clean attempt. Only deterministic outcomes are recorded, because only deterministic outcomes will be identical on replay. If you persist a 503 and replay it for the next 24 hours, you have built a machine for turning a five-second blip into a day-long outage for one customer.&lt;/p&gt;

&lt;p&gt;There is a fourth implicit state worth naming: &lt;strong&gt;abandoned&lt;/strong&gt;. A process claims a key, writes &lt;code&gt;in_flight&lt;/code&gt;, and then the container is evicted mid-transaction. The row is stranded. That is what &lt;code&gt;lease_until&lt;/code&gt; is for. Any record whose lease has expired is fair game for reclamation, and reclamation is just a conditional update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;idempotency_records&lt;/span&gt;
   &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'in_flight'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;lease_until&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'30 seconds'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;request_digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;requester_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;idem_key&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'in_flight'&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;lease_until&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pick the lease to be comfortably longer than your slowest legitimate handler and comfortably shorter than your client's first retry delay. Ours is 30 seconds against a handler p99 of about 400 milliseconds and a first client retry at 2 seconds, which is generous in both directions on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Insert First, Ask Questions Later
&lt;/h2&gt;

&lt;p&gt;The naive claim is &lt;code&gt;SELECT&lt;/code&gt; then &lt;code&gt;INSERT&lt;/code&gt; if absent. That is the same race we are trying to eliminate, one layer up. Two concurrent duplicates both see nothing, both insert, one gets a primary-key violation, and now your handler has an error path it did not plan for.&lt;/p&gt;

&lt;p&gt;Do the claim as a single statement and let the index arbitrate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;idempotency_records&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requester_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idem_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_digest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lease_until&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'in_flight'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'30 seconds'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'30 days'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requester_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idem_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;NOTHING&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="n"&gt;first_seen_at&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you get a row back, you own the claim and you proceed. If you get zero rows, somebody else owns it or owned it, and you go read the existing record to decide what to say. That branch is where the three answers live: replay the stored response, return 409 for a digest mismatch, or tell the caller that an attempt is currently running.&lt;/p&gt;

&lt;p&gt;For the third case, I favour failing fast over waiting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;409 Conflict
Retry-After: 1
{"error": "request_in_progress", "key": "01J9XW..."}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Holding the HTTP connection open while polling for the other transaction to finish is superficially friendlier and operationally awful. It ties up a worker, it converts a duplicate-request problem into a connection-pool-exhaustion problem, and during the October surge it is precisely the moment you cannot afford either. A client that receives &lt;code&gt;request_in_progress&lt;/code&gt; and honours &lt;code&gt;Retry-After&lt;/code&gt; gets the real answer one second later, having cost you nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capacity Is a Physical Fact, Not a Counter
&lt;/h2&gt;

&lt;p&gt;Now the second half of the problem, and the half people get wrong more often.&lt;/p&gt;

&lt;p&gt;We have four indoor bays. Not "a capacity of four" — four actual concrete rectangles, two of which have the oil-service plumbing, one of which is closest to the balancer and therefore preferred for road-force work. That distinction is not pedantry. If you model capacity as an integer you decrement, you have thrown away the constraint that a specific vehicle occupies a specific bay for a specific interval, and you will not be able to answer "which bay is this vehicle going into" without inventing an assignment step later, under time pressure, in the middle of the rush.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;service_kind&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;ENUM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'tire_changeover'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'tire_installation'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'wheel_balancing'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'tire_repair'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'oil_change'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;bays&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;bay_id&lt;/span&gt;       &lt;span class="nb"&gt;smallint&lt;/span&gt;    &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;label&lt;/span&gt;        &lt;span class="nb"&gt;text&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;oil_plumbed&lt;/span&gt;  &lt;span class="nb"&gt;boolean&lt;/span&gt;     &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;active&lt;/span&gt;       &lt;span class="nb"&gt;boolean&lt;/span&gt;     &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;reservations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;reservation_id&lt;/span&gt;  &lt;span class="n"&gt;uuid&lt;/span&gt;         &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;bay_id&lt;/span&gt;          &lt;span class="nb"&gt;smallint&lt;/span&gt;     &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;bays&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bay_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;service&lt;/span&gt;         &lt;span class="n"&gt;service_kind&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;during&lt;/span&gt;          &lt;span class="n"&gt;tstzrange&lt;/span&gt;    &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt;          &lt;span class="nb"&gt;text&lt;/span&gt;         &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'held'&lt;/span&gt;
                  &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'held'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'confirmed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'cancelled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'completed'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;customer_ref&lt;/span&gt;    &lt;span class="n"&gt;uuid&lt;/span&gt;         &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;plate&lt;/span&gt;           &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;raised_at&lt;/span&gt;       &lt;span class="n"&gt;timestamptz&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;during_is_bounded&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;during&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;during&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note &lt;code&gt;during tstzrange&lt;/code&gt; rather than a start column and an end column. That choice is what makes the rest of this section short.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SELECT-Then-INSERT Cannot Work
&lt;/h2&gt;

&lt;p&gt;The obvious handler reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Step 1&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;reservations&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;bay_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'held'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'confirmed'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;during&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;tstzrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2026-10-20 08:00-06'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-10-20 08:45-06'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[)'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Step 2, if the count was zero&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;reservations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bay_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;during&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'tire_changeover'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tstzrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2026-10-20 08:00-06'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-10-20 08:45-06'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[)'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under Postgres's default READ COMMITTED isolation, each statement sees a snapshot taken at the start of that statement. Uncommitted rows from other transactions are invisible. So two transactions running this pair concurrently both observe an empty bay, both insert, and both commit. No error is raised. Nothing in the database objects. You have a read-modify-write race with a comfortable window measured in milliseconds, which during a surge is more than wide enough to hit several times an hour.&lt;/p&gt;

&lt;p&gt;This is not a Postgres quirk. It is what READ COMMITTED is defined to do. The check you performed was a &lt;em&gt;predicate&lt;/em&gt; over rows that did not exist yet, and no isolation level below SERIALIZABLE gives predicates any protection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Transactions, One Bay: The Exact Interleaving
&lt;/h2&gt;

&lt;p&gt;Here is the sequence, written out, because I find the abstract version never lands the way a trace does. T1 and T2 are two application workers handling what turned out to be the same customer's duplicate submission, though it would look identical for two different customers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;T1&lt;/th&gt;
&lt;th&gt;T2&lt;/th&gt;
&lt;th&gt;Committed state&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0 ms&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BEGIN;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;bay 3 at 08:00 is free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 ms&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SELECT count(*) ... during &amp;amp;&amp;amp; [08:00,08:45)&lt;/code&gt; returns &lt;strong&gt;0&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;unchanged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9 ms&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BEGIN;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;unchanged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12 ms&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SELECT count(*) ...&lt;/code&gt; returns &lt;strong&gt;0&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;unchanged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18 ms&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;INSERT ... reservation A&lt;/code&gt; succeeds&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;A exists but is invisible to T2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21 ms&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;INSERT ... reservation B&lt;/code&gt; succeeds&lt;/td&gt;
&lt;td&gt;B exists, invisible to T1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25 ms&lt;/td&gt;
&lt;td&gt;&lt;code&gt;COMMIT;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;A is durable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27 ms&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;COMMIT;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A and B are both durable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At 27 ms the invariant is broken and no software component noticed. The only reason we ever found out is that a service advisor looked at a printed run sheet and saw the same plate twice.&lt;/p&gt;

&lt;p&gt;Now the same trace with the exclusion constraint in place, which we will define in a moment:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;T1&lt;/th&gt;
&lt;th&gt;T2&lt;/th&gt;
&lt;th&gt;What the engine does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;18 ms&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;INSERT ... A&lt;/code&gt; succeeds&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;GiST index entry taken for &lt;code&gt;(3, [08:00,08:45))&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21 ms&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;INSERT ... B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;blocks&lt;/strong&gt; — conflicting index entry is uncommitted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25 ms&lt;/td&gt;
&lt;td&gt;&lt;code&gt;COMMIT;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(still blocked)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;A becomes durable, lock released&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25.1 ms&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;raises &lt;code&gt;23P01 exclusion_violation&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;B never exists&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;T2's handler catches &lt;code&gt;23P01&lt;/code&gt;, maps it to &lt;code&gt;slot_unavailable&lt;/code&gt;, and returns a 409 with a list of nearby alternatives. The invariant held without the application having to think about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Overbooking Physically Impossible
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;btree_gist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;reservations&lt;/span&gt;
  &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;reservations_one_vehicle_per_bay&lt;/span&gt;
  &lt;span class="n"&gt;EXCLUDE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;gist&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;bay_id&lt;/span&gt;  &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;during&lt;/span&gt;  &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'held'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'confirmed'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that as: no two rows in the qualifying set may have both an equal &lt;code&gt;bay_id&lt;/code&gt; and overlapping &lt;code&gt;during&lt;/code&gt; ranges. &lt;code&gt;btree_gist&lt;/code&gt; is what lets a scalar column like &lt;code&gt;bay_id&lt;/code&gt; participate in a GiST index alongside a range column; without it, the equality operator has no GiST operator class.&lt;/p&gt;

&lt;p&gt;Three properties make this the right primitive rather than one option among several.&lt;/p&gt;

&lt;p&gt;It is enforced by the storage engine, so it holds regardless of which code path performs the insert. The admin console, the batch importer that loads &lt;a href="https://calgaryrimandtire.ca/fleet-management" rel="noopener noreferrer"&gt;fleet work&lt;/a&gt;, a psql session at 11 PM, a future service written by somebody who never read this article — all of them are covered. Application-level checks protect only the applications that remember to perform them.&lt;/p&gt;

&lt;p&gt;It works at READ COMMITTED. You do not need SERIALIZABLE, you do not need a retry loop for serialization failures, and you do not pay the SSI bookkeeping cost on every read.&lt;/p&gt;

&lt;p&gt;The partial &lt;code&gt;WHERE&lt;/code&gt; clause means cancelled and completed rows drop out of the constraint automatically. A cancelled 08:00 slot stops blocking new work the instant its status changes, with no tombstone handling and no separate cleanup.&lt;/p&gt;

&lt;p&gt;The cost is real but small: the GiST index is larger and slower to update than a btree, and range queries against it are not always as fast as a well-chosen composite btree on &lt;code&gt;(bay_id, start_time)&lt;/code&gt;. At our volume that is irrelevant. At a scale where it matters, you keep the exclusion constraint for correctness and add a btree for the read path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ranges Beat Timestamp Pairs When Durations Vary
&lt;/h2&gt;

&lt;p&gt;The half-open bound notation &lt;code&gt;'[)'&lt;/code&gt; in &lt;code&gt;tstzrange(start, finish, '[)')&lt;/code&gt; is not decoration. It says the range includes its lower bound and excludes its upper. That is what makes a job ending at 09:00 and a job starting at 09:00 &lt;em&gt;not&lt;/em&gt; overlap, which is exactly the semantics you want for back-to-back work.&lt;/p&gt;

&lt;p&gt;Get this wrong and you produce one of two bugs. With inclusive upper bounds &lt;code&gt;'[]'&lt;/code&gt;, consecutive jobs conflict and your bay utilisation drops by a slot per boundary. With exclusive lower bounds &lt;code&gt;'()'&lt;/code&gt;, zero-length and adjacent ranges behave in ways that will surprise you at 7 AM.&lt;/p&gt;

&lt;p&gt;The overlapping-duration case from the assignment brief is where ranges really earn their keep. Consider a 75-minute installation starting at 09:00 in bay 2, and someone trying to place a 30-minute repair at 09:45 in the same bay:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;tstzrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2026-10-20 09:00-06'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'2026-10-20 10:15-06'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'[)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;tstzrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2026-10-20 09:45-06'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'2026-10-20 10:15-06'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'[)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;conflicts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- conflicts =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a start column and a duration column you would be writing that predicate by hand in every query, and every hand-written version is a chance to invert a comparison. With ranges the &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; operator is the predicate, it is the same predicate the index uses, and there is no second implementation to drift.&lt;/p&gt;

&lt;p&gt;A related benefit: buffer time becomes a range operation instead of arithmetic scattered through the code. If you want ten minutes of cushion between vehicles for the pull-in and pull-out, widen the stored range at write time and let the constraint do the rest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Buffered range helper, applied at insert time&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;tstzrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;during&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'5 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="k"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;during&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'5 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="s1"&gt;'[)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;tstzrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;during&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Unique Partial Index Alternative
&lt;/h2&gt;

&lt;p&gt;If your durations really are uniform, a unique partial index is simpler and cheaper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;one_job_per_bay_slot&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;reservations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bay_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;during&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'held'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'confirmed'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the same physical impossibility with a plain btree, no extension, and no GiST overhead. It is a perfectly good answer for a fixed grid — every job is exactly 30 minutes, every start is on the half hour, nothing ever runs long.&lt;/p&gt;

&lt;p&gt;It collapses the moment durations vary. A 75-minute installation starting at 09:00 has a distinct &lt;code&gt;lower(during)&lt;/code&gt; from a 30-minute repair starting at 09:45, so both indexes entries are unique, and both rows are accepted despite occupying the same bay at the same time. This is a classic way for a "quick" change allowing long jobs to reintroduce the exact bug the index was meant to kill. If there is any chance your service catalogue will grow — and a catalogue covering &lt;a href="https://calgaryrimandtire.ca/calgarys-local-tire-shop" rel="noopener noreferrer"&gt;oil service alongside tire work&lt;/a&gt; already mixes durations — start with the range type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advisory Locks and Row Locks: Where They Fit
&lt;/h2&gt;

&lt;p&gt;Two other tools come up constantly in this conversation, and both have a place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; on a parent row&lt;/strong&gt; serialises writers through a row lock:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;bay_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;bays&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;bay_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- every writer for bay 3 now queues here&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;reservations&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;bay_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;during&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
                                    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'held'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'confirmed'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;reservations&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is correct, and it is easy to reason about. The weakness is that its correctness is a convention. Nothing forces the next writer to take the lock. The importer written six months later will not, and the constraint you thought you had was really an agreement between the two functions that happened to exist when you wrote it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advisory locks&lt;/strong&gt; are the same idea without a row to hang it on, useful when the thing you are serialising is not a table row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_advisory_xact_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;hashtextextended&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bay:%s:%s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pg_advisory_xact_lock&lt;/code&gt; releases automatically at transaction end, which is almost always what you want; the session-scoped variant leaks locks when a pooled connection is handed back without cleanup. Note the hash collision surface: two distinct keys can map to the same bigint, and the consequence is spurious serialisation rather than a correctness bug, so it is tolerable — but it is another reason to prefer a constraint that names the real objects.&lt;/p&gt;

&lt;p&gt;My rule: use the exclusion constraint as the source of truth, and reach for a lock only when you need to serialise a &lt;em&gt;multi-row&lt;/em&gt; decision that no single constraint can express. Assigning a vehicle to the best available bay out of four, for example, involves reading all four and picking one; a lock makes that read-then-choose deterministic, while the constraint still catches you if the logic is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isolation Levels, Stated Plainly
&lt;/h2&gt;

&lt;p&gt;Worth being precise, because this is where hand-waving does the most damage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;READ COMMITTED&lt;/strong&gt; (the Postgres default): each statement sees rows committed before that statement began. It prevents dirty reads. It does not prevent non-repeatable reads, phantom reads, or the write skew that our double-insert is a form of. Two transactions can each observe "no conflict" and each create one. This is the level at which the exclusion constraint saves you, because the constraint is enforced in the index, not in a snapshot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;REPEATABLE READ&lt;/strong&gt;: one snapshot for the whole transaction. Postgres's implementation gives you snapshot isolation, which does prevent non-repeatable and phantom reads within your own transaction. It still does not prevent write skew across transactions, because two transactions reading disjoint snapshots and writing disjoint rows never collide. Our race survives here untouched. People are frequently surprised by this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SERIALIZABLE&lt;/strong&gt;: Postgres uses Serializable Snapshot Isolation, tracking read-write dependencies between concurrent transactions and aborting one when it detects a dangerous structure. This &lt;em&gt;does&lt;/em&gt; catch our race. T2 fails at commit with SQLSTATE &lt;code&gt;40001&lt;/code&gt;, &lt;code&gt;serialization_failure&lt;/code&gt;, and you retry the whole transaction from the top.&lt;/p&gt;

&lt;p&gt;SERIALIZABLE is genuinely correct and I do not want to talk anyone out of it. But be aware of the bill. Every transaction in the system pays predicate-lock bookkeeping, not only the ones you were worried about. Aborts are probabilistic and can fire on transactions with no real conflict, so &lt;em&gt;every&lt;/em&gt; write path needs a retry loop with backoff, including the ones you wrote before you turned it on. And the failure mode under load is a retry storm precisely when load is highest.&lt;/p&gt;

&lt;p&gt;The comparison in one table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Stops our race?&lt;/th&gt;
&lt;th&gt;Isolation needed&lt;/th&gt;
&lt;th&gt;Enforced against all writers&lt;/th&gt;
&lt;th&gt;Retry loop required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;App-level check, no lock&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;FOR UPDATE&lt;/code&gt; on bay row&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;READ COMMITTED&lt;/td&gt;
&lt;td&gt;No, by convention only&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advisory transaction lock&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;READ COMMITTED&lt;/td&gt;
&lt;td&gt;No, by convention only&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unique partial index&lt;/td&gt;
&lt;td&gt;Yes, fixed grid only&lt;/td&gt;
&lt;td&gt;READ COMMITTED&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GiST exclusion constraint&lt;/td&gt;
&lt;td&gt;Yes, any durations&lt;/td&gt;
&lt;td&gt;READ COMMITTED&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SERIALIZABLE, no constraint&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;SERIALIZABLE&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes, everywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We landed on the exclusion constraint at READ COMMITTED, with a transaction-scoped advisory lock only in the auto-assignment path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Handler, Start to Finish
&lt;/h2&gt;

&lt;p&gt;Putting both halves together. This is psycopg3-flavoured Python, trimmed of logging and validation but structurally what runs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;psycopg&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;

&lt;span class="n"&gt;LEASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30 seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;RECORD_TTL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30 days&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;KeyReuse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AttemptRunning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SlotUnavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;CLAIM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
INSERT INTO idempotency_records
       (requester_id, endpoint, idem_key, request_digest,
        state, lease_until, expires_at)
VALUES (%(req)s, %(ep)s, %(key)s, %(dig)s, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;in_flight&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,
        now() + interval %(lease)s, now() + interval %(ttl)s)
    ON CONFLICT (requester_id, endpoint, idem_key) DO NOTHING
RETURNING first_seen_at
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;FETCH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
SELECT state, request_digest, response_status, response_body, lease_until
  FROM idempotency_records
 WHERE requester_id = %(req)s AND endpoint = %(ep)s AND idem_key = %(key)s
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;PLACE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
INSERT INTO reservations (bay_id, service, during, customer_ref, plate)
VALUES (%(bay)s, %(svc)s, tstzrange(%(from)s, %(to)s, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;),
        %(cust)s, %(plate)s)
RETURNING reservation_id
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;SETTLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
UPDATE idempotency_records
   SET state = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;completed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, response_status = %(status)s,
       response_body = %(body)s, result_ref = %(ref)s,
       finished_at = now(), lease_until = NULL
 WHERE requester_id = %(req)s AND endpoint = %(ep)s AND idem_key = %(key)s
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;place_reservation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requester&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;dig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;request_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;req&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;requester&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ep&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST /v1/reservations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;claimed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;CLAIM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dig&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lease&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LEASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ttl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RECORD_TTL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;claimed&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FETCH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stored_dig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lease_until&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stored_dig&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;KeyReuse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in_flight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;lease_until&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;AttemptRunning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;          &lt;span class="c1"&gt;# verbatim replay
&lt;/span&gt;            &lt;span class="c1"&gt;# lease has lapsed: fall through and reclaim it
&lt;/span&gt;            &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RECLAIM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dig&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lease&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LEASE&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PLACE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bay&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bay_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;svc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;starts_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ends_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cust&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_ref&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExclusionViolation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slot_unavailable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bay_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bay_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
            &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SETTLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Jsonb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ref&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SlotUnavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bay_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reservation_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;held&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SETTLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Jsonb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ref&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important structural property: the claim, the reservation insert, and the settle all live inside one transaction. If any of them fails for a transient reason, the whole thing rolls back including the claim, and the client's retry finds no record and gets a genuinely fresh attempt. That is what makes transient failures safe to retry without leaving zombie &lt;code&gt;in_flight&lt;/code&gt; rows behind.&lt;/p&gt;

&lt;p&gt;Notice also that &lt;code&gt;slot_unavailable&lt;/code&gt; is settled into the record as a completed 409 before the exception propagates. That is deliberate. A capacity rejection is a deterministic outcome for that exact request body, and replaying it is correct: if you ask for the same bay at the same time tomorrow morning with the same key, the answer has not changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Client Owes the Server
&lt;/h2&gt;

&lt;p&gt;Idempotency is a two-party contract, and half of it is client-side discipline. The rule that gets violated most often is the simplest one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reuse the same key on retry.&lt;/strong&gt; A retry is not a new intention. If your HTTP wrapper mints a fresh UUID on every attempt, every one of your carefully designed server mechanisms is bypassed, and you have built a very sophisticated way to create duplicates. Generate the key when the human commits to the action — the moment the submit control is pressed — and hold it for the lifetime of that intention, including across app restarts if you have an offline queue.&lt;/p&gt;

&lt;p&gt;Which responses are safe to retry:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status / condition&lt;/th&gt;
&lt;th&gt;Retry?&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Connection reset, DNS failure, TLS handshake failure&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Request may never have landed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client-side timeout, no response&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Ambiguous by construction; this is the core case&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;408, 425&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Server explicitly says the attempt did not complete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;429&lt;/td&gt;
&lt;td&gt;Yes, after &lt;code&gt;Retry-After&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Rate limit, not a semantic error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500, 502, 503, 504&lt;/td&gt;
&lt;td&gt;Yes, with backoff&lt;/td&gt;
&lt;td&gt;Transient or ambiguous&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;409 &lt;code&gt;request_in_progress&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes, after &lt;code&gt;Retry-After&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Another attempt of yours is mid-flight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;409 &lt;code&gt;slot_unavailable&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deterministic; retrying returns the same answer forever&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;409 &lt;code&gt;idempotency_key_reuse&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Client defect; escalate, do not loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400, 422&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The payload is wrong and will stay wrong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;401, 403&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Credentials, not concurrency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nothing to do&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of those rows are the ones I would put in a code review checklist: retrying &lt;code&gt;slot_unavailable&lt;/code&gt; is how you build a client that hammers a full facility until the customer force-quits the app, and retrying &lt;code&gt;idempotency_key_reuse&lt;/code&gt; is how you turn one bug into a sustained one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backoff, Jitter, and the Herd After a Snowfall Warning
&lt;/h2&gt;

&lt;p&gt;Fixed-interval retries synchronise. If a thousand clients all get a 503 at the same instant because a deploy briefly dropped a replica, and all of them retry at exactly 1s, 2s, and 4s, you have built a distributed drum circle that will keep the service down long after the original cause is gone. Calgary gives us a very reliable trigger for this: the hour after a snowfall warning is published, when everyone in the city thinks about winter rubber at once.&lt;/p&gt;

&lt;p&gt;Full jitter is the version I would default to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;      &lt;span class="c1"&gt;# seconds
&lt;/span&gt;&lt;span class="n"&gt;CAP&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;20.0&lt;/span&gt;      &lt;span class="c1"&gt;# seconds
&lt;/span&gt;&lt;span class="n"&gt;MAX_ATTEMPTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;with_retries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idem_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAX_ATTEMPTS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idem_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# SAME key every time
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;terminal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt;
        &lt;span class="n"&gt;ceiling&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CAP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ceiling&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RetriesExhausted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idem_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;random.uniform(0, ceiling)&lt;/code&gt; rather than &lt;code&gt;ceiling&lt;/code&gt; alone is the whole trick: it spreads the herd across the interval instead of stacking it on the boundary. Equal jitter (&lt;code&gt;ceiling/2 + random.uniform(0, ceiling/2)&lt;/code&gt;) is a reasonable variant when you care about a floor on latency. Decorrelated jitter is better still under sustained pressure, but full jitter is easy to explain to whoever is on rotation at 6 AM, and that has value.&lt;/p&gt;

&lt;p&gt;Two additions that matter more than the exact formula. Cap total elapsed time, not just attempt count, because six attempts with a 20-second cap can span two minutes and the human gave up ninety seconds ago. And carry an attempt counter in a header for observability, excluded from the digest, so you can graph how deep clients are actually going.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a TTL You Can Defend
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;expires_at&lt;/code&gt; is the field where a lazy default becomes a correctness bug six months later.&lt;/p&gt;

&lt;p&gt;The rule is: &lt;strong&gt;the TTL must exceed the longest interval over which a client might legitimately replay the same key.&lt;/strong&gt; If a record expires and is deleted, and then the original request arrives one more time, your server has no memory of it and will happily create a second reservation. The idempotency guarantee has a shelf life, and you chose it.&lt;/p&gt;

&lt;p&gt;Twenty-four hours is the common default and it is often too short. Ours is 30 days, for reasons that are specific and boring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The mobile client has an offline queue. A phone that loses signal in a parkade, gets put on a charger, and is not opened until Monday will drain that queue on Monday. A weekend plus a holiday is easily 72 hours.&lt;/li&gt;
&lt;li&gt;Integration partners who submit &lt;a href="https://calgaryrimandtire.ca/commercial-tire-services" rel="noopener noreferrer"&gt;commercial and fleet work&lt;/a&gt; run batch jobs whose failure handling is, charitably, unhurried. A stuck batch resubmitted the following week is a real thing that has happened.&lt;/li&gt;
&lt;li&gt;The rows are small. Thirty days of records at our volume is a few hundred megabytes. Buying a month of safety for that is not a difficult trade.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you cannot make the TTL long enough — and some systems genuinely cannot — then add a second line of defence that does not expire. A natural-key uniqueness constraint on the business meaning of the request survives idempotency-record expiry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;reservations_no_repeat_intent&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;reservations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;during&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'held'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'confirmed'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That will not catch every duplicate — a customer legitimately placing two jobs for two vehicles at the same time trips it, so you may need the vehicle in the key — but it converts a silent duplicate into a loud constraint violation, and loud is always better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reaping Old Records Without Hurting Live Traffic
&lt;/h2&gt;

&lt;p&gt;A single unbounded &lt;code&gt;DELETE FROM idempotency_records WHERE expires_at &amp;lt; now()&lt;/code&gt; will, on a busy table, take locks for long enough to matter and generate a spike of WAL and autovacuum work at the worst possible moment. Batch it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;doomed&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ctid&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;idempotency_records&lt;/span&gt;
   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'in_flight'&lt;/span&gt;
   &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt;
   &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;idempotency_records&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
 &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;doomed&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Loop that until it deletes fewer than the limit, sleep between iterations, and run it well outside the morning rush. The &lt;code&gt;state &amp;lt;&amp;gt; 'in_flight'&lt;/code&gt; guard is not optional: deleting a claim out from under a running handler produces exactly the duplicate you built the table to prevent.&lt;/p&gt;

&lt;p&gt;The better answer at volume is to stop deleting rows at all. Partition by day and drop whole partitions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;idempotency_records&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;RANGE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_seen_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;idempotency_records_p2026_10_20&lt;/span&gt;
  &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="n"&gt;idempotency_records&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2026-10-20'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2026-10-21'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- retirement, once the window has passed&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;idempotency_records_p2026_09_20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a real tradeoff hiding in that snippet and I want to name it rather than skip past it. Postgres requires the partition key to be part of any unique constraint on a partitioned table, so the primary key becomes &lt;code&gt;(first_seen_at, requester_id, endpoint, idem_key)&lt;/code&gt;. Uniqueness is now enforced &lt;em&gt;within&lt;/em&gt; a partition, not across the table. If the same key arrives on two different days, the index will not stop you. In practice that is fine when your retention window vastly exceeds any plausible replay window, and it is a live hazard when it does not. Decide on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconciliation: Finding What Slipped Through
&lt;/h2&gt;

&lt;p&gt;Constraints prevent the failures you anticipated. Reconciliation finds the ones you did not. Two queries run nightly against the previous day.&lt;/p&gt;

&lt;p&gt;Near-simultaneous identical intentions, which is the fingerprint of a client that is not reusing its key properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;ordered&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;reservation_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;during&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raised_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reservation_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;prior_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raised_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;prior_raised&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;reservations&lt;/span&gt;
   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'held'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'confirmed'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;raised_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'1 day'&lt;/span&gt;
  &lt;span class="k"&gt;WINDOW&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;during&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;raised_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reservation_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prior_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;raised_at&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;prior_raised&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;gap&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ordered&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;prior_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;raised_at&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;prior_raised&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'10 minutes'&lt;/span&gt;
 &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And one customer holding overlapping slots in different bays, which the per-bay constraint cannot see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reservation_id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;earlier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reservation_id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;later&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;during&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;during&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;shared_window&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;reservations&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;reservations&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_ref&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reservation_id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reservation_id&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;during&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;during&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'held'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'confirmed'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'held'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'confirmed'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;*&lt;/code&gt; operator there is range intersection, which gives you the exact overlapping window rather than a boolean. Handy in a report a human has to act on.&lt;/p&gt;

&lt;p&gt;A third check worth running compares the count of &lt;code&gt;completed&lt;/code&gt; idempotency records against the count of reservations created in the same window. A persistent gap means some write path is bypassing the claim entirely, and that path is your next incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metrics That Earn Their Storage
&lt;/h2&gt;

&lt;p&gt;Instrument the branches, not the endpoint. The interesting signal is in which arm of the state machine traffic takes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;idempotency_claims_total&lt;/code&gt; — new claims. Your baseline for everything else.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;idempotency_replays_total{outcome="completed"|"failed"}&lt;/code&gt; — verbatim replays served. A rising ratio against claims means clients are retrying more, which usually means latency has crept up somewhere upstream. That makes it a leading indicator rather than a lagging one, which is why it belongs on a dashboard instead of in a weekly report.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;idempotency_digest_mismatch_total&lt;/code&gt; — 409s for key reuse. This should be flat at approximately zero. Any movement is a client release regression; alert on it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;idempotency_in_flight_rejections_total&lt;/code&gt; — how often a duplicate arrives while the original is still running. Correlates directly with handler latency.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;idempotency_lease_reclaims_total&lt;/code&gt; — abandoned claims recovered. Non-zero means processes are dying mid-request.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reservation_exclusion_violations_total{bay_id}&lt;/code&gt; — genuine capacity contention. Per-bay labels tell you which bay is the bottleneck, which is an operational answer, not just an engineering one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;idempotency_record_age_seconds&lt;/code&gt; — histogram of &lt;code&gt;now() - first_seen_at&lt;/code&gt; at replay time. This is how you validate your TTL empirically instead of by argument. If the p99 is creeping toward your expiry window, lengthen it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reap_rows_deleted_total&lt;/code&gt; and reaper duration. A reaper that stops keeping up is a slow-motion disk alert.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two I would put on a wall: digest mismatches, because they are always a bug, and the replay-to-claim ratio, because it is the earliest signal that something is degrading for real users.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Concurrency Test That Actually Fails on the Naive Version
&lt;/h2&gt;

&lt;p&gt;A test that passes against both implementations is worse than no test, because it tells you the race is handled when it is not. The requirement is that the test reliably fails when you remove the constraint.&lt;/p&gt;

&lt;p&gt;Three ingredients: a real Postgres (not SQLite, whose locking model will hide the bug), enough concurrency to make the window wide, and a barrier so every worker arrives at the critical section together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;concurrent.futures&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cf&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="n"&gt;WORKERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_only_one_reservation_survives_the_stampede&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seeded_bay&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;gate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Barrier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORKERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-10-20T08:00:00-06:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-10-20T08:45:00-06:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bay_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;seeded_bay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tire_changeover&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;starts_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ends_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_ref&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                 &lt;span class="c1"&gt;# release all threads together
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;place_reservation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REQUESTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;SlotUnavailable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;WORKERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORKERS&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT count(*) FROM reservations &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WHERE bay_id = %s AND status IN (&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;held&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;confirmed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seeded_bay&lt;/span&gt;&lt;span class="p"&gt;,)).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;overbooked: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows survived&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that each worker uses a &lt;em&gt;different&lt;/em&gt; idempotency key and a &lt;em&gt;different&lt;/em&gt; customer. That is deliberate: this test targets the capacity mechanism specifically, with the duplicate-suppression mechanism removed from the picture. Its twin uses one shared key across all 32 workers and asserts that exactly one row is created and that every other worker got either a replay of the same &lt;code&gt;reservation_id&lt;/code&gt; or &lt;code&gt;AttemptRunning&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the stampede test is flaky against the naive implementation — sometimes it produces 2 rows, sometimes 1 — widen the window deterministically. Inject a controllable pause between the read and the write in a test build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;HOOK&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RACE_HOOK_MS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HOOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1000.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;RACE_HOOK_MS=50&lt;/code&gt;, the naive path fails every single run, and the exclusion-constraint path still produces exactly one row. That is the pair of outcomes that proves the mechanism rather than the timing.&lt;/p&gt;

&lt;p&gt;One more test I recommend and rarely see: assert the &lt;em&gt;error code&lt;/em&gt;, not just that an exception occurred. A test that accepts any exception will keep passing when a typo turns your capacity rejection into a &lt;code&gt;NameError&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Design Still Bites
&lt;/h2&gt;

&lt;p&gt;I would rather list the sharp edges than pretend there are none.&lt;/p&gt;

&lt;p&gt;Retries at the load balancer are invisible to you. If your ingress retries idempotent-looking requests on its own — and some do, by default, for reads — you can get a duplicate that never passed through client code and therefore has no key discipline behind it. Check your ingress configuration.&lt;/p&gt;

&lt;p&gt;Clock skew shifts the meaning of &lt;code&gt;lease_until&lt;/code&gt; and &lt;code&gt;expires_at&lt;/code&gt; if you compute them in application code across several hosts. Compute them in SQL with &lt;code&gt;now()&lt;/code&gt; so a single authority owns time. Every timestamp in the DDL above does this.&lt;/p&gt;

&lt;p&gt;Cancellation and rescheduling need their own keys, and the same 409 discipline. A cancel that runs twice is usually harmless; a reschedule that runs twice is a duplicate wearing a different hat.&lt;/p&gt;

&lt;p&gt;Multi-resource jobs break the single-constraint model. A vehicle needing both a bay and the balancer for part of its stay is two resources with two overlap constraints, and the ordering of those two inserts becomes a deadlock question. The pragmatic option is widening the bay range to cover the whole job, which is slightly wasteful and completely safe. The general version is a lock ordering convention, and it is the part of this design I like least.&lt;/p&gt;

&lt;p&gt;Off-site work would be a separate resource pool entirely. A &lt;a href="https://calgaryrimandtire.ca/mobile-tire-service" rel="noopener noreferrer"&gt;mobile service unit&lt;/a&gt; is not a bay, its capacity is bounded by drive time rather than floor space, and the constraint governing it also has to encode the &lt;a href="https://calgaryrimandtire.ca/service-areas" rel="noopener noreferrer"&gt;geographic coverage rules&lt;/a&gt;. Same idempotency machinery, different exclusion predicate — and a travel-time term that the bay model does not need at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Design Buys You
&lt;/h2&gt;

&lt;p&gt;The point is not that duplicates get cleaned up faster. It is that the class of bug stops being reachable. Once non-overlap is a schema constraint rather than a convention, a new service path — say &lt;a href="https://calgaryrimandtire.ca/emergency-services" rel="noopener noreferrer"&gt;after-hours handling&lt;/a&gt; — inherits the guarantee without anyone writing fresh concurrency code for it. That is the entire argument for pushing the invariant down into the database instead of leaving it in a function somebody has to remember to invoke.&lt;/p&gt;

&lt;p&gt;The design in one paragraph, for anyone skimming to the end: generate the key on the client at the moment of human intent and reuse it on every retry; claim it server-side with a single conditional insert; store a canonical digest and the serialised response; return 409 for a mismatched body and 409 with &lt;code&gt;Retry-After&lt;/code&gt; for a concurrent attempt; model bays as rows and occupancy as a &lt;code&gt;tstzrange&lt;/code&gt;; enforce non-overlap with a GiST exclusion constraint so no write path can violate it regardless of isolation level; pick a TTL longer than your worst realistic replay delay and back it with a natural-key index if you cannot; reap in batches or by dropping partitions; reconcile nightly; and write the stampede test with a barrier so it fails loudly against the naive implementation.&lt;/p&gt;

&lt;p&gt;The work this machinery would schedule is described in plain language on the &lt;a href="https://calgaryrimandtire.ca/online-bookings" rel="noopener noreferrer"&gt;customer-facing scheduling pages&lt;/a&gt;, and the reasoning behind the service definitions themselves lives in our &lt;a href="https://calgaryrimandtire.ca/be-tire-smart" rel="noopener noreferrer"&gt;tire education material&lt;/a&gt;. Software of this kind exists to make sure the physical thing — a vehicle, a bay, a technician, forty-five minutes in October — matches what the database says. Everything above exists to keep those two pictures identical.&lt;/p&gt;

</description>
      <category>automotive</category>
      <category>calgary</category>
      <category>maintenance</category>
      <category>tires</category>
    </item>
    <item>
      <title>Encoding Tire Repairability as a Versioned Decision Table Instead of Nested Conditionals</title>
      <dc:creator>KMJ Tire Calgary</dc:creator>
      <pubDate>Mon, 10 Aug 2026 16:17:53 +0000</pubDate>
      <link>https://dev.to/kmjtirecalgary/encoding-tire-repairability-as-a-versioned-decision-table-instead-of-nested-conditionals-5558</link>
      <guid>https://dev.to/kmjtirecalgary/encoding-tire-repairability-as-a-versioned-decision-table-instead-of-nested-conditionals-5558</guid>
      <description>&lt;h1&gt;
  
  
  Encoding Tire Repairability as a Versioned Decision Table Instead of Nested Conditionals
&lt;/h1&gt;

&lt;p&gt;KMJ Tire is a Calgary operation whose entire service catalogue is tires and oil changes. Sitting beside a business shaped like that, the obvious piece of software to want is a work-order pre-screen: feed it a damage assessment, get back a signal about whether a damaged tire is likely to go back on the vehicle or likely to be scrapped. The first version of such a thing is always a function with a lot of &lt;code&gt;if&lt;/code&gt; statements in it. Four hundred lines later it has an ordering bug nobody can see, and it produces answers nobody can explain afterwards.&lt;/p&gt;

&lt;p&gt;What follows is a design study of the replacement: an ordered, data-driven rule set — a decision table with explicit precedence, three-valued outcomes, per-rule provenance, and version pinning so a verdict issued eleven months ago can be reproduced under the criteria that were in force at the time. The domain grounding is real; the system is a design exercise rather than a description of live production infrastructure, and every figure in it is illustrative. The domain is tires, but the shape of the problem is not: safety-adjacent classification with incomplete inputs, where the wrong answer is worse than no answer.&lt;/p&gt;

&lt;p&gt;One boundary up front, because it matters more than any code in this article. The criteria below are the industry criteria a tire service business works from, encoded here for illustration. This table is not an authoritative safety standard, and nothing here overrides a manufacturer's own guidance. The final authority is a trained technician with the tire off the rim and the inner liner under a light. The software exists to be consistent and explainable, not to be right on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four hundred lines that lied to us
&lt;/h2&gt;

&lt;p&gt;The original pre-screen was written the way these things always get written. Someone had the criteria on a laminated sheet, transcribed them top to bottom, and each criterion became a branch that returned early.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;can_repair&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sidewall&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shoulder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hole_mm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prior_repairs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prior_repairs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tread_mm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1.6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four failure modes, and they compound.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;early return hides ordering&lt;/strong&gt;. The moment one branch answers, none of the later branches run. That is fine when every branch produces the same verdict, and it is a trap the instant one branch produces a different one. Ours did: a later revision added a run-flat check that returned &lt;code&gt;True&lt;/code&gt; for a specific construction, placed above the prior-repair loop by whoever merged it. A unit came in with a 5 mm injury 62 mm off the tread centreline on a 225-section tire, with an existing repair roughly 14 cm away around the circumference. The function said repairable. The technician who demounted it said no, twice, and was right both times.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;a boolean cannot express doubt&lt;/strong&gt;. Look at &lt;code&gt;t.get("tread_mm", 99)&lt;/code&gt;. Someone defaulted a missing measurement to a value that guarantees the check passes. That is not a typo; it is the only thing you can do when your return type has two inhabitants and one of them means "go ahead." Missing data silently became permission.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;there is no provenance&lt;/strong&gt;. The function returns &lt;code&gt;False&lt;/code&gt;. Which criterion produced that? You can only find out by re-reading the code and mentally replaying it against the input. When a fleet coordinator asks why a $340 casing was condemned, "the system said so" is not an answer anyone accepts.&lt;/p&gt;

&lt;p&gt;Fourth, &lt;strong&gt;there is no version&lt;/strong&gt;. The criteria drift. Manufacturers publish revised guidance, internal practice tightens, a legal threshold changes. Six months later you cannot reproduce a past verdict, because the only copy of the old logic is in git history nobody wants to run.&lt;/p&gt;

&lt;p&gt;Every one of those is a structural property of "criteria as control flow." You do not fix them by writing more careful conditionals. You fix them by making the criteria data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The criteria a service business actually works from
&lt;/h2&gt;

&lt;p&gt;Before any modelling, the plain-language version. Whether a punctured passenger or light-truck tire may be repaired is governed by a cluster of criteria that most of the trade agrees on, with variation at the edges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The injury must fall in the crown — the central portion of the tread — not in the shoulder and never in the sidewall. Shoulder and sidewall flex through their entire duty cycle, and a patch there works loose.&lt;/li&gt;
&lt;li&gt;The injury diameter has a ceiling. Six millimetres (about a quarter inch) is the common passenger-tire figure. Beyond that, the belt and body-ply damage is no longer something a patch spans.&lt;/li&gt;
&lt;li&gt;The injury angle matters. A nail that entered at a steep angle travels laterally inside the casing, so the entry point on the tread and the actual damage path are not the same place. Steeply angled injuries are treated as larger than they measure.&lt;/li&gt;
&lt;li&gt;A legitimate repair requires demounting. The tire comes off the rim, the inner liner gets inspected, the injury channel is filled and the inner surface is sealed with a patch. An externally inserted plug with the tire still on the wheel is a get-home measure, not a repair.&lt;/li&gt;
&lt;li&gt;Repairs must not overlap, and there are limits on how many a casing may carry and how close together they may sit.&lt;/li&gt;
&lt;li&gt;Run-flat construction and high speed ratings carry manufacturer-specific caveats; some brands decline repair entirely on certain lines.&lt;/li&gt;
&lt;li&gt;Evidence that the tire was driven while deflated — a scuffed or wrinkled inner liner, rubber dust inside the casing — condemns it regardless of how small the puncture is.&lt;/li&gt;
&lt;li&gt;Remaining tread depth and casing age can make the whole question moot.&lt;/li&gt;
&lt;li&gt;A tire previously plugged from the outside without demounting has an unknown internal condition until somebody looks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want the customer-facing version of the same material, we keep &lt;a href="https://calgaryrimandtire.ca/tire-repair-in-calgary" rel="noopener noreferrer"&gt;an explainer on how a proper demounted repair is performed&lt;/a&gt;, and a separate reference on &lt;a href="https://calgaryrimandtire.ca/tire-sidewall-information" rel="noopener noreferrer"&gt;reading what is moulded into the sidewall&lt;/a&gt;, which is where half of these inputs come from in the first place.&lt;/p&gt;

&lt;p&gt;Notice how many of those clauses are about &lt;em&gt;evidence&lt;/em&gt; rather than about &lt;em&gt;geometry&lt;/em&gt;. That distribution is the single most important thing to carry into the data model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Geometry: turning "crown" and "shoulder" into numbers
&lt;/h2&gt;

&lt;p&gt;"In the crown" is not a machine-checkable predicate. We need a coordinate system.&lt;/p&gt;

&lt;p&gt;We measure lateral offset in millimetres from the tread centreline, positive outboard, and we derive the crown boundary from tread width rather than section width. Section width is the moulded figure on the sidewall and includes the bulge; tread width is the part that actually meets the road. For a typical passenger fitment, tread width runs somewhere around 78–82 % of section width, and we store a per-fitment override when we have the real number from the manufacturer's data sheet.&lt;/p&gt;

&lt;p&gt;The illustrative geometry we use in the examples below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tread_width_mm = round(0.80 * section_width_mm)&lt;/code&gt; unless overridden&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crown_half_width_mm = 0.5 * tread_width_mm - shoulder_band_mm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shoulder_band_mm = 12&lt;/code&gt; for passenger fitments in this illustration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So a 225-section tire gets a 180 mm tread width, a 90 mm half-width, and a crown that extends to 78 mm either side of centre. An injury at 62 mm is comfortably inside. An injury at 79 mm is not. An injury at 78 mm is the interesting one, and we will come back to it.&lt;/p&gt;

&lt;p&gt;Angular position around the circumference is a second coordinate, in degrees, with an arbitrary but recorded datum — we use the DOT stamp as zero, because it is the one landmark on the tire that is guaranteed to exist and guaranteed not to move. Arc distance between two points is then a wrap-aware computation on the rolling circumference, which we derive from the fitment. Anyone who has worked through the diameter math will recognize this; the &lt;a href="https://calgaryrimandtire.ca/tire-load-index-explained" rel="noopener noreferrer"&gt;load index and speed symbol reference&lt;/a&gt; we keep for technicians covers the sidewall codes those figures come from.&lt;/p&gt;

&lt;h2&gt;
  
  
  A typed record for the assessment, not a dict
&lt;/h2&gt;

&lt;p&gt;Here is the input model. It is deliberately verbose about the difference between "we measured this" and "nobody wrote it down."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntEnum&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Zone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;CROWN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;SHOULDER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shoulder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;SIDEWALL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sidewall&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;BEAD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bead&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Construction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;STANDARD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;standard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;RUN_FLAT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run_flat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;UNDETERMINED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;undetermined&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PriorRepairKind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;INTERNAL_PATCH_PLUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;internal_patch_plug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;EXTERNAL_PLUG_ONLY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;external_plug_only&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;UNIDENTIFIED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unidentified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Measured&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;A number that remembers how it was obtained and how wrong it might be.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;tolerance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;          &lt;span class="c1"&gt;# half-width of the plausible band, same units
&lt;/span&gt;    &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;               &lt;span class="c1"&gt;# steel_rule | digital_caliper | visual_estimate
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;below&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tolerance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tolerance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;           &lt;span class="c1"&gt;# band straddles the threshold; refuse to guess
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;above&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;flipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;below&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flipped&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;flipped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Measured&lt;/code&gt; type is doing real work. A tread-depth reading taken with a steel rule by a technician wearing gloves in an unheated bay in February has a tolerance around ±0.4 mm. A digital caliper on an injury diameter is closer to ±0.2 mm. When the plausible band crosses a threshold, &lt;code&gt;below&lt;/code&gt; returns &lt;code&gt;None&lt;/code&gt;, and that &lt;code&gt;None&lt;/code&gt; will propagate all the way to the verdict rather than being rounded away. Three-valued logic starts at the measurement, not at the rule.&lt;/p&gt;

&lt;p&gt;The rest of the record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PriorRepair&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;angular_position_deg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;lateral_offset_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PriorRepairKind&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Assessment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;assessment_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;captured_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;                        &lt;span class="c1"&gt;# RFC 3339, always with offset
&lt;/span&gt;    &lt;span class="n"&gt;section_width_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;rim_diameter_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;tread_width_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;           &lt;span class="c1"&gt;# override when known
&lt;/span&gt;    &lt;span class="n"&gt;construction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Construction&lt;/span&gt;
    &lt;span class="n"&gt;speed_symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Zone&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;injury_diameter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Measured&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;injury_angle_deg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Measured&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    &lt;span class="c1"&gt;# 0 = perpendicular to the surface
&lt;/span&gt;    &lt;span class="n"&gt;lateral_offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Measured&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;      &lt;span class="c1"&gt;# from the tread centreline
&lt;/span&gt;    &lt;span class="n"&gt;angular_position_deg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;tread_depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Measured&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;dot_week&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;dot_year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;liner_inspected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;liner_shows_deflated_running&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;prior_repairs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PriorRepair&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every field that a technician might fail to record is &lt;code&gt;Optional&lt;/code&gt;. There are no sentinel defaults, no &lt;code&gt;-1&lt;/code&gt;, no &lt;code&gt;99&lt;/code&gt;. If you take one idea from this article, take that one: the absence of a measurement must be representable, and it must be distinguishable from a measurement of zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three verdicts, because two is a bug
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;REPAIRABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repairable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;REPLACE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;INSUFFICIENT_EVIDENCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;insufficient_evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;INSUFFICIENT_EVIDENCE&lt;/code&gt; is not an error state and it is not a failure of the engine. It is a legitimate, frequently correct answer that carries a payload: the list of inputs that would resolve it. Run the table across an illustrative assessment set and it lands on roughly a fifth of them, with about two-thirds of those resolving to a definite verdict once a single additional field arrives.&lt;/p&gt;

&lt;p&gt;Two design commitments follow from having three values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fail closed on ambiguity that leans dangerous.&lt;/strong&gt; Where an unknown could only make the situation worse, we do not return &lt;code&gt;INSUFFICIENT_EVIDENCE&lt;/code&gt;; we return &lt;code&gt;REPLACE&lt;/code&gt;. Evidence of running deflated is the clean example: if the liner was never inspected and the vehicle arrived on a tire that was flat at the roadside, the engine does not ask politely for more data, it condemns. The cost asymmetry is not close. A wrongly scrapped casing costs a few hundred dollars. A wrongly returned casing carries a belt separation down Stoney Trail at 100 km/h.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never let a gap become a permission.&lt;/strong&gt; There is exactly one rule in the table that can emit &lt;code&gt;REPAIRABLE&lt;/code&gt;, it sits at the lowest precedence tier, and it fires only when every field it depends on is present and every exclusion has been evaluated to a definite &lt;code&gt;False&lt;/code&gt;. Permission is the residue left after all the ways of saying no have been exhausted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The table itself
&lt;/h2&gt;

&lt;p&gt;This is the artifact that matters. It is reviewed by people who do not read Python, printed and marked up, and argued over. The code is downstream of it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;R-010&lt;/td&gt;
&lt;td&gt;EXCLUSION&lt;/td&gt;
&lt;td&gt;Zone is sidewall or bead&lt;/td&gt;
&lt;td&gt;REPLACE&lt;/td&gt;
&lt;td&gt;No patch survives full-cycle flex there&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-020&lt;/td&gt;
&lt;td&gt;EXCLUSION&lt;/td&gt;
&lt;td&gt;Zone is shoulder&lt;/td&gt;
&lt;td&gt;REPLACE&lt;/td&gt;
&lt;td&gt;Belt edge region, excluded by practice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-030&lt;/td&gt;
&lt;td&gt;EXCLUSION&lt;/td&gt;
&lt;td&gt;Lateral offset beyond crown half-width&lt;/td&gt;
&lt;td&gt;REPLACE&lt;/td&gt;
&lt;td&gt;Geometric restatement of R-020 for measured inputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-040&lt;/td&gt;
&lt;td&gt;EXCLUSION&lt;/td&gt;
&lt;td&gt;Injury diameter above 6.0 mm&lt;/td&gt;
&lt;td&gt;REPLACE&lt;/td&gt;
&lt;td&gt;Illustrative passenger ceiling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-050&lt;/td&gt;
&lt;td&gt;EXCLUSION&lt;/td&gt;
&lt;td&gt;Injury angle above 25° from perpendicular&lt;/td&gt;
&lt;td&gt;REPLACE&lt;/td&gt;
&lt;td&gt;Damage path is longer than the entry hole suggests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-060&lt;/td&gt;
&lt;td&gt;EXCLUSION&lt;/td&gt;
&lt;td&gt;Liner shows deflated running&lt;/td&gt;
&lt;td&gt;REPLACE&lt;/td&gt;
&lt;td&gt;Casing compromised independent of the puncture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-070&lt;/td&gt;
&lt;td&gt;EXCLUSION&lt;/td&gt;
&lt;td&gt;Any prior repair within 40 cm arc of the new injury&lt;/td&gt;
&lt;td&gt;REPLACE&lt;/td&gt;
&lt;td&gt;Illustrative spacing floor; overlap is unconditional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-080&lt;/td&gt;
&lt;td&gt;EXCLUSION&lt;/td&gt;
&lt;td&gt;Three or more prior internal repairs present&lt;/td&gt;
&lt;td&gt;REPLACE&lt;/td&gt;
&lt;td&gt;Cumulative casing load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-090&lt;/td&gt;
&lt;td&gt;EXCLUSION&lt;/td&gt;
&lt;td&gt;Tread depth below 1.6 mm&lt;/td&gt;
&lt;td&gt;REPLACE&lt;/td&gt;
&lt;td&gt;Legal minimum reached; the repair question is moot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-100&lt;/td&gt;
&lt;td&gt;EXCLUSION&lt;/td&gt;
&lt;td&gt;Casing age at or beyond 10 years from DOT date&lt;/td&gt;
&lt;td&gt;REPLACE&lt;/td&gt;
&lt;td&gt;Illustrative retirement horizon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-200&lt;/td&gt;
&lt;td&gt;REFERRAL&lt;/td&gt;
&lt;td&gt;Construction is run-flat&lt;/td&gt;
&lt;td&gt;REPLACE_UNLESS_OEM_PERMITS&lt;/td&gt;
&lt;td&gt;Escalates to manufacturer guidance, not to the engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-210&lt;/td&gt;
&lt;td&gt;REFERRAL&lt;/td&gt;
&lt;td&gt;Speed symbol W, Y or above&lt;/td&gt;
&lt;td&gt;REPLACE_UNLESS_OEM_PERMITS&lt;/td&gt;
&lt;td&gt;Same escalation path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-300&lt;/td&gt;
&lt;td&gt;EVIDENCE&lt;/td&gt;
&lt;td&gt;Prior external plug present, liner not inspected&lt;/td&gt;
&lt;td&gt;INSUFFICIENT_EVIDENCE&lt;/td&gt;
&lt;td&gt;Internal condition unknown until demounted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-310&lt;/td&gt;
&lt;td&gt;EVIDENCE&lt;/td&gt;
&lt;td&gt;Tread depth not recorded&lt;/td&gt;
&lt;td&gt;INSUFFICIENT_EVIDENCE&lt;/td&gt;
&lt;td&gt;R-090 cannot be evaluated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-320&lt;/td&gt;
&lt;td&gt;EVIDENCE&lt;/td&gt;
&lt;td&gt;Zone and lateral offset both absent&lt;/td&gt;
&lt;td&gt;INSUFFICIENT_EVIDENCE&lt;/td&gt;
&lt;td&gt;R-020 and R-030 both unevaluable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-330&lt;/td&gt;
&lt;td&gt;EVIDENCE&lt;/td&gt;
&lt;td&gt;Injury diameter not recorded&lt;/td&gt;
&lt;td&gt;INSUFFICIENT_EVIDENCE&lt;/td&gt;
&lt;td&gt;R-040 cannot be evaluated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-340&lt;/td&gt;
&lt;td&gt;EVIDENCE&lt;/td&gt;
&lt;td&gt;DOT date unreadable&lt;/td&gt;
&lt;td&gt;INSUFFICIENT_EVIDENCE&lt;/td&gt;
&lt;td&gt;R-100 cannot be evaluated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-900&lt;/td&gt;
&lt;td&gt;PERMIT&lt;/td&gt;
&lt;td&gt;All exclusions definitely false, no gaps outstanding&lt;/td&gt;
&lt;td&gt;REPAIRABLE&lt;/td&gt;
&lt;td&gt;The residual case&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Eighteen rows. The whole domain fits on one page, which is the point — the nested version was four hundred lines because control flow forces you to spell out the interactions between rules, whereas a table lets the evaluator handle interactions once, generically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules as values
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;EXCLUSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;REFERRAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;EVIDENCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;PERMIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;


&lt;span class="n"&gt;Predicate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Assessment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rule_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tier&lt;/span&gt;
    &lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Predicate&lt;/span&gt;
    &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;
    &lt;span class="n"&gt;rationale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;                       &lt;span class="c1"&gt;# printed verbatim on the work order
&lt;/span&gt;    &lt;span class="n"&gt;requires&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;       &lt;span class="c1"&gt;# fields whose absence makes it moot
&lt;/span&gt;    &lt;span class="n"&gt;introduced_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;retired_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rationale&lt;/code&gt; is a string on the rule, not a lookup keyed by rule id in some other module. Anything that separates a rule from its explanation will drift; the explanation will describe a criterion that the predicate stopped checking two refactors ago, and nobody will notice because no test asserts on prose.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Predicate&lt;/code&gt; returns &lt;code&gt;Optional[bool]&lt;/code&gt;, which is the Kleene three-valued logic move. &lt;code&gt;True&lt;/code&gt; means the rule fires. &lt;code&gt;False&lt;/code&gt; means it definitely does not. &lt;code&gt;None&lt;/code&gt; means we cannot tell from what we were given, and the evaluator must treat that as an outstanding gap rather than as a quiet &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The predicates are ordinary named functions, not lambdas embedded in the table literal. That is a deliberate reversal of our first attempt. Lambdas in a table read beautifully for about a week and then become impossible to unit-test individually, impossible to name in a stack trace, and impossible to reuse across two rules that share a sub-condition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;CROWN_SHOULDER_BAND_MM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;12.0&lt;/span&gt;
&lt;span class="n"&gt;MAX_INJURY_DIAMETER_MM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;
&lt;span class="n"&gt;MAX_INJURY_ANGLE_DEG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;25.0&lt;/span&gt;
&lt;span class="n"&gt;MIN_TREAD_DEPTH_MM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.6&lt;/span&gt;
&lt;span class="n"&gt;MIN_REPAIR_SEPARATION_MM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;400.0&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;crown_half_width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Assessment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tread_width_mm&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.80&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;section_width_mm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;tread&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;CROWN_SHOULDER_BAND_MM&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;in_sidewall_or_bead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Assessment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIDEWALL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BEAD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;offset_outside_crown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Assessment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lateral_offset&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lateral_offset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;above&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crown_half_width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;injury_too_large&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Assessment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;injury_diameter&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;injury_diameter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;above&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAX_INJURY_DIAMETER_MM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deflated_running_seen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Assessment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;liner_shows_deflated_running&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;liner_inspected&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;liner_shows_deflated_running&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read &lt;code&gt;deflated_running_seen&lt;/code&gt; closely. It is the one predicate whose &lt;code&gt;None&lt;/code&gt; we later convert to &lt;code&gt;REPLACE&lt;/code&gt; rather than to a gap, and it is written so that a &lt;code&gt;False&lt;/code&gt; is only reachable when somebody actually looked. You cannot clear that flag by leaving the field blank.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arc separation, and why 14 cm is not 14 cm
&lt;/h2&gt;

&lt;p&gt;Two repair sites 14 cm apart earns a permanent place in the fixture set, because it exposes a modelling error that is unusually easy to ship.&lt;/p&gt;

&lt;p&gt;The first implementation compared angular positions directly and applied a degree threshold. That is wrong, because the same angular gap is a different physical distance on a 15-inch fitment than on a 20-inch one. What the criterion is really about is the amount of intact casing between two weakened points, which is arc length.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rolling_circumference_mm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Assessment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Illustrative: rim diameter plus twice a nominal sidewall height.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;rim_mm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rim_diameter_in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;25.4&lt;/span&gt;
    &lt;span class="n"&gt;aspect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.55&lt;/span&gt;                              &lt;span class="c1"&gt;# placeholder for the example
&lt;/span&gt;    &lt;span class="n"&gt;sidewall_mm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aspect&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;section_width_mm&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rim_mm&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sidewall_mm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;arc_gap_mm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Assessment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other_deg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angular_position_deg&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angular_position_deg&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;other_deg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mf"&gt;360.0&lt;/span&gt;
    &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;360.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# shortest way round
&lt;/span&gt;    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;360.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;rolling_circumference_mm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;crowded_by_prior_repair&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Assessment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prior_repairs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;gaps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;arc_gap_mm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angular_position_deg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prior_repairs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MIN_REPAIR_SEPARATION_MM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two bugs are fixed here and both were real. The &lt;code&gt;% 360.0&lt;/code&gt; followed by &lt;code&gt;min(delta, 360 - delta)&lt;/code&gt; handles the wrap: a repair at 355° and an injury at 5° are 10° apart, not 350°. And returning &lt;code&gt;None&lt;/code&gt; when any position is missing stops a partially-recorded repair history from producing a confident answer about spacing.&lt;/p&gt;

&lt;p&gt;For a 225/55R17 in the example, rolling circumference lands near 1,940 mm. Two sites 14 cm apart occupy about 26° of arc and leave 140 mm of casing between them, well under the 400 mm illustrative floor. Verdict: &lt;code&gt;REPLACE&lt;/code&gt;, cited to R-070. The nested version got this wrong not because the arithmetic was hard but because the run-flat branch above it had already returned.&lt;/p&gt;

&lt;h2&gt;
  
  
  An evaluator you can read in one sitting
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Firing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rule_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;
    &lt;span class="n"&gt;rationale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tier&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;
    &lt;span class="n"&gt;table_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;firings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Firing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;
    &lt;span class="n"&gt;outstanding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;
    &lt;span class="n"&gt;evaluated_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;input_digest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Assessment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Firing&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;fired&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Firing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rationale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requires&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;

    &lt;span class="n"&gt;fired&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fired&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;fired&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;Tier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REFERRAL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;chosen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fired&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;chosen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INSUFFICIENT_EVIDENCE&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;fired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;chosen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fired&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;chosen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INSUFFICIENT_EVIDENCE&lt;/span&gt;      &lt;span class="c1"&gt;# empty table, fail closed
&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chosen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;table_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;firings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fired&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;outstanding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
        &lt;span class="n"&gt;evaluated_at&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;input_digest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every rule is evaluated. Nothing short-circuits. That is the structural difference from the original, and it costs almost nothing — the whole table runs in single-digit microseconds, so there is no performance argument for early exit at this size.&lt;/p&gt;

&lt;p&gt;The resolution order encodes a real policy judgement, so it is worth stating plainly rather than leaving it implicit in a comparison operator. A definite exclusion beats an outstanding gap: if the injury is in the sidewall, we do not need the tread depth to know the answer, and demanding it would waste a technician's time. A gap beats a permission: if anything at all is unresolved, permission is withheld. Referrals sit between exclusions and gaps because "the manufacturer has to answer this" is a definite finding about who decides, not an absence of information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provenance: every verdict names its parent rule
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Decision&lt;/code&gt; record carries &lt;code&gt;firings&lt;/code&gt; — every rule that matched, in precedence order, with the rationale text attached. Rendering an explanation is then trivial, which is the whole point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decision&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INSUFFICIENT_EVIDENCE&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outstanding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outstanding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cannot decide under &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;table_version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Outstanding evidence: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No criterion matched under &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;table_version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;extra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; further criterion matched)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;extra&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; per &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rationale&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That string goes on the work order and into the fleet coordinator's export. It has settled more disputes than any amount of accuracy improvement, because the argument stops being "your system is wrong" and becomes "R-070 uses a 40 cm separation floor and we think that is too conservative for our application" — which is a productive conversation about a number in a table, not a defence of a codebase.&lt;/p&gt;

&lt;p&gt;Reporting every match rather than just the decisive one also gives you a cheap monitoring signal. If R-050 has never once been the top firing but appears as a secondary match on 40 % of assessments, either the threshold is miscalibrated or technicians are entering a default angle. Both are worth knowing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking the table, not just the code
&lt;/h2&gt;

&lt;p&gt;Once criteria are data, you can interrogate the whole rule set as an object. Three checks run in CI on every change to the table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reachability.&lt;/strong&gt; Enumerate a coarse grid over the input space and confirm each rule is the decisive firing for at least one cell. A rule that never decides anything is either dead or shadowed by a higher tier, and both deserve a look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conflict.&lt;/strong&gt; Within a single tier, find cells where two rules fire with different verdicts. Across tiers this is expected and resolved by precedence; within a tier it is an authoring mistake, because tier membership is a claim that the rules are commensurable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coverage of the permit path.&lt;/strong&gt; Confirm the grid contains at least one cell that reaches &lt;code&gt;REPAIRABLE&lt;/code&gt;. An over-eager tightening of R-030 can make permission unreachable, and nothing fails loudly when it does. The engine simply says &lt;code&gt;REPLACE&lt;/code&gt; to everything, which from the outside looks exactly like a bad week for punctures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;itertools&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sweep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;zones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CROWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SHOULDER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIDEWALL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;diameters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;9.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;depths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;decisive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;conflicts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dia&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;itertools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depths&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diameter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dia&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1970-01-01T00:00:00Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;decisive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;decisive&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;conflicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dia&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

    &lt;span class="n"&gt;unreached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;decisive&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decisive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unreached&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conflicts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A coarse grid is not a proof. It catches the class of error that matters, which is a rule that can never win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Properties worth asserting
&lt;/h2&gt;

&lt;p&gt;Example-based tests pin down the cases you thought of. Properties pin down the shape of the function, and in a safety-adjacent classifier the shape is where the real invariants live.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hypothesis&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategies&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;


&lt;span class="nd"&gt;@given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;20.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_monotone_in_injury_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Making the injury bigger can never make it more permissible.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;small&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diameter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;large&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diameter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;small&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RULES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TABLE_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RULES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TABLE_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REPAIRABLE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REPAIRABLE&lt;/span&gt;


&lt;span class="nd"&gt;@given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sampled_from&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tread_depth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;injury_diameter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_erasure_never_grants_permission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Deleting a field must not upgrade the verdict.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;full&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diameter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CROWN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;thin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RULES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TABLE_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;e1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt;
    &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RULES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TABLE_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;e2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REPAIRABLE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REPAIRABLE&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REPAIRABLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INSUFFICIENT_EVIDENCE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_determinism_under_rule_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verdict must not depend on the order rules sit in the tuple.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;shuffled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RULES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shuffled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diameter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CROWN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RULES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TABLE_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shuffled&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;TABLE_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; \
           &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That third property is the one that found a genuine defect. An early version sorted firings by tier alone, so two exclusions in the same tier resolved in whatever order the tuple happened to hold — stable, but dependent on authoring sequence. The verdict was identical either way, yet the &lt;em&gt;cited rule&lt;/em&gt; changed, which meant the explanation printed on two identical work orders could differ. Sorting by &lt;code&gt;(tier, rule_id)&lt;/code&gt; fixed it. Monotonicity and erasure-safety, by contrast, have never failed; they are regression armour for the day somebody adds a clever optimisation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Golden cases, kept in a format the bay can read
&lt;/h2&gt;

&lt;p&gt;Properties do not replace worked examples, because the examples are how non-programmers review the logic. Ours live in YAML, one file, reviewed by whoever is running the service floor that quarter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clean_crown_nail&lt;/span&gt;
  &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;routine gravel-season puncture, everything measured&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crown&lt;/span&gt;
    &lt;span class="na"&gt;section_width_mm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;215&lt;/span&gt;
    &lt;span class="na"&gt;injury_diameter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;4.2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;tolerance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;0.2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;digital_caliper&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;injury_angle_deg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;6.0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;tolerance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;3.0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;visual_estimate&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;lateral_offset&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;41.0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;tolerance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;2.0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;steel_rule&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;tread_depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;6.4&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;tolerance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;0.4&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;steel_rule&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;liner_inspected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;liner_shows_deflated_running&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;dot_week&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
    &lt;span class="na"&gt;dot_year&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2022&lt;/span&gt;
  &lt;span class="na"&gt;expect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repairable&lt;/span&gt;
    &lt;span class="na"&gt;cited&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;R-900&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shoulder_boundary_exact&lt;/span&gt;
  &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;offset lands precisely on the computed crown limit&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crown&lt;/span&gt;
    &lt;span class="na"&gt;section_width_mm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;225&lt;/span&gt;
    &lt;span class="na"&gt;lateral_offset&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;78.0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;tolerance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;1.5&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;steel_rule&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;injury_diameter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;3.1&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;tolerance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;0.2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;digital_caliper&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;tread_depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;7.0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;tolerance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;0.4&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;steel_rule&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;liner_inspected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;liner_shows_deflated_running&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;expect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;insufficient_evidence&lt;/span&gt;
    &lt;span class="na"&gt;outstanding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;lateral_offset&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;two_repairs_fourteen_centimetres&lt;/span&gt;
  &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prior repair too close around the circumference&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crown&lt;/span&gt;
    &lt;span class="na"&gt;section_width_mm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;225&lt;/span&gt;
    &lt;span class="na"&gt;rim_diameter_in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;
    &lt;span class="na"&gt;angular_position_deg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12.0&lt;/span&gt;
    &lt;span class="na"&gt;prior_repairs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;angular_position_deg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;38.0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;lateral_offset_mm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;30.0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
         &lt;span class="nv"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;internal_patch_plug&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;expect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;replace&lt;/span&gt;
    &lt;span class="na"&gt;cited&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;R-070&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runner is fifteen lines and does nothing clever. What earns its keep is the &lt;code&gt;note&lt;/code&gt; field, which forces whoever adds a case to say why it exists. Half of our regressions have been caught by somebody reading a note during review and saying that is not what we do any more.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boundary case that changed our mind about rounding
&lt;/h2&gt;

&lt;p&gt;Take &lt;code&gt;shoulder_boundary_exact&lt;/code&gt; above. A 225-section fitment gives a crown half-width of exactly 78.0 mm. The technician records a lateral offset of 78.0 mm using a steel rule with a ±1.5 mm tolerance. Is the injury in the crown or the shoulder?&lt;/p&gt;

&lt;p&gt;The honest answer is that we do not know, and the plausible band spans both. &lt;code&gt;Measured.above(78.0)&lt;/code&gt; returns &lt;code&gt;None&lt;/code&gt; because &lt;code&gt;76.5 &amp;lt; 78.0 &amp;lt;= 79.5&lt;/code&gt;. R-030 contributes a gap rather than a firing, and the verdict is &lt;code&gt;INSUFFICIENT_EVIDENCE&lt;/code&gt; with &lt;code&gt;lateral_offset&lt;/code&gt; named as the outstanding item. The remedy is a caliper measurement, which takes about thirty seconds and collapses the tolerance to ±0.2 mm.&lt;/p&gt;

&lt;p&gt;We debated failing closed to &lt;code&gt;REPLACE&lt;/code&gt; here instead, on the theory that the safe default should apply. We did not, and the reasoning is worth spelling out because it is the one place we deliberately do not fail closed. A &lt;code&gt;REPLACE&lt;/code&gt; verdict is terminal — the casing goes in the scrap pile and nobody re-measures. &lt;code&gt;INSUFFICIENT_EVIDENCE&lt;/code&gt; is a request that costs half a minute and produces a defensible answer either way. Failing closed is correct when better evidence is unobtainable or when the unknown is itself a danger signal; it is not correct when the unknown is simply a measurement nobody has bothered to take yet. Those are different situations and collapsing them wastes casings.&lt;/p&gt;

&lt;p&gt;Note also the half-open interval. &lt;code&gt;above&lt;/code&gt; uses &lt;code&gt;&amp;gt;=&lt;/code&gt; on the lower comparison, so an offset of exactly 78.0 mm with zero tolerance would be treated as outside the crown. Boundary inclusivity is a decision, it should be written down in the table, and it should have a golden case pinning it. Ours does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Missing tread depth: a gap with a name
&lt;/h2&gt;

&lt;p&gt;The third nasty fixture is the simplest and the most common. A technician records everything about the injury and never gets a tread-depth reading, because the vehicle was on the hoist for an oil change and the tire never came off.&lt;/p&gt;

&lt;p&gt;R-310 fires — &lt;code&gt;tread_depth&lt;/code&gt; is &lt;code&gt;None&lt;/code&gt; — contributing the outstanding field. No exclusion has fired definitely, so the verdict is &lt;code&gt;INSUFFICIENT_EVIDENCE&lt;/code&gt; and the explanation reads: &lt;em&gt;Cannot decide under repairability/2026.02.1. Outstanding evidence: tread_depth.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Compare that with the original code, where &lt;code&gt;t.get("tread_mm", 99)&lt;/code&gt; turned an unmeasured tire into one with 99 mm of tread. The difference is not that the new engine is smarter. It is that the new engine has somewhere to put "I do not know," and the old one did not, so the unknown had to masquerade as something.&lt;/p&gt;

&lt;p&gt;This shows up disproportionately in &lt;a href="https://calgaryrimandtire.ca/mobile-tire-service" rel="noopener noreferrer"&gt;work performed by our mobile service unit&lt;/a&gt;, where the assessment happens in a parking lot rather than a bay. Mobile assessments produce roughly twice the &lt;code&gt;INSUFFICIENT_EVIDENCE&lt;/code&gt; rate of in-bay ones, and that is not a defect in the software — it is the software correctly reporting that a driveway in a February wind is a worse measurement environment than a lift. The &lt;a href="https://calgaryrimandtire.ca/service-areas" rel="noopener noreferrer"&gt;coverage map for that unit&lt;/a&gt; determines how often it happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versioning, so last year's verdict can be replayed
&lt;/h2&gt;

&lt;p&gt;Criteria change. When they do, every decision already issued was issued under the old table, and you need to be able to say so.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RuleSet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;                 &lt;span class="c1"&gt;# e.g. repairability/2026.02.1
&lt;/span&gt;    &lt;span class="n"&gt;effective_from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;effective_to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rationale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="n"&gt;REGISTRY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RuleSet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ruleset_in_force&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;RuleSet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;REGISTRY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
               &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_from&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt;
               &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_to&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_to&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;LookupError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rule sets in force at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three properties fall out. Persisted decisions store &lt;code&gt;table_version&lt;/code&gt;, so replay is a registry lookup rather than an archaeology expedition. The digest covers predicate &lt;em&gt;names&lt;/em&gt; and rationale text, which means renaming a predicate or editing an explanation forces a version bump — annoying by design, because a changed explanation is a changed decision from the reader's point of view. And &lt;code&gt;ruleset_in_force&lt;/code&gt; raising on ambiguity means a botched effective-date edit fails at startup instead of silently picking whichever set the dict happened to yield first.&lt;/p&gt;

&lt;p&gt;The migration test is the payoff. Before a new table goes live, we replay the last twelve months of stored assessments under both versions and diff the verdicts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;replay_diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RuleSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RuleSet&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;changed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;digest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;digest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assessment_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assessment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;was_cited&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;now_cited&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firings&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;changed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time we ran this against a proposed revision, it flipped 11 % of historical verdicts from &lt;code&gt;REPLACE&lt;/code&gt; to &lt;code&gt;REPAIRABLE&lt;/code&gt;. That was not a bug in the new table; it was an accurate report that we had been over-conservative on injury angle for a year. Knowing the magnitude before shipping changed how we communicated the revision to fleet customers, which is worth more than the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision log
&lt;/h2&gt;

&lt;p&gt;Decisions are appended, never updated. A correction is a new record referencing the one it supersedes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dec_01HQ4R9K2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assessment_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asm_2026_08_10_0113"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"supersedes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"replace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"table_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repairability/2026.02.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"table_digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9f2a7c1e04b3d8aa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"firings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"rule_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"R-070"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"rationale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Prior repair within the illustrative 40 cm arc floor"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outstanding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:4b1d..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evaluated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-10T09:41:22-06:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engine_build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rules-engine 0.9.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prescreen-service"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;input_digest&lt;/code&gt; is a hash over the canonicalised assessment, which lets you prove that a replay used identical inputs without storing a second copy. &lt;code&gt;engine_build&lt;/code&gt; is separate from &lt;code&gt;table_version&lt;/code&gt; because the evaluator and the criteria change independently, and you will eventually have a defect that lives in one and not the other.&lt;/p&gt;

&lt;p&gt;We keep decision records for the life of the casing plus two years. Retention is not a compliance box here so much as the only way to answer "has this casing been repaired before, and where" when it comes back on a different vehicle eighteen months later — which happens constantly on &lt;a href="https://calgaryrimandtire.ca/fleet-management" rel="noopener noreferrer"&gt;the fleet side of the business&lt;/a&gt;, where wheels and tires migrate between units.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Calgary actually presses on the model
&lt;/h2&gt;

&lt;p&gt;Some of this is genuinely local, and it changes the numbers rather than the structure.&lt;/p&gt;

&lt;p&gt;Gravel season is the obvious one. Chip-seal work and the winter aggregate that lingers into spring produce a puncture wave from roughly April into June, and the injuries skew small, shallow, and squarely in the crown — exactly the population where a decision table earns its money, because volume plus a clear-cut criterion is where human inconsistency shows up. Two technicians will not disagree about a sidewall gash. They will absolutely disagree about a 5.8 mm injury measured with a rule.&lt;/p&gt;

&lt;p&gt;Changeover volume is the second. The compression of seasonal work into a few weeks each spring and fall — the &lt;a href="https://calgaryrimandtire.ca/seasonal-tire-changes" rel="noopener noreferrer"&gt;seasonal changeover service&lt;/a&gt; is the single busiest thing the business does — means a large number of tires get inspected in a short window by people who are tired. Consistency under fatigue is precisely what you buy with a rule table, and it is also when the pre-screen's &lt;code&gt;INSUFFICIENT_EVIDENCE&lt;/code&gt; rate climbs, which is a useful staffing signal in its own right.&lt;/p&gt;

&lt;p&gt;Road salt and brine change what "prior repair" evidence looks like. Corrosion around the bead seat and valve area can obscure or mimic damage, and a patch applied three seasons ago may sit under a layer of deposit. That is a data-capture problem, not a rules problem, but it is why &lt;code&gt;liner_inspected&lt;/code&gt; is a separate boolean from &lt;code&gt;liner_shows_deflated_running&lt;/code&gt; — "we looked and saw nothing" and "we could not see" are different states.&lt;/p&gt;

&lt;p&gt;Then there is the fleet population. Units running Deerfoot and Stoney Trail accumulate distance fast, so casing age and remaining depth interact with the repair question more often than they do on a family vehicle that does 12,000 km a year. A casing that is technically repairable but has 3 mm left is a different economic proposition, and the engine deliberately does not model that — economics belong to whoever is paying, and mixing them into a safety table is how safety tables get quietly relaxed. What we do instead is emit the verdict plus the measured depth, and let &lt;a href="https://calgaryrimandtire.ca/commercial-tire-services" rel="noopener noreferrer"&gt;the commercial service side&lt;/a&gt; make the money argument separately. Seasonal fitment choice interacts too, since &lt;a href="https://calgaryrimandtire.ca/winter-tires-calgary" rel="noopener noreferrer"&gt;dedicated winter rubber&lt;/a&gt; spends half the year in storage and comes back with a service history the pre-screen has to reconstruct.&lt;/p&gt;

&lt;p&gt;One more operational note: after any repair the wheel assembly goes back on the balancer, because a patch and the material removed to prepare for it change the mass distribution. That is downstream of the engine entirely, but it is the reason our work-order template links the verdict to &lt;a href="https://calgaryrimandtire.ca/wheel-balancing" rel="noopener noreferrer"&gt;the balancing step&lt;/a&gt; rather than treating the repair as the end of the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost, placement, and what the engine is not
&lt;/h2&gt;

&lt;p&gt;The evaluator is cheap. Eighteen rules over a frozen dataclass runs in a few microseconds; we have never profiled it because there has never been a reason to. If your rule set grows into the thousands you would index predicates by the fields they read and skip the ones whose inputs are absent, but at this scale that optimisation would be pure complexity.&lt;/p&gt;

&lt;p&gt;The expensive part is data capture, and it is expensive in minutes rather than milliseconds. Getting a caliper reading, a DOT date, and a liner inspection onto a record adds two or three minutes per tire during the busiest weeks of the year. That is the actual cost of the system, and it is worth being honest that a rules engine does not reduce it — it relocates it, from an argument after the fact to a measurement before it.&lt;/p&gt;

&lt;p&gt;The engine also sits in front of a human, never behind one. It runs at intake and on fleet pre-screens, producing a recommendation with a citation. It does not gate the technician. If R-040 says replace and the technician demounts the tire and finds a 4 mm injury rather than the 7 mm somebody eyeballed in the lane, the technician's finding wins and the assessment gets corrected, which produces a new decision record superseding the old one. General background on how any of this looks from the driver's side lives in our &lt;a href="https://calgaryrimandtire.ca/be-tire-smart" rel="noopener noreferrer"&gt;tire-safety primer&lt;/a&gt;, and the roadside cases that skip the pre-screen entirely go through &lt;a href="https://calgaryrimandtire.ca/emergency-services" rel="noopener noreferrer"&gt;emergency service dispatch&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we would do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Write the table before the evaluator.&lt;/strong&gt; We did it the other way and spent a week retrofitting a data structure onto assumptions the code had already baked in. The markdown table in this article existed as a printed sheet with pen marks on it before a line of Python was written the second time round, and that ordering was worth more than any refactor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not build a DSL.&lt;/strong&gt; Our first design had a small expression language so that criteria could be edited without a deploy. It was seductive and it was wrong: we ended up with an interpreter, a parser, an error-reporting story, and a testing story, all to avoid a five-minute deploy. Predicates as plain named functions, versioned in the same repository as everything else, has cost us nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version from the first commit.&lt;/strong&gt; We added &lt;code&gt;table_version&lt;/code&gt; in month four and have a gap in the record where earlier decisions cannot be replayed. There is no way to backfill that. It costs eight lines to do on day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resist the fourth verdict.&lt;/strong&gt; There has been steady pressure to add something like &lt;code&gt;REPAIRABLE_WITH_CAVEAT&lt;/code&gt;. Every proposed instance turned out to be either a referral (the manufacturer decides) or an economic judgement (the owner decides), and both belong outside the safety classification. Three values is not a limitation; it is the discipline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat the explanation as an interface.&lt;/strong&gt; As soon as fleet coordinators start reading rationale strings, those strings become a published surface. Edit one and somebody notices. That is a feature, and it is why the digest covers them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions we get asked
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why not an off-the-shelf rules engine?&lt;/strong&gt; Eighteen rules over one flat record does not justify a Rete network, a separate language, or a runtime whose evaluation semantics we would have to explain to a technician. The list-of-predicates approach fits in a file, is trivially testable, and has no dependency to upgrade. Reach for the heavy tooling when rules number in the hundreds and are authored by people outside the engineering group.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not learn the rules from historical outcomes?&lt;/strong&gt; Two reasons. The training signal is contaminated — historical verdicts were produced by the buggy nested code and by inconsistent human judgement — and a model cannot cite R-070. Explainability here is not a nice-to-have; it is most of the product's value. There is a defensible role for vision models in &lt;em&gt;measuring&lt;/em&gt; injury diameter from a photograph, feeding the table better inputs. That is a perception problem, and it should stay separate from the decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do manufacturer-specific caveats fit?&lt;/strong&gt; They are the referral tier. R-200 and R-210 do not decide anything; they route to a human with the manufacturer's documentation. Attempting to encode every brand's line-by-line position on run-flat repair would produce a table that is wrong the moment a bulletin is revised, and wrong in a way nobody notices for months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when a technician disagrees with the verdict?&lt;/strong&gt; The technician wins, and the disagreement is recorded with a reason code. We review overrides quarterly. A rule that is overridden 30 % of the time is a mis-specified rule, and the override log is the highest-value dataset the system produces — better than any accuracy metric, because it points at the specific criterion under dispute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the table get bigger over time?&lt;/strong&gt; Slower than you would expect. It went from fourteen rules to eighteen in two years. Most requested additions turn out to be a threshold change to an existing rule rather than a new criterion, which is a good sign that the decomposition is roughly right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where do the input values come from?&lt;/strong&gt; Mostly a tablet form in the bay, with the fitment fields pre-populated from the size code read off the sidewall. The parsing of that code is its own small problem, and getting it wrong poisons the geometry — which is why the crown half-width calculation carries an override field rather than trusting a derived tread width when a real figure exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the engine's authority ends
&lt;/h2&gt;

&lt;p&gt;The table does not know that the casing in front of you was sitting in a puddle of coolant, or that the inner liner has a wrinkle pattern the technician has seen four times and associates with a particular failure, or that this specific fleet runs a load profile the criteria were never written for. It knows eighteen things and it knows them consistently, which is precisely the value: it removes the variance that comes from the fourteenth tire of a Saturday in November, and it leaves the judgement that comes from ten years of demounting tires exactly where it belongs.&lt;/p&gt;

&lt;p&gt;That division is the design. Encode what is codifiable, refuse to guess at what is not, name every unknown out loud, and make every answer traceable to the criterion that produced it. The nested version failed not because its criteria were wrong — most of them were fine — but because control flow is a terrible medium for a rule set that has to be reviewed, explained, and replayed. Data is a better medium. The evaluator is forty lines and has not needed to change in a year, while the table has been revised five times, which is exactly the ratio you want.&lt;/p&gt;

&lt;p&gt;If you are building something in this shape, the test I would apply is narrow: can a domain expert who does not read your language review the criteria, and can you reproduce any past answer exactly? If either is a no, the criteria are still hiding in the code.&lt;/p&gt;

</description>
      <category>automotive</category>
      <category>calgary</category>
      <category>maintenance</category>
      <category>tires</category>
    </item>
    <item>
      <title>Building a Weather-Triggered Tire Advisory Pipeline: Ingesting Environment Canada Data to Drive Maintenance Alerts for Calgary Drivers</title>
      <dc:creator>KMJ Tire Calgary</dc:creator>
      <pubDate>Sun, 09 Aug 2026 16:11:23 +0000</pubDate>
      <link>https://dev.to/kmjtirecalgary/building-a-weather-triggered-tire-advisory-pipeline-ingesting-environment-canada-data-to-drive-45o6</link>
      <guid>https://dev.to/kmjtirecalgary/building-a-weather-triggered-tire-advisory-pipeline-ingesting-environment-canada-data-to-drive-45o6</guid>
      <description>&lt;h1&gt;
  
  
  Building a Weather-Triggered Tire Advisory Pipeline: Ingesting Environment Canada Data to Drive Maintenance Alerts for Calgary Drivers
&lt;/h1&gt;

&lt;p&gt;I run the technical side of a small Calgary tire business, and for the past two winters I have been iterating on a pipeline that watches Environment and Climate Change Canada (ECCC) open data and decides, automatically, when a weather situation is relevant enough to tires that a driver should hear about it. Not "it might snow" — everyone in Calgary owns a window. The pipeline exists for the narrower, more actionable events: the first sustained cold stretch that makes winter rubber matter, a chinook swing that silently pulls four or five PSI out of every tire in the city, freezing rain, summer hail cells, and the deep-cold snaps where TPMS dashboards light up across town for reasons that are physics, not punctures.&lt;/p&gt;

&lt;p&gt;This is a design writeup, not a product pitch. I am going to cover the ECCC data surfaces worth ingesting, the normalized schema I landed on after two bad first drafts, the advisory rule catalogue with actual thresholds, the hysteresis and deduplication machinery that stops the system from flapping, how I backtested rule precision and recall against past seasons, and the delivery layer with rate limiting and audit logging. All numeric thresholds shown are design examples from my configuration — tune them against your own data before trusting them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Calgary Weather Is a Legitimately Hard Input
&lt;/h2&gt;

&lt;p&gt;Calgary sits at roughly 1,045 metres elevation on the dry side of the Rockies, and its weather has three properties that make naive alerting embarrassing.&lt;/p&gt;

&lt;p&gt;First, chinooks. Warm, dry downslope winds can raise the air temperature 15 to 25°C within hours. Calgary averages somewhere in the range of 20 to 30 chinook days per winter depending on how you count them. A rule that fires on "temperature crossed 0°C" will fire constantly from November through March and train every recipient to ignore it.&lt;/p&gt;

&lt;p&gt;Second, variance around the seasonal transition. The historical median for the first hard freeze lands in early-to-mid October, but the spread is wide: I have replayed years where the first sustained sub-7°C stretch arrived in late September and years where it held off until November. A calendar-based reminder ("it is October 15, think about winter rubber") is wrong by weeks in either direction more often than it is right. The 7°C figure itself is the industry-standard crossover point where winter tread compounds begin outperforming all-season compounds — that is the physical basis for the &lt;a href="https://calgaryrimandtire.ca/winter-tires-calgary" rel="noopener noreferrer"&gt;winter tire timing guidance&lt;/a&gt; we publish, and it is the number the pipeline hunts for in forecast data.&lt;/p&gt;

&lt;p&gt;Third, hail. Calgary sits inside what meteorologists informally label hailstorm alley. Convective cells between June and September produce short-notice severe weather warnings, and the tire-relevant advice ("get the vehicle under cover; inspect sidewalls afterward") has a useful lead time measured in tens of minutes. That pushes part of the architecture toward push-based ingestion rather than polling.&lt;/p&gt;

&lt;p&gt;So the input signal is genuinely volatile, the false-positive cost is recipient fatigue, and the false-negative cost is a driver on all-seasons in the first -15°C week. Those constraints shaped everything below.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three ECCC Data Surfaces and When to Use Each
&lt;/h2&gt;

&lt;p&gt;ECCC exposes its open data through several distinct mechanisms, and the pipeline uses three of them for different jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  GeoMet OGC APIs for structured queries
&lt;/h3&gt;

&lt;p&gt;The GeoMet platform (&lt;code&gt;api.weather.gc.ca&lt;/code&gt;) serves OGC API - Features collections over plain HTTPS with JSON responses. The collections I lean on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;climate-daily&lt;/code&gt; — quality-controlled historical daily observations (max/min/mean temperature, precipitation) per station. This is the backbone of backtesting.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;climate-stations&lt;/code&gt; — station metadata, used once to pin the Calgary International Airport station (climate identifier 3031094 in my configuration) as the canonical observation source.&lt;/li&gt;
&lt;li&gt;CAP alert collections — active weather alerts as GeoJSON features with polygon geometry, which lets you do a point-in-polygon test against a Calgary coordinate instead of string-matching region names.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A representative request for historical dailies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET https://api.weather.gc.ca/collections/climate-daily/items?
    CLIMATE_IDENTIFIER=3031094
    &amp;amp;datetime=2023-09-01/2024-04-30
    &amp;amp;sortby=LOCAL_DATE
    &amp;amp;limit=1000
    &amp;amp;f=json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paginate with &lt;code&gt;offset&lt;/code&gt;, respect the &lt;code&gt;numberMatched&lt;/code&gt; field, and cache aggressively — historical dailies are immutable once quality control settles, so I store them locally and never re-fetch a closed month.&lt;/p&gt;

&lt;h3&gt;
  
  
  Citypage XML for the human-aligned forecast
&lt;/h3&gt;

&lt;p&gt;The citypage weather product is the same forecast Calgarians see on official channels, published as XML per site under &lt;code&gt;dd.weather.gc.ca/citypage_weather/&lt;/code&gt;. Calgary is site &lt;code&gt;s0000047&lt;/code&gt; in Alberta's directory. The payload includes current conditions, seven-day forecast periods with textual summaries and numeric highs/lows, and issue timestamps.&lt;/p&gt;

&lt;p&gt;Why use a legacy-feeling XML product when model data exists? Because advisory text ultimately reaches humans, and the citypage forecast is the forecast a recipient can verify against every other weather surface in their life. If my pipeline says "sustained cold arriving Thursday" and their phone's weather app disagrees, trust erodes. Anchoring rules to the public forecast keeps the system's claims checkable.&lt;/p&gt;

&lt;h3&gt;
  
  
  MSC Datamart AMQP for push latency
&lt;/h3&gt;

&lt;p&gt;The Datamart also publishes an AMQP 0-9-1 message feed (the &lt;code&gt;xpublic&lt;/code&gt; exchange on &lt;code&gt;dd.weather.gc.ca&lt;/code&gt;, anonymous credentials, topic-based routing) announcing every new file the moment it lands. The Sarracenia project wraps this, but a plain &lt;code&gt;pika&lt;/code&gt; consumer works fine for a narrow subscription. I subscribe to two topic patterns: the Alberta citypage updates and the CAP alert directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pika&lt;/span&gt;

&lt;span class="n"&gt;AMQP_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amqps://anonymous:anonymous@dd.weather.gc.ca:5671&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TOPICS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v02.post.citypage_weather.xml.AB.#&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v02.post.alerts.cap.#&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_consumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;on_announcement&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pika&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;URLParameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AMQP_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pika&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BlockingConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queue_declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exclusive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auto_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TOPICS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queue_bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xpublic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="n"&gt;routing_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basic_consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;on_message_callback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_announcement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;auto_ack&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_consuming&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each announcement message carries the path of the new file; the consumer fetches it over HTTPS, hands it to the parser, and acknowledges. The division of labour that emerged: AMQP for anything latency-sensitive (alerts, forecast refreshes), scheduled HTTPS polling as a belt-and-suspenders fallback every 30 minutes, and GeoMet queries for historical and station data. If the AMQP connection drops overnight — it happens — the poller means the worst case is a half-hour-stale forecast, not a silent outage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normalizing Everything Into One Typed Schema
&lt;/h2&gt;

&lt;p&gt;The first draft of this system passed raw parsed XML dictionaries around, and every rule reimplemented unit handling and missing-value checks. The second draft fixed that with a small set of typed models that every ingestion path must produce. Rules only ever see normalized records.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;CITYPAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;citypage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;GEOMET_DAILY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;geomet_daily&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;CAP_ALERT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cap_alert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ForecastPeriod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;              &lt;span class="c1"&gt;# e.g. "s0000047"
&lt;/span&gt;    &lt;span class="n"&gt;issued_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;       &lt;span class="c1"&gt;# UTC, from the feed's own timestamp
&lt;/span&gt;    &lt;span class="n"&gt;period_start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;        &lt;span class="c1"&gt;# local Calgary date the period covers
&lt;/span&gt;    &lt;span class="n"&gt;is_overnight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;temp_high_c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;temp_low_c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;pop_percent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;        &lt;span class="c1"&gt;# probability of precipitation
&lt;/span&gt;    &lt;span class="n"&gt;condition_codes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;frozenset&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# normalized: {"freezing_rain", ...}
&lt;/span&gt;    &lt;span class="n"&gt;text_summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;station_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;observed_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
    &lt;span class="n"&gt;temp_c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;wind_kmh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;station_pressure_kpa&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HazardAlert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cap_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;               &lt;span class="c1"&gt;# CAP message identifier, natural dedup key
&lt;/span&gt;    &lt;span class="n"&gt;event_kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;           &lt;span class="c1"&gt;# normalized: "freezing_rain", "hail", ...
&lt;/span&gt;    &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;             &lt;span class="c1"&gt;# CAP severity: minor/moderate/severe/extreme
&lt;/span&gt;    &lt;span class="n"&gt;onset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;expires&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;covers_calgary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;      &lt;span class="c1"&gt;# result of point-in-polygon test
&lt;/span&gt;    &lt;span class="n"&gt;raw_headline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two normalization decisions earned their keep:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Condition codes are a controlled vocabulary.&lt;/strong&gt; Citypage text summaries are free-form English ("Periods of freezing drizzle changing to snow near noon"). A mapping layer reduces each summary to a set of canonical codes — &lt;code&gt;freezing_rain&lt;/code&gt;, &lt;code&gt;freezing_drizzle&lt;/code&gt;, &lt;code&gt;snow&lt;/code&gt;, &lt;code&gt;rain&lt;/code&gt;, &lt;code&gt;thunderstorm&lt;/code&gt;, &lt;code&gt;hail_risk&lt;/code&gt; — via ordered keyword rules with an explicit &lt;code&gt;unmapped&lt;/code&gt; bucket that gets logged and reviewed weekly. Rules never regex the prose directly. When ECCC rewords a summary format (it has happened), the blast radius is one mapping table, not every rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timestamps are stored in UTC, bucketed in local time.&lt;/strong&gt; Forecast periods and advisory windows are meaningful to humans in America/Edmonton local time, including the daylight-saving transitions. Everything persists as UTC; every rule that reasons about "days" converts through the timezone explicitly. The one early bug I shipped in this area — a winter-window evaluation that shifted a day at the November DST boundary — is why the conversion now lives in exactly one function with a regression test pinned to the 2023 transition dates.&lt;/p&gt;

&lt;p&gt;The persistence layer is PostgreSQL. Trimmed DDL for the two hot tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;forecast_snapshot&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;              &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;site_id&lt;/span&gt;         &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;issued_at&lt;/span&gt;       &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;period_start&lt;/span&gt;    &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_overnight&lt;/span&gt;    &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;temp_high_c&lt;/span&gt;     &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;temp_low_c&lt;/span&gt;      &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;pop_percent&lt;/span&gt;     &lt;span class="nb"&gt;SMALLINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;condition_codes&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;text_summary&lt;/span&gt;    &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ingested_at&lt;/span&gt;     &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;issued_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;period_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_overnight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;advisory_event&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;               &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rule_id&lt;/span&gt;          &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dedup_key&lt;/span&gt;        &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;fired_at&lt;/span&gt;         &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;window_start&lt;/span&gt;     &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;window_end&lt;/span&gt;       &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;evidence&lt;/span&gt;         &lt;span class="n"&gt;JSONB&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;state&lt;/span&gt;            &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;
        &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'delivered'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'suppressed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'expired'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;UNIQUE&lt;/code&gt; constraint on &lt;code&gt;forecast_snapshot&lt;/code&gt; makes re-ingestion of the same issuance a no-op (&lt;code&gt;ON CONFLICT DO NOTHING&lt;/code&gt;), which matters because the AMQP consumer and the fallback poller will regularly both fetch the same file. The &lt;code&gt;evidence&lt;/code&gt; JSONB column stores the exact snapshot rows a rule saw when it fired — indispensable later when a recipient asks why they received something, and equally useful when they ask why they did not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Advisory Rule Catalogue
&lt;/h2&gt;

&lt;p&gt;Each rule is a pure function from &lt;code&gt;(normalized data, rule state)&lt;/code&gt; to &lt;code&gt;(optional advisory, new rule state)&lt;/code&gt;. The catalogue, with my current design-example thresholds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule ID&lt;/th&gt;
&lt;th&gt;Trigger summary&lt;/th&gt;
&lt;th&gt;Arm threshold (design example)&lt;/th&gt;
&lt;th&gt;Disarm / reset&lt;/th&gt;
&lt;th&gt;Season gate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;winter_window&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;First sustained sub-7°C forecast stretch&lt;/td&gt;
&lt;td&gt;≥5 of next 7 daily highs &amp;lt; 7°C&lt;/td&gt;
&lt;td&gt;Re-arms next Aug 1&lt;/td&gt;
&lt;td&gt;Sep 1 – Dec 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;chinook_swing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Large fast temperature rise or reversal&lt;/td&gt;
&lt;td&gt;≥18°C swing within 36 h in forecast or obs&lt;/td&gt;
&lt;td&gt;72 h cooldown&lt;/td&gt;
&lt;td&gt;Oct 15 – Apr 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pressure_drop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cold arrival implies meaningful PSI loss&lt;/td&gt;
&lt;td&gt;Forecast mean drops ≥15°C vs trailing 7-day obs mean&lt;/td&gt;
&lt;td&gt;96 h cooldown&lt;/td&gt;
&lt;td&gt;Year-round&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;freezing_rain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Freezing rain / drizzle in forecast or CAP&lt;/td&gt;
&lt;td&gt;Condition code present with PoP ≥ 60%, or CAP severity ≥ moderate&lt;/td&gt;
&lt;td&gt;Clears when window passes&lt;/td&gt;
&lt;td&gt;Year-round&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hail_risk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Severe thunderstorm watch/warning covering Calgary&lt;/td&gt;
&lt;td&gt;CAP event mapped to hail-capable kinds&lt;/td&gt;
&lt;td&gt;Clears on CAP expiry&lt;/td&gt;
&lt;td&gt;May 1 – Sep 30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deep_cold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extreme cold with TPMS side-effects&lt;/td&gt;
&lt;td&gt;≥2 consecutive forecast lows ≤ -25°C&lt;/td&gt;
&lt;td&gt;Ends when forecast lows &amp;gt; -20°C (hysteresis gap)&lt;/td&gt;
&lt;td&gt;Nov 1 – Mar 31&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Season gates are not cosmetic. They cut the evaluation surface — &lt;code&gt;hail_risk&lt;/code&gt; simply cannot fire in January — which both prevents absurd advisories and shrinks the false-positive space the backtests have to measure.&lt;/p&gt;

&lt;p&gt;A few of these rules deserve a closer look, because the interesting engineering hides in their edge cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  The winter-window rule and its off-by-one-season traps
&lt;/h3&gt;

&lt;p&gt;The intent: detect the first stretch of the autumn where the forecast says daytime highs will sit below 7°C for most of a week, because that is when compound physics starts favouring winter tread. Drivers running &lt;a href="https://calgaryrimandtire.ca/all-weather-tires-calgary" rel="noopener noreferrer"&gt;all-weather rubber&lt;/a&gt; (the mountain-snowflake-rated year-round category) get a differently worded advisory than drivers on pure &lt;a href="https://calgaryrimandtire.ca/all-season-tires-calgary" rel="noopener noreferrer"&gt;all-seasons&lt;/a&gt;, whose compound is the one actually going hard below the crossover.&lt;/p&gt;

&lt;p&gt;The naive implementation — "count forecast days with high &amp;lt; 7" — flapped badly in my 2023 replay. A single chinook-warmed Wednesday in an otherwise cold week resets the count, the advisory un-fires, then re-fires Friday. Three fixes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Quorum, not streak.&lt;/strong&gt; Require 5 of 7, not 7 consecutive. Chinooks poke one-day holes in cold stretches constantly; a quorum tolerates them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fire-once latching.&lt;/strong&gt; Once &lt;code&gt;winter_window&lt;/code&gt; fires, it stays fired for the season. There is no natural "un-trigger" for "winter is coming" — the state resets on August 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence requirement.&lt;/strong&gt; The quorum must hold across two consecutive forecast issuances at least 12 hours apart. A single aggressive model run at 4 a.m. that walks back by the afternoon issuance never reaches recipients.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eval_winter_window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;periods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ForecastPeriod&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                       &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RuleState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Advisory&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RuleState&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;latched&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;in_season&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;SEP_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEC_15&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;
    &lt;span class="n"&gt;week&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;daytime_periods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;periods&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;cold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;temp_high_c&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
            &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;temp_high_c&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;7.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;held_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;        &lt;span class="c1"&gt;# persistence across issuances
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;advisory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Advisory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;rule_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;winter_window&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;week&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;period_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;period_start&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;evidence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cold_days&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;highs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;temp_high_c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;advisory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;omitted&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;helpers&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;are&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;boring&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(That closing pseudo-tag is a joke; the helpers are ordinary date filters.) The practical effect of the three fixes in the 2023-24 replay: fire count dropped from 6 to 1, and the single firing landed on October 11 — four days before the first snowfall that season reached the ground. Design-example replay figures, but they sold me on latching.&lt;/p&gt;

&lt;p&gt;One more subtlety: the rule reads &lt;em&gt;forecast&lt;/em&gt; data, but the season's &lt;em&gt;observed&lt;/em&gt; dailies from &lt;code&gt;climate-daily&lt;/code&gt; are what the backtest later uses to decide whether the firing was "correct." Keeping those two roles separate in code — forecast triggers, observations judge — kept the evaluation honest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chinooks, the ideal gas law, and where the PSI actually goes
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;pressure_drop&lt;/code&gt; and &lt;code&gt;chinook_swing&lt;/code&gt; rules share a physical core, so let me do the worked math once, properly. Tire pressure follows the ideal gas approximation well enough for advisory purposes: at fixed volume and air mass, absolute pressure scales with absolute temperature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P2_abs = P1_abs × (T2_K / T1_K)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trap everyone hits: gauges read &lt;em&gt;gauge&lt;/em&gt; pressure, and the scaling law applies to &lt;em&gt;absolute&lt;/em&gt; pressure. In Calgary the distinction is bigger than at sea level, because at ~1,045 m the ambient pressure is around 88.5 kPa ≈ 12.8 PSI rather than 14.7.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worked design example.&lt;/strong&gt; A driver sets tires to 35.0 PSI (gauge) on a mild chinook afternoon at +5°C. A cold front ends the chinook and the overnight temperature reaches -25°C — an entirely ordinary Calgary reversal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T1 = 5°C   = 278.15 K
T2 = -25°C = 248.15 K

P1_abs = 35.0 + 12.8            = 47.8 PSI (absolute)
P2_abs = 47.8 × (248.15/278.15) = 42.6 PSI (absolute)
P2_gauge = 42.6 − 12.8          = 29.8 PSI

Loss ≈ 5.2 PSI over a 30°C drop  →  ~1 PSI per 5.8°C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the familiar rule of thumb — roughly 1 PSI per 5-6°C — derived rather than asserted. Note what it means in practice: a tire that read a healthy 35 in the warm reads under 30 in the cold, without any leak, and 29.8 is below many door-placard specs. The vehicle placard and the sidewall marking are different numbers with different meanings, which trips up enough people that we wrote a plain-language &lt;a href="https://calgaryrimandtire.ca/tire-sidewall-information" rel="noopener noreferrer"&gt;sidewall marking explainer&lt;/a&gt; for exactly this confusion; the placard is the inflation target, the sidewall figure is a maximum.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;pressure_drop&lt;/code&gt; rule turns the derivation into a trigger: compute the trailing 7-day mean observed temperature from &lt;code&gt;climate-daily&lt;/code&gt; plus recent hourly observations, compare against the forward 3-day forecast mean, and if the drop is ≥15°C, emit an advisory whose body includes the &lt;em&gt;computed&lt;/em&gt; estimated PSI loss for a placard-typical inflation, clearly labelled as an estimate. Recipients respond much better to "expect roughly 3-4 PSI lower readings by Thursday" than to "cold weather affects tire pressure," which is weather-app wallpaper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Trailing observed mean vs. forward forecast mean (design example)&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;trailing&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mean_temp_c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;obs_mean&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;climate_daily_local&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;station_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'3031094'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;local_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'7 days'&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="k"&gt;forward&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;temp_high_c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;temp_low_c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;fc_mean&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;latest_forecast&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;period_start&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                         &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;obs_mean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fc_mean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obs_mean&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fc_mean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;drop_c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obs_mean&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fc_mean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;should_consider_firing&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;trailing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;should_consider_firing&lt;/code&gt;, not &lt;code&gt;should_fire&lt;/code&gt; — the SQL is a candidate filter; hysteresis state in the application layer makes the final decision.&lt;/p&gt;

&lt;p&gt;The chinook-specific angle deserves a sentence more. A chinook &lt;em&gt;rise&lt;/em&gt; also matters: air warms, pressure climbs back, and a driver who topped up during the cold snap is now a couple PSI &lt;em&gt;over&lt;/em&gt; placard, then under again when the chinook collapses. The &lt;code&gt;chinook_swing&lt;/code&gt; advisory therefore talks about re-checking after the swing settles rather than chasing the gauge mid-swing, and it links the general inflation-habits material on &lt;a href="https://calgaryrimandtire.ca/be-tire-smart" rel="noopener noreferrer"&gt;pressure-aware tire care&lt;/a&gt; rather than urging immediate action. Advising &lt;em&gt;inaction&lt;/em&gt; at the right moment is an underrated advisory type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Freezing rain and hail: the CAP-driven rules
&lt;/h3&gt;

&lt;p&gt;These two rules are structurally simpler because ECCC has already done the meteorology; the pipeline's job is filtering and framing. CAP alert XML arrives via the AMQP subscription within seconds of publication. Processing steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse the CAP envelope; extract event type, severity, urgency, onset/expiry, and the polygon.&lt;/li&gt;
&lt;li&gt;Point-in-polygon test against a fixed Calgary coordinate set (I use five points — downtown plus four quadrant centroids — so an alert clipping only the far northwest still registers).&lt;/li&gt;
&lt;li&gt;Map the event to the internal vocabulary. Freezing rain and freezing drizzle map directly. For hail, ECCC issues severe thunderstorm watches and warnings whose text frequently mentions hail size; the mapper marks any severe-thunderstorm CAP as &lt;code&gt;hail_risk&lt;/code&gt; and elevates confidence when the text matches hail-size patterns.&lt;/li&gt;
&lt;li&gt;Frame the tire-relevant angle. For freezing rain: stopping distances, the limits of any tread compound on wet ice, and a pointer to our &lt;a href="https://calgaryrimandtire.ca/winter-tires-calgary" rel="noopener noreferrer"&gt;winter traction primer&lt;/a&gt; for the compound background. For hail: move the vehicle under cover if feasible, and afterwards inspect sidewalls and tread for bruising — with a note that impact damage is often &lt;a href="https://calgaryrimandtire.ca/tire-repair-in-calgary" rel="noopener noreferrer"&gt;repairable when caught early&lt;/a&gt; if it is in the tread face rather than the sidewall.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;cap_id&lt;/code&gt; field gives natural deduplication: CAP updates reference their predecessors, so an updated warning replaces rather than duplicates the advisory. More on the general mechanism below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deep cold and the TPMS false-alarm problem
&lt;/h3&gt;

&lt;p&gt;Below about -25°C, two things happen that generate confused drivers. First, the pure physics above: a tire inflated to placard at 0°C is down roughly 4-5 PSI at -30°C, which is enough to cross the typical TPMS trigger threshold of 25% below placard on some vehicles. Second, direct-TPMS sensor batteries and radio electronics behave worse in extreme cold, so intermittent sensor-fault indications (as opposed to low-pressure warnings) become more common.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;deep_cold&lt;/code&gt; advisory therefore has an unusual goal: &lt;em&gt;pre-empt panic&lt;/em&gt;. It fires when the forecast shows two consecutive lows at or below -25°C, and its content explains that a TPMS lamp on the first brutal morning most likely means "cold air is denser," what the difference between the low-pressure symbol and the sensor-fault symbol is, and that the correct response is to check with a gauge and add air — not to assume a puncture. For drivers who cannot comfortably do that at -30°C, the advisory mentions that our &lt;a href="https://calgaryrimandtire.ca/mobile-tire-service" rel="noopener noreferrer"&gt;mobile service unit&lt;/a&gt; operates through cold snaps across the usual &lt;a href="https://calgaryrimandtire.ca/service-areas" rel="noopener noreferrer"&gt;coverage zones&lt;/a&gt;. That is the sum total of self-reference this rule carries; the advisory is 90% physics explainer by word count, deliberately.&lt;/p&gt;

&lt;p&gt;The -25°C arm / -20°C disarm pair is the hysteresis gap doing visible work. Calgary cold snaps frequently hover near the threshold; a symmetric threshold would toggle the advisory state daily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hysteresis: Designing Rules That Refuse to Flap
&lt;/h2&gt;

&lt;p&gt;Flapping is the failure mode that kills advisory systems, so it gets its own machinery rather than per-rule improvisation. Every rule runs inside a shared state wrapper with four orthogonal anti-flap controls:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Split thresholds.&lt;/strong&gt; Arm and disarm conditions differ by a deliberate gap (fire at -25°C, clear at -20°C; fire at ≥18°C swing, ignore swings under 12°C while active). The gap width is a tunable per rule, chosen from the backtest's flap counts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Minimum hold time.&lt;/strong&gt; Once armed, a rule cannot disarm for a configured period (12-72 hours depending on the rule) even if conditions momentarily recede. Forecast issuances wobble run to run; the hold time absorbs the wobble.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistence-to-fire.&lt;/strong&gt; Conditions must hold across N consecutive evaluations before the first firing, as in the winter-window rule. This trades a few hours of latency for a large reduction in retracted advisories, and for every rule except the CAP-driven pair the trade is obviously right. CAP rules skip persistence — ECCC has already applied its own issuance discipline, and freezing-rain lead time is precious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cooldown after firing.&lt;/strong&gt; After emitting, a rule sleeps for a per-rule window regardless of input. A chinook that oscillates for five days produces one advisory, not five.&lt;/p&gt;

&lt;p&gt;The wrapper is small enough to show nearly whole:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RuleState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;armed_since&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;last_fired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;latched&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;consecutive_hits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;observed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RuleState&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;consecutive_hits&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;armed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;armed_since&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;armed&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;armed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consecutive_hits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;armed_since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;armed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;held_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;armed_since&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
                &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;armed_since&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;in_cooldown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_fired&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
                &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_fired&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;State persists to a &lt;code&gt;rule_state&lt;/code&gt; table keyed by &lt;code&gt;rule_id&lt;/code&gt;, written transactionally with any &lt;code&gt;advisory_event&lt;/code&gt; insert, so a process restart mid-evaluation cannot double-fire. That transactional pairing — state transition and event emission committing together or not at all — is the single most load-bearing line of design in the whole system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deduplication and Idempotency Keys
&lt;/h2&gt;

&lt;p&gt;Hysteresis prevents rapid re-firing from the same rule instance; deduplication prevents the same &lt;em&gt;logical event&lt;/em&gt; from producing multiple advisories through different paths. The two overlap but are not the same problem. Sources of logical duplicates I have actually observed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The AMQP consumer and the fallback poller ingesting the same forecast issuance (handled at the snapshot layer by the unique constraint).&lt;/li&gt;
&lt;li&gt;A CAP warning being updated four times during one freezing-rain event, each update a new file.&lt;/li&gt;
&lt;li&gt;A restart replaying an evaluation against data that already produced an advisory.&lt;/li&gt;
&lt;li&gt;Two rules describing one physical event — a sharp cold arrival can satisfy both &lt;code&gt;chinook_swing&lt;/code&gt; and &lt;code&gt;pressure_drop&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mechanism is a deterministic dedup key per advisory, unique-constrained in the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dedup_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;window_start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;window_end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;basis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rule_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;window_start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;window_end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;basis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key deliberately excludes the firing timestamp and the evidence payload: two firings about the same window are the same advisory no matter when the pipeline noticed. For CAP rules, &lt;code&gt;window_start/window_end&lt;/code&gt; come from the CAP onset/expiry, and the &lt;code&gt;references&lt;/code&gt; chain in updated CAP messages collapses updates onto the original key with the evidence column appended rather than a new row inserted.&lt;/p&gt;

&lt;p&gt;Cross-rule duplication gets a coarser tool: a suppression matrix. If &lt;code&gt;pressure_drop&lt;/code&gt; wants to fire while a &lt;code&gt;chinook_swing&lt;/code&gt; advisory for an overlapping window is already in &lt;code&gt;delivered&lt;/code&gt; state, it writes its event with &lt;code&gt;state='suppressed'&lt;/code&gt; — recorded, auditable, never sent. The matrix is a static config of (suppressor, suppressed, max window overlap) triples, currently three entries. I resisted making it clever; three static rows have covered every real collision in two seasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backtesting Against Past Seasons
&lt;/h2&gt;

&lt;p&gt;Threshold numbers without evaluation are vibes. The backtest harness replays historical data through the exact production rule code — same functions, same state wrapper — with a clock abstraction so &lt;code&gt;utcnow()&lt;/code&gt; follows the replay.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data.&lt;/strong&gt; For observations, &lt;code&gt;climate-daily&lt;/code&gt; gives clean ground truth back decades. Forecasts are the harder half: ECCC does not archive citypage forecasts in an easily queryable way, so I have been archiving every issuance myself since instrumenting the pipeline (which now covers two full winters), and for older seasons I approximate "what the forecast plausibly said" by applying a lag-and-noise model to observed dailies. Findings from the approximated seasons get a lower evidence tier and never justify a threshold change on their own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Labels.&lt;/strong&gt; Precision and recall require ground truth for "an advisory should have existed." I hand-labelled event windows per season from observed data plus ECCC's public seasonal summaries: the actual first sustained cold stretch, each ≥15°C 36-hour swing, each freezing-rain event, each hail event with confirmed Calgary impact, each ≤-25°C snap. Labelling five past seasons took an evening per season. It is boring work and it is the only reason the numbers below mean anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metrics.&lt;/strong&gt; Per rule, per season: true positives (fired within the labelled window or within its defined lead margin), false positives (fired with no matching label), false negatives (label with no firing), plus two operational counts — flaps (fire/clear/fire on one label) and lead time (hours between firing and label onset).&lt;/p&gt;

&lt;p&gt;Design-example results from the replay of the two fully-archived winters, after the current thresholds settled:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Labels&lt;/th&gt;
&lt;th&gt;TP&lt;/th&gt;
&lt;th&gt;FP&lt;/th&gt;
&lt;th&gt;FN&lt;/th&gt;
&lt;th&gt;Precision&lt;/th&gt;
&lt;th&gt;Recall&lt;/th&gt;
&lt;th&gt;Median lead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;winter_window&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;~5 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;chinook_swing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;0.85&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;td&gt;~14 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pressure_drop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0.73&lt;/td&gt;
&lt;td&gt;0.89&lt;/td&gt;
&lt;td&gt;~30 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;freezing_rain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;~4 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deep_cold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;~2 days&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Reading that table honestly: &lt;code&gt;chinook_swing&lt;/code&gt; misses one swing in five, almost always the ones the forecast itself underpredicted — an input ceiling, not a rule bug. &lt;code&gt;pressure_drop&lt;/code&gt; runs the loosest precision, and I accept that because its advisory is the cheapest to receive (a gauge check costs a minute). The tuning principle that fell out of a season of fiddling: &lt;strong&gt;set each rule's threshold according to the cost asymmetry of its errors.&lt;/strong&gt; Winter-window false negatives are the most expensive outcome in the whole catalogue, so that rule runs the most sensitive quorum the flap budget allows. Hail false positives are nearly free ("move the vehicle under cover" wastes five minutes), so &lt;code&gt;hail_risk&lt;/code&gt; forwards every severe-thunderstorm CAP without second-guessing.&lt;/p&gt;

&lt;p&gt;One meta-lesson: keep the backtest harness runnable in CI against a frozen fixture season. Twice, an innocuous refactor changed rule behaviour (a timezone slip; a quorum boundary going from &lt;code&gt;&amp;lt;&lt;/code&gt; to &lt;code&gt;&amp;lt;=&lt;/code&gt;), and the fixture replay caught both before production did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delivery: Rate Limiting, Quiet Hours, and the Audit Trail
&lt;/h2&gt;

&lt;p&gt;An advisory in &lt;code&gt;pending&lt;/code&gt; state enters the delivery layer, which owns four responsibilities and refuses to learn anything about meteorology.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-recipient rate limiting.&lt;/strong&gt; Token bucket per recipient: capacity 3, refill one per 48 hours (design-example values). When the bucket is empty, lower-priority advisories drop — recorded with &lt;code&gt;state='suppressed'&lt;/code&gt; and a &lt;code&gt;suppression_reason&lt;/code&gt; — while the two highest-priority kinds (&lt;code&gt;freezing_rain&lt;/code&gt;, &lt;code&gt;hail_risk&lt;/code&gt;) may overdraw the bucket into a bounded deficit. The priority ordering is static config, reviewed seasonally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quiet hours with priority override.&lt;/strong&gt; Nothing routine sends between 21:00 and 07:30 local; it queues for morning. Imminent-hazard kinds override, since a 22:10 hail cell does not wait for breakfast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Channel fan-out.&lt;/strong&gt; Email and SMS behind a common interface. Every message body is generated from the advisory evidence plus a template per (rule, audience) pair, and every message ends with the same two fixed elements: a plain-language "why you received this" line derived from the evidence JSON, and an unsubscribe path. Recipients opted in explicitly through the notification-preferences step of our &lt;a href="https://calgaryrimandtire.ca/online-bookings" rel="noopener noreferrer"&gt;online reservation flow&lt;/a&gt;; the pipeline maintains no other recipient source, and the seasonal-changeover reminder list from our &lt;a href="https://calgaryrimandtire.ca/seasonal-tire-changes" rel="noopener noreferrer"&gt;changeover information page&lt;/a&gt; feeds the same consent store rather than a parallel one. Consent lives in one table with one meaning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit logging.&lt;/strong&gt; Every delivery attempt writes an append-only row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;delivery_audit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;              &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;advisory_id&lt;/span&gt;     &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;advisory_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;recipient_hash&lt;/span&gt;  &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;-- HMAC of recipient id, not the id&lt;/span&gt;
    &lt;span class="n"&gt;channel&lt;/span&gt;         &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;attempted_at&lt;/span&gt;    &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;outcome&lt;/span&gt;         &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;-- sent / bounced / rate_limited / quiet_hours&lt;/span&gt;
    &lt;span class="n"&gt;provider_ref&lt;/span&gt;    &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;body_sha256&lt;/span&gt;     &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Storing &lt;code&gt;body_sha256&lt;/code&gt; instead of the body keeps recipient content out of the audit table while still letting me prove exactly which template revision a given person received — the rendered bodies live in short-retention storage keyed by that hash. The audit table answered a real dispute last winter: a recipient was sure they had never been warned before a freezing-rain morning; the trail showed a &lt;code&gt;bounced&lt;/code&gt; outcome from their expired address, which turned a trust complaint into an address update.&lt;/p&gt;

&lt;p&gt;The whole delivery layer is intentionally dumb. Every interesting decision — should this exist, is it a duplicate, is it worth a human's attention — happens upstream where it can be backtested. The layer that touches humans only schedules, throttles, renders, and records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Modes Worth Designing For
&lt;/h2&gt;

&lt;p&gt;A grab-bag of production lessons that did not fit elsewhere, each earned the annoying way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The AMQP connection dies silently.&lt;/strong&gt; Heartbeats help but do not catch everything. The real safeguard is an end-to-end freshness check: if the newest &lt;code&gt;forecast_snapshot.issued_at&lt;/code&gt; is older than 8 hours, page me — citypage forecasts refresh far more often than that, so staleness means ingestion is broken regardless of which component lied.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECCC file formats drift.&lt;/strong&gt; Rarely and usually announced, but the parser treats any unmapped structure as a soft failure that quarantines the file and alerts, rather than a crash or — worse — a silent partial parse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Station data has holes.&lt;/strong&gt; &lt;code&gt;climate-daily&lt;/code&gt; rows can arrive late or flagged. Every aggregate over observations carries a minimum-coverage requirement (≥5 of 7 trailing days present) and rules degrade to forecast-only evidence, noted in the evidence JSON, when coverage fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clock discipline.&lt;/strong&gt; The replay clock abstraction leaked into production once, freezing a rule's idea of "now" at process start. Rule code now receives time as an argument; nothing below the entrypoint imports a clock.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Threshold drift review.&lt;/strong&gt; Once a season, the labelled-event set gets extended and every threshold re-justified against the updated table. Thresholds that survive because nobody looked are just superstition with a config file. The &lt;a href="https://calgaryrimandtire.ca/be-tire-smart" rel="noopener noreferrer"&gt;general tire-knowledge material&lt;/a&gt; we maintain gets the same seasonal review pass, for the same reason.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Would Tell You to Build First
&lt;/h2&gt;

&lt;p&gt;If you are standing up something similar — for tires, HVAC, roofing, agriculture, anything where weather drives maintenance urgency — the ordering that would have saved me a month:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Archive forecasts from day one&lt;/strong&gt;, even before you have rules. The observation history is free from GeoMet forever; the forecast history only exists if you save it, and every serious evaluation question needs both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the normalized schema and the labelling workflow before the rules.&lt;/strong&gt; Rules are an afternoon each once the data is typed and the ground truth exists; they are endless if every rule is also a parser and its own judge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make hysteresis shared infrastructure&lt;/strong&gt;, not per-rule cleverness. The four controls above cover every flapping pattern I have met.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wire the audit trail before the first real recipient.&lt;/strong&gt; Retrofitting provenance is miserable; recording it from the start is one table.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The pipeline is a few thousand lines of Python, one PostgreSQL database, and a handful of static config tables. Nothing in it is architecturally exotic. Its value comes from the specificity of the domain rules — the 7°C compound crossover, the 1 PSI per 5-6°C derivation at Calgary's altitude, the arm/disarm gaps sized to chinook behaviour — and from the discipline of measuring the rules against seasons that actually happened. Weather data is abundant and free; restraint is the part you have to build yourself.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;KMJ Tire is an independent Calgary tire and oil-change business. Everything above describes internal tooling; thresholds and results are design examples from our configuration and replays, not universal constants. The public-facing side of the winter-timing rule lives on our &lt;a href="https://calgaryrimandtire.ca/seasonal-tire-changes" rel="noopener noreferrer"&gt;seasonal changeover page&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>automotive</category>
      <category>calgary</category>
      <category>maintenance</category>
      <category>tires</category>
    </item>
    <item>
      <title>Modeling Seasonal Tire-Changeover Demand: Queueing Math and Slot-Scheduler Design for a Calgary Tire Shop</title>
      <dc:creator>KMJ Tire Calgary</dc:creator>
      <pubDate>Sat, 08 Aug 2026 16:10:44 +0000</pubDate>
      <link>https://dev.to/kmjtirecalgary/modeling-seasonal-tire-changeover-demand-queueing-math-and-slot-scheduler-design-for-a-calgary-2c7a</link>
      <guid>https://dev.to/kmjtirecalgary/modeling-seasonal-tire-changeover-demand-queueing-math-and-slot-scheduler-design-for-a-calgary-2c7a</guid>
      <description>&lt;h1&gt;
  
  
  Modeling Seasonal Tire-Changeover Demand: Queueing Math and Slot-Scheduler Design for a Calgary Tire Shop
&lt;/h1&gt;

&lt;p&gt;Twice a year, a tire counter in Calgary experiences something that most service businesses never see: demand that is gated not by a date, but by a weather forecast. When Environment Canada's seven-day outlook shows the first hard freeze of the fall, request volume for &lt;a href="https://calgaryrimandtire.ca/seasonal-tire-changes" rel="noopener noreferrer"&gt;seasonal tire changeovers&lt;/a&gt; can triple within forty-eight hours. When the first real snowfall actually lands, it triples again. The spring side is gentler but still lumpy: a string of warm days pulls everyone in at once to get winter rubber off before it wears down on hot pavement.&lt;/p&gt;

&lt;p&gt;This article is a design study. It walks through how you would model that demand mathematically, sanity-check capacity with queueing theory, replace the closed-form math with a discrete-event simulation when the assumptions break, and then design the slot-scheduling data model that turns the analysis into software. The operational grounding is real — KMJ Tire is a Calgary operation that does tire work and oil changes, and the seasonal spike described here is the actual shape of its year — but the system described is a design exercise, not a description of live production infrastructure. All numeric figures in the worked examples are illustrative and labelled as such.&lt;/p&gt;

&lt;p&gt;One scoping note before the math: the service catalog here is deliberately narrow. Tire changeovers, mounting and balancing, &lt;a href="https://calgaryrimandtire.ca/tire-repair-in-calgary" rel="noopener noreferrer"&gt;flat repairs&lt;/a&gt;, and oil changes. No alignments, no brakes, no diagnostics. That narrowness is a modeling gift. A general repair garage has service times ranging from twenty minutes to three days, which makes capacity planning a nightmare. A tire-and-oil operation has maybe five job types, each with a tight, estimable duration distribution. If you ever get to choose a domain for your first scheduling system, choose one like this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Changeover Demand Is Not a Calendar Event
&lt;/h2&gt;

&lt;p&gt;A naive scheduler assumes demand follows the calendar: busy in late October, busy in late April, quiet otherwise. Calgary breaks that assumption in both directions.&lt;/p&gt;

&lt;p&gt;First, the trigger is meteorological. The fall rush does not start on October 15th; it starts when the forecast says overnight lows will hold below zero, and it goes vertical the morning after the first snowfall that sticks. In some years that is October 2nd. In others it is November 9th. A model keyed to calendar dates will be five weeks wrong in either direction, which at peak volume means either an empty schedule grid or a two-week backlog.&lt;/p&gt;

&lt;p&gt;Second, chinooks generate false starts. A chinook arch can take the city from minus fifteen to plus twelve in a day. Early-season cold snaps followed by two warm weeks produce a demand spike, then a dead zone, then a second spike when winter arrives for real. Drivers who held out through the first snap because "it'll melt" — and it did — show up in the second wave with more urgency. Any intensity model needs to handle multiple partial triggers per season, not one clean step function.&lt;/p&gt;

&lt;p&gt;Third, the two seasons are asymmetric, and this matters more than most people expect. The fall changeover is deadline-driven: there is a morning when the roads are white and every all-season tire in the city becomes a liability at once. The spring changeover has no deadline. Winter tires on dry warm pavement wear faster and cost the owner money slowly, but nobody slides into an intersection because they left them on until June. Fall demand is therefore a sharp impulse with a heavy leading edge; spring demand is a wide, flat-topped hump spread over five or six weeks. A model trained on one and applied to the other will fail quietly, which is worse than failing loudly.&lt;/p&gt;

&lt;p&gt;Fourth, product mix shifts the load. Customers running &lt;a href="https://calgaryrimandtire.ca/all-weather-tires-calgary" rel="noopener noreferrer"&gt;all-weather tires&lt;/a&gt; opt out of the changeover cycle entirely — that is much of their appeal — while &lt;a href="https://calgaryrimandtire.ca/winter-tires-calgary" rel="noopener noreferrer"&gt;dedicated winter tire&lt;/a&gt; owners are locked into two visits a year. As the all-weather share of the local fleet grows, the seasonal amplitude damps. Your demand model has a slow secular trend underneath the annual cycle, and if you fit only on last year you will miss it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demand as a Non-Homogeneous Poisson Process
&lt;/h2&gt;

&lt;p&gt;The standard formalism for arrival streams whose rate varies over time is the non-homogeneous Poisson process (NHPP). Requests arrive independently, but the arrival intensity λ(t) is a function of time rather than a constant. For our problem, λ(t) is a function of time &lt;em&gt;and&lt;/em&gt; weather state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;λ(t) = λ_base(t) · m_weather(F_t) · m_backlog(B_t)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;λ_base(t)&lt;/code&gt; is the baseline seasonal-and-weekly pattern: day-of-week effects (Saturdays run hot, Tuesdays run cold), plus a smooth annual curve.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;m_weather(F_t)&lt;/code&gt; is a multiplier driven by the forecast feature vector &lt;code&gt;F_t&lt;/code&gt; — things like minimum forecast temperature over the next 7 days, probability of accumulating snowfall, and days since the first freeze of the season.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;m_backlog(B_t)&lt;/code&gt; captures reflexive demand: when the wait to the next open slot stretches past a few days, some fraction of would-be requesters defer or go elsewhere, which suppresses observed arrivals. Ignoring this term makes you overestimate peak intensity from historical data, because the historical peaks were already demand-suppressed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The weather multiplier is where the Calgary-specific behaviour lives. A reasonable functional form is a saturating response to a "trigger score":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ForecastFeatures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;min_temp_7d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;        &lt;span class="c1"&gt;# coldest forecast overnight low, next 7 days (Celsius)
&lt;/span&gt;    &lt;span class="n"&gt;snow_prob_72h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;      &lt;span class="c1"&gt;# probability of accumulating snow within 72h, 0..1
&lt;/span&gt;    &lt;span class="n"&gt;days_since_first_freeze&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# None if it has not happened yet
&lt;/span&gt;    &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;               &lt;span class="c1"&gt;# "fall" or "spring"
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trigger_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ForecastFeatures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Scalar in [0, ~3] summarizing how strongly weather is pushing demand.

    Coefficients below are illustrative placeholders, not fitted values.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;season&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fall&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Cold forecast pulls demand forward even before snow falls.
&lt;/span&gt;        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_temp_7d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# ramps as lows drop below +2C
&lt;/span&gt;        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;1.6&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snow_prob_72h&lt;/span&gt;                       &lt;span class="c1"&gt;# imminent snow dominates
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;days_since_first_freeze&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Urgency decays ~2 weeks after the first freeze as the herd clears.
&lt;/span&gt;            &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;days_since_first_freeze&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;9.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# spring
&lt;/span&gt;        &lt;span class="c1"&gt;# Sustained warmth, not a single warm day, drives spring swaps.
&lt;/span&gt;        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_temp_7d&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;weather_multiplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ForecastFeatures&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;4.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Map trigger score to a demand multiplier with a saturating ceiling.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;trigger_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cap&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two design choices deserve defense. The exponential saturation reflects a hard truth: demand cannot multiply forever, because the population of vehicles needing a swap is finite. Every changeover performed depletes the pool. A proper treatment would model the susceptible population explicitly — an SIR-flavoured depletion model, where "infection" is the first snowfall — but the saturating multiplier gets you most of the way with far less machinery.&lt;/p&gt;

&lt;p&gt;The decay term on &lt;code&gt;days_since_first_freeze&lt;/code&gt; encodes the herd-clearing effect. Ten days after the first snow, most of the panicked demand has either been served or has given up for the moment. Historical intensity plots (in this shop's records and, anecdotally, at every tire counter in the city) show a spike with roughly exponential relaxation, punctuated by secondary spikes on each subsequent snowfall.&lt;/p&gt;

&lt;p&gt;To generate synthetic arrivals from an NHPP for simulation, thinning (Lewis and Shedler's method) is the standard tool: simulate a homogeneous process at the maximum rate, then keep each arrival with probability λ(t)/λ_max.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;nhpp_arrivals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lam_max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Thinning: yield arrival times in [0, t_end) for intensity function lam(t).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expovariate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lam_max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;t_end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;lam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;lam_max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fitting λ(t) from historical data is a Poisson regression: count requests per day, regress on the weather features and day-of-week dummies with a log link. Even a plain GLM does respectably here because the feature set is small and physically motivated. Resist the urge to reach for a gradient-boosted model with forty features; you have at most a dozen seasonal transitions in your entire dataset, and flexibility without data is just a curve-fitting liability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service Times: Two Distributions, Not One
&lt;/h2&gt;

&lt;p&gt;The second half of any queueing model is the service process, and here the job-type split is everything. A changeover comes in two fundamentally different flavours:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Swap on rims.&lt;/strong&gt; The customer owns two full sets of mounted wheels. The job is: lift, off, on, torque, set pressures, verify TPMS. Fast, predictable, low variance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mount and balance.&lt;/strong&gt; One set of rims, two sets of rubber. Each tire comes off its rim and the seasonal tire goes on, followed by &lt;a href="https://calgaryrimandtire.ca/wheel-balancing" rel="noopener noreferrer"&gt;balancing on the spinner&lt;/a&gt;. Slower, and with a longer tail: corroded bead seats, stubborn TPMS relearn procedures, and the occasional stripped-thread surprise on the lug hardware all live in that tail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oil changes ride along as a third job type — shorter still, and usefully counter-cyclical, since they can backfill quiet slots in the off-season without competing hard for peak-season bay time.&lt;/p&gt;

&lt;p&gt;Illustrative distribution parameters, stated as such (these are plausible for a shop of this type, not measured production values):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job type&lt;/th&gt;
&lt;th&gt;Distribution&lt;/th&gt;
&lt;th&gt;Median&lt;/th&gt;
&lt;th&gt;p90&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Swap on rims&lt;/td&gt;
&lt;td&gt;Lognormal(μ=ln 32, σ=0.22)&lt;/td&gt;
&lt;td&gt;32 min&lt;/td&gt;
&lt;td&gt;~42 min&lt;/td&gt;
&lt;td&gt;Includes vehicle in/out and torque check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mount + balance (4 tires)&lt;/td&gt;
&lt;td&gt;Lognormal(μ=ln 62, σ=0.35)&lt;/td&gt;
&lt;td&gt;62 min&lt;/td&gt;
&lt;td&gt;~97 min&lt;/td&gt;
&lt;td&gt;Tail driven by bead corrosion, TPMS relearn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oil change&lt;/td&gt;
&lt;td&gt;Lognormal(μ=ln 24, σ=0.18)&lt;/td&gt;
&lt;td&gt;24 min&lt;/td&gt;
&lt;td&gt;~30 min&lt;/td&gt;
&lt;td&gt;Filler work between peak jobs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why lognormal rather than exponential? Because the exponential assumption — memoryless service, mode at zero — is flatly wrong for physical work. Nobody completes a four-wheel swap in ninety seconds, and the empirical histograms of hands-on automotive tasks are right-skewed with a hump well away from zero. Lognormal (or gamma) fits that shape. The distinction matters practically: the M/M/c formulas assume exponential service, and using them with lognormal reality means your closed-form answers are approximations from the start. They are still worth computing — as a sanity check, not as a forecast.&lt;/p&gt;

&lt;p&gt;Estimating these distributions is the easiest data problem in the whole system, provided your point-of-sale or work-order timestamps are trustworthy. Take start/finish pairs per job type, filter out the obviously-broken records (the four-second mount/balance means someone closed the ticket late), and fit by maximum likelihood on the log of duration. Fifty observations per job type gives you a serviceable estimate; a full season gives you a good one. The one trap: peak-season service times are &lt;em&gt;longer&lt;/em&gt; than off-season ones for the same job type, because techs are interrupted more, staging space is congested, and the vehicles arriving are disproportionately the annoying ones. Fit peak and off-peak separately or include a load covariate.&lt;/p&gt;

&lt;h2&gt;
  
  
  An M/M/c Sanity Check, and Where It Lies to You
&lt;/h2&gt;

&lt;p&gt;With arrival and service rates in hand, the classical move is Erlang-C: model the operation as an M/M/c queue with c parallel servers (bays with a tech), Poisson arrivals at rate λ, exponential service at rate μ per server, and compute the probability an arriving job waits, plus the expected wait.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;factorial&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;erlang_c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offered_load&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;P(wait &amp;gt; 0) for M/M/c with offered load a = lambda/mu erlangs.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;offered_load&lt;/span&gt;
    &lt;span class="n"&gt;rho&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rho&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;  &lt;span class="c1"&gt;# unstable: queue grows without bound
&lt;/span&gt;    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;denom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rho&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;p_wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rho&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;denom&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p_wait&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mean_wait_hours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lam&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lam&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;mu&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;erlang_c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;mu&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the part practitioners underestimate. Utilization of 85–90% &lt;em&gt;sounds&lt;/em&gt; healthy — it is what a spreadsheet-driven manager will target — but in a stochastic system it is already deep into the pain zone, because waits blow up hyperbolically as ρ approaches 1. We will put concrete numbers on that in a moment.&lt;/p&gt;

&lt;p&gt;Where the model lies, and why you should not stop here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Non-stationarity.&lt;/strong&gt; M/M/c describes steady state. A changeover surge is a transient — the system never settles. Steady-state formulas applied to a two-week impulse answer a question nobody asked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong service distribution.&lt;/strong&gt; Exponential service understates the probability of medium-length jobs and overstates very short ones. M/M/c wait estimates are optimistic when reality is lognormal with our σ values (the coefficient of variation is below 1, which actually helps — but the tail events hurt in ways the mean does not capture).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heterogeneous jobs and resources.&lt;/strong&gt; A bay with a tire machine and spinner can do everything; a flat stall can only do swaps and oil. That is not "c identical servers."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled arrivals are not Poisson.&lt;/strong&gt; The entire point of a reservation system is to &lt;em&gt;destroy&lt;/em&gt; the Poisson property — to replace random arrivals with a controlled admission process. M/M/c models the walk-in-only world you are trying to escape.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So use Erlang-C the way you use a back-of-envelope structural calculation: to determine whether the design is off by a factor of three, before you spend a week building the simulation that answers the real question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked Example: Four Bays Against a Freeze Warning
&lt;/h2&gt;

&lt;p&gt;Everything in this section is an illustrative calculation with invented-but-plausible inputs, labelled as such. It exists to show the method, not to publish operating data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup.&lt;/strong&gt; Four equivalent bays, nine working hours per day. Job mix during the fall surge: 60% swap-on-rims, 40% mount-and-balance. Using the medians above as means (close enough for this level), the blended mean service time is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;E[S] = 0.6 × 32 min + 0.4 × 62 min = 19.2 + 24.8 = 44 min ≈ 0.733 h
μ ≈ 1.36 jobs per bay-hour
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Scenario A — a moderately busy shoulder day.&lt;/strong&gt; Suppose requests arrive at λ = 4.5 per hour. Offered load a = λ/μ = 3.31 erlangs across c = 4 bays, so utilization ρ = 0.83. Plugging into Erlang-C:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P(wait) ≈ 0.66
Wq = P(wait) / (cμ − λ) ≈ 0.66 / (5.44 − 4.50) ≈ 0.70 h ≈ 42 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that again: at 83% utilization — a number most planning spreadsheets would color green — two-thirds of arriving vehicles wait, and the average wait is roughly three-quarters of a service time. Push λ to 5.1 per hour (ρ = 0.94) and mean wait quadruples to nearly three hours. That hyperbolic blowup near saturation is the single most important intuition queueing theory offers, and it is exactly why "we'll just work faster during the rush" is not a plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario B — the morning after the first snowfall.&lt;/strong&gt; Historical pattern (again, stylized): request volume equivalent to 110 changeovers in one day. Daily throughput ceiling with four bays is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4 bays × 9 h × 1.36 jobs/bay-hour ≈ 49 jobs/day
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Demand is 2.2× capacity. No queueing formula rescues this; the system is simply oversubscribed, and the interesting quantity stops being "wait in the lobby" and becomes "days until the next open slot." The 61 unserved requests roll forward, meet the next day's fresh arrivals, and the backlog integrates upward until either intensity decays (the herd-clearing effect) or capacity rises (surge policy — extended hours, overflow days, or diverting swap-on-rims jobs to a &lt;a href="https://calgaryrimandtire.ca/mobile-tire-service" rel="noopener noreferrer"&gt;mobile service unit&lt;/a&gt; that handles them in customer driveways and office parking lots).&lt;/p&gt;

&lt;p&gt;This is the pivotal reframing for the whole design: &lt;strong&gt;during the surge, the queue does not live in the waiting room; it lives in the schedule.&lt;/strong&gt; The engineering objective is not minimizing lobby wait — it is shaping the multi-day backlog: keeping p90 time-to-next-open-slot bounded, protecting emergency capacity, and making sure the reservation grid, not a phone-line free-for-all, absorbs the shock.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discrete-Event Simulation When the Formulas Run Out
&lt;/h2&gt;

&lt;p&gt;Once you accept non-stationary arrivals, lognormal services, heterogeneous bays, reservations, no-shows, and walk-ins, closed forms are gone. A discrete-event simulation (DES) handles all of it and stays small enough to read in one sitting. The core is a priority queue of timestamped events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heapq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChangeoverSim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Minimal DES: reserved arrivals + walk-ins competing for bays.

    All parameters illustrative. Times in hours from day start.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_bays&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day_len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;9.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n_bays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n_bays&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;day_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;day_len&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;            &lt;span class="c1"&gt;# (time, seq, kind, payload)
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free_bays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n_bays&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;             &lt;span class="c1"&gt;# waiting jobs (on-site)
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;served&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;waits&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;balked&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_seq&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;heapq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;heappush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;service_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job_type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;med&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;swap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.22&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                      &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                      &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oil&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.18&lt;/span&gt;&lt;span class="p"&gt;)}[&lt;/span&gt;&lt;span class="n"&gt;job_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lognormvariate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;med&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;arrive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free_bays&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;walkin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;balked&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;        &lt;span class="c1"&gt;# walk-in sees a full lobby, leaves
&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t_arr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free_bays&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;waits&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t_arr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;service_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;heapq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;heappop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arrive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free_bays&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;served&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feed it two arrival streams: the reserved stream (slot times, each realized with probability &lt;code&gt;1 − p_noshow&lt;/code&gt;, plus a lateness jitter of a few minutes either way) and the walk-in stream (NHPP via the thinning generator above, with intensity spiking on snowfall mornings). Run a few hundred replications per scenario and look at distributions, not means — the p90 lobby wait and the count of jobs still unfinished at closing time are the numbers that decide staffing arguments.&lt;/p&gt;

&lt;p&gt;What the simulation buys you over the algebra, concretely: you can ask &lt;em&gt;policy&lt;/em&gt; questions. What happens to overtime hours if we admit walk-ins only into dedicated walk-in slots? How much does a 15-minute buffer after every third mount-and-balance reduce end-of-day overruns? Is one extended-hours evening worth more than a Sunday overflow day? Each of those is a one-line configuration change in the sim and an unanswerable question in Erlang-land.&lt;/p&gt;

&lt;p&gt;Two implementation notes from the trenches. Seed your random generators explicitly and log the seed with every run, or you will chase phantom regressions between "identical" scenarios. And validate the sim against the M/M/c case first: set exponential services, constant λ, homogeneous bays, and confirm the simulated waits match Erlang-C within confidence bounds. A DES that cannot reproduce the textbook case has a bug you will otherwise discover much later, in an argument, with money on the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing the Slot Data Model
&lt;/h2&gt;

&lt;p&gt;Now the software half. The reservation grid is the mechanism that converts stochastic chaos into scheduled work, and its data model determines what policies you can even express. The entities that have earned their place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Job types.&lt;/strong&gt; Each with a planning duration (not the mean — typically the p75 of the fitted distribution, so that half your slots do not run over), a required-equipment flag set, and a seasonal-relevance tag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resources.&lt;/strong&gt; Bays and techs, modelled separately. A bay without a tire-machine-certified tech is storage; a tech without a bay is idle. Capacity at any instant is the min of qualified-tech count and equipped-bay count for a given job type. Small operations can collapse this to "staffed bay" as a single resource and split it later; the schema below leaves room.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slot templates.&lt;/strong&gt; The pattern that generates concrete slots: "weekdays, bay 1–3, 45-minute grid from 08:00 to 17:00; Saturday adds bay 4." Templates carry an effective-date range, which is exactly the hook that surge policy will use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slots.&lt;/strong&gt; Materialized rows, generated from templates out to a rolling horizon (three weeks is a comfortable default). Materializing — rather than computing availability on the fly — makes the availability query trivial, makes capacity auditable ("show me every slot we offered in week 44"), and gives overbooking a concrete object to attach to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reservations.&lt;/strong&gt; A customer request pinned to a slot, carrying job type, vehicle info, status lifecycle (&lt;code&gt;held → confirmed → arrived → in_service → completed | no_show | cancelled&lt;/code&gt;), and the channel it came through (&lt;a href="https://calgaryrimandtire.ca/online-bookings" rel="noopener noreferrer"&gt;the online reservation page&lt;/a&gt;, phone, or counter).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Walk-in log.&lt;/strong&gt; Every walk-in, served or not. The unserved ones are your only direct evidence of demand exceeding supply — treasure them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The buffer policy deserves its own paragraph because it is the most commonly botched piece. If planning durations are honest p75s, roughly one job in four still overruns its slot. Without buffers, overruns cascade: by 3 p.m. every bay is running forty minutes late and the lobby is hostile. The fix is structural slack — either a buffer minute-count appended per slot, or (cleaner) sacrificial catch-up slots: one slot per bay per half-day that is never offered for sale and exists to absorb accumulated drift. Which is better depends on your overrun distribution; the sim answers it in an afternoon.&lt;/p&gt;

&lt;p&gt;Overbooking is the same idea mirrored. With a no-show probability of, illustratively, 8–12% on freeze-week days (people's plans change fast when a chinook rolls in and the snow melts), selling exactly one reservation per slot wastes a tenth of your scarcest capacity. Deliberately over-admitting into designated overbookable slots — at a ratio derived from the no-show rate, airline style but far lower stakes — recovers it. Critically, both buffers and overbooking ratios must live in &lt;em&gt;data&lt;/em&gt;, not code, because you will tune them seasonally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schema Sketches
&lt;/h2&gt;

&lt;p&gt;PostgreSQL flavour. Abbreviated to the load-bearing columns; production versions would add tenancy, audit, and soft-delete concerns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;job_types&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;            &lt;span class="n"&gt;SMALLSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;code&lt;/span&gt;          &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- 'swap_rims' | 'mount_balance' | 'oil_change'&lt;/span&gt;
    &lt;span class="n"&gt;plan_minutes&lt;/span&gt;  &lt;span class="nb"&gt;SMALLINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;-- p75 of fitted duration, reviewed seasonally&lt;/span&gt;
    &lt;span class="n"&gt;needs_mounter&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;seasonal&lt;/span&gt;      &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;         &lt;span class="n"&gt;SMALLSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt;       &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bay'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'tech'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;label&lt;/span&gt;      &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;capability&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;   &lt;span class="c1"&gt;-- {"mounter": true, "hoist": "10k"}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;slot_templates&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;            &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;          &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;-- 'weekday_standard', 'surge_evening'&lt;/span&gt;
    &lt;span class="n"&gt;dow_mask&lt;/span&gt;      &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="c1"&gt;-- bitmask Mon..Sun&lt;/span&gt;
    &lt;span class="n"&gt;start_local&lt;/span&gt;   &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;end_local&lt;/span&gt;     &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;grid_minutes&lt;/span&gt;  &lt;span class="nb"&gt;SMALLINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bay_id&lt;/span&gt;        &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;effective&lt;/span&gt;     &lt;span class="n"&gt;DATERANGE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;-- surge policy activates/retires templates&lt;/span&gt;
    &lt;span class="n"&gt;overbook_pct&lt;/span&gt;  &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;buffer_min&lt;/span&gt;    &lt;span class="nb"&gt;SMALLINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;slots&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;          &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;template_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;slot_templates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;bay_id&lt;/span&gt;      &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;        &lt;span class="n"&gt;TSTZRANGE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sellable&lt;/span&gt;    &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;-- false for catch-up/buffer slots&lt;/span&gt;
    &lt;span class="n"&gt;max_admits&lt;/span&gt;  &lt;span class="nb"&gt;SMALLINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;-- &amp;gt;1 only where overbooking applies&lt;/span&gt;
    &lt;span class="n"&gt;EXCLUDE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;gist&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bay_id&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;-- no overlapping slots per bay&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;reservations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;           &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;slot_id&lt;/span&gt;      &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;job_type_id&lt;/span&gt;  &lt;span class="nb"&gt;SMALLINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;job_types&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;vehicle_ref&lt;/span&gt;  &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;channel&lt;/span&gt;      &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'web'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'phone'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'counter'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;       &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'held'&lt;/span&gt;
                 &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'held'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'confirmed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'arrived'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'in_service'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                   &lt;span class="s1"&gt;'completed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'no_show'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'cancelled'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt;   &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;status_at&lt;/span&gt;    &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;walkin_log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;          &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;seen_at&lt;/span&gt;     &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;job_type_id&lt;/span&gt; &lt;span class="nb"&gt;SMALLINT&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;job_types&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;outcome&lt;/span&gt;     &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'served'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'deferred_to_slot'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'left'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;est_wait_quoted_min&lt;/span&gt; &lt;span class="nb"&gt;SMALLINT&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few deliberate choices worth defending:&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;EXCLUDE USING gist&lt;/code&gt; constraint on &lt;code&gt;slots&lt;/code&gt; is PostgreSQL doing your concurrency control for you: two overlapping spans on the same bay cannot both commit, no matter how badly your slot-generation job misbehaves. Range types plus exclusion constraints are the single most underused feature in scheduling systems built on Postgres.&lt;/p&gt;

&lt;p&gt;Admission control belongs in one place. The invariant "count of active reservations on a slot ≤ &lt;code&gt;max_admits&lt;/code&gt;" is enforced in a single transactional path — a serializable transaction or an advisory lock keyed on &lt;code&gt;slot_id&lt;/code&gt; — never sprinkled across application layers. Every double-admission bug in every scheduling system traces back to someone checking availability in one query and inserting in another without isolation.&lt;/p&gt;

&lt;p&gt;Status history matters more than current status. The sketch shows only &lt;code&gt;status_at&lt;/code&gt;; a production build appends every transition to an event table instead. The reason is the analytics, not the operations: no-show modelling &lt;em&gt;requires&lt;/em&gt; knowing when the no-show became apparent, and demand modelling requires &lt;code&gt;created_at&lt;/code&gt; (when did they ask?) separated from slot time (when did we serve them?). Lead-time distribution — the gap between the two — is itself a surge signal: it collapses toward zero right before the crunch, as urgency rises.&lt;/p&gt;

&lt;h2&gt;
  
  
  No-Shows and Walk-Ins Are Inputs, Not Noise
&lt;/h2&gt;

&lt;p&gt;It is tempting to treat no-shows and walk-ins as operational irritants to be minimized. In the model they are first-class stochastic inputs, and each carries information.&lt;/p&gt;

&lt;p&gt;No-shows during freeze week have a distinctive cause: weather whiplash. A driver reserves in a panic on Tuesday's forecast; Thursday a chinook wipes the snow out; Friday's slot quietly goes unused. This means the no-show probability is itself weather-conditional — rising when the trigger that generated the reservation reverses. A two-variable logistic model (lead time at creation, temperature delta between creation day and slot day) captures most of it. The practical payoff: on forecast-reversal days you can raise the overbooking ratio on overbookable slots with quantified confidence rather than a shrug, and release predicted-dead capacity back to the waitlist a day early.&lt;/p&gt;

&lt;p&gt;Walk-ins are the inverse problem: unscheduled supply-seeking. The morning after the first snowfall, some fraction of the city skips the reservation system entirely and drives over. A slot grid with zero walk-in allowance turns those visits into pure friction — every one interrupts the counter, and most leave unserved. A grid that holds back a small ration of same-day capacity (two or three sellable-but-unreleased slots per day, released each morning) converts walk-in pressure into scheduled work a few hours later. The &lt;code&gt;walkin_log&lt;/code&gt; table exists to size that ration: &lt;code&gt;outcome = 'left'&lt;/code&gt; rows are censored demand, the people the schedule failed. During surge analysis they get added back into the intensity estimate, because the reservation stream alone systematically undercounts true demand at exactly the moments you most need to know it. The same censoring logic applies to &lt;a href="https://calgaryrimandtire.ca/commercial-tire-services" rel="noopener noreferrer"&gt;commercial and fleet work&lt;/a&gt;, where a fleet coordinator who cannot get five vans in this week may quietly move all five elsewhere — high-value demand that vanishes without a trace unless logged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Surge Policy Is Configuration, Not Code
&lt;/h2&gt;

&lt;p&gt;The worst version of surge handling is a developer editing constants the night after the first snowfall. The whole point of the template/effective-range design is that surge response is a &lt;em&gt;data change&lt;/em&gt;: activate prebuilt templates, adjust ratios, done.&lt;/p&gt;

&lt;p&gt;A surge configuration might look like this (structure illustrative):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;surge_tiers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;watch&lt;/span&gt;          &lt;span class="c1"&gt;# freeze forecast inside 7 days, no snow yet&lt;/span&gt;
    &lt;span class="na"&gt;activate_templates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
    &lt;span class="na"&gt;overbook_pct&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;
    &lt;span class="na"&gt;walkin_holdback_per_day&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="na"&gt;demand_multiplier_floor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.4&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;freeze_rush&lt;/span&gt;    &lt;span class="c1"&gt;# first freeze confirmed or snowfall prob &amp;gt; 60% in 72h&lt;/span&gt;
    &lt;span class="na"&gt;activate_templates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;surge_evening&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;       &lt;span class="c1"&gt;# weekday 17:00-20:00 grid&lt;/span&gt;
    &lt;span class="na"&gt;overbook_pct&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;
    &lt;span class="na"&gt;walkin_holdback_per_day&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
    &lt;span class="na"&gt;max_mount_balance_share&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.45&lt;/span&gt;             &lt;span class="c1"&gt;# protect throughput with faster jobs&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;snowfall_peak&lt;/span&gt;  &lt;span class="c1"&gt;# accumulating snow on the ground&lt;/span&gt;
    &lt;span class="na"&gt;activate_templates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;surge_evening&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;overflow_sunday&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;overbook_pct&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;
    &lt;span class="na"&gt;walkin_holdback_per_day&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;
    &lt;span class="na"&gt;defer_nonseasonal_jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;              &lt;span class="c1"&gt;# oil changes offered next-week slots&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;max_mount_balance_share&lt;/code&gt; knob. During the crunch, throughput is king, and swap-on-rims jobs move nearly twice as many vehicles per bay-hour as mount-and-balance jobs. Biasing the sellable mix toward swaps — while routing mount-and-balance requests a few days out — raises vehicles-served-per-day exactly when that metric matters most. That is a policy decision with real trade-offs (mount-and-balance customers wait longer), which is precisely why it should sit in reviewable configuration with an audit trail, not in an &lt;code&gt;if&lt;/code&gt; statement someone forgets.&lt;/p&gt;

&lt;p&gt;Tier transitions should be proposed automatically (the weather-feature pipeline below emits a recommended tier each morning) but &lt;em&gt;applied&lt;/em&gt; by a human. Full automation of surge policy is how you end up with an evening shift activated by a botched forecast parse. The human is cheap; the false activation is not.&lt;/p&gt;

&lt;p&gt;Geography enters here too. Surge templates for the mobile unit look different from bay templates — drive time between stops is the dominant cost, so its "slots" are route-clustered by quadrant rather than gridded by the hour, and the &lt;a href="https://calgaryrimandtire.ca/service-areas" rel="noopener noreferrer"&gt;service-area definition&lt;/a&gt; effectively becomes part of capacity policy: tightening the served radius during peak week is another lever that trades coverage for throughput.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weather Features Feeding the Demand Multiplier
&lt;/h2&gt;

&lt;p&gt;The forecasting layer can stay almost embarrassingly simple. Inputs, refreshed each morning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;7-day forecast: daily highs, lows, snowfall probability and expected accumulation.&lt;/li&gt;
&lt;li&gt;Season state: days since first freeze (fall) or days of sustained warmth (spring).&lt;/li&gt;
&lt;li&gt;Trailing demand: requests per day over the last 14 days, which anchors &lt;code&gt;λ_base&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From these, compute tomorrow-through-day-7 expected intensity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;daily_intensity_forecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_by_dow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;features_by_day&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return list of (date, expected_requests) for the next 7 days.

    base_by_dow: fitted baseline requests per day-of-week (off-season anchor)
    features_by_day: list of (date, ForecastFeatures)
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;feats&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;features_by_day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;lam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_by_dow&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;weather_multiplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lam&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compare forecast intensity against materialized sellable capacity per day, and emit three numbers to whoever runs the counter: expected requests, open slots, and projected p90 days-to-next-slot if the forecast verifies. That last number is the early-warning siren. When it crosses a threshold (say, four days — illustrative), the system recommends the next surge tier, and the humans decide.&lt;/p&gt;

&lt;p&gt;A note on data sourcing discipline: forecast features must be &lt;em&gt;snapshotted&lt;/em&gt; at prediction time. If you later backfill weather data with what actually happened, your model evaluation silently upgrades from "what did the forecast know" to "what did the atmosphere do," and every skill metric you compute becomes a lie. Store the forecast as-of each morning, forever. Disk is cheap; unreproducible evaluations are not.&lt;/p&gt;

&lt;p&gt;The educational content angle deserves one sentence: demand shaping is also possible upstream. Content that helps drivers decide &lt;em&gt;earlier&lt;/em&gt; — like the explainer material in the &lt;a href="https://calgaryrimandtire.ca/be-tire-smart" rel="noopener noreferrer"&gt;Be Tire Smart pages&lt;/a&gt; or a plain-language piece on &lt;a href="https://calgaryrimandtire.ca/tire-sidewall-information" rel="noopener noreferrer"&gt;reading sidewall markings&lt;/a&gt; — flattens the spike slightly by converting day-after-snowfall panic into week-before-freeze planning. The effect is modest, but it is the only lever that adds capacity by &lt;em&gt;moving demand&lt;/em&gt; instead of moving staff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metrics That Decide Arguments
&lt;/h2&gt;

&lt;p&gt;Instrument the system around a small set of numbers, each answering a specific operational question:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;th&gt;Question it answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;p90 time-to-next-slot&lt;/td&gt;
&lt;td&gt;90th percentile, per job type, of (earliest open sellable slot − now)&lt;/td&gt;
&lt;td&gt;Are we absorbing demand or falling behind?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bay utilization (busy)&lt;/td&gt;
&lt;td&gt;In-service minutes ÷ staffed-bay minutes&lt;/td&gt;
&lt;td&gt;Are we paying for idle capacity?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bay utilization (committed)&lt;/td&gt;
&lt;td&gt;(In-service + reserved-future) ÷ sellable&lt;/td&gt;
&lt;td&gt;How full is the forward grid?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overrun rate&lt;/td&gt;
&lt;td&gt;Share of jobs exceeding plan_minutes&lt;/td&gt;
&lt;td&gt;Are planning durations still honest?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No-show rate (weather-split)&lt;/td&gt;
&lt;td&gt;No-shows ÷ confirmed, segmented by forecast-reversal flag&lt;/td&gt;
&lt;td&gt;Is the overbooking ratio calibrated?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Walk-in loss&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;walkin_log.outcome = 'left'&lt;/code&gt; per day&lt;/td&gt;
&lt;td&gt;How much demand did we visibly turn away?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reservation lead time&lt;/td&gt;
&lt;td&gt;Median (slot time − created_at)&lt;/td&gt;
&lt;td&gt;Is the surge approaching? (It collapses first.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End-of-day drift&lt;/td&gt;
&lt;td&gt;Actual last-job finish − scheduled last-slot end&lt;/td&gt;
&lt;td&gt;Are buffers sized right?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of these earn special emphasis. The &lt;strong&gt;lead-time collapse&lt;/strong&gt; is the best leading indicator in the whole system: days before request volume spikes, the gap between "when people ask" and "when they want service" shrinks from a week to a day. It is demand urgency made measurable, and it typically fires before the weather multiplier does, because drivers watch forecasts too. And &lt;strong&gt;walk-in loss&lt;/strong&gt; is the only metric on the list measuring what did &lt;em&gt;not&lt;/em&gt; happen — the abandonment that every purely reservation-based dataset is blind to. A dashboard without it will tell you the surge went fine when in fact you shed a triple-digit number of visits to competitors across the city; for a single-location independent among &lt;a href="https://calgaryrimandtire.ca/calgarys-local-tire-shop" rel="noopener noreferrer"&gt;Calgary's local tire operations&lt;/a&gt;, those are exactly the first-time customers whose second visit you never get.&lt;/p&gt;

&lt;p&gt;Resist vanity metrics. Total vehicles served per week is seasonal noise wearing a KPI costume; it mostly measures the weather. Every number on the dashboard should change someone's decision — slot ratios, staffing, surge tier — or it is decoration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls: The Ways This Model Fails
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Overfitting to last season.&lt;/strong&gt; One year of Calgary data contains exactly one fall trigger and one spring thaw, each with its own idiosyncratic timing. A model fit tightly to last October will confidently predict this October wrong. The defense is structural: keep the weather-response function low-dimensional and physically interpretable (cold → more demand, saturation, decay), pool multiple years even if older data is scrappier, and treat every fitted coefficient with the suspicion appropriate to n = 3 seasonal transitions. When forced to choose between a clever model and a dumb robust one, in this domain, choose dumb.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ignoring the fall/spring asymmetry.&lt;/strong&gt; Already discussed, but it recurs at the &lt;em&gt;policy&lt;/em&gt; layer too: fall surge tiers built around throughput protection make no sense in spring, when the problem is a long moderate hump, not an impulse. Spring's failure mode is the opposite — weeks of slightly-elevated demand that never technically triggers a surge tier but accumulates a quiet backlog. Separate tier definitions per season; do not parameterize one set and hope.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trusting duration data collected under load.&lt;/strong&gt; Peak-week service times are contaminated by the peak itself. If you refit &lt;code&gt;plan_minutes&lt;/code&gt; from surge-week data, durations inflate, slots lengthen, sellable capacity drops next season, and you have institutionalized your worst week. Fit from shoulder-season data; validate against peak.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chinook double-triggering.&lt;/strong&gt; A naive trigger fires on the first freeze, decays, then fires again on the next cold snap at full strength — but the second wave draws from a partially depleted population. Without a depletion term (or at minimum a season-to-date served-count discount), you will over-staff the second spike. The population of not-yet-swapped vehicles is finite and every completed changeover shrinks it; by late November the pool is mostly drained regardless of what the thermometer does. Track cumulative seasonal completions as an explicit state variable — it also quietly improves the &lt;a href="https://calgaryrimandtire.ca/fleet-management" rel="noopener noreferrer"&gt;fleet-side planning&lt;/a&gt;, since fleet accounts complete their changeovers in scheduled blocks that deplete pooled demand in visible chunks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Letting the abstraction leak upward.&lt;/strong&gt; The final trap is organizational. Queueing math and simulations produce distributions; managers want single numbers. The moment someone summarizes "p90 next-slot of 3.1 days under the 60%-snowfall scenario" as "we're fine," the modelling effort becomes theatre. Report ranges. Show the bad percentile. The entire value of the stochastic treatment is refusing to pretend the future is a point estimate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Notes From the Bay Floor
&lt;/h2&gt;

&lt;p&gt;Strip away the notation and the design reduces to four moves. Model arrivals as weather-driven and non-stationary, because in this city they are. Use closed-form queueing only to bound the problem, then simulate, because every real complication — lognormal tails, mixed job types, no-shows, walk-ins — breaks the textbook assumptions in ways that matter. Put every policy knob in configuration with an effective-date range, because surge response has to move at forecast speed, not release-cycle speed. And instrument the censored demand — the visits that never happened — because that is where oversubscribed systems hide their failures.&lt;/p&gt;

&lt;p&gt;None of this requires heavy infrastructure. The whole stack sketched here is a Postgres schema, a few hundred lines of Python, a YAML file, and a morning cron job reading a public forecast feed. What it requires instead is respect for the problem's actual structure: a finite, weather-triggered, twice-yearly stampede through a small number of bays, asymmetric between seasons, observed only partially. Get the structure right and modest tools are plenty. Get it wrong and no amount of machinery will save the second week of November — and anyone who has watched the first-snowfall morning from behind a tire counter, phone ringing over the torque wrenches, knows exactly which week that is.&lt;/p&gt;

</description>
      <category>automotive</category>
      <category>calgary</category>
      <category>maintenance</category>
      <category>tires</category>
    </item>
    <item>
      <title>Modelling a Mobile Service Area: Why Postal Codes Lie and Drive-Time Polygons Do Not</title>
      <dc:creator>KMJ Tire Calgary</dc:creator>
      <pubDate>Fri, 07 Aug 2026 16:13:47 +0000</pubDate>
      <link>https://dev.to/kmjtirecalgary/modelling-a-mobile-service-area-why-postal-codes-lie-and-drive-time-polygons-do-not-fjf</link>
      <guid>https://dev.to/kmjtirecalgary/modelling-a-mobile-service-area-why-postal-codes-lie-and-drive-time-polygons-do-not-fjf</guid>
      <description>&lt;h1&gt;
  
  
  Modelling a Mobile Service Area: Why Postal Codes Lie and Drive-Time Polygons Do Not
&lt;/h1&gt;

&lt;p&gt;Every business that sends a vehicle to a customer eventually writes the same function. It takes an address and returns a boolean. Somebody names it &lt;code&gt;is_serviceable&lt;/code&gt;, somebody else names it &lt;code&gt;in_zone&lt;/code&gt;, and the first implementation is almost always a Python set of postal code prefixes copied out of a spreadsheet that a dispatcher maintained by hand.&lt;/p&gt;

&lt;p&gt;I write software for KMJ Tire, a small Calgary operation whose entire service catalogue is tires and oil changes. No mechanical work, no diagnostics, no suspension. That narrowness is a gift for a systems person, because it strips away most of the domain noise and leaves a handful of genuinely hard problems. The hardest one is not inventory and it is not scheduling. It is geography. A van leaves a yard, drives to wherever the vehicle is sitting, and does the work in a driveway or a parking lot. Whether we can profitably reach a given address, and how much of the day that address consumes, is the question the whole &lt;a href="https://calgaryrimandtire.ca/mobile-tire-service" rel="noopener noreferrer"&gt;mobile service operation&lt;/a&gt; rests on.&lt;/p&gt;

&lt;p&gt;This article is about getting that question right. It walks through why two common models — a postal code allowlist and a radius circle — fail in specific, instructive ways; what the correct geometric primitives are; how to pick a coordinate reference system for Alberta and why the default choice is wrong; what an isochrone actually is under the hood; how to test a point against a polygon at scale without lighting your CPU on fire; and how to store all of it so that a decision made in April can still be explained in September. Every number in the worked examples is illustrative and invented for the purpose of the example. None of them are operating statistics.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Question Behind the Question
&lt;/h2&gt;

&lt;p&gt;"Can we serve this address" is two questions wearing one coat.&lt;/p&gt;

&lt;p&gt;The first is a feasibility question: is the location reachable by a service van within the operating constraints we have set? The second is an economics question: what does reaching it cost in van-minutes, and does that cost fit the job we are being asked to do? A twenty-minute tire rotation ninety minutes away is a loss even if the van can physically get there.&lt;/p&gt;

&lt;p&gt;Naive systems answer only the first and pretend the second does not exist. That is how you end up with a dispatcher manually overriding the software every morning, which is the real symptom you are looking for when you audit one of these systems. If humans routinely override the boolean, the boolean is modelling the wrong thing.&lt;/p&gt;

&lt;p&gt;The output we actually want is richer than a bit:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;covered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;Reachable under the active policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zone_version_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;uuid&lt;/td&gt;
&lt;td&gt;Exactly which polygon produced the answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;drive_time_band&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;enum&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;t20&lt;/code&gt;, &lt;code&gt;t35&lt;/code&gt;, &lt;code&gt;t50&lt;/code&gt;, &lt;code&gt;outside&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;confidence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;enum&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;geocoded_exact&lt;/code&gt;, &lt;code&gt;geocoded_interpolated&lt;/code&gt;, &lt;code&gt;unresolved&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;decided_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamptz&lt;/td&gt;
&lt;td&gt;When the evaluation ran&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once the return type looks like that, most of the design follows. You need polygons with identity, you need versions, and you need a record of what was decided and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Postal Codes Are an Addressing Artifact, Not a Geography
&lt;/h2&gt;

&lt;p&gt;A Canadian postal code has the form &lt;code&gt;A1A 1A1&lt;/code&gt;. The first three characters form the Forward Sortation Area — the FSA. The last three form the Local Delivery Unit, the LDU. In &lt;code&gt;T2P 1J9&lt;/code&gt;, &lt;code&gt;T2P&lt;/code&gt; is downtown Calgary and &lt;code&gt;1J9&lt;/code&gt; narrows it to a specific side of a specific block, or a specific large building, or a specific set of post office boxes.&lt;/p&gt;

&lt;p&gt;The second character carries a signal that surprises people: a zero means rural. &lt;code&gt;T0L&lt;/code&gt; is rural southwest Alberta. &lt;code&gt;T2P&lt;/code&gt; is urban. That single digit is the difference between an FSA covering a few dozen city blocks and an FSA covering an area you could lose a small country in.&lt;/p&gt;

&lt;p&gt;Here is the first structural problem. Postal codes are not areas. They are routes. Canada Post assigns them to sequences of delivery points so that a letter carrier or a rural driver can walk or drive them efficiently. The polygon you get when you download an "FSA boundary file" is a derived product — typically built by taking every dissemination block whose addresses carry that FSA and unioning them together. It is a reasonable approximation of where the addresses are. It is not an authoritative statement about where a line sits on the ground, and nobody at Canada Post is maintaining it as one.&lt;/p&gt;

&lt;p&gt;The second problem is straddling. Municipal boundaries and postal routes were drawn by different organizations for different reasons and they were never reconciled. &lt;code&gt;T3Z&lt;/code&gt; covers Springbank, which is in Rocky View County, west of the city limits. &lt;code&gt;T1X&lt;/code&gt; spans Chestermere and reaches into county land around it. &lt;code&gt;T3R&lt;/code&gt; includes both far northwest Calgary and acreage subdivisions well outside the city. If your allowlist contains &lt;code&gt;T3Z&lt;/code&gt;, you have implicitly promised service to properties scattered across a corridor of foothills, some of which are a pleasant twenty-five minutes away and some of which are up a gravel range road that eats forty minutes in each direction.&lt;/p&gt;

&lt;p&gt;The third problem is scale. A rural FSA such as &lt;code&gt;T0M&lt;/code&gt; or &lt;code&gt;T0J&lt;/code&gt; can cover hundreds of square kilometres. Including it in an allowlist is not a coverage decision; it is a coin flip with the geography.&lt;/p&gt;

&lt;p&gt;The fourth problem is time. Calgary builds new communities continuously. When a new neighbourhood goes in, the addresses exist before the postal codes do, and the postal codes exist before anybody updates your spreadsheet. An allowlist written in 2023 is silently wrong about every subdivision occupied since. The failure is invisible: the request arrives, the lookup misses, and the customer is told no. Nothing logs an error, because from the software's perspective nothing went wrong.&lt;/p&gt;

&lt;p&gt;The fifth problem is ownership. Ask who maintains the list. In every organization I have seen, the honest answer is "the person who made it, who has since moved to a different role." An unowned lookup table in a repository is technical debt that accrues interest in the form of lost work you never find out about.&lt;/p&gt;

&lt;p&gt;A quick summary of what each failure mode costs you:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure mode&lt;/th&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;How you find out&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Route, not area&lt;/td&gt;
&lt;td&gt;Boundary disputes at the edges&lt;/td&gt;
&lt;td&gt;Dispatcher overrides&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Straddling FSA&lt;/td&gt;
&lt;td&gt;Wildly variable travel for one code&lt;/td&gt;
&lt;td&gt;Van runs long&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rural FSA scale&lt;/td&gt;
&lt;td&gt;Accidental hundred-kilometre promises&lt;/td&gt;
&lt;td&gt;Angry driver&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New development gap&lt;/td&gt;
&lt;td&gt;Silent refusals&lt;/td&gt;
&lt;td&gt;Never, usually&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No owner&lt;/td&gt;
&lt;td&gt;Slow drift from reality&lt;/td&gt;
&lt;td&gt;Quarterly panic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Radius Circle and the Lie of Euclidean Distance
&lt;/h2&gt;

&lt;p&gt;The natural second attempt is a circle. Take the depot coordinates, pick a radius, and test whether the customer's point falls inside. It is one line of code and it feels principled because it is at least about distance.&lt;/p&gt;

&lt;p&gt;It is wrong for a reason that has nothing to do with implementation quality: euclidean distance and network distance are different measurements, and the ratio between them is not a constant.&lt;/p&gt;

&lt;p&gt;That ratio has a name in transport geography — the circuity ratio, or detour index. In a clean grid with no obstacles it hovers around 1.2 to 1.3. Rivers, rail corridors, and limited-access highways push it much higher in specific places. Calgary has all three. The Bow River cuts through the city from northwest to southeast and the Elbow joins it downtown. Two addresses six hundred metres apart across the Bow can be a seven-kilometre drive if the nearest crossing is inconvenient, and the number of crossings is small relative to the length of the river.&lt;/p&gt;

&lt;p&gt;Limited-access roads invert the intuition in the other direction. Deerfoot Trail is a freeway with interchanges, not intersections. You cannot enter it from the adjacent residential street; you enter it where an interchange exists. So a point three hundred metres from a Deerfoot lane may be structurally far from Deerfoot. Meanwhile a location fifteen kilometres up the corridor with an interchange at its doorstep is functionally close.&lt;/p&gt;

&lt;p&gt;Stoney Trail, the ring road, does the same thing at city scale. It makes perimeter movement dramatically cheaper than radial movement through the core. A circle drawn around the depot is isotropic — it assumes travel cost grows at the same rate in every direction. The actual cost surface is nothing like that. It is stretched along the freeway corridors and compressed across the river valleys and the rail yards.&lt;/p&gt;

&lt;p&gt;There is a second, subtler defect. A circle drawn in degrees is not even a circle. If you write &lt;code&gt;ST_DWithin(depot, customer, 0.35)&lt;/code&gt; against geometries in EPSG:4326, the unit is degrees, and a degree of longitude at Calgary's latitude is much shorter than a degree of latitude. Your "circle" is an ellipse squashed east-west by roughly 37 percent. I have found this bug in production more than once, and it never announces itself; it just quietly refuses service to the east and west while over-promising to the north and south.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anisotropy, Deerfoot, and the 16:45 Problem
&lt;/h2&gt;

&lt;p&gt;Consider two hypothetical service requests, both illustrative:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Request A&lt;/strong&gt; sits eight kilometres from the yard in a straight line, but on the far side of the core, and the van would have to cross the river and traverse downtown.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request B&lt;/strong&gt; sits fifteen kilometres from the yard in a straight line, but almost all of that distance is ring-road running with an interchange at each end.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At 10:30 on a Tuesday these might both be roughly twenty-two minutes. At 16:45 on a Thursday in October, Request A is forty-plus minutes and Request B is still around twenty-five. A radius model ranks A as nearer than B in both scenarios and is therefore wrong exactly when being wrong is most expensive — during the afternoon peak of the autumn changeover, when the &lt;a href="https://calgaryrimandtire.ca/seasonal-tire-changes" rel="noopener noreferrer"&gt;seasonal swap&lt;/a&gt; rush has every slot in the day already committed.&lt;/p&gt;

&lt;p&gt;This is anisotropy: cost depends on direction, not just magnitude. And it is non-stationary: the anisotropy field itself changes through the day. Any model that collapses to a single scalar radius has thrown away both properties.&lt;/p&gt;

&lt;p&gt;The right abstraction is a level set of the travel-time function. Fix the origin, fix the departure time, fix the vehicle profile, and ask: what is the set of all points reachable within &lt;em&gt;t&lt;/em&gt; minutes? That set is a polygon. It is lumpy, it has fingers running along the freeways, it has bites taken out of it where the river blocks movement, and it changes shape depending on when you ask. That polygon is an isochrone, and it is the honest answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Geometry Primitives You Actually Need
&lt;/h2&gt;

&lt;p&gt;Before writing anything spatial, fix the vocabulary. The OGC Simple Features model gives you a small set of types and you will use four of them.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Point&lt;/strong&gt; is a coordinate pair. In a geographic system it is (longitude, latitude) in that order — which is the reverse of how humans say it, and the source of a truly enormous number of bugs. GeoJSON, PostGIS, and most routing APIs use x-then-y. Leaflet's &lt;code&gt;LatLng&lt;/code&gt; does not. Write a single conversion helper and never do it inline.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;LineString&lt;/strong&gt; is an ordered sequence of points. Roads are linestrings. Your depot-to-customer path is a linestring. Nothing about it is closed.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Polygon&lt;/strong&gt; is one exterior ring plus zero or more interior rings, where a ring is a closed linestring whose first and last coordinates are identical. Interior rings are holes. A service zone with a genuine exclusion — a military reserve, a large restricted industrial site — is a polygon with a hole, not two polygons.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;MultiPolygon&lt;/strong&gt; is a set of disjoint polygons treated as one geometry. This is what you actually want for a service zone, because real coverage is often disconnected. A town twenty minutes down the highway with nothing serviceable in between is a separate part of the same zone.&lt;/p&gt;

&lt;p&gt;Winding order deserves its own paragraph because it will eventually cost you a day. RFC 7946, the GeoJSON specification, requires exterior rings to be counterclockwise and interior rings clockwise — the right-hand rule. The Esri shapefile format uses the opposite convention for exteriors. PostGIS mostly does not care, because it treats rings as planar and infers holes from containment. Spherical geometry libraries such as S2, and by extension several cloud data warehouses, care intensely: on a sphere a ring divides the surface into two finite regions, and orientation is the only thing that tells you which one you meant. Get it backwards and your Calgary service zone becomes "the entire planet except Calgary." The symptom is unmistakable once you have seen it, and baffling the first time.&lt;/p&gt;

&lt;p&gt;Two invariants worth asserting on ingest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The geometry is valid under OGC rules. Self-intersecting exterior rings are the usual offender; &lt;code&gt;ST_IsValid&lt;/code&gt; plus &lt;code&gt;ST_IsValidReason&lt;/code&gt; will name the problem, and &lt;code&gt;ST_MakeValid&lt;/code&gt; will usually fix it at the cost of silently changing your data.&lt;/li&gt;
&lt;li&gt;Ring orientation is normalized. &lt;code&gt;ST_ForcePolygonCCW&lt;/code&gt; on the way in means downstream consumers never have to guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Choosing a Coordinate Reference System for Alberta
&lt;/h2&gt;

&lt;p&gt;EPSG:4326 is WGS 84, a geographic coordinate system whose units are degrees of angle. It is the right storage and interchange format. It is the wrong computation format for anything involving length or area, and the reason is straightforward: a degree is not a distance.&lt;/p&gt;

&lt;p&gt;At Calgary's latitude, roughly 51.05 degrees north, one degree of latitude is about 111.2 kilometres. One degree of longitude is that same figure scaled by the cosine of the latitude — about 0.629 — giving roughly 70.0 kilometres. So the coordinate grid is anisotropic by a factor of 1.59 before you have done anything at all. Any planar distance, area, or buffer computed on raw lat/lon inherits that distortion.&lt;/p&gt;

&lt;p&gt;The three ways out, in ascending order of how much I like them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use geography types.&lt;/strong&gt; PostGIS &lt;code&gt;geography&lt;/code&gt; computes on the spheroid and returns metres. &lt;code&gt;ST_DWithin(a::geography, b::geography, 25000)&lt;/code&gt; is correct and readable. The cost is that the geography operator set is narrower and the functions are slower, sometimes considerably, on complex polygons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Web Mercator.&lt;/strong&gt; Do not. EPSG:3857 is a presentation projection. Its scale factor is the secant of the latitude, so at 51 degrees north every length is inflated by about 1.59 and every area by about 2.53. It exists so that map tiles are square. It has no business in a measurement pipeline, and the fact that it is the default in several visualization stacks has produced a lot of quietly wrong analytics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use a local projected CRS.&lt;/strong&gt; This is the right answer for repeated planar work. The question is which one.&lt;/p&gt;

&lt;p&gt;The obvious candidate is UTM. Calgary sits at about 114.07 degrees west, which places it in UTM zone 11N, EPSG:32611 — but only just. The boundary between zone 11 and zone 12 runs along the 114th meridian, roughly seven kilometres east of downtown. So the moment your service area extends east toward Chestermere at 113.7 west or Strathmore at 113.4 west, you are working across a zone boundary. You can force everything into zone 11 and accept the extrapolated distortion; near the zone edge the scale factor drifts to roughly 1.001, which is a metre per kilometre. Over a forty-kilometre run that is forty metres of error. Tolerable for coverage decisions, irritating for anything else, and philosophically unsatisfying.&lt;/p&gt;

&lt;p&gt;Alberta publishes a better option. The provincial 3TM system uses three-degree-wide transverse Mercator zones with central meridians at 111, 114, 117, and 120 degrees west, and — critically — a scale factor of exactly 1.0 at the central meridian rather than UTM's 0.9996. The 114 W zone spans 112.5 to 115.5 degrees west, which comfortably contains Cochrane, Airdrie, Calgary, Okotoks, Chestermere, and Strathmore in a single undistorted frame with the central meridian running essentially through the city.&lt;/p&gt;

&lt;p&gt;That definition is commonly catalogued as EPSG:3401 for the NAD83 datum, with NAD83(CSRS) realizations in the neighbouring 3775–3779 block. Verify against your own PROJ installation rather than trusting a number from an article — &lt;code&gt;projinfo EPSG:3401&lt;/code&gt; will print the WKT and you can confirm the central meridian yourself. Getting the datum realization wrong is a sub-metre error and usually irrelevant for coverage; getting the central meridian wrong is not.&lt;/p&gt;

&lt;p&gt;A comparison of the candidates on the axes that matter:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CRS&lt;/th&gt;
&lt;th&gt;Units&lt;/th&gt;
&lt;th&gt;Distortion near Calgary&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EPSG:4326&lt;/td&gt;
&lt;td&gt;degrees&lt;/td&gt;
&lt;td&gt;n/a — not planar&lt;/td&gt;
&lt;td&gt;Storage and interchange&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EPSG:3857&lt;/td&gt;
&lt;td&gt;metres&lt;/td&gt;
&lt;td&gt;~1.59x length, ~2.53x area&lt;/td&gt;
&lt;td&gt;Tiles only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EPSG:32611&lt;/td&gt;
&lt;td&gt;metres&lt;/td&gt;
&lt;td&gt;~1.001 near zone edge&lt;/td&gt;
&lt;td&gt;Workable, awkward east&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alberta 3TM 114 W&lt;/td&gt;
&lt;td&gt;metres&lt;/td&gt;
&lt;td&gt;~1.0 across the region&lt;/td&gt;
&lt;td&gt;Preferred for planar math&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;geography&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;metres&lt;/td&gt;
&lt;td&gt;spheroidal, correct&lt;/td&gt;
&lt;td&gt;Good default when speed allows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My standing recommendation: store in 4326, index in 4326, and reproject to the 3TM 114 W frame inside any function that buffers, measures, or computes area. Keep the reprojection at one boundary in the code so it is auditable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an Isochrone Really Is
&lt;/h2&gt;

&lt;p&gt;An isochrone is a contour of the travel-time field. The definition is clean; the construction is not, and understanding the construction tells you what to distrust.&lt;/p&gt;

&lt;p&gt;A routing engine — Valhalla, OSRM with a custom profile, GraphHopper, pgRouting over an imported network — holds the road network as a directed graph. Nodes are intersections, edges are road segments, and each edge carries a traversal cost derived from length, classification, speed data, turn restrictions, and access rules for the vehicle profile.&lt;/p&gt;

&lt;p&gt;To build a fifteen-minute isochrone the engine runs a single-source shortest-path expansion from the origin, typically Dijkstra with a cost ceiling instead of a target node. It stops expanding when accumulated cost exceeds the budget. What comes back is not a polygon; it is a labelled subgraph — every node with its minimum cost from the origin.&lt;/p&gt;

&lt;p&gt;Turning that into an area requires interpolation. Valhalla's approach is representative: it rasterizes costs onto a regular grid, assigning each cell the lowest cost reachable within it, then runs marching squares over that grid to extract the iso-cost contour, then generalizes and polygonizes the result. Every step of that pipeline introduces artifacts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grid resolution&lt;/strong&gt; determines how faithfully narrow features survive. A single road threading through otherwise unreachable terrain may be widened into a lobe or dropped entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generalization&lt;/strong&gt; trades vertex count for fidelity. A polygon with two hundred vertices tests fast and lies a little. One with twenty thousand vertices tests slowly and lies less.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Holes&lt;/strong&gt; appear where an enclosed area is genuinely unreachable within the budget, and also where the grid was too coarse to notice the road that reaches it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Time-of-day dependence is the property people forget. If the engine is running free-flow speeds derived from road classification, the polygon describes an empty city at three in the morning. Valhalla accepts a departure timestamp and will use predicted traffic if you have loaded a traffic tile set. OSRM has no native concept of time; the usual workaround is to compile separate graphs with per-bucket speed tables and route against whichever matches the hour. Either way, the polygon is a function of &lt;code&gt;(origin, budget, profile, departure_bucket, graph_version)&lt;/code&gt;, and if your cache key omits any of those, you will eventually serve an answer generated for a different world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versioning and Caching Drive-Time Polygons
&lt;/h2&gt;

&lt;p&gt;Generating isochrones on the request path is a mistake. It is a network hop to a service that is doing a graph expansion and a contouring pass, and it puts a third-party dependency in the latency budget of something a customer is waiting on.&lt;/p&gt;

&lt;p&gt;Generate them offline. A batch job runs per origin, per budget, per departure bucket, writes the resulting geometry into the database with full provenance, and marks it active. Coverage evaluation then reads a local polygon and never touches the routing engine.&lt;/p&gt;

&lt;p&gt;The provenance record should be complete enough to regenerate the exact polygon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"lon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-114.0719&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;51.0447&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cost_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"auto"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"departure_bucket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"weekday_pm_peak"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"valhalla"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engine_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3.4.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"graph_extract"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alberta-2026-07-14.osm.pbf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"generalize_m"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"denoise"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"generated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-15T04:12:08Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three rules keep this honest.&lt;/p&gt;

&lt;p&gt;First, versions are immutable. You never update a geometry in place. A new generation writes a new row with a new version number and a validity window; the old row stays exactly as it was. This is what makes historical explanation possible, and it is worth every byte.&lt;/p&gt;

&lt;p&gt;Second, the cache key is the full provenance tuple, not the origin alone. I have watched a team spend a day debugging "the polygon changed by itself" when in fact two jobs with different departure buckets were writing to the same key.&lt;/p&gt;

&lt;p&gt;Third, regeneration is scheduled against the graph extract, not the calendar. When the OpenStreetMap extract updates and the network topology changes — a new interchange opens, a bridge closes for construction — the polygons are stale regardless of how recently you rebuilt them. Tie the trigger to the input, not to a cron expression that somebody picked arbitrarily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Point in Polygon: Ray Casting, Winding Numbers, and the Gap Between Them
&lt;/h2&gt;

&lt;p&gt;Given a polygon and a point, decide containment. Two classical algorithms, both O(n) in the vertex count, both worth understanding before you delegate to a library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray casting&lt;/strong&gt;, also called the crossing number or even-odd rule, shoots a ray from the query point in a fixed direction — conventionally along positive x — and counts how many polygon edges it crosses. Odd means inside, even means outside. It is four lines of arithmetic per edge and it is what most naive implementations use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winding number&lt;/strong&gt; computes how many times the polygon boundary wraps around the point, summing signed angles or, more practically, using orientation tests on each edge. Nonzero means inside.&lt;/p&gt;

&lt;p&gt;For a simple, non-self-intersecting polygon the two agree everywhere. They diverge on self-intersecting geometry: a figure-eight's crossover lobe is outside under even-odd and inside under nonzero winding. Real isochrone output is not always clean, especially after generalization, so this is not purely academic. My preference is to validate and repair geometries on ingest so that the question never arises, and then use whichever predicate the library implements — but to know which one that is.&lt;/p&gt;

&lt;p&gt;Complexity matters once you have more than a couple of zones. The naive loop is O(Z·n) for Z zones averaging n vertices. With a dozen active zone versions at a few thousand vertices each, a single evaluation touches tens of thousands of edges. At a few hundred requests a minute that is measurable, and it is entirely avoidable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spatial Indexes and the Bounding-Box Prefilter
&lt;/h2&gt;

&lt;p&gt;The fix is the standard two-phase spatial query: cheap filter, then exact refinement.&lt;/p&gt;

&lt;p&gt;Phase one uses an R-tree. Every geometry contributes its minimum bounding rectangle to a balanced tree of nested rectangles. A point query descends the tree and returns the small set of candidates whose MBR contains the point. That is O(log Z) expected, and it eliminates the overwhelming majority of zones with four float comparisons each.&lt;/p&gt;

&lt;p&gt;Phase two runs the exact predicate on the survivors only. If your zones are roughly convex and well separated, phase one usually leaves one candidate. If they are long and diagonal — a corridor along a highway is the classic case — the MBR is a poor approximation and you will get more false positives, which is an argument for splitting a straggly zone into a MultiPolygon of tighter parts.&lt;/p&gt;

&lt;p&gt;PostGIS gives you this for free. A GIST index over a geometry column is an R-tree over the bounding boxes, implemented on top of the generalized search tree framework. The &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; operator is bounding-box overlap and is index-accelerated. Functions like &lt;code&gt;ST_Contains&lt;/code&gt; and &lt;code&gt;ST_Intersects&lt;/code&gt; are defined as &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; composed with the exact underscore-prefixed predicate, so the planner uses the index automatically. What is not automatic: an index on &lt;code&gt;geom&lt;/code&gt; does nothing for a query that wraps the column in &lt;code&gt;ST_Transform&lt;/code&gt;. Either index the expression or store a second projected column.&lt;/p&gt;

&lt;p&gt;Shapely's &lt;code&gt;STRtree&lt;/code&gt; is a packed Sorted-Tile-Recursive R-tree — built once, queried many times, not modifiable afterwards. That fits the isochrone workload exactly, because zone versions change on a batch schedule rather than per request.&lt;/p&gt;

&lt;p&gt;One more trick worth its weight: prepared geometries. Both GEOS and Shapely can precompute an edge index for a polygon that will be tested repeatedly. &lt;code&gt;shapely.prepared.prep&lt;/code&gt; returns an object whose &lt;code&gt;contains&lt;/code&gt; is dramatically faster on high-vertex polygons after the first invocation. Build them once at index construction and hold them for the process lifetime.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Data Model That Can Explain Itself Later
&lt;/h2&gt;

&lt;p&gt;Here is the schema I would defend in review. PostGIS 3.x, PostgreSQL 15 or newer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;btree_gist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;service_zones&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;            &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;slug&lt;/span&gt;          &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;display_name&lt;/span&gt;  &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;zone_kind&lt;/span&gt;     &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone_kind&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'drive_time'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'manual'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'union'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;depot_id&lt;/span&gt;      &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;depots&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;notes&lt;/span&gt;         &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt;    &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The zone is an identity and a policy statement. It carries no geometry, because the geometry changes and the identity does not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;zone_versions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;              &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;zone_id&lt;/span&gt;         &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;service_zones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;revision&lt;/span&gt;        &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;geom&lt;/span&gt;            &lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MultiPolygon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;geom_3tm&lt;/span&gt;        &lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MultiPolygon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_Transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3401&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;STORED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;source_kind&lt;/span&gt;     &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;provenance&lt;/span&gt;      &lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cost_seconds&lt;/span&gt;    &lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;departure_bucket&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;validity&lt;/span&gt;        &lt;span class="n"&gt;tstzrange&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;authored_by&lt;/span&gt;     &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt;      &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;geom_is_valid&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_IsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;no_overlapping_windows&lt;/span&gt;
        &lt;span class="n"&gt;EXCLUDE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;gist&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone_id&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validity&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;zone_versions_geom_gix&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;zone_versions&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;zone_versions_live_idx&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;zone_versions&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;validity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things in that definition are doing real work. The generated projected column means planar measurement never pays a runtime transform and the index on it stays usable. The &lt;code&gt;CHECK (ST_IsValid(geom))&lt;/code&gt; constraint refuses malformed contour output at the door rather than three weeks later during an incident. The exclusion constraint — which needs &lt;code&gt;btree_gist&lt;/code&gt; to mix an equality column with a range column — makes it structurally impossible for one zone to have two simultaneously active versions. That single line removes an entire category of ambiguity from the system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;coverage_checks&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;                 &lt;span class="n"&gt;bigserial&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;requested_at&lt;/span&gt;       &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;raw_address&lt;/span&gt;        &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;normalized_address&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;geocoder&lt;/span&gt;           &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;geocode_quality&lt;/span&gt;    &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;point&lt;/span&gt;              &lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;zone_version_id&lt;/span&gt;    &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;zone_versions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;decision&lt;/span&gt;           &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
                       &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'covered'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'outside'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'undetermined'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;decision_reason&lt;/span&gt;    &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;boundary_margin_m&lt;/span&gt;  &lt;span class="nb"&gt;double&lt;/span&gt; &lt;span class="nb"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;served_from_cache&lt;/span&gt;  &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;latency_ms&lt;/span&gt;         &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;coverage_checks_point_gix&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;coverage_checks&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;coverage_checks_recent_idx&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;coverage_checks&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requested_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zone_version_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The foreign key from a check to a version is the whole point of the design. Someone reviewing a job from four months ago asks why the system said yes to an address it now says no to. Without that column the answer is an archaeology project. With it, you join two tables and print the polygon that was live at the time, alongside the provenance blob describing which graph extract and which departure bucket produced it. &lt;code&gt;boundary_margin_m&lt;/code&gt; earns its place too — the distance to the nearest boundary tells you whether a decision was comfortable or a coin flip, and aggregating it reveals where the polygon needs attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Queries Worth Writing Down
&lt;/h2&gt;

&lt;p&gt;Which zones cover a point right now, ordered by tightest drive-time budget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;version_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cost_seconds&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;zone_versions&lt;/span&gt; &lt;span class="n"&gt;zv&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;service_zones&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zone_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;zv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;validity&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;zv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ST_SetSRID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_MakePoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;ST_Intersects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ST_SetSRID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_MakePoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;zv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cost_seconds&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; clause is redundant in the sense that &lt;code&gt;ST_Intersects&lt;/code&gt; already applies it, but writing it explicitly documents the intent and survives refactors where somebody swaps the predicate for one that is not index-backed.&lt;/p&gt;

&lt;p&gt;How far is this point from the edge of the zone, in metres:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ST_Distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="n"&gt;ST_Transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_SetSRID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_MakePoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3401&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="n"&gt;ST_Boundary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geom_3tm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;margin_m&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;zone_versions&lt;/span&gt; &lt;span class="n"&gt;zv&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;zv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the transform on the point and the pre-projected column on the polygon. Doing it the other way — transforming the polygon per row — is the single most common performance mistake in PostGIS code I review.&lt;/p&gt;

&lt;p&gt;Area of a zone in square kilometres, which is a sanity check you should run on every generated version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;zv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;ST_Area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geom_3tm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;e6&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;area_km2&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;zone_versions&lt;/span&gt; &lt;span class="n"&gt;zv&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;service_zones&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zone_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;zv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;validity&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;area_km2&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a regeneration changes the area by more than a threshold you have chosen — twenty percent is a reasonable illustrative starting point — that is a signal, not a routine event. Something upstream moved. Hold the new version in a staging state and make a human look at it before it goes live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python: Prefilter, Exact Test, and a Ray Caster for Teaching
&lt;/h2&gt;

&lt;p&gt;Production code should use Shapely and let GEOS do the arithmetic. Here is the structure I use, with the two-phase filter made explicit rather than hidden.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;shapely.geometry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shape&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;shapely.geometry.base&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseGeometry&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;shapely.prepared&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;prep&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;shapely.strtree&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;STRtree&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ZoneVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;version_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;cost_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BaseGeometry&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;covered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;version_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoverageIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Immutable spatial index over the zone versions live at build time.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ZoneVersion&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_versions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_geoms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geometry&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_versions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_prepared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_geoms&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_bounds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_geoms&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;STRtree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_geoms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_geoms&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_bbox_hit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;minx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;miny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_bounds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;minx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;maxx&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;miny&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;maxy&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;probe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;probe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no_bbox_candidate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_bbox_hit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_prepared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;probe&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bbox_hit_exact_miss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cost_seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inside:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,)):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_position&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;_resolve&lt;/code&gt; shim exists because Shapely 2.0 changed &lt;code&gt;STRtree.query&lt;/code&gt; to return integer indices where 1.8 returned geometry objects. If you maintain a library that must span both, hide the difference in one place.&lt;/p&gt;

&lt;p&gt;Now the pedagogical version. Nobody should ship this, but everybody who works on spatial systems should be able to write it from memory, because when a library gives you an answer you do not believe, this is how you find out who is wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;point_in_ring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Crossing-number test with a half-open edge rule.

    The ring is a closed sequence of (x, y) pairs. The half-open
    comparison (yi &amp;gt; y) != (yj &amp;gt; y) treats each edge as containing
    its lower endpoint and excluding its upper one, which makes a
    ray that passes exactly through a vertex count once, not twice
    and not zero times.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;inside&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;xi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;xj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yi&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yj&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yj&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;crossing_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xi&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xj&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;xi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;crossing_x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;inside&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;inside&lt;/span&gt;
        &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inside&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;point_in_polygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exterior&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;holes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;point_in_ring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exterior&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;point_in_ring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hole&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;hole&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;holes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The half-open rule in that conditional is the entire trick. A naive implementation writes &lt;code&gt;yi &amp;lt;= y &amp;lt; yj or yj &amp;lt;= y &amp;lt; yi&lt;/code&gt; and gets it right by accident; an implementation that writes &lt;code&gt;min(yi, yj) &amp;lt;= y &amp;lt;= max(yi, yj)&lt;/code&gt; double-counts vertex hits and returns the wrong parity for any point whose latitude exactly matches a vertex. Since isochrone vertices come out of a regular grid, and grid latitudes are extremely round numbers, that case is far more common than a uniform-random model would predict.&lt;/p&gt;

&lt;h2&gt;
  
  
  Floating Point on the Boundary
&lt;/h2&gt;

&lt;p&gt;Three degenerate cases, and what to do about each.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The point lies exactly on an edge.&lt;/strong&gt; Mathematically the predicate is undefined unless you state a convention. Computationally, whether it lands on the edge depends on whether &lt;code&gt;t&lt;/code&gt; and &lt;code&gt;crossing_x&lt;/code&gt; round the way you hoped. Decide the policy explicitly — boundary counts as inside is the humane choice for a service area — and implement it as a separate test rather than hoping the crossing count works out. In PostGIS that means &lt;code&gt;ST_Covers&lt;/code&gt; rather than &lt;code&gt;ST_Contains&lt;/code&gt;; &lt;code&gt;ST_Contains&lt;/code&gt; returns false for boundary points and &lt;code&gt;ST_Covers&lt;/code&gt; returns true, and confusing the two produces a bug that appears roughly one time in ten thousand and is never reproducible on demand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ray passes through a vertex.&lt;/strong&gt; Handled by the half-open rule above. Verify it with a fixture: a point at exactly the same y as a polygon vertex, once with the vertex to the left and once to the right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two computations disagree.&lt;/strong&gt; This is the one that ruins afternoons. The same address evaluated twice returns different answers because one path went through a projection and the other did not, or because a value round-tripped through JSON at fifteen significant digits instead of seventeen. Determinism is a property you have to engineer.&lt;/p&gt;

&lt;p&gt;Three practices buy it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Snap on ingest.&lt;/strong&gt; &lt;code&gt;ST_SnapToGrid(geom, 1e-7)&lt;/code&gt; quantizes coordinates to about 1.1 centimetres of latitude. Every downstream computation then starts from the same numbers, and the crossing arithmetic becomes reproducible across library versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serialize with full precision.&lt;/strong&gt; &lt;code&gt;ST_AsGeoJSON(geom, 15)&lt;/code&gt; and a JSON encoder that does not truncate. A double needs 17 significant decimal digits for a lossless round trip; most defaults give you fewer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refuse to answer near the boundary.&lt;/strong&gt; Compute the margin. If the point sits within a tolerance band of the edge — five metres is a defensible illustrative choice — return &lt;code&gt;undetermined&lt;/code&gt; and let a human resolve it. This is not a cop-out. It is an honest statement that the polygon's own accuracy is coarser than the question being asked, and it converts a silent wrong answer into a visible, countable event.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Underneath all of this, if you need genuinely exact orientation tests, the answer is adaptive-precision arithmetic — Shewchuk's robust predicates, which JTS implements with double-double arithmetic and GEOS inherits. Knowing it exists is usually enough; needing it means your tolerances are wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Geocoding Failures and Failing Closed
&lt;/h2&gt;

&lt;p&gt;Coverage evaluation has a dependency most designs underweight: turning text into coordinates. If geocoding is wrong, everything downstream is confidently wrong.&lt;/p&gt;

&lt;p&gt;Calgary makes this harder than average because of quadrants. The city is divided into NW, NE, SW, and SE, and street numbering restarts in each. "123 5 Street" is four different places. A form that lets a customer omit the quadrant, or a normalizer that drops it, will hand the geocoder an ambiguous string, and the geocoder will pick one — usually the most populous match, usually silently. The van is then dispatched to a location twelve kilometres from the vehicle. Quadrant is not optional metadata; treat a missing quadrant as a validation failure and resolve it before geocoding.&lt;/p&gt;

&lt;p&gt;The normalizer should also handle unit designators, since a driveway visit needs the building and a bay number needs the unit; the abbreviation zoo of ST, AV, DR, CR, BV, TR, WY, GA, LI, PZ; and directional suffixes that some sources write as &lt;code&gt;N.W.&lt;/code&gt; and others as &lt;code&gt;NW&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then fail closed. Every geocoder returns a quality signal, and the tiers matter:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quality&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Policy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rooftop / parcel&lt;/td&gt;
&lt;td&gt;Resolved to the property&lt;/td&gt;
&lt;td&gt;Evaluate normally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interpolated&lt;/td&gt;
&lt;td&gt;Estimated along a street segment&lt;/td&gt;
&lt;td&gt;Evaluate, widen the margin band&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Street centroid&lt;/td&gt;
&lt;td&gt;Street matched, number did not&lt;/td&gt;
&lt;td&gt;Undetermined, route to review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Locality only&lt;/td&gt;
&lt;td&gt;City or town matched&lt;/td&gt;
&lt;td&gt;Undetermined&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No match&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Undetermined&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An interpolated result on a long rural road can be hundreds of metres off, which is exactly where the polygon edge tends to be. Treating "interpolated" as equivalent to "rooftop" is a quiet source of bad dispatches, and the correction is small: multiply the boundary tolerance by the positional uncertainty of the geocode tier.&lt;/p&gt;

&lt;p&gt;Failing closed means an unresolved address never returns &lt;code&gt;covered&lt;/code&gt;. It returns &lt;code&gt;undetermined&lt;/code&gt; with a reason code, and something in the workflow — a queue, a flagged record, a note on the service request — routes it to a person. Customers near a boundary are also frequently near an edge case in the data, and rural addresses on range roads are both. For a business whose &lt;a href="https://calgaryrimandtire.ca/service-areas" rel="noopener noreferrer"&gt;service area&lt;/a&gt; genuinely extends past the city limits, that queue is not an exception path; it is a normal part of Tuesday.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching on Rounded Coordinates
&lt;/h2&gt;

&lt;p&gt;Once volume grows, evaluating the same location repeatedly is waste. The obvious cache key is the coordinate pair, and the obvious refinement is to round it so that near-identical points share an entry.&lt;/p&gt;

&lt;p&gt;Round to four decimal places and, at Calgary's latitude, a cell is roughly 11.1 metres north-south by 7.0 metres east-west. Five decimals gives about 1.1 by 0.7 metres. The tradeoff is direct: coarser cells mean a higher hit rate and a larger maximum positional error introduced by the key itself.&lt;/p&gt;

&lt;p&gt;The failure mode is specific. If a rounding cell straddles a zone boundary, two genuinely different addresses collapse to one key, and whichever was evaluated first determines the answer for both. One of them is wrong, and the record shows a clean, plausible, cached decision.&lt;/p&gt;

&lt;p&gt;The mitigation is to make cacheability conditional on the margin you already compute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;CELL_DEG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1e-4&lt;/span&gt;
&lt;span class="n"&gt;CELL_DIAGONAL_M&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;13.2&lt;/span&gt;  &lt;span class="c1"&gt;# illustrative, at ~51 deg N
&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cache_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;qx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;CELL_DEG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;qy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;CELL_DEG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cov:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;policy_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;qx&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;qy&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;maybe_store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;margin_m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;undetermined&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;margin_m&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;margin_m&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CELL_DIAGONAL_M&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comfortable decisions get cached; marginal ones are recomputed every time. Since the marginal population is small, the hit rate barely moves and the correctness problem disappears. Include the policy or zone-set identifier in the key so that publishing a new zone version invalidates by construction rather than by remembering to flush.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the Routing Provider Is Down
&lt;/h2&gt;

&lt;p&gt;Because isochrones are precomputed, a routing outage cannot break coverage evaluation. That is most of the value of the offline design, and it is worth stating explicitly to whoever is reviewing the architecture.&lt;/p&gt;

&lt;p&gt;What an outage does break is regeneration, and the failure is quiet. Polygons drift out of date, nothing alarms, and three weeks later somebody notices the new interchange is not represented. Alert on generation age, not on generation errors.&lt;/p&gt;

&lt;p&gt;For the routing that does happen live — estimating travel time to a specific address so a dispatcher can size the day — build a ladder:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Primary.&lt;/strong&gt; The routing engine with a strict timeout. Two seconds is a reasonable illustrative ceiling for an interactive path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secondary.&lt;/strong&gt; A cached matrix of depot-to-cell travel times at a coarse grid resolution. Stale by hours, accurate within a few minutes, and available instantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tertiary.&lt;/strong&gt; Straight-line distance multiplied by a direction-dependent circuity factor derived from historical data. Crude, but it degrades gracefully and it is better than an error page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Floor.&lt;/strong&gt; Return the polygon-based coverage verdict with the estimate marked provisional, and let the human decide.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each tier tags its output with the method used, and that tag lands in the service record. When you later analyze which estimates were poor, you can separate "the model is bad" from "the model was not running."&lt;/p&gt;

&lt;p&gt;The same discipline applies to the geocoder. A second provider behind a circuit breaker costs a day to integrate and removes a single point of failure from the most upstream step in the chain. For urgent work — a &lt;a href="https://calgaryrimandtire.ca/tire-repair-in-calgary" rel="noopener noreferrer"&gt;flat repair&lt;/a&gt; on a vehicle stranded in a lot, or the kind of &lt;a href="https://calgaryrimandtire.ca/emergency-services" rel="noopener noreferrer"&gt;roadside situation&lt;/a&gt; where minutes are the whole story — a degraded answer delivered now beats a perfect answer delivered after the customer has given up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Coverage Without Brittleness
&lt;/h2&gt;

&lt;p&gt;Spatial tests fail badly when written naively, because the natural assertion — "this coordinate is inside this polygon" — breaks whenever the polygon is regenerated, which is often. The tests then get deleted, and coverage logic ends up with the worst test suite in the codebase despite being one of its riskiest components.&lt;/p&gt;

&lt;p&gt;Three layers that hold up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Golden points.&lt;/strong&gt; A fixture file of coordinates with expected verdicts and a human-written rationale. Choose points that are unambiguous by construction — deep inside the core, well outside in open country, on the far side of an obvious barrier — not points scraped from a map near an edge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;GOLDEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;depot_yard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;114.0719&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;51.0447&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;covered&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inner_city_core&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;114.0630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;51.0450&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;covered&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;north_ring_edge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;114.0100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;51.1800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;covered&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;far_east_prairie&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;112.4000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;50.9500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outside&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mountain_park&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;116.0300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;51.1700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outside&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assert the verdict and the reason code. If a regeneration flips a golden point, the build fails and a person looks at the polygon — which is the outcome you want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Property tests.&lt;/strong&gt; These survive polygon churn because they assert invariants rather than facts. The most valuable one is projection stability: reproject a point from 4326 into the working CRS and back, and require that the containment verdict is unchanged and the coordinate agrees to within a tight tolerance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hypothesis&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategies&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyproj&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Transformer&lt;/span&gt;

&lt;span class="n"&gt;FWD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Transformer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_crs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EPSG:4326&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EPSG:3401&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;always_xy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;INV&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Transformer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_crs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EPSG:3401&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EPSG:4326&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;always_xy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_value&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mf"&gt;115.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_value&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mf"&gt;112.6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;49.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;52.2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_reprojection_round_trip_preserves_verdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FWD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1e-9&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1e-9&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;INDEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;covered&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;INDEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;covered&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other invariants that pay for themselves: every stored geometry is valid; no two active versions of one zone overlap in time; a point inside a tighter drive-time band is inside every looser band from the same origin; and the same input evaluated twice in one process yields byte-identical output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boundary tests without brittleness.&lt;/strong&gt; Do not assert what happens on the line. Assert monotonicity around it. Take a boundary vertex, step fifty metres along the inward normal, and require &lt;code&gt;covered&lt;/code&gt;. Step fifty metres outward and require not covered. Between those, require only that the answer is stable and that the margin computation reports a value below the tolerance. You are testing the shape of the decision function rather than the coordinates of the polygon, which is what you actually care about.&lt;/p&gt;

&lt;p&gt;One more thing worth automating: an area-delta check between consecutive versions, run in CI against a fixture polygon set. Contouring parameters get tuned, somebody changes a generalization value, and the polygon quietly loses a limb. A percentage-change assertion catches it in the pipeline instead of in dispatch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seasonality as a Time-Windowed Zone Version
&lt;/h2&gt;

&lt;p&gt;Alberta has two weeks in spring and two in fall when the entire province changes its tires at once. Demand during the &lt;a href="https://calgaryrimandtire.ca/winter-tires-calgary" rel="noopener noreferrer"&gt;changeover peak&lt;/a&gt; is a different distribution from the rest of the year, not merely a larger one, and it breaks any assumption that coverage is a static property.&lt;/p&gt;

&lt;p&gt;The arithmetic is unforgiving. A van that completes a fixed number of jobs a day at forty minutes of driving between them completes meaningfully fewer than one averaging twenty. During peak weeks the marginal long-distance job does not add revenue; it removes two closer jobs from the day. So the rational operating policy is a smaller effective service area precisely when demand is highest.&lt;/p&gt;

&lt;p&gt;The wrong way to implement that is to edit the polygon. Somebody opens the geometry, shrinks it, and in April somebody else expands it again. History is destroyed, the audit trail lies, and a service record from March cannot be explained.&lt;/p&gt;

&lt;p&gt;The right way is a second zone version with a bounded validity window, competing for the same zone identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;zone_versions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;zone_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source_kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provenance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cost_seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;departure_bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authored_by&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;zone_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
       &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;zone_versions&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;zone_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;zone_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;ST_Multi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_GeomFromGeoJSON&lt;/span&gt;&lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="n"&gt;geojson&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="s1"&gt;'isochrone'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;provenance&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                          &lt;span class="c1"&gt;-- 25 minutes, tightened from 30&lt;/span&gt;
    &lt;span class="s1"&gt;'weekday_pm_peak'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tstzrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2026-10-05 00:00+00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-10-26 00:00+00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[)'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'seasonal-policy-job'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exclusion constraint enforces the rest. If the peak window overlaps the standing version's window, the insert fails, and you are forced to close the standing window explicitly rather than creating a silent ambiguity. That is the constraint doing its job: converting a policy question into a schema error at the moment somebody tries to be careless.&lt;/p&gt;

&lt;p&gt;A few consequences fall out of this design that I did not anticipate the first time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quotes issued before the window opens must be honoured against the version live at issue time, which means the quote stores a &lt;code&gt;zone_version_id&lt;/code&gt; and the fulfilment path reads it rather than re-evaluating.&lt;/li&gt;
&lt;li&gt;The tightened polygon is a different isochrone, not a scaled copy. Regenerate it with the peak departure bucket; a negative buffer of the standing polygon shrinks it uniformly, which is exactly the isotropic mistake the whole model exists to avoid.&lt;/li&gt;
&lt;li&gt;Announcing the window in advance is cheaper than declining requests during it. Whatever surfaces coverage to customers — the &lt;a href="https://calgaryrimandtire.ca/online-bookings" rel="noopener noreferrer"&gt;self-serve scheduling page&lt;/a&gt;, a dispatcher's screen — should read the version that will be live on the requested date, not the one live right now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same mechanism handles other temporal policies without new machinery: a reduced winter zone when a road closes seasonally, an expanded window when a second van joins the rotation, a temporary exclusion around a construction detour. Every one of them is a row with a validity range, and every one of them is explainable afterward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Latency, Monitoring, and What to Alert On
&lt;/h2&gt;

&lt;p&gt;Coverage evaluation sits on an interactive path, so it needs a latency budget and observability that matches.&lt;/p&gt;

&lt;p&gt;The realistic decomposition for a cold request:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Typical share&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Address normalization&lt;/td&gt;
&lt;td&gt;small&lt;/td&gt;
&lt;td&gt;Pure CPU, string work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Geocoding&lt;/td&gt;
&lt;td&gt;dominant&lt;/td&gt;
&lt;td&gt;Network, external provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Index query and exact test&lt;/td&gt;
&lt;td&gt;small&lt;/td&gt;
&lt;td&gt;Microseconds with prepared geometry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Margin computation&lt;/td&gt;
&lt;td&gt;small&lt;/td&gt;
&lt;td&gt;One projected distance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persisting the check record&lt;/td&gt;
&lt;td&gt;small&lt;/td&gt;
&lt;td&gt;Single insert, can be async&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Geocoding is the entire story. Everything else is noise. That has two implications: cache geocodes aggressively and separately from coverage verdicts, since a normalized address maps to a coordinate far more stably than a coordinate maps to a verdict; and instrument the provider call with its own timer so a provider slowdown is not misattributed to your spatial code.&lt;/p&gt;

&lt;p&gt;Metrics I would page on, or at least chart on a wall:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;p50, p95, and p99 end-to-end evaluation latency, split by cache-hit status. Averaging across both hides everything.&lt;/li&gt;
&lt;li&gt;Geocode failure rate by tier. A rise in "street centroid" results usually means a normalizer regression, not a provider problem.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;undetermined&lt;/code&gt; rate. This is your honesty metric. A sudden climb means the geocoder degraded or a new polygon has a boundary running through a populated area.&lt;/li&gt;
&lt;li&gt;Boundary-margin distribution. If a large share of requests cluster within a few hundred metres of an edge, the polygon is drawn through demand rather than around it, and the budget should move.&lt;/li&gt;
&lt;li&gt;Decision-flip rate across version transitions. Replay a recent sample of points against the incoming version before it goes live; a flip count outside the expected range blocks publication.&lt;/li&gt;
&lt;li&gt;Age of the newest zone version per origin, compared against the age of the graph extract.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one has caught more real problems than the rest combined, because stale geometry produces answers that look completely normal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Build Differently
&lt;/h2&gt;

&lt;p&gt;Three things I got wrong on the first pass, offered as a shortcut past them.&lt;/p&gt;

&lt;p&gt;I started with a single polygon per origin and a boolean verdict. Bands should have existed from the beginning. The moment dispatch wants to sequence a day, "inside" is not enough information; they need the drive-time tier, and retrofitting bands meant reworking every consumer. Model concentric budgets — twenty, thirty-five, fifty minutes — from the start, and let the boolean be a derived view over the innermost band that policy currently accepts.&lt;/p&gt;

&lt;p&gt;I treated the peak-season constraint as an operational note rather than a data-model requirement. It is a data-model requirement. Anything that changes coverage over time belongs in the versioning mechanism, and discovering that in the middle of an October rush is an unpleasant way to learn it.&lt;/p&gt;

&lt;p&gt;I under-invested in the &lt;code&gt;undetermined&lt;/code&gt; path. The first version had two outcomes, and every ambiguous case was forced into one of them, which meant the system was confidently wrong at exactly the addresses where being wrong mattered most — acreages, new subdivisions, rural routes. A third state plus a review queue turned an invisible error rate into a visible work item, and the work item turned out to be small.&lt;/p&gt;

&lt;p&gt;The broader lesson generalizes past tires. A service area is not a fact about your business; it is a model of a cost surface that changes with the road network, the hour, the season, and the number of vehicles you are running. Postal codes model none of that. A radius models one dimension of it badly. Versioned isochrones with explicit provenance model most of it, and — more importantly — they tell you when they are unsure.&lt;/p&gt;

&lt;p&gt;If you want to see the shape of the real-world constraints this design is serving, the operational side is documented plainly: the &lt;a href="https://calgaryrimandtire.ca/fleet-management" rel="noopener noreferrer"&gt;fleet programs&lt;/a&gt; that drive most of the multi-vehicle routing, the &lt;a href="https://calgaryrimandtire.ca/commercial-tire-services" rel="noopener noreferrer"&gt;commercial work&lt;/a&gt; that pushes the polygon outward toward industrial parks, and the &lt;a href="https://calgaryrimandtire.ca/calgarys-local-tire-shop" rel="noopener noreferrer"&gt;Calgary yard&lt;/a&gt; that anchors every isochrone in this article. The software exists to answer one question honestly, and the geometry is how it does that.&lt;/p&gt;

</description>
      <category>automotive</category>
      <category>calgary</category>
      <category>maintenance</category>
      <category>tires</category>
    </item>
    <item>
      <title>Parsing Tire Size Codes: Building a Fitment Data Model That Survives Real-World Inputs</title>
      <dc:creator>KMJ Tire Calgary</dc:creator>
      <pubDate>Thu, 06 Aug 2026 16:12:50 +0000</pubDate>
      <link>https://dev.to/kmjtirecalgary/parsing-tire-size-codes-building-a-fitment-data-model-that-survives-real-world-inputs-30cj</link>
      <guid>https://dev.to/kmjtirecalgary/parsing-tire-size-codes-building-a-fitment-data-model-that-survives-real-world-inputs-30cj</guid>
      <description>&lt;h1&gt;
  
  
  Parsing Tire Size Codes: Building a Fitment Data Model That Survives Real-World Inputs
&lt;/h1&gt;

&lt;p&gt;A tire size designation looks trivial until you try to parse one that a human typed. &lt;code&gt;P215/60R16 94H&lt;/code&gt; reads like a product code, and most developers who encounter it for the first time reach for a single regular expression, ship it, and move on. Then the intake queue delivers &lt;code&gt;LT265/70R17 121/118S E&lt;/code&gt;, followed by &lt;code&gt;35x12.50R17&lt;/code&gt;, followed by &lt;code&gt;225 60 16&lt;/code&gt; pasted from a text message, followed by &lt;code&gt;2256016&lt;/code&gt; with no separators at all, and the single regex becomes a graveyard of nested optional groups that nobody on the team wants to touch.&lt;/p&gt;

&lt;p&gt;I work on the software side of KMJ Tire, a small Calgary operation that handles tire work and oil changes and nothing else — no mechanical repair, no diagnostics, just rubber, wheels, and lubricant. That narrow scope means our data problems are narrow too, and the deepest one is fitment: given a string a customer typed into a web form or a size a technician read off a sidewall, what tire is actually being described, does it fit the vehicle in question, and is it safe relative to what the manufacturer specified? This article walks through the grammar of tire sizes, a tokenizer and parser that handle the real-world mess, a normalized data model, the diameter math behind equivalence and plus-sizing, and the validation layer that refuses dangerous fitments. All code is Python and runnable in spirit; illustrative examples are labelled as such, and none of the numbers here are business statistics.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Sidewall Is a Grammar, Not a String
&lt;/h2&gt;

&lt;p&gt;The designation moulded into a sidewall is a compact sentence in a formal language. &lt;code&gt;P215/60R16 94H&lt;/code&gt; decomposes into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;P&lt;/code&gt; — an optional service-type prefix (&lt;code&gt;P&lt;/code&gt; for passenger, &lt;code&gt;LT&lt;/code&gt; for light truck, &lt;code&gt;T&lt;/code&gt; for temporary spare, &lt;code&gt;ST&lt;/code&gt; for special trailer, &lt;code&gt;C&lt;/code&gt; suffix conventions in Europe).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;215&lt;/code&gt; — section width in millimetres, the widest point of the inflated tire.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;60&lt;/code&gt; — aspect ratio, sidewall height as a percentage of section width.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;R&lt;/code&gt; — internal construction (&lt;code&gt;R&lt;/code&gt; radial, &lt;code&gt;D&lt;/code&gt; diagonal/bias, &lt;code&gt;B&lt;/code&gt; belted bias). You will also meet &lt;code&gt;ZR&lt;/code&gt;, which historically encoded a speed capability above 240 km/h and still appears combined with a modern speed symbol.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;16&lt;/code&gt; — rim diameter in inches. Yes, the same designation mixes millimetres, percentages, and inches. This is the industry standard and it is never going away.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;94&lt;/code&gt; — load index, an integer that maps through a standardized table to a maximum load in kilograms.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;H&lt;/code&gt; — speed symbol, a letter mapping to a maximum sustained speed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Light-truck sizes extend the sentence. &lt;code&gt;LT265/70R17 121/118S E&lt;/code&gt; carries a dual load index — &lt;code&gt;121&lt;/code&gt; for single-wheel fitment, &lt;code&gt;118&lt;/code&gt; per tire when mounted as duals on one axle end — plus a load range letter (&lt;code&gt;E&lt;/code&gt;) that encodes ply rating and inflation ceiling. Flotation sizes abandon the metric pattern entirely: &lt;code&gt;35x12.50R17&lt;/code&gt; states overall diameter in inches, then section width in inches, then construction and rim. Three families, three grammars, one input field on your intake form.&lt;/p&gt;

&lt;p&gt;Anyone who wants the customer-facing version of this decoding can read the &lt;a href="https://calgaryrimandtire.ca/tire-sidewall-information" rel="noopener noreferrer"&gt;sidewall markings walkthrough&lt;/a&gt; we keep for drivers; this article is the engineering companion to that page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Families of Size, One Input Field
&lt;/h2&gt;

&lt;p&gt;Before writing any code, pin down the three concrete syntaxes you must accept.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metric (P-metric and Euro-metric).&lt;/strong&gt; &lt;code&gt;P215/60R16 94H&lt;/code&gt;, &lt;code&gt;215/60R16 94H&lt;/code&gt;, &lt;code&gt;215/60ZR16&lt;/code&gt;, &lt;code&gt;215/60R16 94H XL&lt;/code&gt;. The &lt;code&gt;P&lt;/code&gt; prefix is optional and its absence changes load calculation standards (Euro-metric tires at the same size often carry slightly different maximum loads). &lt;code&gt;XL&lt;/code&gt; or &lt;code&gt;RF&lt;/code&gt; marks reinforced/extra-load casings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LT-metric.&lt;/strong&gt; &lt;code&gt;LT265/70R17 121/118S E&lt;/code&gt;. Prefix mandatory, dual load index common, load range letter (&lt;code&gt;C&lt;/code&gt;, &lt;code&gt;D&lt;/code&gt;, &lt;code&gt;E&lt;/code&gt;, &lt;code&gt;F&lt;/code&gt;) frequently appended. These dominate the three-quarter-ton pickups that fill Calgary driveways, and they matter commercially because &lt;a href="https://calgaryrimandtire.ca/commercial-tire-services" rel="noopener noreferrer"&gt;commercial tire work&lt;/a&gt; is disproportionately LT-metric.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flotation.&lt;/strong&gt; &lt;code&gt;35x12.50R17LT 121Q&lt;/code&gt;. Diameter-first, inches throughout, &lt;code&gt;LT&lt;/code&gt; sometimes trailing the rim diameter rather than prefixing the string. The decimal in &lt;code&gt;12.50&lt;/code&gt; is load-bearing syntax: &lt;code&gt;12.5&lt;/code&gt; and &lt;code&gt;12.50&lt;/code&gt; must normalize to the same value.&lt;/p&gt;

&lt;p&gt;A grammar in EBNF-ish form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;size        := metric | lt_metric | flotation
metric      := [prefix] width "/" aspect construction rim [service_desc]
lt_metric   := "LT" width "/" aspect construction rim [dual_service] [load_range]
flotation   := diameter "x" fwidth construction rim ["LT"] [service_desc]
prefix      := "P" | "T" | "ST"
construction:= ["Z"] ("R" | "D" | "B")
service_desc:= load_index [ "/" load_index ] speed_symbol ["XL" | "RF"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That sketch is already more honest than a regex, because it names the alternatives instead of burying them in &lt;code&gt;(?:...)?&lt;/code&gt; groups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the One-Regex Approach Collapses
&lt;/h2&gt;

&lt;p&gt;The naive implementation looks like this, and versions of it exist in production at plenty of companies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;NAIVE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;([A-Z]*)(\d{3})/(\d{2})R(\d{2})\s*(\d{2,3})([A-Z])&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enumerate its failures against genuine intake strings (illustrative examples drawn from the kinds of input any tire retailer sees, not a statistical sample):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Optional everything.&lt;/strong&gt; &lt;code&gt;215/60R16&lt;/code&gt; with no service description is a valid size. The regex demands load index and speed symbol.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual load index.&lt;/strong&gt; &lt;code&gt;121/118S&lt;/code&gt; breaks the single &lt;code&gt;(\d{2,3})&lt;/code&gt; group, and a greedy fix silently swallows the second index into the aspect of a mangled parse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ZR markers.&lt;/strong&gt; &lt;code&gt;245/40ZR18 97Y&lt;/code&gt; puts a &lt;code&gt;Z&lt;/code&gt; before the &lt;code&gt;R&lt;/code&gt;. Patterns anchored on a bare &lt;code&gt;R&lt;/code&gt; either miss it or mis-split the aspect ratio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flotation.&lt;/strong&gt; &lt;code&gt;35x12.50R17&lt;/code&gt; shares almost no surface syntax with metric sizes. One regex covering both families becomes unreadable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human separators.&lt;/strong&gt; Web forms deliver &lt;code&gt;215-60-16&lt;/code&gt;, &lt;code&gt;215 60 16&lt;/code&gt;, &lt;code&gt;215/60/16&lt;/code&gt;, &lt;code&gt;215/60-R16&lt;/code&gt;, and the gloriously ambiguous &lt;code&gt;2156016&lt;/code&gt;. A rigid pattern rejects all of them, and rejected input at intake means a person re-keys data, which is where transcription errors breed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Case and whitespace.&lt;/strong&gt; &lt;code&gt;p215/60r16&lt;/code&gt; from a phone keyboard, tabs from spreadsheet paste, trailing &lt;code&gt;M+S&lt;/code&gt; or &lt;code&gt;3PMSF&lt;/code&gt; badges the customer helpfully included.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three-digit aspect and two-digit width don't exist — but 82-series legacy sizes and rim diameters like &lt;code&gt;22.5&lt;/code&gt; (heavy truck) do.&lt;/strong&gt; Half-inch rim diameters destroy &lt;code&gt;(\d{2})&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The failure mode that hurts most is not rejection — it is a &lt;strong&gt;confident wrong parse&lt;/strong&gt;. &lt;code&gt;LT265/70R17 121/118S&lt;/code&gt; parsed by a greedy single-index pattern can yield load index &lt;code&gt;121&lt;/code&gt; and drop &lt;code&gt;118S&lt;/code&gt; on the floor, and the record now overstates nothing but silently loses the dual rating that a dually fitment decision needs. Wrong-but-plausible data is worse than an error, which is the core argument for a real tokenizer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lexing the Sidewall: Tokens Before Meaning
&lt;/h2&gt;

&lt;p&gt;Treat the input the way a compiler treats source: normalize, tokenize, then parse. The tokenizer's only job is to slice the string into typed chunks without deciding what they mean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Iterator&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;      &lt;span class="c1"&gt;# NUM, DEC, SEP, X, CONSTRUCT, WORD
&lt;/span&gt;    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

&lt;span class="n"&gt;TOKEN_SPEC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\d{1,3}\.\d{1,2}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;          &lt;span class="c1"&gt;# 12.50, 22.5
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\d{1,4}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                    &lt;span class="c1"&gt;# 215, 60, 16, 94, 121
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[xX×]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                      &lt;span class="c1"&gt;# flotation separator
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CONSTRUCT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ZR|R|D|B(?![A-Z])&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;          &lt;span class="c1"&gt;# construction markers
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SEP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[/\-\s]+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                   &lt;span class="c1"&gt;# slash, dash, spaces
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WORD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[A-Z]{1,5}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                 &lt;span class="c1"&gt;# P, LT, XL, S, E, TL...
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;MASTER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(?P&amp;lt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TOKEN_SPEC&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Iterator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;×&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;MASTER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finditer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastgroup&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SEP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SEP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two deliberate choices deserve comment. First, &lt;code&gt;DEC&lt;/code&gt; outranks &lt;code&gt;NUM&lt;/code&gt; in the alternation so &lt;code&gt;12.50&lt;/code&gt; never fractures into &lt;code&gt;12&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;50&lt;/code&gt;. Second, every separator collapses to a canonical &lt;code&gt;/&lt;/code&gt;, which is how &lt;code&gt;215-60-16&lt;/code&gt; and &lt;code&gt;215 60 16&lt;/code&gt; stop being special cases before the parser ever sees them.&lt;/p&gt;

&lt;p&gt;There is a subtlety in &lt;code&gt;CONSTRUCT&lt;/code&gt; versus &lt;code&gt;WORD&lt;/code&gt;: the letter &lt;code&gt;R&lt;/code&gt; is both a construction marker and a possible fragment of a word token. Resolving that requires context, and context is the parser's job, not the lexer's. The lexer emits &lt;code&gt;CONSTRUCT&lt;/code&gt; greedily and the parser is written to re-interpret when the grammar says a construction marker cannot appear at that position. This division of labour is exactly why the two-stage design stays maintainable while the one-regex design rots.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Recursive-Descent Parser for Three Grammars
&lt;/h2&gt;

&lt;p&gt;With tokens in hand, the parser is a small recursive-descent routine with one lookahead decision: does the stream look flotation-shaped (&lt;code&gt;NUM/DEC&lt;/code&gt;, &lt;code&gt;X&lt;/code&gt;, ...) or metric-shaped (&lt;code&gt;WORD?&lt;/code&gt;, &lt;code&gt;NUM&lt;/code&gt;, &lt;code&gt;SEP&lt;/code&gt;, ...)?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;                       &lt;span class="c1"&gt;# "metric" | "lt_metric" | "flotation"
&lt;/span&gt;    &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;      &lt;span class="c1"&gt;# P, LT, T, ST
&lt;/span&gt;    &lt;span class="n"&gt;section_width_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;aspect_ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;overall_diameter_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;   &lt;span class="c1"&gt;# flotation only
&lt;/span&gt;    &lt;span class="n"&gt;section_width_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;      &lt;span class="c1"&gt;# flotation only
&lt;/span&gt;    &lt;span class="n"&gt;construction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;zr_marker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;rim_diameter_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="n"&gt;load_index_single&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;load_index_dual&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;speed_symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;load_range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# C, D, E, F
&lt;/span&gt;    &lt;span class="n"&gt;reinforced&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;          &lt;span class="c1"&gt;# XL / RF
&lt;/span&gt;    &lt;span class="n"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SizeParseError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SizeParser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SEP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;want&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;any token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SizeParseError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; at position &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;skip_seps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SEP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;skip_seps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;looks_flotation&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_flotation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_metric_family&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;looks_flotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
            &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The metric branch shows how optional prefixes and dual indexes are handled without regex contortions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_metric_family&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metric&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WORD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;P&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;family&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lt_metric&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;section_width_mm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;skip_seps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aspect_ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;skip_seps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CONSTRUCT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zr_marker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;construction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;construction marker missing; assumed R&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;rim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SizeParseError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rim diameter expected after construction&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rim_diameter_in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_service_description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_service_description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;skip_seps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;nxt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nxt&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;nxt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_index_single&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;skip_seps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_index_dual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;skip_seps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WORD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CONSTRUCT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;XL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reinforced&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;D&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;E&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;F&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;family&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lt_metric&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed_symbol&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed_symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unrecognized suffix token: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the re-interpretation trick: in service-description position, a &lt;code&gt;CONSTRUCT&lt;/code&gt; token like a stray &lt;code&gt;R&lt;/code&gt; (a speed symbol!) or &lt;code&gt;D&lt;/code&gt; (a load range!) is consumed as a word. The lexer's greedy guess gets corrected by grammatical context. &lt;code&gt;245/40ZR18&lt;/code&gt; sets &lt;code&gt;zr_marker=True&lt;/code&gt; and &lt;code&gt;construction="R"&lt;/code&gt;, preserving the historical marker without letting it pollute the structural fields.&lt;/p&gt;

&lt;p&gt;The flotation branch is short because its shape is rigid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_flotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flotation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;overall_diameter_in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;section_width_in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CONSTRUCT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;construction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rim_diameter_in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;nxt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nxt&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;nxt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WORD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;nxt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_service_description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrap the pipeline in one function and every call site stays clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;SizeParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;))).&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;P215/60R16 94H&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# metric, LI 94, speed H
&lt;/span&gt;&lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lt265/70r17 121/118S E&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# lt_metric, dual LI, load range E
&lt;/span&gt;&lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;35x12.50R17LT 121Q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# flotation
&lt;/span&gt;&lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;215 60 16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;               &lt;span class="c1"&gt;# metric with warnings, no service desc
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run-Together Digits and the Ambiguity Budget
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;2156016&lt;/code&gt; is the intake string that teaches humility. Seven digits, no separators, typed by someone reading their own sidewall in a parkade. A parser cannot split it purely syntactically — but it can generate candidates and score them against physical plausibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;VALID_WIDTHS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;455&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;145&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;155&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;165&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# metric widths
&lt;/span&gt;&lt;span class="n"&gt;VALID_ASPECTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;82&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;VALID_RIMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;split_run_together&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Yield plausible (width, aspect, rim) splits of an unseparated digit run.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;wi&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,):&lt;/span&gt;
            &lt;span class="n"&gt;ri_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;wi&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ri_len&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,):&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;wi&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;wi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;wi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;wi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;VALID_WIDTHS&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;VALID_ASPECTS&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;VALID_RIMS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;yield &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;split_run_together&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2156016&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# [(215, 60, 16)]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For seven digits the split is usually unique; six digits (&lt;code&gt;2256016&lt;/code&gt; minus a digit, say &lt;code&gt;225616&lt;/code&gt;) can be genuinely ambiguous, and the correct system behaviour is to &lt;em&gt;refuse to guess silently&lt;/em&gt;. Return all candidates, force a human confirmation at intake, and record which candidate was chosen. The design principle: spend your ambiguity budget on asking one good question rather than on being wrong quietly. The same principle underlies the plain-language advice on our &lt;a href="https://calgaryrimandtire.ca/be-tire-smart" rel="noopener noreferrer"&gt;be-tire-smart explainer&lt;/a&gt; — when a marking is unclear, verify before acting on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Normalized Fitment Model
&lt;/h2&gt;

&lt;p&gt;Parsing produces a struct; a business needs entities. The model that has held up for us separates five concerns that tutorials usually mash into one table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;tire_size&lt;/code&gt;&lt;/strong&gt; — the pure geometric designation, deduplicated. &lt;code&gt;215/60R16&lt;/code&gt; is one row no matter how many products or vehicles reference it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;service_rating&lt;/code&gt;&lt;/strong&gt; — load index (single and dual), speed symbol, load range, reinforced flag. Kept apart from size because one geometric size ships with many ratings: &lt;code&gt;215/60R16&lt;/code&gt; exists as &lt;code&gt;94H&lt;/code&gt;, &lt;code&gt;95H XL&lt;/code&gt;, &lt;code&gt;99T&lt;/code&gt; winter, and conflating them is precisely how unsafe substitutions sneak into records.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;vehicle_fitment&lt;/code&gt;&lt;/strong&gt; — vehicle (year/make/model/trim) joined to an OE (original equipment) size-and-rating pair, with a flag for front/rear staggered setups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;fitment_option&lt;/code&gt;&lt;/strong&gt; — approved alternatives per vehicle: plus-size and minus-size equivalents with computed diameter deviation. Minus-sizing shows up constantly in winter: many drivers run a smaller rim with a taller sidewall on their &lt;a href="https://calgaryrimandtire.ca/winter-tires-calgary" rel="noopener noreferrer"&gt;winter set&lt;/a&gt; for pothole resilience and narrower contact patch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. &lt;code&gt;intake_record&lt;/code&gt;&lt;/strong&gt; — the raw string as typed, the parse result, parser warnings, and the resolved &lt;code&gt;tire_size&lt;/code&gt; id. Never discard the raw input; it is your regression-test corpus and your audit trail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schema Sketch in SQL
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tire_size&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;              &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;family&lt;/span&gt;          &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;family&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'metric'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'lt_metric'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'flotation'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="k"&gt;prefix&lt;/span&gt;          &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;section_width_mm&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aspect_ratio&lt;/span&gt;    &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;diameter_in&lt;/span&gt;     &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- flotation overall diameter&lt;/span&gt;
    &lt;span class="n"&gt;width_in&lt;/span&gt;        &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- flotation section width&lt;/span&gt;
    &lt;span class="n"&gt;construction&lt;/span&gt;    &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'R'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rim_diameter_in&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;canonical_text&lt;/span&gt;  &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;service_rating&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;              &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;size_id&lt;/span&gt;         &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;tire_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;load_index&lt;/span&gt;      &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;load_index_dual&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;speed_symbol&lt;/span&gt;    &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;load_range&lt;/span&gt;      &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;reinforced&lt;/span&gt;      &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;load_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;load_index_dual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speed_symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;load_range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reinforced&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;vehicle_fitment&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;              &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vehicle_key&lt;/span&gt;     &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- e.g. '2019|toyota|rav4|xle'&lt;/span&gt;
    &lt;span class="n"&gt;axle&lt;/span&gt;            &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'all'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- 'all' | 'front' | 'rear'&lt;/span&gt;
    &lt;span class="n"&gt;oe_rating_id&lt;/span&gt;    &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;service_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;fitment_option&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;              &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;fitment_id&lt;/span&gt;      &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;vehicle_fitment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;rating_id&lt;/span&gt;       &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;service_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt;            &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'oe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'plus1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'plus2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'minus1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'winter_alt'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;diam_deviation_pct&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;intake_record&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;              &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;received_at&lt;/span&gt;     &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;raw_text&lt;/span&gt;        &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;channel&lt;/span&gt;         &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- 'web_form','counter','phone','mobile_unit'&lt;/span&gt;
    &lt;span class="n"&gt;parse_ok&lt;/span&gt;        &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;parse_warnings&lt;/span&gt;  &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;resolved_rating_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;service_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;resolved_by&lt;/span&gt;     &lt;span class="nb"&gt;TEXT&lt;/span&gt;                    &lt;span class="c1"&gt;-- 'auto' | 'staff_confirmed'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;canonical_text&lt;/code&gt; uniqueness constraint is the workhorse. Every parse serializes back to one canonical string, and the database — not application code — enforces that &lt;code&gt;215/60 R 16&lt;/code&gt;, &lt;code&gt;215-60-16&lt;/code&gt;, and &lt;code&gt;P215/60R16&lt;/code&gt; collapse toward the intended identity (prefix retained where it is semantically meaningful, since P-metric and Euro-metric load standards differ).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;family&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flotation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;overall_diameter_in&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;section_width_in&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;construction&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rim_diameter_in&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zr_marker&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;section_width_mm&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aspect_ratio&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;construction&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rim_diameter_in&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;svc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_index_single&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;svc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_index_single&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_index_dual&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;svc&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_index_dual&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed_symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;svc&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed_symbol&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;svc&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_range&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reinforced&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;svc&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; XL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Round-tripping — &lt;code&gt;canonical(parse_size(canonical(parse_size(x))))&lt;/code&gt; equals &lt;code&gt;canonical(parse_size(x))&lt;/code&gt; — is a property test worth wiring into CI on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overall Diameter: The Equivalence Math
&lt;/h2&gt;

&lt;p&gt;Two sizes are interchangeable candidates only when their overall diameters land close together, because diameter drives speedometer accuracy, gearing, drivetrain stress on AWD systems, and body clearance. The formula for metric sizes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sidewall_height_mm = section_width_mm × (aspect_ratio / 100)
overall_diameter_mm = rim_diameter_in × 25.4 + 2 × sidewall_height_mm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In code, with flotation handled in the same function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;overall_diameter_mm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;family&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flotation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;overall_diameter_in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;25.4&lt;/span&gt;
    &lt;span class="n"&gt;sidewall&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;section_width_mm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aspect_ratio&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rim_diameter_in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;25.4&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sidewall&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deviation_pct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;overall_diameter_mm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;overall_diameter_mm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worked example — a plus-one conversion, numbers shown in full so you can check the arithmetic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reference: 205/55R16
  sidewall = 205 × 0.55           = 112.75 mm
  diameter = 16 × 25.4 + 2×112.75 = 406.40 + 225.50 = 631.90 mm  (24.88 in)

Candidate: 225/45R17
  sidewall = 225 × 0.45           = 101.25 mm
  diameter = 17 × 25.4 + 2×101.25 = 431.80 + 202.50 = 634.30 mm  (24.97 in)

Deviation = (634.30 − 631.90) / 631.90 = +0.38 %   → excellent match
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a flotation-to-metric comparison, which the unified function makes one-line work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;35x12.50R17         → 35 × 25.4              = 889.00 mm
Nearest LT-metric: LT315/70R17
  sidewall = 315 × 0.70            = 220.50 mm
  diameter = 431.80 + 441.00       = 872.80 mm
Deviation = (872.80 − 889.00) / 889.00 = −1.82 %   → acceptable, flag for clearance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Revolutions per kilometre fall out of the same value and matter for odometer-sensitive fleet records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;revs_per_km&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;1_000_000.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;overall_diameter_mm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# 205/55R16 → 1,000,000 / (π × 631.90) ≈ 503.8 rev/km
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A practical threshold set, encoded as policy rather than folklore: deviation within ±1.5 % auto-approves as an equivalence candidate, ±1.5–3 % flags for human review with a clearance note, beyond ±3 % rejects. Those bands are a policy choice for our operation, not a universal standard — the point is that they live in one table, not scattered through &lt;code&gt;if&lt;/code&gt; statements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load Index and Speed Symbol: Small Tables, Big Consequences
&lt;/h2&gt;

&lt;p&gt;Load index is a lookup, not a formula. A slice of the standardized table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;LOAD_INDEX_KG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;560&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;89&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;580&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;615&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;93&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;650&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;94&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;670&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;690&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;710&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;97&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;730&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;98&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;750&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;775&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1060&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;116&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1320&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1450&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;SPEED_KMH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;P&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;170&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;190&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;U&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;H&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;V&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;W&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;270&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two consequences follow. First, load index &lt;code&gt;94&lt;/code&gt; means 670 kg per tire, so an OE spec of &lt;code&gt;94H&lt;/code&gt; on a crossover establishes a floor: a candidate rated &lt;code&gt;91&lt;/code&gt; (615 kg) carries 55 kg less per corner than the manufacturer engineered for, and that difference compounds under a roof box, five passengers, and a Highway 2 grade. The plain-English version of this lives on our &lt;a href="https://calgaryrimandtire.ca/tire-load-index-explained" rel="noopener noreferrer"&gt;load index explainer&lt;/a&gt;; the systems version is a hard validation rule below. Second, speed symbols are ordered but not alphabetically — &lt;code&gt;H&lt;/code&gt; sits between &lt;code&gt;U&lt;/code&gt; and &lt;code&gt;V&lt;/code&gt; for historical reasons — so comparisons must go through the table, never through &lt;code&gt;ord()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Dual-index LT ratings add a wrinkle worth encoding: &lt;code&gt;121/118S&lt;/code&gt; means 1450 kg in single fitment but 1320 kg per tire when run as duals, because heat dissipation between paired tires is worse. If your model stores only the single figure, dually capacity math silently overstates by roughly 10 %.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation Rules That Refuse Dangerous Fitments
&lt;/h2&gt;

&lt;p&gt;Validation is where the data model pays rent. Every quote line and every changeover record passes through a rule engine before a size is accepted against a vehicle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Severity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;BLOCK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;block&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;REVIEW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;NOTE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_fitment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ParsedSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="n"&gt;li_c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;li_o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_index_single&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_index_single&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;li_c&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;li_o&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;li_c&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;li_o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BLOCK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;load index &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;li_c&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; below OE spec &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;li_o&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;LOAD_INDEX_KG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;li_c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; kg vs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;LOAD_INDEX_KG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;li_o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; kg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed_symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed_symbol&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SPEED_KMH&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SPEED_KMH&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;SPEED_KMH&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;SPEED_KMH&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;sev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REVIEW&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;winter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BLOCK&lt;/span&gt;
        &lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;sev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;speed rating downgrade &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;→&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SPEED_KMH&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;→&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SPEED_KMH&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; km/h)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deviation_pct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BLOCK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;diameter deviation &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% exceeds 3%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REVIEW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;diameter deviation &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%; check clearance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;oe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;family&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lt_metric&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;family&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metric&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BLOCK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;P-metric candidate against LT OE spec; derate rules not satisfied&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;findings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The winter carve-out on speed downgrades reflects a widely accepted industry convention: dedicated winter tires are commonly permitted one step below the OE speed symbol because winter compounds rarely reach the highest ratings, and the trade is disclosed to the driver. Encoding it as &lt;code&gt;REVIEW&lt;/code&gt; rather than auto-pass keeps a human in that disclosure loop. The LT-versus-P-metric rule guards the opposite direction: passenger-metric load figures cannot be compared one-to-one against light-truck specs without derating math, so the safe default is refusal.&lt;/p&gt;

&lt;p&gt;What the engine deliberately does not do is silently "fix" anything. A blocked fitment produces a human-readable finding that a staff member resolves with the customer — usually by steering toward the correct spec on our &lt;a href="https://calgaryrimandtire.ca/buying-tires-in-calgary" rel="noopener noreferrer"&gt;guide to buying tires in Calgary&lt;/a&gt; terms: match or exceed OE load, respect the diameter envelope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fuzzy Matching the Typos People Actually Make
&lt;/h2&gt;

&lt;p&gt;Real intake text contains a small, learnable set of corruption patterns (illustrative catalogue, not a frequency claim):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Character confusions: &lt;code&gt;O&lt;/code&gt;↔&lt;code&gt;0&lt;/code&gt;, &lt;code&gt;l&lt;/code&gt;↔&lt;code&gt;1&lt;/code&gt;, &lt;code&gt;S&lt;/code&gt;↔&lt;code&gt;5&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt;↔&lt;code&gt;8&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Transpositions: &lt;code&gt;215/06R16&lt;/code&gt; for &lt;code&gt;215/60R16&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Dropped construction letter: &lt;code&gt;215/6016&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Slash-for-R: &lt;code&gt;225/60/16&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Star-for-x in flotation: &lt;code&gt;35*12.5*17&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Unit smuggling: &lt;code&gt;215/60R16"&lt;/code&gt; with a stray inch mark.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A correction layer sits in front of the tokenizer and proposes, never imposes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;CONFUSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maketrans&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;O&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;l&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prenormalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CONFUSIONS&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(\d{3})/(\d{2})/(\d{2}(?:\.5)?)$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\1/\2R\3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# slash-for-R
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;levenshtein&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
    &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ca&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ca&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;suggest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;max_dist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;prenormalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;scored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;levenshtein&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scored&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;max_dist&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The catalog is not "all syntactically valid sizes" — it is the finite set of sizes that physically exist in the market plus everything previously confirmed in &lt;code&gt;intake_record&lt;/code&gt;. That last part matters: aspect ratio &lt;code&gt;06&lt;/code&gt; is syntactically fine and physically absurd, and a market catalog rejects it while pure grammar cannot. Suggestions above edit distance 2 are discarded because a wrong confident suggestion is the same disease as a wrong confident parse. When nothing scores, the record routes to staff, exactly like an ambiguous digit run.&lt;/p&gt;

&lt;p&gt;Transposition detection earns a special case. Damerau-Levenshtein (which counts adjacent swaps as one edit) treats &lt;code&gt;215/06R16 → 215/60R16&lt;/code&gt; as distance 1, and combined with the plausibility check — &lt;code&gt;06&lt;/code&gt; is not a valid aspect ratio, &lt;code&gt;60&lt;/code&gt; is — the system can rank that correction first with high confidence while still routing it through confirmation.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a Small Operation Actually Uses This
&lt;/h2&gt;

&lt;p&gt;Concrete flows, because architecture without usage is decoration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quote intake.&lt;/strong&gt; A driver submits a size through the web form or the &lt;a href="https://calgaryrimandtire.ca/online-bookings" rel="noopener noreferrer"&gt;online request page&lt;/a&gt;. The string hits &lt;code&gt;prenormalize → tokenize → parse → validate&lt;/code&gt;, and one of three things happens: a clean parse attaches a &lt;code&gt;service_rating&lt;/code&gt; id to the quote; a parse-with-warnings shows staff the raw string beside the interpretation for one-click confirmation; a failed parse presents fuzzy suggestions. The measurable win is that staff review interpretations instead of re-keying strings, and every confirmation enriches the catalog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seasonal changeover records.&lt;/strong&gt; Calgary compresses tire logistics into two brutal windows — the October scramble onto winter rubber and the April–May return to three-season sets, the annual rhythm described on our &lt;a href="https://calgaryrimandtire.ca/seasonal-tire-changes" rel="noopener noreferrer"&gt;seasonal changeover page&lt;/a&gt;. During those weeks, the interesting data problem is that each customer has &lt;em&gt;two&lt;/em&gt; recorded sets, and the model must answer: which set is on the vehicle, which is in the customer's garage, and do the two sets agree with the vehicle's OE envelope? Storing both as &lt;code&gt;service_rating&lt;/code&gt; references against the same &lt;code&gt;vehicle_fitment&lt;/code&gt; row makes the winter/summer diff a query, not a phone conversation. Minus-one winter setups are the norm here, so the &lt;code&gt;fitment_option.kind = 'winter_alt'&lt;/code&gt; rows with their precomputed diameter deviations do real work every October.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mixed-set detection.&lt;/strong&gt; When a &lt;a href="https://calgaryrimandtire.ca/mobile-tire-service" rel="noopener noreferrer"&gt;mobile unit&lt;/a&gt; records sizes wheel-by-wheel at a customer's driveway, four independent parses land per vehicle. A trivial &lt;code&gt;GROUP BY&lt;/code&gt; catches the vehicle running &lt;code&gt;215/60R16&lt;/code&gt; on three corners and &lt;code&gt;205/60R16&lt;/code&gt; on the fourth — a mismatch the owner usually inherited from a roadside change and never noticed. Same parser, zero extra code, genuinely useful safety catch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fleet records.&lt;/strong&gt; For &lt;a href="https://calgaryrimandtire.ca/fleet-management" rel="noopener noreferrer"&gt;fleet accounts&lt;/a&gt;, &lt;code&gt;revs_per_km&lt;/code&gt; from the diameter function reconciles hub odometer readings across mixed LT-metric and flotation fitments, and dual-index handling stops dually capacity from being overstated. None of this requires the operation to be large; it requires the model to be honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge Cases From the Intake Pile
&lt;/h2&gt;

&lt;p&gt;A grab bag of real-shaped inputs (again illustrative, anonymized to their structure) and how the pipeline handles each:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;225/65R17 102H 102H&lt;/code&gt; — the customer pasted the rating twice. The parser reads the second &lt;code&gt;102&lt;/code&gt; as a dual index, then a plausibility rule fires: dual index on a non-LT family is a warning, and equal single/dual values collapse to single with a note.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;235/55R19 105V XL (front) 255/50R19 107V XL (rear)&lt;/code&gt; — a staggered fitment in one field. The tokenizer's leftover-token count tells the parser input remains after a complete parse; the pipeline re-enters and emits two records tagged front/rear from the parenthetical words.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;205/55R16 91H M+S&lt;/code&gt; — &lt;code&gt;M+S&lt;/code&gt; is an all-season mud-and-snow badge, not part of the size grammar. It lands in &lt;code&gt;warnings&lt;/code&gt; and maps to a season attribute, which is exactly the metadata that distinguishes a three-season set on our &lt;a href="https://calgaryrimandtire.ca/all-season-tires-calgary" rel="noopener noreferrer"&gt;all-season overview&lt;/a&gt; from a mountain-snowflake-rated one covered by the &lt;a href="https://calgaryrimandtire.ca/all-weather-tires-calgary" rel="noopener noreferrer"&gt;all-weather overview&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LT235/85R16 120/116Q LRE&lt;/code&gt; — &lt;code&gt;LRE&lt;/code&gt; is "load range E" glued into one token. A tiny suffix rewrite (&lt;code&gt;LR([C-F]) → \1&lt;/code&gt;) in prenormalization handles the whole family of glued load ranges.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;31x10.5R15&lt;/code&gt; versus &lt;code&gt;31x10.50R15&lt;/code&gt; — decimal-width normalization; both canonicalize to &lt;code&gt;31x10.50R15&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;275/65R18 en hiver&lt;/code&gt; — French annotations appear in Alberta intake more often than you'd guess. Unknown words fall into &lt;code&gt;warnings&lt;/code&gt; and never corrupt structural fields; the parse succeeds.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;225,60,16&lt;/code&gt; — comma separators from European-formatted spreadsheets. One character class addition to &lt;code&gt;SEP&lt;/code&gt;, plus a guard so commas inside digit groups (&lt;code&gt;1,450 kg&lt;/code&gt;) don't split numbers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every one of these started life as a parser bug. The &lt;code&gt;intake_record&lt;/code&gt; table is why they stopped being bugs: raw strings became regression fixtures the same day they were mis-handled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Property Tests Beat Example Tests Here
&lt;/h2&gt;

&lt;p&gt;Example-based tests freeze yesterday's bugs; property-based tests hunt tomorrow's. Three properties carry most of the weight:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hypothesis&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategies&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;

&lt;span class="n"&gt;widths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sampled_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VALID_WIDTHS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;aspects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sampled_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VALID_ASPECTS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;rims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sampled_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VALID_RIMS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nd"&gt;@given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rims&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_roundtrip_is_stable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;R&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;once&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;twice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;once&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;once&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;twice&lt;/span&gt;

&lt;span class="nd"&gt;@given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sampled_from&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_separator_invariance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;messy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;clean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;R&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messy&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nd"&gt;@given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rims&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_diameter_positive_and_sane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;R&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;overall_diameter_mm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mf"&gt;450.0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1400.0&lt;/span&gt;   &lt;span class="c1"&gt;# physical envelope for road tires
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The separator-invariance property alone caught more regressions than the entire example suite, because every tokenizer refactor risks quietly changing how junk whitespace is folded. A fourth suite fuzzes pure garbage — emoji, SQL fragments, 10 kB strings — asserting only that the parser raises &lt;code&gt;SizeParseError&lt;/code&gt; cleanly rather than looping or mis-parsing. Hostile-input discipline is cheap insurance for anything wired to a public form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundaries: What This Model Refuses to Know
&lt;/h2&gt;

&lt;p&gt;A data model is defined by its exclusions as much as its entities, and ours excludes anything beyond tire and lubricant work, because that is the entire service surface at &lt;a href="https://calgaryrimandtire.ca/calgarys-local-tire-shop" rel="noopener noreferrer"&gt;our storefront&lt;/a&gt;. There is no suspension geometry table, no brake-clearance calculator for oversized fitments, no steering-angle model — when a plus-two fitment raises clearance questions on lowered vehicles, the finding text tells the customer to verify with a mechanical specialist, full stop. Encoding your operational boundary into the schema keeps software from promising what the business doesn't perform, and it also keeps the model small enough that one developer can hold it in their head.&lt;/p&gt;

&lt;p&gt;The parser itself has boundaries too. It does not attempt speed-symbol inference from ZR markers (a &lt;code&gt;ZR&lt;/code&gt; with no explicit symbol stores &lt;code&gt;speed_symbol=None&lt;/code&gt; plus a warning, because guessing &lt;code&gt;Y&lt;/code&gt; versus &lt;code&gt;W&lt;/code&gt; is a 30 km/h error). It does not auto-resolve P-metric versus Euro-metric load standards; it stores the prefix and lets the rating table carry the truth. And it never deletes a raw input string, ever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways for Anyone Building One of These
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Model the designation as a grammar with three families; the moment you write &lt;code&gt;metric | lt_metric | flotation&lt;/code&gt; as branches, ZR markers and dual load indexes become local problems instead of regex-wide ones.&lt;/li&gt;
&lt;li&gt;Lex first, parse second, and let grammatical context re-interpret ambiguous tokens like a service-position &lt;code&gt;R&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keep geometric size and service rating as separate entities; every dangerous substitution I've seen traces back to conflating them.&lt;/li&gt;
&lt;li&gt;Make canonical text a database constraint and round-tripping a CI property.&lt;/li&gt;
&lt;li&gt;Do the diameter math in one function that spans families, and put approval thresholds in data.&lt;/li&gt;
&lt;li&gt;Validate with severities — block, review, note — and never silently repair safety-relevant fields.&lt;/li&gt;
&lt;li&gt;Constrain fuzzy suggestions with a physical-market catalog and route low confidence to humans.&lt;/li&gt;
&lt;li&gt;Hoard raw intake strings; they are the best test corpus your system will ever get for free.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole apparatus — lexer, parser, schema, validators, fuzzers — is maybe twelve hundred lines. Small enough for a tire-and-oil operation in Calgary, rigorous enough that a mis-typed aspect ratio gets caught before it becomes a wrong set of tires on a real vehicle driving down Deerfoot in January. That trade is the entire reason to treat a sidewall string as a language instead of a product code.&lt;/p&gt;

</description>
      <category>automotive</category>
      <category>calgary</category>
      <category>maintenance</category>
      <category>tires</category>
    </item>
    <item>
      <title>Designing a Tire-Wear Image Evidence Pipeline: Capture Protocols, Metadata Schemas, Quality Scoring, and Audit Trails</title>
      <dc:creator>KMJ Tire Calgary</dc:creator>
      <pubDate>Tue, 04 Aug 2026 16:10:50 +0000</pubDate>
      <link>https://dev.to/kmjtirecalgary/designing-a-tire-wear-image-evidence-pipeline-capture-protocols-metadata-schemas-quality-f9l</link>
      <guid>https://dev.to/kmjtirecalgary/designing-a-tire-wear-image-evidence-pipeline-capture-protocols-metadata-schemas-quality-f9l</guid>
      <description>&lt;h1&gt;
  
  
  Designing a Tire-Wear Image Evidence Pipeline: Capture Protocols, Metadata Schemas, Quality Scoring, and Audit Trails
&lt;/h1&gt;

&lt;p&gt;Photographs of tire wear often enter an operations system as informal attachments: a few phone images, a vehicle identifier, and perhaps a short note. That is enough for a human who was present, but it is weak evidence for someone reviewing the record weeks later. The image may be sharp yet show the wrong wheel. A useful close-up may have lost its relationship to the vehicle overview. Compression may erase small cracks. A file timestamp may describe an export rather than capture. If a computer-vision project later consumes that archive, hidden inconsistency becomes labelled-data debt.&lt;/p&gt;

&lt;p&gt;This article designs a photographic evidence pipeline rather than a tread-depth measurement system. Its job is to establish what was photographed, when and how it was captured, whether the pixels are usable, who changed each interpretation, and which facts remain uncertain. The design deliberately keeps image observations separate from service decisions. KMJ Tire performs tire services and oil changes; a photograph suggesting a non-tire mechanical concern should be documented and referred to an appropriate mechanical provider rather than represented as work KMJ performs.&lt;/p&gt;

&lt;p&gt;Calgary makes this problem interesting. Chinook-driven thaw and refreeze cycles change what is visible on a tire. Road salt leaves pale films. Gravel season adds dust and small stones. A vehicle arriving from Deerfoot Trail may carry wet spray, while one returning from Highway 1 west can arrive with packed snow or mountain-road grime. An evidence pipeline must record these conditions instead of pretending every image came from a clean laboratory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start With an Evidence Contract, Not a Camera App
&lt;/h2&gt;

&lt;p&gt;The first engineering artifact should be a short evidence contract. It states what the pipeline can prove and what it cannot. A defensible contract might promise that every accepted image is attached to one inspection session, retains the original bytes, records capture context, receives automated quality measurements, and preserves reviewer actions. It should not promise that a photo alone determines tire safety, proves a root cause, or substitutes for an in-person assessment.&lt;/p&gt;

&lt;p&gt;Model the session as the unit of work. A session belongs to a vehicle or fleet asset, but the identifier should be internal and minimally revealing. Within it, create wheel-position records, capture attempts, accepted originals, derived assets, automated checks, observations, and review events. That hierarchy prevents the familiar folder problem in which &lt;code&gt;IMG_4832.jpg&lt;/code&gt; sits beside a note saying “rear tire” with no reliable indication of which rear position was intended.&lt;/p&gt;

&lt;p&gt;The capture protocol needs required views. An operational baseline can include a vehicle-context frame, one wheel-context frame for each inspected position, a straight-on tread overview, and focused detail frames for notable areas. The protocol should allow extra images without treating quantity as quality. Ten nearly identical blurry frames are worse than four deliberate views with clear relationships.&lt;/p&gt;

&lt;p&gt;Document exclusions too. Faces, house numbers, unrelated license plates, papers on a seat, and personal items have no evidentiary value for tire wear. The interface should help the operator avoid them before capture. Redaction is a fallback, not a reason to collect broadly. For driver education about tire condition, &lt;a href="https://calgaryrimandtire.ca/be-tire-smart" rel="noopener noreferrer"&gt;Be Tire Smart&lt;/a&gt; supplies useful background without turning an image record into a diagnosis.&lt;/p&gt;

&lt;p&gt;Acceptance is a state transition, not a feeling. A frame can be &lt;code&gt;captured&lt;/code&gt;, &lt;code&gt;uploaded&lt;/code&gt;, &lt;code&gt;validated&lt;/code&gt;, &lt;code&gt;needs_recapture&lt;/code&gt;, &lt;code&gt;accepted&lt;/code&gt;, &lt;code&gt;redacted&lt;/code&gt;, &lt;code&gt;superseded&lt;/code&gt;, or &lt;code&gt;rejected&lt;/code&gt;. Only accepted originals and approved derivatives should reach review datasets. Raw attempts can have a shorter retention policy, provided the policy is explicit and legal requirements are respected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give Every Capture a Stable Identity
&lt;/h2&gt;

&lt;p&gt;Never use a filename as the primary key. Mobile operating systems reuse names, messaging apps rewrite names, and exports reorder sequences. Generate a cryptographically random capture ID before the shutter action if the client permits it, then include that ID in the upload envelope. The server separately computes a content digest from the received bytes. Identity answers “which record?” while the digest answers “are these exact bytes already known?”&lt;/p&gt;

&lt;p&gt;A useful naming convention is only a convenience:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{session_id}_{position}_{view_role}_{capture_id}.{ext}
01J4..._LR_TREAD_OVERVIEW_01J5....jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not embed customer names, phone numbers, addresses, or full vehicle identification numbers in object keys. Object-storage paths leak into logs, caches, support screenshots, and access reports. A random tenant-scoped prefix plus opaque IDs provides enough organization. Human-readable labels belong in authorized application views.&lt;/p&gt;

&lt;p&gt;Capture identity also needs lineage. The original is immutable. Rotation, colour normalization, redaction, thumbnails, and annotation overlays are derivatives with their own digests and parent references. A reviewer must always be able to reach the untouched original, subject to access controls, and see which transformation produced the displayed asset.&lt;/p&gt;

&lt;p&gt;The digest should be computed server-side using SHA-256 or a similarly appropriate contemporary algorithm. A client digest can accelerate resumable upload checks, but it is an assertion until the server verifies it. Store byte length, detected media type, decoder result, dimensions, colour profile, and digest in the same ingestion transaction. Reject polyglot or malformed files rather than trusting the extension.&lt;/p&gt;

&lt;p&gt;Idempotency matters on weak connections. A phone moving between a service bay network and mobile data may repeat a request. An idempotency key scoped to the session and capture ID lets the server return the existing record instead of creating twins. Exact-byte duplicate detection should warn about repeated content while still preserving the attempted relationship for audit purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define a Calgary-Ready Capture Protocol
&lt;/h2&gt;

&lt;p&gt;An effective protocol guides behaviour in seconds. Begin with a context frame showing enough of the vehicle and wheel location to establish position without collecting unrelated surroundings. Then move to the wheel context, tread overview, and any detail. The app should display a silhouette with LF, RF, LR, and RR positions; trailers and dual-wheel assets need an extensible position vocabulary rather than four hard-coded buttons.&lt;/p&gt;

&lt;p&gt;Lighting instructions must respond to reality. Direct sun can make tread valleys black while washing out raised rubber. Indoor LEDs can flicker or create bands. Snow glare shifts exposure. A chinook afternoon can place one side of a vehicle in bright sun and the other in deep shade. The protocol should ask the operator to seek diffuse light, avoid flash reflections on wet rubber, and move the light source rather than digitally brightening a failed frame.&lt;/p&gt;

&lt;p&gt;Surface condition deserves a required field with controlled values such as &lt;code&gt;dry_clean&lt;/code&gt;, &lt;code&gt;wet&lt;/code&gt;, &lt;code&gt;salt_film&lt;/code&gt;, &lt;code&gt;dust&lt;/code&gt;, &lt;code&gt;snow&lt;/code&gt;, &lt;code&gt;ice&lt;/code&gt;, &lt;code&gt;mud&lt;/code&gt;, and &lt;code&gt;unknown&lt;/code&gt;. Multiple values may apply. These are not cosmetic notes: they influence quality scoring and later model training. A network trained mainly on cleaned tires may fail on the slushy conditions common after a Calgary temperature swing.&lt;/p&gt;

&lt;p&gt;Angle guidance should be role-specific. A tread overview needs the optical axis close to perpendicular to the local tread surface. A sidewall record needs lettering in focus and a frame broad enough to retain context; &lt;a href="https://calgaryrimandtire.ca/tire-sidewall-information" rel="noopener noreferrer"&gt;tire sidewall information&lt;/a&gt; helps operators understand which markings matter. Damage-detail frames can be oblique when the angle reveals depth, but they should follow a perpendicular establishing frame.&lt;/p&gt;

&lt;p&gt;Highway context belongs in session metadata, not inferred from wear. An operator may record that an asset normally runs Deerfoot, Stoney Trail, urban deliveries, gravel approaches, or Highway 1 west when that fact is supplied by the fleet. The pipeline must not manufacture route history from pixels. Likewise, load context should reference verified operational data. &lt;a href="https://calgaryrimandtire.ca/tire-load-index-explained" rel="noopener noreferrer"&gt;Load-index guidance&lt;/a&gt; explains the marking, but an image of the marking does not prove the vehicle’s actual carried load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a Metadata Schema That Admits Uncertainty
&lt;/h2&gt;

&lt;p&gt;Metadata should distinguish observed, supplied, measured, inferred, and unknown values. Without provenance at the field level, a reviewer cannot tell whether &lt;code&gt;wheel_position: LR&lt;/code&gt; came from the operator’s selection, a barcode association, or a vision model guess. Every material fact should carry a source and, where applicable, confidence.&lt;/p&gt;

&lt;p&gt;Here is a compact upload envelope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01J4Z8Q7KRF0Y2M5N8D3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"capture_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01J4Z9A2XQJ7C6H1P4TT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"asset_ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fleet_asset_8f42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"wheel_position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"view_role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tread_overview"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"captured_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-04T15:42:18.221Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timezone_offset_minutes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-360&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"surface_conditions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"wet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"salt_film"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"location_class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service_bay"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"temperature_c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"device"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"app_build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026.08.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"camera_facing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rear"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operator_note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Spray remained after Deerfoot arrival"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consent_basis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fleet_service_record"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treat device-generated EXIF as untrusted input. Preserve it in a quarantined metadata document, then normalize selected fields after validation. GPS should be removed unless it has a documented operational need. Device serial numbers and owner names should not enter analytics tables. Capture time can be compared with server receipt time, but clock drift means disagreement should create a flag rather than silently rewrite history.&lt;/p&gt;

&lt;p&gt;Schema evolution must be intentional. Store a version on every envelope and write deterministic migrators. Never reinterpret an old enum in place. If &lt;code&gt;wet&lt;/code&gt; later splits into &lt;code&gt;standing_water&lt;/code&gt; and &lt;code&gt;surface_damp&lt;/code&gt;, old records remain &lt;code&gt;wet&lt;/code&gt; unless a human or reproducible process adds a new derived classification. That discipline is essential when image corpora outlive application releases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate Transactional Records From Binary Objects
&lt;/h2&gt;

&lt;p&gt;Large image bytes belong in object storage; relational records belong in a database. The database should retain the object key, version ID where supported, byte digest, size, media characteristics, ownership boundary, and retention class. A signed URL is a temporary access mechanism, never the stored identity of an image.&lt;/p&gt;

&lt;p&gt;One PostgreSQL sketch looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;evidence_image&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;image_id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;inspection_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;capture_id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;wheel_position&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;view_role&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;object_version&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sha256&lt;/span&gt; &lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;byte_length&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byte_length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;width_px&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;height_px&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;captured_at&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;received_at&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;evidence_state&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;schema_version&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;unique&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;evidence_by_session_position&lt;/span&gt;
  &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;evidence_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wheel_position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view_role&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database transaction can reserve an image record before upload, but it should not mark the record accepted until object verification completes. A reconciliation worker finds abandoned reservations and orphaned objects. Use an outbox table to publish &lt;code&gt;image.verified&lt;/code&gt; events only after the state change commits; this avoids a message announcing data that the database later rolls back.&lt;/p&gt;

&lt;p&gt;Object policies should prevent overwriting an original key. If storage supports versioning and retention locks, configure them according to policy rather than relying only on application promises. Encryption at rest, tenant-scoped authorization, narrow service roles, and access logging are baseline controls. Backups need restore tests that reconnect database records with matching object versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate Pixels Before Asking a Human to Interpret Them
&lt;/h2&gt;

&lt;p&gt;Ingestion validation should be fast, deterministic, and explainable. First decode the entire image, not merely its header. Enforce permitted formats, dimensions, pixel count, and file-size bounds. Normalize orientation for display as a derivative while retaining the original bytes and EXIF orientation. Reject decompression bombs and files whose detected content disagrees with their declared type.&lt;/p&gt;

&lt;p&gt;Then calculate technical signals: Laplacian variance or another focus proxy, motion-blur directionality, clipped-shadow ratio, clipped-highlight ratio, local contrast, colour cast, glare area, obstruction estimate, and subject occupancy. None is universally meaningful by itself. Wet black rubber naturally produces highlights, while deep tread valleys naturally contain shadows. Thresholds need calibration by view role and surface condition.&lt;/p&gt;

&lt;p&gt;Angle validation can use a lightweight segmentation model or geometric cues. For a tread overview, estimate the visible tread region, its principal axis, and perspective convergence. The system need not claim an exact camera angle. It can classify &lt;code&gt;acceptable&lt;/code&gt;, &lt;code&gt;borderline&lt;/code&gt;, or &lt;code&gt;recapture&lt;/code&gt; with reasons such as “tread occupies too little of frame” or “perspective hides shoulder region.” A transparent reason is more useful than a mysterious score of 0.61.&lt;/p&gt;

&lt;p&gt;Run these checks before upload completion when possible, but repeat them server-side. Client validation provides immediate coaching; server validation provides consistency and cannot be bypassed by an old app. Keep algorithm versions beside outputs so a later calibration does not rewrite history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a Quality Score Without Hiding Failure Reasons
&lt;/h2&gt;

&lt;p&gt;A composite score is convenient for queues, yet it should never replace component values. Suppose focus, exposure, framing, angle, and obstruction each range from zero to one. A weighted sum may be appropriate for ranking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Q = 0.28F + 0.18E + 0.22R + 0.22A + 0.10O
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;O&lt;/code&gt; means visibility after accounting for obstruction, not the obstruction amount. The numbers are illustrative calibration weights, not safety thresholds. A hard rule sits alongside the sum: if subject occupancy is below the minimum for the selected view, the frame requires recapture regardless of Q. This prevents excellent lighting from compensating for a tire that occupies a tiny corner.&lt;/p&gt;

&lt;p&gt;Store raw metrics, normalized components, calibration version, decision, and human-readable reasons. A result might say &lt;code&gt;needs_recapture&lt;/code&gt; because focus is below the tread-overview threshold and the centre grooves are clipped. Avoid claims such as “bad tire.” The automated system is judging photographic fitness, not tire fitness.&lt;/p&gt;

&lt;p&gt;Quality drift needs monitoring. Track distributions by device family, app version, bay, capture role, surface condition, and time of day. If a release suddenly raises shadow clipping, perhaps orientation or colour conversion changed. If one bay produces glare failures, the operational fix may be moving a lamp. Aggregate metrics should not become employee surveillance; define access and use boundaries before collection.&lt;/p&gt;

&lt;p&gt;When a frame documents a potential puncture area, route the person to a tire-service assessment rather than issuing a pixel-based verdict. KMJ’s &lt;a href="https://calgaryrimandtire.ca/tire-repair-in-calgary" rel="noopener noreferrer"&gt;tire repair information&lt;/a&gt; provides service context, while the evidence record preserves what was visible at capture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detect Blur, Glare, Lighting, and Framing Carefully
&lt;/h2&gt;

&lt;p&gt;Blur detection is frequently oversimplified. Laplacian variance drops for defocused images, but it also drops on smooth sidewall regions. Compute it over the detected tread or lettering region, not the whole frame. Add edge orientation analysis to distinguish directional motion blur from soft focus. Compare multiple scales so aggressive phone sharpening does not fool the metric.&lt;/p&gt;

&lt;p&gt;Exposure validation should examine spatial maps. A global histogram can look healthy while the tread is nearly black and the concrete floor is bright. Segment the tire region first, then calculate clipped pixels and usable tonal range inside it. For wet tires, specular highlights can be acceptable when they do not obscure the feature of interest. Large continuous glare masks are more concerning than scattered points.&lt;/p&gt;

&lt;p&gt;Framing uses the declared role. A wheel-context frame should include the full wheel and enough vehicle structure to support position. A tread-detail frame should fill more of the image but must not lose all orientation cues. The pipeline can request a paired overview rather than rejecting a valuable close-up solely for lacking context.&lt;/p&gt;

&lt;p&gt;Lighting colour matters less than visibility, but extreme casts can harm later CV work. Preserve the original, estimate white balance only as a metric, and create a normalized derivative if the use case justifies it. Never replace the original with a cosmetically improved file. Road salt may resemble cracking or residue to a naive model; condition metadata and diverse training examples are the defence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat Privacy and Redaction as Data Engineering
&lt;/h2&gt;

&lt;p&gt;Privacy begins with minimization. Configure the capture UI so operators aim tightly, and explain why a wider neighbourhood scene is unnecessary. If the vehicle-context view includes a license plate, decide whether the operational record truly requires it. Often an internal asset reference already establishes identity, making visible plate text redundant.&lt;/p&gt;

&lt;p&gt;Automated detectors can flag faces, plates, documents, QR codes, and screen content. Their output should create a privacy-review task, not silently destroy pixels. Redaction produces a derivative with a mask manifest describing detector version, reviewer, regions, method, and parent digest. Access to the original is narrower than access to the redacted working asset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"derivative_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"privacy_redacted"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parent_sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6a8d...e21c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transform_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"redact-2.3.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"regions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"plate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"box"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;122&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;284&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;143&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"solid_fill"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"review_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"approved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reviewer_role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"privacy_reviewer"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not put sensitive content into free-text logs. Operator notes require length limits, clear guidance, and restricted visibility. Structured route classes are preferable to precise trip narratives. Retention should differ among originals, operational derivatives, temporary uploads, audit events, and model-training exports. A legal or contractual hold must override routine deletion through an explicit workflow.&lt;/p&gt;

&lt;p&gt;Training exports deserve special scrutiny. Consent to maintain a fleet record does not automatically imply permission to train models. Build a dataset eligibility view that considers consent basis, retention, redaction, tenant policy, review status, and exclusions. Export manifests should list immutable digests so researchers cannot quietly substitute files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve an Append-Only Audit Story
&lt;/h2&gt;

&lt;p&gt;Auditability does not require a fashionable ledger. It requires immutable source objects, append-only events, controlled corrections, reliable clocks, and verifiable relationships. A reviewer changing wheel position from LR to RR should create a correction event containing the old value, new value, reason, actor, time, and referenced image digest. The current projection can show RR, while history remains visible.&lt;/p&gt;

&lt;p&gt;Create a hash chain within each session if tamper evidence is important. Each event includes the previous event hash, canonical payload hash, timestamp, and signer identity. Periodically anchor batch roots in a separately controlled system. This does not prove the original observation was true; it helps reveal later alteration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event_hash = SHA256(
  canonical_json(payload) || previous_event_hash || actor_id || occurred_at
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Canonicalization rules must be fixed and tested. JSON key order, Unicode normalization, decimal representation, and timestamp precision can otherwise change hashes without changing meaning. Key rotation and service-account identity also belong in the design. “System” is not a useful actor if five workers share it.&lt;/p&gt;

&lt;p&gt;Audit logs must record reads of sensitive originals as well as writes. Alerts can flag bulk access, unusual export volume, or access outside a role’s normal scope. Keep operational monitoring separate from evidence interpretation: a download anomaly is a security concern, not information about tire condition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Human Review Around Disagreement
&lt;/h2&gt;

&lt;p&gt;The review interface should present context, not just a gallery. Show vehicle asset reference, wheel map, image role, surface conditions, original-versus-derivative status, quality reasons, and linked views. Let reviewers zoom into original resolution without forcing a lossy screenshot workflow. A side-by-side view is valuable when checking a redaction or comparing paired angles.&lt;/p&gt;

&lt;p&gt;Use structured observations such as &lt;code&gt;visible_irregularity&lt;/code&gt;, &lt;code&gt;possible_foreign_object&lt;/code&gt;, &lt;code&gt;sidewall_marking_legible&lt;/code&gt;, &lt;code&gt;area_obscured&lt;/code&gt;, and &lt;code&gt;no_observation_due_to_quality&lt;/code&gt;. These labels describe pixels. They should not be phrased as conclusive causes or service promises. A human can add a note explaining why in-person inspection is needed.&lt;/p&gt;

&lt;p&gt;Disagreement is training data. If two reviewers differ, preserve both labels and route the item to adjudication. Do not overwrite the first interpretation. Measure agreement by label and condition; low agreement may reveal an ambiguous rubric rather than careless people. Gravel, salt residue, shadow, and water can each create systematic confusion.&lt;/p&gt;

&lt;p&gt;Mechanical concerns outside tire service must be handled plainly. If the visible evidence raises a concern that would require alignment, brake, suspension, steering, diagnostic, or other mechanical work, the record should recommend assessment by an appropriate mechanical provider. It must not imply that KMJ offers that work. Wheel-balance symptoms are within tire-service context, and &lt;a href="https://calgaryrimandtire.ca/wheel-balancing" rel="noopener noreferrer"&gt;wheel balancing guidance&lt;/a&gt; can support a separate, properly scoped service discussion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare for Computer Vision Without Polluting Ground Truth
&lt;/h2&gt;

&lt;p&gt;Computer-vision readiness begins with dataset governance, not model selection. Define the prediction target, unit of analysis, exclusion criteria, and label provenance. Split datasets by vehicle or fleet asset, not random images, or nearly identical views from one session can leak across training and test sets. Consider temporal splits to test whether a model survives new devices, seasons, and road residue.&lt;/p&gt;

&lt;p&gt;Store annotations in an open, versioned representation. Bounding boxes may be adequate for foreign-object candidates; segmentation masks may be necessary for tread-region visibility or privacy regions. Each annotation references an image digest, ontology version, annotator role, and adjudication state. Model-generated prelabels must remain distinguishable from human labels.&lt;/p&gt;

&lt;p&gt;Balance includes environments, not just classes. Seek coverage across dry summer rubber, wet shoulder-season conditions, winter snow residue, gravel dust, salt film, indoor bays, outdoor shade, and different phone cameras. Calgary’s seasons can create shortcuts: if every positive example comes from winter, a model may learn snow texture instead of the intended feature.&lt;/p&gt;

&lt;p&gt;Do not normalize away operational reality. Keep originals and produce reproducible training transforms. Record resize method, crop, colour conversion, augmentation seed, and code version in an experiment manifest. A model card should state which capture roles and conditions were evaluated, known failure modes, and that photographic classification does not replace physical tire assessment.&lt;/p&gt;

&lt;p&gt;For seasonal operational context, &lt;a href="https://calgaryrimandtire.ca/seasonal-tire-changes" rel="noopener noreferrer"&gt;seasonal tire changes&lt;/a&gt;, &lt;a href="https://calgaryrimandtire.ca/all-weather-tires-calgary" rel="noopener noreferrer"&gt;all-weather tire guidance&lt;/a&gt;, and &lt;a href="https://calgaryrimandtire.ca/winter-tires-calgary" rel="noopener noreferrer"&gt;winter tire information&lt;/a&gt; can inform user education. Those pages are references, not labels for an image dataset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operate the Pipeline With Queues and Service-Level Signals
&lt;/h2&gt;

&lt;p&gt;An ingestion pipeline should expose state counts and age, not merely request latency. Useful signals include uploads awaiting verification, images stuck in decoding, recapture requests without a replacement, privacy flags awaiting review, accepted sessions missing a required view, and derivatives whose parent cannot be resolved. Alert on backlog age and error-rate changes rather than every single expected rejection.&lt;/p&gt;

&lt;p&gt;Use dead-letter handling with replay controls. A malformed image is a terminal validation result, while a transient object-store timeout is retryable. Classify errors explicitly so exponential backoff does not repeatedly decode malicious or impossible input. Replay must be idempotent and should retain the original algorithm version unless a deliberate reprocessing job is launched.&lt;/p&gt;

&lt;p&gt;Capacity planning should use pixel volume and derivative work, not image count alone. A panoramic frame can cost far more than a normal phone image. Place upper bounds before decode, stream uploads, and isolate expensive CV tasks from the transactional API. Backpressure should coach the client to pause without losing capture identities.&lt;/p&gt;

&lt;p&gt;Observability also needs trace boundaries. Propagate a session-safe trace ID through reservation, upload, verification, quality analysis, redaction, and review events. Do not place personal or vehicle-sensitive fields into metric labels. High-cardinality identifiers belong in secured logs or traces with appropriate retention, not in public dashboards.&lt;/p&gt;

&lt;p&gt;Fleet operators can use &lt;a href="https://calgaryrimandtire.ca/commercial-tire-services" rel="noopener noreferrer"&gt;commercial tire service information&lt;/a&gt; and &lt;a href="https://calgaryrimandtire.ca/fleet-management" rel="noopener noreferrer"&gt;fleet management context&lt;/a&gt; to understand the human service workflow around evidence. The pipeline itself should remain tenant-aware, policy-driven, and explicit about who can view which fleet records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walk Through a Calgary Fleet Capture
&lt;/h2&gt;

&lt;p&gt;Consider a delivery asset returning from Stoney Trail after a cold morning followed by a warm chinook afternoon. The operator starts session &lt;code&gt;S-204&lt;/code&gt;, selects the left-rear position, and records &lt;code&gt;wet&lt;/code&gt; plus &lt;code&gt;salt_film&lt;/code&gt;. A wheel-context image passes framing but contains a distant plate from another vehicle. The privacy detector flags it. The tread overview is sharp, although glare covers one shoulder. A focused second angle makes that shoulder visible.&lt;/p&gt;

&lt;p&gt;The server verifies all three byte digests and retains originals. Quality analysis accepts the context frame for evidentiary linkage, requests redaction for general review use, marks the first tread frame borderline, and accepts the paired angle. The workflow does not average the two tread frames into a fictional certainty. It records that one region was obscured in one view and visible in another.&lt;/p&gt;

&lt;p&gt;A reviewer notices a visible irregular area and labels its location without declaring a cause. The record routes the asset for an in-person tire assessment. If a separate observation suggests a non-tire mechanical issue, the note directs the fleet to an appropriate mechanical provider. Each action becomes an audit event.&lt;/p&gt;

&lt;p&gt;Weeks later, a dataset curator queries only privacy-approved, accepted tread views with adjudicated labels and compatible consent. The export manifest pins digests and excludes the borderline frame. A researcher can reproduce the selection without seeing fleet names. That is what CV readiness looks like: careful operational provenance before clever modelling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Failure Modes, Not Just Happy Paths
&lt;/h2&gt;

&lt;p&gt;Automated tests should submit truncated JPEGs, misleading extensions, oversized dimensions, duplicated bytes under new names, rotated EXIF, missing timestamps, future timestamps, repeated idempotency keys, and invalid wheel positions. Verify that originals cannot be overwritten and that derivatives cannot point to nonexistent parents. Property-based tests are particularly useful for schema migrators and canonical event hashing.&lt;/p&gt;

&lt;p&gt;Run visual test fixtures for glare, motion blur, defocus, shadow clipping, snow obstruction, salt film, and perspective. Threshold tests need boundary cases on both sides. Maintain device-specific fixtures when camera processing changes. A quality model that passes curated examples but fails after a phone update is not production-ready.&lt;/p&gt;

&lt;p&gt;Exercise privacy failures: a detector misses a small face, a reviewer rejects an incomplete mask, an export attempts to include an unapproved original, and a deletion request intersects a retention hold. Confirm least-privilege roles by attempting forbidden reads. Restore an archived session from backup and recalculate digests.&lt;/p&gt;

&lt;p&gt;Chaos tests can interrupt upload finalization between object write and database commit. Reconciliation should either reconnect a verified object to its reservation or quarantine it. Event consumers must tolerate duplicates and out-of-order delivery. A projection can be rebuilt from the append-only stream and compared with the live state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a Deployment Checklist With Explicit Owners
&lt;/h2&gt;

&lt;p&gt;Before rollout, assign owners for capture protocol, schema, storage policy, privacy review, quality calibration, security response, dataset governance, and user support. Write a data-flow diagram showing trust boundaries. Complete a privacy impact assessment appropriate to the organization. Document deletion, hold, export, correction, and breach-response procedures.&lt;/p&gt;

&lt;p&gt;Pilot across different Calgary conditions and device families. Sample failures manually instead of watching only averages. Ask operators whether the guidance is understandable while their hands are cold or wet. Measure recapture burden by reason. Revise thresholds when evidence supports it, and version every revision.&lt;/p&gt;

&lt;p&gt;A compact release checklist is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;original bytes are immutable and digest-verified;&lt;/li&gt;
&lt;li&gt;required views and wheel positions are enforced;&lt;/li&gt;
&lt;li&gt;privacy collection is minimized and redaction is reviewable;&lt;/li&gt;
&lt;li&gt;quality components and reasons remain visible;&lt;/li&gt;
&lt;li&gt;uncertain observations are labelled as uncertain;&lt;/li&gt;
&lt;li&gt;non-tire mechanical concerns are referred elsewhere;&lt;/li&gt;
&lt;li&gt;audit events survive corrections and reprocessing;&lt;/li&gt;
&lt;li&gt;dataset exports require policy eligibility;&lt;/li&gt;
&lt;li&gt;restore, replay, and deletion workflows have been tested;&lt;/li&gt;
&lt;li&gt;dashboards reveal backlog age without exposing personal data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drivers seeking local service-area context can review &lt;a href="https://calgaryrimandtire.ca/service-areas" rel="noopener noreferrer"&gt;KMJ Tire’s service areas&lt;/a&gt;. That operational reference should remain separate from technical evidence: geography may explain capture conditions, but it must not become a proxy label for tire condition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the Evidence Honest From Shutter to Archive
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Model Recapture as a First-Class Operational Loop
&lt;/h2&gt;

&lt;p&gt;Recapture should not be a generic rejection banner. It is a linked workflow connecting the failed attempt, the reason, the replacement, and the final disposition. A request needs a reason code such as &lt;code&gt;focus_tread_region&lt;/code&gt;, &lt;code&gt;missing_position_context&lt;/code&gt;, &lt;code&gt;glare_obscures_subject&lt;/code&gt;, &lt;code&gt;incorrect_view_role&lt;/code&gt;, or &lt;code&gt;privacy_avoidance&lt;/code&gt;. Pair that code with one short instruction written for the operator: move closer, steady the device, step out of direct reflection, or add a context view. Avoid vague advice such as “take a better photo.”&lt;/p&gt;

&lt;p&gt;The replacement carries a &lt;code&gt;supersedes_capture_id&lt;/code&gt; relationship but does not erase the first attempt. This link enables useful analysis: which prompts solve failures, which reasons recur by device, and where the protocol itself causes confusion. Keep failed pixels out of normal reviewer screens after replacement unless an authorized audit view is opened. Operational visibility and clutter control can coexist.&lt;/p&gt;

&lt;p&gt;Set loop limits deliberately. An app that demands six retries in blowing snow may encourage unsafe behaviour or fabricated compliance. After a small number of failed attempts, allow an exception state with a structured reason such as environmental obstruction, accessibility constraint, or device malfunction. A supervisor can decide whether another capture setting is appropriate. The exception is evidence about process limitations, not permission to mark the missing view as successful.&lt;/p&gt;

&lt;p&gt;Offline capture adds another wrinkle. Validate locally with the bundled algorithm version, queue encrypted files, and display upload state clearly. Once connectivity returns, the server repeats validation and may disagree because its calibration is newer. Preserve both results. If server policy requires another frame, explain that the original passed an older on-device check. Silent contradictions destroy operator trust.&lt;/p&gt;

&lt;p&gt;Measure recapture yield rather than rejection alone. If an instruction frequently produces an accepted replacement, it is useful. If “angle too steep” leads to another angle failure, improve the on-screen overlay or examples. Break results down by view role and environment, while maintaining fair-use limits around worker analytics. The objective is better evidence design, not a leaderboard of people working in different conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Govern Reprocessing and Algorithm Change
&lt;/h2&gt;

&lt;p&gt;Quality algorithms, privacy detectors, and CV models will evolve. Reprocessing should create new assessment records tied to the same immutable original, never update old outputs in place. Each assessment includes algorithm name, model digest, configuration digest, runtime environment, start time, completion time, and status. The application chooses which version is current through policy, while an auditor can compare generations.&lt;/p&gt;

&lt;p&gt;A reprocessing plan should answer why the run exists. Perhaps a glare detector was recalibrated for wet winter tires, a privacy model gained better plate recognition, or a decoder vulnerability required fresh validation. Define the eligible population with a saved query and manifest before execution. Record counts selected, skipped, succeeded, and quarantined. A resumable job cursor prevents a partial run from producing an unknowable corpus.&lt;/p&gt;

&lt;p&gt;Do not automatically promote every new score. Run shadow evaluation on representative Calgary conditions, compare false recapture and missed-obstruction rates, and inspect disagreements. A detector that improves on clean indoor images but worsens on salt film may not be ready for winter operations. Promotion needs an approval event referencing evaluation artifacts.&lt;/p&gt;

&lt;p&gt;Rollback means switching policy to a prior approved assessment version; it does not mean deleting the new results. Downstream exports pin versions so a model-training experiment remains reproducible after production policy changes. Cache keys must include algorithm and transform versions, otherwise a thumbnail or score generated under one policy can masquerade as another.&lt;/p&gt;

&lt;p&gt;Finally, budget reprocessing like any batch system. Bound concurrency, isolate queues from live ingestion, watch object-read costs, and pause when customer-facing capture latency rises. Security patches may justify priority, but routine experimentation should never destabilize the active evidence path. Operational discipline protects both the archive and the people depending on it.&lt;/p&gt;

&lt;p&gt;A trustworthy tire-wear image pipeline is less about a sophisticated classifier than disciplined boundaries. The camera client establishes intent. The ingestion layer preserves exact bytes. Metadata admits its sources and uncertainty. Quality checks judge whether pixels are usable, not whether a tire is safe. Privacy controls minimize and redact unrelated information. Human review preserves disagreement. Immutable lineage makes later transformation visible.&lt;/p&gt;

&lt;p&gt;These choices turn a pile of phone photos into an evidence system that can support operations today and carefully governed computer vision later. They also make failure useful. A blurred frame becomes a specific recapture reason. A salt-covered surface becomes documented context. A corrected wheel position becomes an auditable event. A model’s weak performance in snow becomes a known coverage gap rather than a surprise.&lt;/p&gt;

&lt;p&gt;For Calgary fleets, the practical goal is not laboratory perfection. It is repeatable capture across chinooks, road spray, gravel, deep cold, and dramatic day-night temperature swings while retaining enough context for a competent person to review the record. Keep service decisions with qualified people, keep mechanical referrals within their proper scope, and let the pipeline do what software does well: preserve identity, enforce process, expose uncertainty, and make every transformation traceable.&lt;/p&gt;

</description>
      <category>automotive</category>
      <category>calgary</category>
      <category>maintenance</category>
      <category>tires</category>
    </item>
  </channel>
</rss>
